/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <assert.h>
#include <stddef.h>
#include "syscfg/syscfg.h"
#include "sysinit/sysinit.h"
#include "os/os.h"
#include "mem/mem.h"

#include "nimble/ble.h"
#include "nimble/ble_hci_trans.h"
#include "nimble/hci_common.h"

#include <class/bth/bth_device.h>

/*
 * The MBUF payload size must accommodate the HCI data header size plus the
 * maximum ACL data packet length. The ACL block size is the size of the
 * mbufs we will allocate.
 */
#define ACL_BLOCK_SIZE  OS_ALIGN(MYNEWT_VAL(BLE_ACL_BUF_SIZE) \
                                 + BLE_MBUF_MEMBLOCK_OVERHEAD \
                                 + BLE_HCI_DATA_HDR_SZ, OS_ALIGNMENT)

struct usb_ble_hci_pool_cmd {
    uint8_t cmd[BLE_HCI_TRANS_CMD_SZ];
    bool allocated;
};

/* (Pseudo)pool for HCI commands */
static struct usb_ble_hci_pool_cmd usb_ble_hci_pool_cmd;

static ble_hci_trans_rx_cmd_fn *ble_hci_usb_rx_cmd_ll_cb;
static void *ble_hci_usb_rx_cmd_ll_arg;

static ble_hci_trans_rx_acl_fn *ble_hci_usb_rx_acl_ll_cb;
static void *ble_hci_usb_rx_acl_ll_arg;

static struct os_mempool ble_hci_usb_evt_hi_pool;
static os_membuf_t ble_hci_usb_evt_hi_buf[
    OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_HCI_EVT_HI_BUF_COUNT),
                    MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE))
];

static struct os_mempool ble_hci_usb_evt_lo_pool;
static os_membuf_t ble_hci_usb_evt_lo_buf[
    OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_HCI_EVT_LO_BUF_COUNT),
                    MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE))
];

static uint8_t ble_hci_pool_acl_mempool_buf[
    OS_MEMPOOL_BYTES(MYNEWT_VAL(BLE_ACL_BUF_COUNT),
                     ACL_BLOCK_SIZE)];
static struct os_mempool ble_hci_pool_acl_mempool;
static struct os_mbuf_pool ble_hci_pool_acl_mbuf_pool;

static struct os_mbuf *incoming_acl_data;

static struct os_mbuf *
ble_hci_trans_acl_buf_alloc(void)
{
    struct os_mbuf *m;

    m = os_mbuf_get_pkthdr(&ble_hci_pool_acl_mbuf_pool,
                           sizeof(struct ble_mbuf_hdr));
    return m;
}

void
ble_hci_trans_cfg_ll(ble_hci_trans_rx_cmd_fn *cmd_cb,
                     void *cmd_arg,
                     ble_hci_trans_rx_acl_fn *acl_cb,
                     void *acl_arg)
{
    ble_hci_usb_rx_cmd_ll_cb = cmd_cb;
    ble_hci_usb_rx_cmd_ll_arg = cmd_arg;
    ble_hci_usb_rx_acl_ll_cb = acl_cb;
    ble_hci_usb_rx_acl_ll_arg = acl_arg;
}

#define BLE_HCI_USB_EVT_COUNT  \
    (MYNEWT_VAL(BLE_HCI_EVT_HI_BUF_COUNT) + MYNEWT_VAL(BLE_HCI_EVT_LO_BUF_COUNT))

/**
 * A packet to be sent over the USB.  This can be a command, an event, or ACL
 * data.
 */
struct ble_hci_pkt {
    STAILQ_ENTRY(ble_hci_pkt) next;
    void *data;
};

static struct os_mempool ble_hci_pkt_pool;
static os_membuf_t ble_hci_pkt_buf[
    OS_MEMPOOL_SIZE(BLE_HCI_USB_EVT_COUNT + 1 +
                    MYNEWT_VAL(BLE_HCI_ACL_OUT_COUNT),
                    sizeof(struct ble_hci_pkt))];

struct tx_queue {
    STAILQ_HEAD(, ble_hci_pkt) queue;
};

static struct tx_queue ble_hci_tx_acl_queue = {STAILQ_HEAD_INITIALIZER(ble_hci_tx_acl_queue.queue)};
static struct tx_queue ble_hci_tx_evt_queue = { STAILQ_HEAD_INITIALIZER(ble_hci_tx_evt_queue.queue) };

/*
 * TinyUSB callbacks.
 */
