#include "include.h"
#include "wireless_client.h"
#include "wireless_init.h"
#include "usb_audio.h"
#include "usb_hid.h"
#include "usb_vendor.h"
#include "utils_ring_buffer.h"


#define TRACE_EN                            1
#define TRACE_RATE_EN                       0
#define TRACE_RATE_PERIOD                   400     // Unit: connection interval

#define CFG_CONN_INTV                       2       // Unit: 1.25ms
#define CFG_CONN_LAT                        1     // Unit: connection interval
#define CFG_CONN_TO                         20     // Unit: 10ms

#if TRACE_EN
#define TRACE(...)                          my_printf("[WIRELESS] ");\
                                            my_printf(__VA_ARGS__)
#define TRACE_R(...)                        my_print_r(__VA_ARGS__);
#else
#define TRACE(...)
#define TRACE_R(...)
#endif // TRACE_EN

#define SWDIAG(func, filed, value)

#define DBG_FLOW_CTRL_EN                    0


static struct {
    uint16_t                    con_handle;
    bool                        is_bond;
    wireless_addr_info_typedef  bond_addr_info;
} wireless_cb AT(.ram_text.wireless.sta);

static const uint8_t adv_match_seq[4] = {0x03, 0xff, 0x42, 0x06};

/* Expect set to 2 + payload_length(transmit: e.g USB Vendor is 63)  */
static uint8_t transmit_buffer[1 + 2 + 63];

#if TRACE_RATE_EN
static uint32_t statistic_frame_count;
static uint32_t statistic_hid_pkts_count;
#endif

/* The max number of caches uploads from transport layer cannot be modify curr Ver. */
static struct {
    uint8_t *           tbl[5];
    uint8_t             siz[5];
    uint8_t             widx;
    uint8_t             ridx;
    uint8_t             cnt;
    bt_timer_mem_t      fc_timer_intance;
    bt_timer_handle_t   fc_timer_handle;
} wireless_rxbuf_cb AT(.ram_text.wireless.xfer);

#define WIRELESS_RXBUF_RELEASE_TIMEOUT      50  /* ms */

#define wireless_rxbuf_is_empty()           (wireless_rxbuf_cb.cnt == 0)

#define wireless_rxbuf_r_advance()          {wireless_rxbuf_cb.ridx=(wireless_rxbuf_cb.ridx+1)%5;wireless_rxbuf_cb.cnt--;}
#define wireless_rxbuf_r_head_buf()         (wireless_rxbuf_cb.tbl[wireless_rxbuf_cb.ridx])
#define wireless_rxbuf_r_head_siz()         (wireless_rxbuf_cb.siz[wireless_rxbuf_cb.ridx])

#define wireless_rxbuf_w_advance()          {wireless_rxbuf_cb.widx=(wireless_rxbuf_cb.widx+1)%5;wireless_rxbuf_cb.cnt++;}
#define wireless_rxbuf_w_head_buf()         (wireless_rxbuf_cb.tbl[wireless_rxbuf_cb.widx])
#define wireless_rxbuf_w_head_siz()         (wireless_rxbuf_cb.siz[wireless_rxbuf_cb.widx])

#define wireless_conn_is_exist()            (wireless_cb.con_handle != 0)


