#include "include.h"
#include "usb_com.h"
#include "usb_hid.h"
#include "usb_vendor.h"
#include "usb_vendor_desc.h"
#include "fota_proc.h"

#if USB_VENDOR_EN

udv_t udv_0 AT(.usb_buf.usb);

epcb_t ep_vendor_tx AT(.usb_buf.usb);
epcb_t ep_vendor_rx AT(.usb_buf.usb);

u8 ep_vendor_buf_out[VENDOR_EP_OUT_MAX_SIZE + 4] AT(.usb_buf.vendor);          // CRC
u8 ep_vendor_buf_in[VENDOR_EP_IN_MAX_SIZE] AT(.usb_buf.vendor);

static usb_vendor_send_tag_t usb_vendor_send_pool[5] AT(.usb_buf.vendor);
static list_t usb_vendor_send_queue AT(.usb_buf.vendor);
static list_t usb_vendor_free_queue AT(.usb_buf.vendor);


static bool usb_vendor_is_valid(void)
{
    return true;
}

void ude_vendor_setvalid(bool valid)
{
    udv_t *udv = &udv_0;
    udv->valid = valid;
}

void usb_vendor_receive_callback(uint8_t *buf, uint32_t size)
{
#if (AB_FOT_CHANNEL & AB_FOT_CHANNEL_USB)
    usb_vendor_pdu_t *pdu = (usb_vendor_pdu_t *)buf;

    if (pdu->report_id == USB_VENDOR_REPORT_ID_APP) {
        fota_recv_packet_handle(pdu->payload, pdu->length, FOTA_CHL_USB);
    }
#endif
}

AT(.com_text.usb.vendor.kick)
void usb_vendor_send_prepare(uint8_t *buf, uint32_t *size)
{
    GLOBAL_INT_DISABLE();

    usb_vendor_send_tag_t *data = (usb_vendor_send_tag_t *)list_pop_front(&usb_vendor_send_queue);

    if (data) {
        memcpy(buf, &data->pdu, data->length);
        *size = data->length;
        list_push_back(&usb_vendor_free_queue, (list_hdr_t *)data);
    } else {
        *size = 0;
    }

    GLOBAL_INT_RESTORE();
}

AT(.usbdev.com)
void udv_rx_process(void)
{
    u16 size;
    udv_t *udv = &udv_0;

    size = usb_ep_get_rx_len(udv->int_out);

    usb_vendor_receive_callback(udv->int_out->buf, size);

    usb_ep_clear_rx_fifo(udv->int_out);
}

AT(.usbdev.com)
void udv_tx_process(void)
{
    usb_vendor_send_kick();
}

void usb_vendor_ep_reset(void)
{
    udv_t *udv = &udv_0;

    /* Any usb class can't always use ep0, so it't un-init when index is zere */

    if (udv->int_in->index) {
        usb_ep_reset(udv->int_in);
    }

    if (udv->int_out->index) {
        usb_ep_reset(udv->int_out);
    }
}

AT(.com_text.usb.vendor.kick)
void usb_vendor_send_kick(void)
{
    udv_t *udv = &udv_0;
    u32 size = 0;

    if (!usb_vendor_is_valid()) {
        return;
    }

    if (usb_ep_transfer(udv->int_in)) {
        usb_vendor_send_prepare(udv->int_in->buf, &size);
        if (size) {
            usb_ep_start_transfer(udv->int_in, size);
        }
    }
}

#if (AB_FOT_CHANNEL & AB_FOT_CHANNEL_USB)

int usb_vendor_app_mtu_get(uint32_t *mtu)
{
    if (!mtu) {
        return -1;
    }

    /* usb_vendor_pdu_t::payload */
    *mtu = usb_vendor_fmt_size_get(USB_VENDOR_REPORT_ID_APP) - 2;

    /* No error */
    return 0;
}

AT(.com_text.usb.vendor.xfer)
int usb_vendor_app_send(uint8_t *buf, uint16_t size)
{
    GLOBAL_INT_DISABLE();

    usb_vendor_send_tag_t *free = (usb_vendor_send_tag_t *)list_pop_front(&usb_vendor_free_queue);
    int status = -1;

    /* ASSERT(size <= sizeof(usb_vendor_pdu_t::payload)) */

    if (free) {
        free->length        = usb_vendor_fmt_size_get(USB_VENDOR_REPORT_ID_APP);
        free->pdu.report_id = USB_VENDOR_REPORT_ID_APP;
        free->pdu.length    = size;
        memcpy(free->pdu.payload, buf, size);
        memset(free->pdu.payload + size, 0x00, free->length - size - 1);
        list_push_back(&usb_vendor_send_queue, (list_hdr_t *)free);
        status = 0; /* No error */

        thread_driver_msg_post(THREAD_DRIVER_MSG_VENDOR_IN);
    }

    GLOBAL_INT_RESTORE();

    return status;
}

#endif // (AB_FOT_CHANNEL & AB_FOT_CHANNEL_USB)

void udv_init(void)
{
    epcb_t *epcb;
    udv_t *udv = &udv_0;

    udv_0.int_in = &ep_vendor_tx;
    udv_0.int_out = &ep_vendor_rx;

    memset(udv->int_in, 0x00, sizeof(epcb_t));
    memset(udv->int_out, 0x00, sizeof(epcb_t));

    usb_ep_callback_register(udv_tx_process, USB_VENDOR_EP_IN_INDEX, EP_DIR_IN);
    usb_ep_callback_register(udv_rx_process, USB_VENDOR_EP_OUT_INDEX, EP_DIR_OUT);

    memset(ep_vendor_buf_in,  0, sizeof(ep_vendor_buf_in));
    epcb = udv->int_in;
    epcb->dir = EP_DIR_IN;
    epcb->index = USB_VENDOR_EP_IN_INDEX;
    epcb->type = EP_TYPE_INTR;
    epcb->epsize = VENDOR_EP_IN_MAX_SIZE;
    epcb->buf = ep_vendor_buf_in;
    usb_ep_init(epcb);

    memset(ep_vendor_buf_out,  0, sizeof(ep_vendor_buf_out));
    epcb = udv->int_out;
    epcb->dir = EP_DIR_OUT;
    epcb->index = USB_VENDOR_EP_OUT_INDEX;
    epcb->type = EP_TYPE_INTR;
    epcb->epsize = VENDOR_EP_OUT_MAX_SIZE;
    epcb->buf = ep_vendor_buf_out;
    usb_ep_init(epcb);

    ude_vendor_setvalid(false);
}

void usb_vendor_init(void)
{
    usb_vendor_send_queue.first = NULL;
    usb_vendor_send_queue.last  = NULL;

    list_pool_init(
        &usb_vendor_free_queue,
        &usb_vendor_send_pool[0],
        sizeof(usb_vendor_send_pool[0]),
        sizeof(usb_vendor_send_pool) / sizeof(usb_vendor_send_pool[0])
    );

#if (AB_FOT_CHANNEL & AB_FOT_CHANNEL_USB)
    ab_fota_cb_t param;
    param.fota_mtu_check_handle = usb_vendor_app_mtu_get;
    param.fota_send_packet_handle = usb_vendor_app_send;
    fota_init(&param, FOTA_CHL_USB);
#endif
}

void usb_vendor_deinit(void)
{
    ude_vendor_setvalid(false);

#if (AB_FOT_CHANNEL & AB_FOT_CHANNEL_USB)
    fota_disconnected_handle(FOTA_CHL_USB);
#endif
}

void usb_vendor_process(void)
{
    /* Add application layer initialize if need */
}

#endif // USB_VENDOR_EN