void
tud_bt_acl_data_sent_cb(uint16_t sent_bytes)
{
    struct os_mbuf *om;
    struct ble_hci_pkt *curr_acl;
    struct ble_hci_pkt *next_acl;
    os_sr_t sr;

    OS_ENTER_CRITICAL(sr);
    curr_acl = STAILQ_FIRST(&ble_hci_tx_acl_queue.queue);
    OS_EXIT_CRITICAL(sr);

    assert(curr_acl != NULL);
    om = curr_acl->data;
    assert(om != NULL && om->om_len >= sent_bytes);
    os_mbuf_adj(om, sent_bytes);

    while (om != NULL && om->om_len == 0) {
        curr_acl->data = SLIST_NEXT(om, om_next);
        os_mbuf_free(om);
        om = curr_acl->data;
    }

    if (om == NULL) {
        OS_ENTER_CRITICAL(sr);
        STAILQ_REMOVE_HEAD(&ble_hci_tx_acl_queue.queue, next);
        next_acl = STAILQ_FIRST(&ble_hci_tx_acl_queue.queue);
        OS_EXIT_CRITICAL(sr);
        os_memblock_put(&ble_hci_pkt_pool, curr_acl);
        if (next_acl != NULL) {
            om = next_acl->data;
        }
    }

    if (om != NULL) {
        tud_bt_acl_data_send(om->om_data, om->om_len);
    }
}

void
tud_bt_event_sent_cb(uint16_t sent_bytes)
{
    struct ble_hci_pkt *curr_evt;
    struct ble_hci_pkt *next_evt;
    uint8_t *hci_ev;
    os_sr_t sr;

    OS_ENTER_CRITICAL(sr);
    curr_evt = STAILQ_FIRST(&ble_hci_tx_evt_queue.queue);
    OS_EXIT_CRITICAL(sr);
    assert(curr_evt != NULL);
    hci_ev = curr_evt->data;

    assert(hci_ev != NULL && hci_ev[1] + sizeof(struct ble_hci_ev) == sent_bytes);

    ble_hci_trans_buf_free(hci_ev);

    OS_ENTER_CRITICAL(sr);
    STAILQ_REMOVE_HEAD(&ble_hci_tx_evt_queue.queue, next);
    next_evt = STAILQ_FIRST(&ble_hci_tx_evt_queue.queue);
    OS_EXIT_CRITICAL(sr);
    os_memblock_put(&ble_hci_pkt_pool, curr_evt);

    if (next_evt != NULL) {
        hci_ev = next_evt->data;
        tud_bt_event_send(hci_ev, hci_ev[1] + sizeof(struct ble_hci_ev));
    }
}

void
tud_bt_acl_data_received_cb(void *acl_data, uint16_t data_len)
{
    uint8_t *data;
    uint32_t len;
    struct os_mbuf *om = incoming_acl_data;
    int rc;

    if (om == NULL) {
        om = ble_hci_trans_acl_buf_alloc();
        assert(om != NULL);
    }
    assert(om->om_len + data_len <= MYNEWT_VAL(BLE_ACL_BUF_SIZE) + BLE_HCI_DATA_HDR_SZ);

    os_mbuf_append(om, acl_data, data_len);
    incoming_acl_data = om;
    if (om->om_len > BLE_HCI_DATA_HDR_SZ) {
        data = incoming_acl_data->om_data;
        len = data[2] + (data[3] << 8) + BLE_HCI_DATA_HDR_SZ;
        if (len >= incoming_acl_data->om_len) {
            incoming_acl_data = NULL;
            rc = ble_hci_usb_rx_acl_ll_cb(om, ble_hci_usb_rx_acl_ll_arg);
            (void)rc;
        }
    }
}

void
tud_bt_hci_cmd_cb(void *hci_cmd, size_t cmd_len)
{
    uint8_t *buf;
    int rc = -1;

    assert(ble_hci_usb_rx_cmd_ll_cb);
    if (ble_hci_usb_rx_cmd_ll_cb) {
        buf = ble_hci_trans_buf_alloc(BLE_HCI_TRANS_BUF_CMD);
        assert(buf != NULL);
        memcpy(buf, hci_cmd, cmd_len);

        rc = ble_hci_usb_rx_cmd_ll_cb(buf, ble_hci_usb_rx_cmd_ll_arg);
    }

    if (rc != 0) {
        ble_hci_trans_buf_free(buf);
    }
}

static int
ble_hci_trans_ll_tx(struct tx_queue *queue, struct os_mbuf *om)
{
    struct ble_hci_pkt *pkt;
    os_sr_t sr;
    bool first;

    /* If this packet is zero length, just free it */
    if (OS_MBUF_PKTLEN(om) == 0) {
        os_mbuf_free_chain(om);
        return 0;
    }

    pkt = os_memblock_get(&ble_hci_pkt_pool);
    if (pkt == NULL) {
        os_mbuf_free_chain(om);
        return BLE_ERR_MEM_CAPACITY;
    }

    pkt->data = om;
    OS_ENTER_CRITICAL(sr);
    first = STAILQ_EMPTY(&queue->queue);
    STAILQ_INSERT_TAIL(&queue->queue, pkt, next);
    OS_EXIT_CRITICAL(sr);
    if (first) {
        tud_bt_acl_data_send(om->om_data, om->om_len);
    }

    return 0;
}

int
ble_hci_trans_ll_acl_tx(struct os_mbuf *om)
{
    return ble_hci_trans_ll_tx(&ble_hci_tx_acl_queue, om);
}