AT(.com_text.wireless.transmit)
static void wireless_packet_process(uint8_t *buf, uint32_t size)
{
    u8 head = buf[WIRELESS_HEAD_POS];
    u8 pos = WIRELESS_PAYLOAD_POS;
    u8 *hid_data = NULL;
    u8 *mic_data = NULL;
    u8 *cmd_data = NULL, cmd_len = 0, cmd_type;

    /* Unpack data */

    if (head & WIRELESS_HEADER_HID_BIT) {
        hid_data = &buf[pos];
        pos += WIRELESS_DATA_LENGTH_HID;
        #if TRACE_RATE_EN
        if (statistic_frame_count > 0 && statistic_frame_count <= TRACE_RATE_PERIOD) {
            statistic_hid_pkts_count++;
        }
        #endif
    }

    if (head & WIRELESS_HEADER_AUDIO_BIT) {
        mic_data = &buf[pos];
        pos += WIRELESS_DATA_LENGTH_AUDIO;
    }

    if (head & WIRELESS_HEADER_CTRL_BIT) {
        cmd_data = &buf[pos];
        cmd_type = cmd_data[WIRELESS_CMD_DATA_TYPE_POS];
        cmd_len = cmd_data[WIRELESS_CMD_DATA_LENGTH_POS];
        pos += 2 + cmd_len;
    }

    /* Check length valid */

    if (pos != size) {
        TRACE("data length err: %d %d\n", pos, size);
        return;
    }

    /* Process */

#if USB_HID_EN
    if (hid_data && sys_cb.usb_is_active) {
        usb_hid_buffer_push(hid_data, WIRELESS_DATA_LENGTH_HID);
        thread_driver_msg_post(THREAD_DRIVER_MSG_HID_IN);
    }
#else
    (void)hid_data;
#endif

#if USB_MIC_EN
    if (mic_data && sys_cb.usb_is_active) {
        SWDIAG(RECV, IO, ENTER);
        uda_mic_buffer_prepare(mic_data, WIRELESS_DATA_LENGTH_AUDIO);
        thread_driver_msg_post(THREAD_DRIVER_MSG_UAC_DEC);
        SWDIAG(RECV, IO, EXIT);
    }
#else
    (void)mic_data;
#endif

    if (cmd_data) {
        TRACE("receive cmd - type:0x%02x data[%d]:\n", cmd_type, cmd_len);
        TRACE_R(&cmd_data[WIRELESS_CMD_DATA_VALUE_POS], cmd_len);

        switch (cmd_type) {

            default:
                break;
        }
    }
}
/*
AT(.com_text.wireless.transmit)
NO_INLINE static void wireless_packet_rxbuf_check(void)
{
#if DBG_FLOW_CTRL_EN
    uint8_t *packet = wireless_rxbuf_r_head_buf();
    uint8_t size = wireless_rxbuf_r_head_siz();

    if (wireless_rxbuf_is_empty()) {
        return;
    }

    if (!wireless_conn_is_exist()) {
        while (!wireless_rxbuf_is_empty()) {
            wireless_rxbuf_release(packet);
            wireless_rxbuf_r_advance();
            packet = wireless_rxbuf_r_head_buf();
        }
        return;
    }

    if ((packet[WIRELESS_HEAD_POS] & WIRELESS_HEADER_HID_BIT)
      #if USB_HID_EN
        && (!usb_hid_buffer_push_check())
      #endif // USB_HID_EN
      )
    {
        return;
    }

    bt_alarm_timer_stop(wireless_rxbuf_cb.fc_timer_handle);
    wireless_packet_process(packet, size);

    GLOBAL_INT_DISABLE();
    wireless_rxbuf_release(packet);
    wireless_rxbuf_r_advance();
    GLOBAL_INT_RESTORE();

    if (!wireless_rxbuf_is_empty()) {
        bt_alarm_timer_start(wireless_rxbuf_cb.fc_timer_handle);
    }
#endif
}
*/
AT(.text.wireless.transmit)
static void wireless_packet_rxbuf_timeout_callback(bt_timer_handle_t handle)
{
    if (!wireless_rxbuf_is_empty()) {
        do
        {
            wireless_rxbuf_release(wireless_rxbuf_r_head_buf());
            wireless_rxbuf_r_advance();
        }
        while (!wireless_conn_is_exist() && !wireless_rxbuf_is_empty());

        if (!wireless_rxbuf_is_empty()) {
            bt_alarm_timer_start(wireless_rxbuf_cb.fc_timer_handle);
        }
    }
}

AT(.com_text.dbg.statistic)
void wireless_frame_start_hook(void)
{
#if TRACE_RATE_EN
    if (statistic_frame_count <= TRACE_RATE_PERIOD) {
        statistic_frame_count++;
    }
#endif
}

static void wireless_event_packet_handler(uint8_t event_type, uint8_t *packet, uint8_t size)
{
    switch (event_type) {
        case WIRELESS_EVENT_CONNECTED:
            TRACE("[master]connected - con_handle: 0x%04x\n", GET_LE16(&packet[0]));
            wireless_cb.con_handle = GET_LE16(&packet[0]);
            memcpy((u8 *)&wireless_cb.bond_addr_info, &packet[2], 7);
            /* Record link info */
            bsp_wireless_link_info_write(&wireless_cb.bond_addr_info);
            wireless_cb.is_bond = true;
            /* Trigger event that notify peer the mic and usb status */
            thread_driver_msg_post(THREAD_DRIVER_MSG_UAC_MIC_CTRL);
            thread_driver_msg_post(THREAD_DRIVER_MSG_USB_IND);
            wireless_frame_start_hook_register(wireless_cb.con_handle, wireless_frame_start_hook);
            break;

        case WIRELESS_EVENT_DISCONNECTED:
            TRACE("[master]disconnected - con_handle: 0x%04x  reason: 0x%02x\n", GET_LE16(&packet[0]), packet[2]);
            wireless_cb.con_handle = 0;
            wireless_scan_ctrl(true);
            break;

        case WIRELESS_EVENT_ADV_REPORT: {
            wireless_adv_report_t *adv_msg = (wireless_adv_report_t *)&packet[0];
            wireless_adv_slice_t  *adv_sli = (wireless_adv_slice_t  *)adv_msg->report;
            bool found = false;
            TRACE("adv report - type: 0x%02x  addr: ", adv_msg->addr_type);
            TRACE_R(adv_msg->addr, 6);

            do {
                if (wireless_cb.is_bond) {
                    if (0 == memcmp(adv_msg->addr, wireless_cb.bond_addr_info.addr, 6)) {
                        /* It's the last connected device */
                        found = true;
                        break;
                    }
                    // No break: connects to the last device or any non-directional broadcast device.
                }

                /* Try to find the specified seq in undir adv data */
                while (((u8 *)adv_sli - adv_msg->report) < adv_msg->report_len) {
                    if (0 == memcmp(adv_sli, adv_match_seq, 4)) {
                        found = true;
                        break;
                    }
                    adv_sli = (wireless_adv_slice_t *)(adv_sli->data + adv_sli->length - 1);
                }
            } while (0);

            if (found) {
                wireless_scan_ctrl(false);
                wireless_connect_by_addr(adv_msg->addr, adv_msg->addr_type, CFG_CONN_INTV, CFG_CONN_LAT, CFG_CONN_TO);
            }

            break;
        }

        default:
            break;
    }
}

