/*
 * Copyright (c) 2018 ARM Limited
 *
 * Licensed 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 "CordioBLE.h"
#include "CordioHCIDriver.h"
#include "hci_defs.h"
#include "hci_api.h"
#include "hci_cmd.h"
#include "hci_core.h"
#include "bstream.h"
#include "wsf_buf.h"
#include <stdbool.h>
#include "hci_mbed_os_adaptation.h"
#include "H4TransportDriver.h"
#include "OdinCordioInterface.h"

namespace ble {
namespace vendor {
namespace odin_w2 {

class HCIDriver : public cordio::CordioHCIDriver {
public:
    HCIDriver(cordio::CordioHCITransportDriver &transport_driver, PinName shutdown_name, PinName hci_rts_name) :
        cordio::CordioHCIDriver(transport_driver),
        shutdown(shutdown_name, 0),
        hci_rts(hci_rts_name, 0),
        service_pack_index(0),
        service_pack_transfered(false) {
    };

    virtual cordio::buf_pool_desc_t get_buffer_pool_description();

    virtual void do_initialize();

    virtual void do_terminate();

    virtual void start_reset_sequence();

    virtual void handle_reset_sequence(uint8_t *pMsg);

private:
    void start_service_pack_transfer(void)
    {
        service_pack_index = 0;
        service_pack_transfered = false;
        send_service_pack_command();
    }

    void send_service_pack_command(void)
    {
        uint16_t cmd_len = odin_service_pack[service_pack_index + HCI_CMD_HDR_LEN];
        cmd_opcode_ack_expected = (odin_service_pack[service_pack_index + 2] << 8) | odin_service_pack[service_pack_index + 1];
        uint8_t *pBuf = hciCmdAlloc(cmd_opcode_ack_expected, cmd_len);
        if (pBuf) {
            memcpy(pBuf, odin_service_pack + service_pack_index + 1, cmd_len + HCI_CMD_HDR_LEN);
            hciCmdSend(pBuf);
        } else {
            printf("Error cannot allocate memory for the buffer");
        }
    }

    void ack_service_pack_command(uint16_t opcode, uint8_t *msg)
    {
        /* check if response opcode is same as expected command opcode */
        MBED_ASSERT (cmd_opcode_ack_expected == opcode); 

        /* update service pack index */
        service_pack_index += (1 + HCI_CMD_HDR_LEN + odin_service_pack[service_pack_index + HCI_CMD_HDR_LEN]);

        if (service_pack_index < service_pack_size)
            send_service_pack_command();
        else if (opcode == HCID_VS_WRITE_BD_ADDR) {
           /* send an HCI Reset command to start the sequence */
            HciResetCmd();
            service_pack_transfered = true;
        } else {
            /* send BT device hardware address write command */
            send_hci_vs_cmd(HCID_VS_WRITE_BD_ADDR);
            cmd_opcode_ack_expected = HCID_VS_WRITE_BD_ADDR;
        }
    }

    void hci_read_resolving_list_size(void)
    {
        /* if LL Privacy is supported by Controller and included */
        if ((hciCoreCb.leSupFeat & HCI_LE_SUP_FEAT_PRIVACY) &&
            (hciLeSupFeatCfg & HCI_LE_SUP_FEAT_PRIVACY)) {
            /* send next command in sequence */
            HciLeReadResolvingListSize();
        } else {
            hciCoreCb.resListSize = 0;

            /* send next command in sequence */
            hci_read_max_data_len();
        }
    }

    void hci_read_max_data_len(void)
    {
        /* if LE Data Packet Length Extensions is supported by Controller and included */
        if ((hciCoreCb.leSupFeat & HCI_LE_SUP_FEAT_DATA_LEN_EXT) &&
            (hciLeSupFeatCfg & HCI_LE_SUP_FEAT_DATA_LEN_EXT)) {
            /* send next command in sequence */
            HciLeReadMaxDataLen();
        } else {
            /* send next command in sequence */
            HciLeRandCmd();
        }
    }

    DigitalOut      shutdown;                       // power/shutdown pin for bt device
    DigitalOut      hci_rts;                        // request to sent pin
    size_t          service_pack_index;             // Index of command to be recently sent over hci
    bool            service_pack_transfered;        // Flag to notify if service pack is completely transferred or not
    uint16_t        cmd_opcode_ack_expected;        // Command against which acknowledgment is expected
    uint32_t        service_pack_size;              // size of service pack
    char            *odin_service_pack ;            // Service pack needs to be provided by driver
    vs_cmd_send_t   send_hci_vs_cmd ;               // callback function to call vendor specific call handler

};

} // namespace odin_w2
} // namespace vendor
} // namespace ble