int
ble_hci_trans_ll_evt_tx(uint8_t *hci_ev)
{
    struct ble_hci_pkt *pkt;
    os_sr_t sr;
    bool first;

    assert(hci_ev != NULL);

    pkt = os_memblock_get(&ble_hci_pkt_pool);
    if (pkt == NULL) {
        ble_hci_trans_buf_free(hci_ev);
        return BLE_ERR_MEM_CAPACITY;
    }

    pkt->data = hci_ev;
    OS_ENTER_CRITICAL(sr);
    first = STAILQ_EMPTY(&ble_hci_tx_evt_queue.queue);
    STAILQ_INSERT_TAIL(&ble_hci_tx_evt_queue.queue, pkt, next);
    OS_EXIT_CRITICAL(sr);
    if (first) {
        tud_bt_event_send(hci_ev, hci_ev[1] + sizeof(struct ble_hci_ev));
    }

    return 0;
}

uint8_t *
ble_hci_trans_buf_alloc(int type)
{
    uint8_t *buf;

    switch (type) {
    case BLE_HCI_TRANS_BUF_CMD:
        assert(!usb_ble_hci_pool_cmd.allocated);
        usb_ble_hci_pool_cmd.allocated = 1;
        buf = usb_ble_hci_pool_cmd.cmd;
        break;

    case BLE_HCI_TRANS_BUF_EVT_HI:
        buf = os_memblock_get(&ble_hci_usb_evt_hi_pool);
        if (buf == NULL) {
            /* If no high-priority event buffers remain, try to grab a
             * low-priority one.
             */
            buf = ble_hci_trans_buf_alloc(BLE_HCI_TRANS_BUF_EVT_LO);
        }
        break;

    case BLE_HCI_TRANS_BUF_EVT_LO:
        buf = os_memblock_get(&ble_hci_usb_evt_lo_pool);
        break;

    default:
        assert(0);
        buf = NULL;
    }

    return buf;
}

void
ble_hci_trans_buf_free(uint8_t *buf)
{
    int rc;

    /* XXX: this may look a bit odd, but the controller uses the command
     * buffer to send back the command complete/status as an immediate
     * response to the command. This was done to insure that the controller
     * could always send back one of these events when a command was received.
     * Thus, we check to see which pool the buffer came from so we can free
     * it to the appropriate pool
     */
    if (os_memblock_from(&ble_hci_usb_evt_hi_pool, buf)) {
        rc = os_memblock_put(&ble_hci_usb_evt_hi_pool, buf);
        assert(rc == 0);
    } else if (os_memblock_from(&ble_hci_usb_evt_lo_pool, buf)) {
        rc = os_memblock_put(&ble_hci_usb_evt_lo_pool, buf);
        assert(rc == 0);
    } else {
        assert(usb_ble_hci_pool_cmd.allocated);
        usb_ble_hci_pool_cmd.allocated = 0;
    }
    (void)rc;
}

int
ble_hci_trans_reset(void)
{
    return 0;
}

void
ble_hci_usb_init(void)
{
    int rc;

    /* Ensure this function only gets called by sysinit. */
    SYSINIT_ASSERT_ACTIVE();

    rc = mem_init_mbuf_pool(ble_hci_pool_acl_mempool_buf, &ble_hci_pool_acl_mempool, &ble_hci_pool_acl_mbuf_pool,
                            MYNEWT_VAL(BLE_ACL_BUF_COUNT), ACL_BLOCK_SIZE, "ble_hci_acl");
    SYSINIT_PANIC_ASSERT(rc == 0);

    rc = os_mempool_init(&ble_hci_usb_evt_hi_pool,
                         MYNEWT_VAL(BLE_HCI_EVT_HI_BUF_COUNT),
                         MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE),
                         ble_hci_usb_evt_hi_buf,
                         "ble_hci_usb_evt_hi_pool");
    SYSINIT_PANIC_ASSERT(rc == 0);

    rc = os_mempool_init(&ble_hci_usb_evt_lo_pool,
                         MYNEWT_VAL(BLE_HCI_EVT_LO_BUF_COUNT),
                         MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE),
                         ble_hci_usb_evt_lo_buf,
                         "ble_hci_usb_evt_lo_pool");
    SYSINIT_PANIC_ASSERT(rc == 0);

    /*
     * Create memory pool of packet list nodes. NOTE: the number of these
     * buffers should be, at least, the total number of event buffers (hi
     * and lo), the number of command buffers (currently 1) and the total
     * number of buffers that the controller could possibly hand to the host.
     */
    rc = os_mempool_init(&ble_hci_pkt_pool,
                         BLE_HCI_USB_EVT_COUNT + 1 +
                         MYNEWT_VAL(BLE_HCI_ACL_OUT_COUNT),
                         sizeof (struct ble_hci_pkt),
                         ble_hci_pkt_buf,
                         "ble_hci_usb_pkt_pool");
    SYSINIT_PANIC_ASSERT(rc == 0);
}