AT(.com_text.wireless.transmit)
static void wireless_receive_handler(uint16_t con_handle, uint8_t *packet, uint8_t size)
{
#if DBG_FLOW_CTRL_EN
    if (con_handle == wireless_cb.con_handle) {
        if (wireless_rxbuf_is_empty()) {
            bt_alarm_timer_start(wireless_rxbuf_cb.fc_timer_handle);
        } else {
            /* ASSERT: fc timer already start */
        }
        wireless_rxbuf_cb.tbl[wireless_rxbuf_cb.widx] = packet;
        wireless_rxbuf_cb.siz[wireless_rxbuf_cb.widx] = size;
        wireless_rxbuf_w_advance();
    } else {
        wireless_rxbuf_release(packet);
    }
#else
    if (con_handle == wireless_cb.con_handle) {
        wireless_packet_process(packet, size);
    }

    wireless_rxbuf_release(packet);
#endif
}

void wireless_client_init(void)
{
    TRACE("master init\n");

    memset(&wireless_cb, 0x00, sizeof(wireless_cb));
    memset(&wireless_rxbuf_cb, 0x00, sizeof(wireless_rxbuf_cb));

    wireless_cb.is_bond = bsp_wireless_link_info_read(&wireless_cb.bond_addr_info);

    bt_alarm_timer_mem_extend(&wireless_rxbuf_cb.fc_timer_intance, sizeof(wireless_rxbuf_cb.fc_timer_intance));
    bt_alarm_timer_acquire(&wireless_rxbuf_cb.fc_timer_handle, WIRELESS_RXBUF_RELEASE_TIMEOUT, 0, wireless_packet_rxbuf_timeout_callback);

    wireless_event_handler_register(wireless_event_packet_handler);
    wireless_receive_handler_register(wireless_receive_handler);
}
/*
void ble_app_proc(void)
{
#if TRACE_RATE_EN
    if (statistic_frame_count > TRACE_RATE_PERIOD) {
        TRACE("rate: %dhids/%dFrames\n", statistic_hid_pkts_count, TRACE_RATE_PERIOD);
        statistic_hid_pkts_count = 0;
        statistic_frame_count = 0;
    }
#endif

    wireless_packet_rxbuf_check();
}
*/
/* Single packet, no compose with HID or AUDIO */
void wireless_send_ctrl_cmd(uint8_t type, uint8_t length, uint8_t *value)
{
    u8 *cmd_data = &transmit_buffer[WIRELESS_PAYLOAD_POS];
    u8 packet_len = 1 + 2 + length;
    u32 tick, timeout = 500;
    u8 mtu = 0;

    wireless_mtu_get(wireless_cb.con_handle, &mtu);

    if (packet_len > mtu || packet_len > sizeof(transmit_buffer)) {
        return;
    }

    cmd_data[WIRELESS_CMD_DATA_TYPE_POS] = type;
    cmd_data[WIRELESS_CMD_DATA_LENGTH_POS] = length;

    if (length && value) {
        memcpy(&cmd_data[WIRELESS_CMD_DATA_VALUE_POS], value, length);
    }

    transmit_buffer[WIRELESS_HEAD_POS] = WIRELESS_HEADER_CTRL_BIT;

    tick = tick_get();
    while (wireless_send_for_con(wireless_cb.con_handle, transmit_buffer, packet_len)) {
        if (tick_check_expire(tick, timeout)) {
            break;
        }
        delay_5ms(1);
    }
}