ble::vendor::cordio::buf_pool_desc_t ble::vendor::odin_w2::HCIDriver::get_buffer_pool_description()
{
    // Use default buffer pool
    return ble::vendor::cordio::CordioHCIDriver::get_default_buffer_pool_description();
}

void ble::vendor::odin_w2::HCIDriver::do_initialize()
{
    cordio_callback_s    callback;

    hci_rts = 1;            // Flow Control is OFF

    shutdown = 0;           // BT Power is OFF
    wait_ms(20);
    shutdown = 1;           // BT Power is ON
    wait_ms(500);

    hci_rts = 0;            // Flow Control is ON

    /* ODIN ble driver initialization function  */
    cbCordio_Btinit(&callback);

    odin_service_pack = callback.Service_pack;
    send_hci_vs_cmd = callback.vs_command_callback;
    service_pack_size = callback.service_pack_size;
}

void ble::vendor::odin_w2::HCIDriver::do_terminate()
{
    
}

void ble::vendor::odin_w2::HCIDriver::start_reset_sequence()
{
    /* Update baudrate of ble to speed up setup time */
    send_hci_vs_cmd(HCID_VS_UPDATE_UART_BAUD_RATE);

}

void ble::vendor::odin_w2::HCIDriver::handle_reset_sequence(uint8_t *pMsg)
{
    uint16_t       opcode;
    static uint8_t randCnt;

    /* if event is a command complete event */
    if (*pMsg == HCI_CMD_CMPL_EVT) {
        /* parse parameters */
        pMsg += HCI_EVT_HDR_LEN;
        pMsg++;                   /* skip num packets */
        BSTREAM_TO_UINT16(opcode, pMsg);
        pMsg++;                   /* skip status */

        if (opcode == HCID_VS_UPDATE_UART_BAUD_RATE) {
            update_uart_baud_rate();
            start_service_pack_transfer();
            return;
        }

        if (service_pack_transfered == false) {
            ack_service_pack_command(opcode, pMsg);
            return;
        }

        /* decode opcode */
        switch (opcode) {

            case HCI_OPCODE_RESET:
                /* Send (fast and slow) clock configuration command */
                send_hci_vs_cmd(HCID_VS_FAST_CLOCK_CONFIG_BTIP);
                break;

            case HCID_VS_FAST_CLOCK_CONFIG_BTIP:
                /* Send deep-sleep behavior control command (setting retransmission, inactivity and rts pulse width for Bt) */
                send_hci_vs_cmd(HCID_VS_HCILL_PARS_CFG);
                break;

            case HCID_VS_HCILL_PARS_CFG:
                /* Send sleep mode configuration command */
                send_hci_vs_cmd(HCID_VS_SLEEP_PROTOCOLS_CFG);
                break;

            case HCID_VS_SLEEP_PROTOCOLS_CFG:
                /* initialize rand command count */
                randCnt = 0;

                /* send next command in sequence */
                HciSetEventMaskCmd((uint8_t *)hciEventMask);
                break;

            case HCI_OPCODE_SET_EVENT_MASK:
                /* send next command in sequence */
                HciLeSetEventMaskCmd((uint8_t *)hciLeEventMask);
                break;

            case HCI_OPCODE_LE_SET_EVENT_MASK:
                /* send next command in sequence */
                HciSetEventMaskPage2Cmd((uint8_t *)hciEventMaskPage2);
                break;

            case HCI_OPCODE_SET_EVENT_MASK_PAGE2:
                /* send next command in sequence */
                HciReadBdAddrCmd();
                break;

            case HCI_OPCODE_READ_BD_ADDR:
                /* parse and store event parameters */
                BdaCpy(hciCoreCb.bdAddr, pMsg);

                /* send next command in sequence */
                HciLeReadBufSizeCmd();
                break;

            case HCI_OPCODE_LE_READ_BUF_SIZE:
                /* parse and store event parameters */
                BSTREAM_TO_UINT16(hciCoreCb.bufSize, pMsg);
                BSTREAM_TO_UINT8(hciCoreCb.numBufs, pMsg);

                /* initialize ACL buffer accounting */
                hciCoreCb.availBufs = hciCoreCb.numBufs;

                /* send next command in sequence */
                HciLeReadSupStatesCmd();
                break;

            case HCI_OPCODE_LE_READ_SUP_STATES:
                /* parse and store event parameters */
                memcpy(hciCoreCb.leStates, pMsg, HCI_LE_STATES_LEN);

                /* send next command in sequence */
                HciLeReadWhiteListSizeCmd();
                break;

            case HCI_OPCODE_LE_READ_WHITE_LIST_SIZE:
                /* parse and store event parameters */
                BSTREAM_TO_UINT8(hciCoreCb.whiteListSize, pMsg);

                /* send next command in sequence */
                HciLeReadLocalSupFeatCmd();
                break;

            case HCI_OPCODE_LE_READ_LOCAL_SUP_FEAT:
                /* parse and store event parameters */
                BSTREAM_TO_UINT16(hciCoreCb.leSupFeat, pMsg);

                /* send next command in sequence */
                hci_read_resolving_list_size();
                break;

            case HCI_OPCODE_LE_READ_RES_LIST_SIZE:
                /* parse and store event parameters */
                BSTREAM_TO_UINT8(hciCoreCb.resListSize, pMsg);

                /* send next command in sequence */
                hci_read_max_data_len();
                break;

            case HCI_OPCODE_LE_READ_MAX_DATA_LEN:
                uint16_t maxTxOctets;
                uint16_t maxTxTime;

                BSTREAM_TO_UINT16(maxTxOctets, pMsg);
                BSTREAM_TO_UINT16(maxTxTime, pMsg);

               /* use Controller's maximum supported payload octets and packet duration times
                * for transmission as Host's suggested values for maximum transmission number
                * of payload octets and maximum packet transmission time for new connections.
                */
                HciLeWriteDefDataLen(maxTxOctets, maxTxTime);
                break;

            case HCI_OPCODE_LE_WRITE_DEF_DATA_LEN:
                if (hciCoreCb.extResetSeq) {
                    /* send first extended command */
                    (*hciCoreCb.extResetSeq)(pMsg, opcode);
                } else {
                    /* initialize extended parameters */
                    hciCoreCb.maxAdvDataLen = 0;
                    hciCoreCb.numSupAdvSets = 0;
                    hciCoreCb.perAdvListSize = 0;

                    /* send next command in sequence */
                    HciLeRandCmd();
                }
                break;

            case HCI_OPCODE_LE_READ_MAX_ADV_DATA_LEN:
            case HCI_OPCODE_LE_READ_NUM_SUP_ADV_SETS:
            case HCI_OPCODE_LE_READ_PER_ADV_LIST_SIZE:
                if (hciCoreCb.extResetSeq) {
                    /* send next extended command in sequence */
                    (*hciCoreCb.extResetSeq)(pMsg, opcode);
                }
                break;

            case HCI_OPCODE_LE_RAND:
                /* check if need to send second rand command */
                if (randCnt < (HCI_RESET_RAND_CNT - 1)) {
                    randCnt++;
                    HciLeRandCmd();
                } else {
                    uint8_t addr[6] = { 0 };
                    memcpy(addr, pMsg, sizeof(addr));
                    DM_RAND_ADDR_SET(addr, DM_RAND_ADDR_STATIC);
                    // note: will invoke set rand address
                    cordio::BLE::deviceInstance().getGap().setAddress(
                        BLEProtocol::AddressType::RANDOM_STATIC,
                        addr
                    );
                }
                break;

            case HCI_OPCODE_LE_SET_RAND_ADDR:
                /* send next command in sequence */
                signal_reset_sequence_done();
                break;

            default:
                break;
        }
    }
}

ble::vendor::cordio::CordioHCIDriver& ble_cordio_get_hci_driver() {
    static ble::vendor::cordio::H4TransportDriver transport_driver (/* cbCFG_PIO_PIN_BT_HCI_TX */ PG_14, 
                                                                    /* cbCFG_PIO_PIN_BT_HCI_RX */ PC_7, 
                                                                    /* cbCFG_PIO_PIN_BT_HCI_CTS */ PG_15, 
                                                                    /* cbCFG_PIO_PIN_BT_HCI_RTS */ PG_12, 
                                                                    115200);
    static ble::vendor::odin_w2::HCIDriver hci_driver ( transport_driver, 
                                                        /* cbCFG_PIO_PIN_BT_ENABLE  */ PG_7, 
                                                        /* cbCFG_PIO_PIN_BT_HCI_RTS */ PG_12);

    return hci_driver;
}
