#ifdef SUPPORT_MS_EXTENSIONS
#pragma bss_seg(".usb.data.bss")
#pragma data_seg(".usb.data")
#pragma code_seg(".usb.text")
#pragma const_seg(".usb.text.const")
#pragma str_literal_override(".usb.text.const")
#endif

#include "includes.h"
#include "asm/includes.h"
#include "app_config.h"
#include "system/timer.h"
#include "device/ioctl_cmds.h"
#include "device_drive.h"
#if TCFG_HID_HOST_ENABLE
#include "usb/host/usb_host.h"
#include "usb_ctrl_transfer.h"
#include "usb_bulk_transfer.h"
#include "hid.h"
#include "usb_config.h"
#include "usb_hid_keys.h"

#define MAIN_ITEM    0
#define GLOBAL_ITEM  1
#define LOCAL_ITEM   2

#define LOG_TAG_CONST       USB
#define LOG_TAG             "[HID]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
/* #define LOG_DUMP_ENABLE */
#define LOG_CLI_ENABLE
#include "debug.h"


struct hid_device_t hid_device[USB_MAX_HW_NUM][MAX_HOST_INTERFACE];

static int set_power(struct usb_host_device *host_dev, u32 value)
{
    const usb_dev usb_id = host_device2id(host_dev);
    memset(hid_device[usb_id], 0, sizeof(hid_device[usb_id]));
    return DEV_ERR_NONE;
}

static int get_power(struct usb_host_device *host_dev, u32 value)
{
    return DEV_ERR_NONE;
}

static const struct interface_ctrl hid_ctrl = {
    .interface_class = USB_CLASS_HID,
    .set_power = set_power,
    .get_power = get_power,
    .ioctl = NULL,
};

static const struct usb_interface_info _usb_if[USB_MAX_HW_NUM][MAX_HOST_INTERFACE] = {
    {
        {
            .ctrl = (struct interface_ctrl *) &hid_ctrl,
            .dev.hid = &hid_device[0][0],
        },
        {
            .ctrl = (struct interface_ctrl *) &hid_ctrl,
            .dev.hid = &hid_device[0][1],
        },
        {
            .ctrl = (struct interface_ctrl *) &hid_ctrl,
            .dev.hid = &hid_device[0][2],
        },
        {
            .ctrl = (struct interface_ctrl *) &hid_ctrl,
            .dev.hid = &hid_device[0][3],
        },
    },
#if USB_MAX_HW_NUM > 1
    {
        {
            .ctrl = (struct interface_ctrl *) &hid_ctrl,
            .dev.hid = &hid_device[1][0],
        },
        {
            .ctrl = (struct interface_ctrl *) &hid_ctrl,
            .dev.hid = &hid_device[1][1],
        },
        {
            .ctrl = (struct interface_ctrl *) &hid_ctrl,
            .dev.hid = &hid_device[1][2],
        },
        {
            .ctrl = (struct interface_ctrl *) &hid_ctrl,
            .dev.hid = &hid_device[1][3],
        },
    },
#endif
};

static u8 interval[USB_MAX_HW_NUM][16];

int usb_hid_parser(struct usb_host_device *host_dev, u8 interface_num, const u8 *pBuf)
{
    struct usb_interface_descriptor *interface = (struct usb_interface_descriptor *)pBuf;
    pBuf += sizeof(struct usb_interface_descriptor);
    int len = 0;
    const usb_dev usb_id = host_device2id(host_dev);

    struct usb_endpoint_descriptor *endpoint;

    pBuf += 9;//hid desc;

    const struct usb_interface_info *usb_if = &_usb_if[usb_id][interface_num];

    memset(usb_if->dev.p, 0, sizeof(struct hid_device_t));

    host_dev->interface_info[interface_num] = usb_if;
    usb_if->dev.hid->parent = host_dev;

    log_info("hid eps %d  %d %x %x", interface->bNumEndpoints, interface_num, usb_if, usb_if->dev.p);

    log_info("parent %x hid @ interface %d usb_if %x hid %x",
             host_dev, interface_num, usb_if, usb_if->dev.hid);

    if ((interface->bInterfaceProtocol == 0x02) ||
        (interface->bInterfaceProtocol == 0x01)) { //mouse & keyboard

        usb_if->dev.hid->bNumEndpoints = interface->bNumEndpoints;
        usb_if->dev.hid->report_list[0].usage = interface->bInterfaceProtocol;
        for (int i = 0 ; i < interface->bNumEndpoints; i++) {
            endpoint = (struct usb_endpoint_descriptor *)pBuf;
            if (USB_DIR_IN & endpoint->bEndpointAddress) {
                const u8 ep = endpoint->bEndpointAddress & 0x0f;
                usb_if->dev.hid->ep_pair[i] = ep;
                interval[usb_id][ep] = endpoint->bInterval;
                log_info("interfacenum = %d,endpoint = %x interval = %x",
                         interface->bInterfaceNumber, endpoint->bEndpointAddress, endpoint->bInterval);
            }
            pBuf += endpoint->bLength;
        }
    } else {
        log_info("vendor");
        host_dev->interface_info[interface_num] = NULL; //???
        for (int i = 0 ; i < interface->bNumEndpoints; i++) {
            endpoint = (struct usb_endpoint_descriptor *)pBuf;
            if (USB_DIR_IN & endpoint->bEndpointAddress) {
                /* interface_endpoint[interface->bInterfaceNumber] = endpoint->bEndpointAddress & 0x0f; */
                log_info("interfacenum = %d,endpoint = %x interval = %x",
                         interface->bInterfaceNumber, endpoint->bEndpointAddress, endpoint->bInterval);
            }
            pBuf += endpoint->bLength;
        }
        return sizeof(struct usb_interface_descriptor);
    }

    return pBuf - (u8 *)interface;
}

static u32 _hid_report_parse(struct hid_device_t *hid, const u8 *report, u32 len)
{
    hid->report_count = 0;
    struct report_info_t null_rpt;
    struct report_info_t *const rpt = &null_rpt;

    memset(rpt, 0, sizeof(*rpt));

    unsigned char ops;
    int index = 0;
    u8 report_size = 0;
    u8 report_count = 0;
    u8 cur_ops_is_report_size_count = 0;
    u8 old_ops_is_report_size_count = 0;
    s8 cur_section_bit = 0;
    u8 input_bit_index = 0;
    u8 total_bits = 0;
    u8 output_bit_index = 0;
    u8 cur_usage = 0;
    u32 undef_type = 0;
    u8 undef_usage = 0;
    u8 collection_deep = 0;

    while (index < len) {
        ops = (char)report[index++];
        char bSize = ops & 0x03;
        bSize = bSize == 3 ? 4 : bSize; // size is 4 when bSize is 3
        char bType = (ops >> 2) & 0x03;
        char bTag = (ops >> 4) & 0x0F;

        cur_ops_is_report_size_count = 0;
        char bSizeActual = 0;
        int itemVal = 0;
        for (int j = 0; j < bSize; j++) {
            if (index + j < len) {
                itemVal += report[index + j] << (8 * j);
                bSizeActual++;
            }
        }
        if (undef_type) {
            undef_type ++;
            if (bTag == 0x0A) {
                undef_usage ++;
            } else if (bTag == 0x0C) {
                undef_usage --;
            }
            if (undef_usage == 0 && undef_type > 2) {
                undef_type = 0;
            }
            index += bSize;
            continue;
        }
        if (undef_type) {
            index += bSize;
            continue;
        }
        if (itemVal == 0xffb5) {
            undef_type = 1;
            index += bSize;
            continue;
        } else {
            undef_type = 0;
        }
        if (bType == MAIN_ITEM) {
            if (old_ops_is_report_size_count) {
                cur_section_bit += report_size * report_count;
            }
            if (bTag == 0x08) {
                /* log_info("input %X", itemVal);                       */
                /* log_info("\tusage %x", cur_usage);                   */
                /* log_info("\t\tcur_section_bit %d", cur_section_bit); */
                if (rpt->usage == 0x02) { //mouse
                    if (cur_usage == 1) {
                        if (rpt->btn_start_bit == 0) {
                            rpt->btn_start_bit = total_bits ;
                        }
                        rpt->btn_width += cur_section_bit ;
                        /* log_info("btn_width %d-%d", rpt->btn_start_bit, rpt->btn_width); */
                    } else if ((cur_usage == 0x30) || (cur_usage == 0x31)) {

                        if (rpt->xy_start_bit == 0) {
                            rpt->xy_start_bit = total_bits;
                        }
                        rpt->xy_width = cur_section_bit;
                        /* log_info("xy_width %d-%d", rpt->xy_start_bit, rpt->xy_width); */
                    } else if (cur_usage == 0x38) {
                        if (rpt->wheel_start_bit == 0) {
                            rpt->wheel_start_bit = total_bits;
                        }
                        if (rpt->xy_width || cur_section_bit < 24) {
                            rpt->wheel_width = cur_section_bit;
                            /* log_info("wheel_width %d-%d", rpt->wheel_start_bit, rpt->wheel_width); */
                        } else {
                            rpt->wheel_width = rpt->xy_width = cur_section_bit / 3;

                            rpt->xy_start_bit = total_bits;
                            rpt->wheel_start_bit = rpt->xy_start_bit + rpt->xy_width * 2;
                            /* log_info("wheel_width %d-%d", rpt->wheel_start_bit, rpt->wheel_width); */
                            /* log_info("xy_width %d-%d", rpt->xy_start_bit, rpt->xy_width);          */
                        }
                    } else if (cur_usage == 0xb8) {
                        rpt->wheel_width = rpt->xy_width = cur_section_bit / 4;
                        rpt->xy_start_bit = total_bits;
                        rpt->wheel_start_bit = rpt->xy_start_bit + rpt->xy_width * 2;
                    }
                }
                total_bits += cur_section_bit;
                /* input_bit[input_bit_index++] = cur_section_bit; */
                cur_section_bit = -1;
            } else if (bTag == 0x09) {
                /* log_info("OUTPUT %X", itemVal);                      */
                /* log_info("\tusage %x", cur_usage);                   */
                /* log_info("\t\tcur_section_bit %d", cur_section_bit); */
                /* output_bit[output_bit_index++] = cur_section_bit; */
                cur_section_bit = -1;
            } else if (bTag == 0x0B) {
                /* log_info("Feature %X", itemVal);                     */
                /* log_info("\tusage %x", cur_usage);                   */
                /* log_info("\t\tcur_section_bit %d", cur_section_bit); */
                /* output_bit[output_bit_index++] = cur_section_bit; */
                cur_section_bit = -1;
            } else if (bTag == 0x0A) {
                collection_deep ++ ;
                log_info("Collection %d %x", collection_deep, rpt->usage);
            } else if (bTag == 0x0C) {
                collection_deep --;
                log_info("End Collection %d %x", collection_deep, rpt->usage);
                if (collection_deep == 0) {
                    if (rpt->usage == 0x02 ||
                        rpt->usage == 0x06 ||
                        rpt->usage == 0x07) {

                        memcpy(&hid->report_list[hid->report_count], rpt, sizeof(*rpt));
                        memset(rpt, 0, sizeof(*rpt));

                        hid->report_count ++;
                    }
                }
                if (index < len) {
                    continue;
                }
            } else {
                log_info("MAIN_ITEM Unknown btag :%x", bTag);
                return 1;
            }
        } else if (bType == GLOBAL_ITEM) {
            /* log_info("GLOBAL_ITEM"); */
            if (bTag == 0x00) {
                /* log_info("Usage Page %x", itemVal); */

                if (rpt->usage == 0x06) {
                    if (itemVal == 0x07) {
                        rpt->usage = 0x07;
                        log_info("re set type %x", 0x07);
                    }
                }
                if (itemVal == 0x02) {
                    rpt->usage = 0x02;
                    log_info("re set type %x", 0x02);
                }

            } else if (bTag == 0x01) {
                //log_info("Logical Minimum %x", itemVal);
            } else if (bTag == 0x02) {
                //log_info("Logical Maximum %x", itemVal);
            } else if (bTag == 0x03) {
                /* log_info("Physical Minimum %x", itemVal); */
            } else if (bTag == 0x04) {
                /* log_info("Physical Maximum %x", itemVal); */
            } else if (bTag == 0x05) {
                /* log_info("Unit Exponent %x", itemVal); */
            } else if (bTag == 0x06) {
                /* log_info("Unit %x", itemVal); */
            } else if (bTag == 0x07) {
                /* log_info("Report Size %x", itemVal); */
                report_size = itemVal;
                cur_ops_is_report_size_count = 1;
            } else if (bTag == 0x08) {
                log_info("Report ID %x", itemVal, rpt->usage);
                rpt->report_id = itemVal;
            } else if (bTag == 0x09) {
                /* log_info("Report Count %x", itemVal); */
                report_count = itemVal;
                cur_ops_is_report_size_count = 1;
            } else if (bTag == 0x0A) {
                /* log_info("Push %x", bSizeActual); */
            } else if (bTag == 0x0B) {
                /* log_info("Pop %x", bSizeActual); */
            } else {
                log_info("GLOBAL_ITEM Unknown btag :%x", bTag);
                return 2;
            }
        } else if (bType == LOCAL_ITEM) {
            /* log_info("LOCAL_ITEM"); */
            if (bTag == 0x00) {
                if (rpt->usage == 0) {
                    rpt->usage = itemVal;
                    log_info("set type %x", rpt->usage);
                }
                if (itemVal == 0x30) { //X
                } else if (itemVal == 0x31) { //y
                } else if (itemVal == 0x38) { //wheel
                } else {
                }
                /* log_info("\t change usage %x -> %x", cur_usage, itemVal); */
                cur_usage = itemVal;
                if (!collection_deep) {
                    if (itemVal == 0x06 || itemVal == 0x07 || itemVal == 0x02) {
                        //仅限键盘和鼠标
                        rpt->usage = itemVal;
                        log_info("set typee %x", rpt->usage);
                    }
                }
                /* type = itemVal; */
            } else if (bTag == 0x01) {
                // log_info("Usage Minimum %x", itemVal);
            } else if (bTag == 0x02) {
                // log_info("Usage Maximum %x", itemVal);
            } else if (bTag == 0x03) {
                /* log_info("Designator Index %x", itemVal); */
            } else if (bTag == 0x04) {
                /* log_info("Designator Minimum %x", itemVal); */
            } else if (bTag == 0x05) {
                /* log_info("Designator Maximum %x", itemVal); */
            } else if (bTag == 0x07) {
                /* log_info("String Index %x", itemVal); */
            } else if (bTag == 0x08) {
                /* log_info("String Minimum %x", itemVal); */
            } else if (bTag == 0x09) {
                /* log_info("String Maximum %x", itemVal); */
            } else if (bTag == 0x0A) {
                /* log_info("Delimiter %x", itemVal); */
            } else {
                log_info("LOCAL_ITEM Unknown btag :%x", bTag);
                return 3;
            }
        } else {
            log_info("OTHER Unknown btag :%x", bTag);
            return 4;
        }
        if (!cur_ops_is_report_size_count && old_ops_is_report_size_count) {
            if (cur_section_bit != -1) {
                cur_section_bit += report_size * report_count;
            } else {
                cur_section_bit = 0;
            }
        }
        if (cur_section_bit == -1) {
            cur_section_bit = 0;
        }
        old_ops_is_report_size_count = cur_ops_is_report_size_count;
        index += bSize;

    }
    return 0;
}
static u32 hid_report_parse(struct hid_device_t *hid, const u8 *report, u32 len)
{
    u8 type = _hid_report_parse(hid, report, len);
    for (int i = 0; i < hid->report_count; i++) {
        struct report_info_t *rpt = &hid->report_list[i];
        if (rpt->usage == 0x02) {
            if (rpt->report_id == 0) {
                rpt->report_id = 0xff;
            }
            rpt->btn_start_bit /= 8;
            rpt->btn_width /= 8;
            rpt->xy_start_bit /= 8;
            rpt->xy_width /= 8;
            rpt->wheel_start_bit /= 8;
            rpt->wheel_width /= 8;

            log_info("mouse report_id %d",
                     rpt->report_id);

            log_info("btn_width %d-%d",
                     rpt->btn_start_bit, rpt->btn_width);
            log_info("xy_width %d-%d",
                     rpt->xy_start_bit, rpt->xy_width);
            log_info("wheel_width %d-%d",
                     rpt->wheel_start_bit, rpt->wheel_width);

            if (rpt->btn_width != 2) {
                rpt->btn_width = 1;
            }

            log_info("btn_width %d-%d",
                     rpt->btn_start_bit, rpt->btn_width);

        } else if (rpt->usage == 6 || rpt->usage == 7) {
            if (rpt->report_id == 0) {
                rpt->report_id = 0xff;
            }
            log_info("keyboard report_id %d", rpt->report_id);
        } else {
            log_info("unknown usage %d", rpt->usage);
        }
    }
    return 0;
}

void mouse_route(const struct mouse_data_t *p);
/* __attribute__((weak)) void mouse_route(const struct mouse_data_t *p) */
/* {                                                                    */
/*     log_info("btn: %x x-y %d %d wheel %d ac_pan %d",                 */
/*              p->btn, p->x, p->y, p->wheel, p->ac_pan);               */
/* }                                                                    */
static void hid_convert_mouse(const struct report_info_t *mouse, const u8 *buffer)
{
    struct mouse_data_t mouse_data;
    memset(&mouse_data, 0, sizeof(mouse_data));
    if (mouse->report_id != 0xff) {
        if (mouse->report_id != buffer[0]) {
            log_error("report_id = %x buffer[0] = %x", mouse->report_id, buffer[0]);
            return;
        }
        buffer++;
    }
    const u8 *ptr;
    ptr = &buffer[mouse->btn_start_bit];
    if (mouse->btn_width == 2) {
        mouse_data.btn = ptr[0] | (ptr[1] << 8);
    } else {
        mouse_data.btn = ptr[0] ;
    }
    s16 tmp;
    ptr = &buffer[mouse->xy_start_bit];
    if (mouse->xy_width == 1 || mouse->xy_width == 2) {
        mouse_data.x = (char)ptr[0];
        mouse_data.y = (char)ptr[1];
    } else if (mouse->xy_width == 4) {
        mouse_data.x = ptr[0] | (ptr[1] << 8);
        ptr += 2;
        mouse_data.y = ptr[0] | (ptr[1] << 8);
    } else if (mouse->xy_width == 3) {
        tmp = (ptr[1] & 0xf) << 12 | ptr[0] << 4;
        tmp = tmp >> 4;
        mouse_data.x = tmp;

        tmp = (ptr[2] << 8) | ((ptr[1] >> 4) << 4);
        tmp = tmp >> 4;
        mouse_data.y = tmp;
    } else {
        log_error("error mouse xy_width %d", mouse->xy_width);
    }

    ptr = &buffer[mouse->wheel_start_bit];

    if (mouse->wheel_width == 1) {
        mouse_data.wheel = (char)ptr[0];
    } else {
        mouse_data.wheel = ptr[0] | (ptr[1] << 8);
    }

    mouse_route(&mouse_data);
}
__attribute__((weak)) void keyboard_route(const u8  *p)
{
    log_info("keyboard_buffer:");
    printf_buf(p, 12);
}
void hid_convert_krbd(const struct report_info_t *kbd, u8 *buffer)
{
#if 0
    u8 keyboard_buffer[12];
    memset(keyboard_buffer, 0, sizeof(keyboard_buffer));
    if (kbd->report_id != 0xff) {
        if (kbd->report_id != buffer[0]) {
            log_error("report_id = %x buffer[0] = %x", kbd->report_id, buffer[0]);
            return;
        }
        buffer++;
    }

    u8 idx = 0;
    u8 index = 0;
    int i = 0;
    for (i = 0; i < 8; i++) {
        if (buffer[0] & BIT(i)) {
            keyboard_buffer[idx++] = 0xe0 + i;
        }
    }

    if (buffer[1] == 0) {
        buffer += 2;
    }
    index = idx;
    for (; idx < 12; idx++) {
        if (*buffer) {
            keyboard_buffer[index] = *buffer;
            index++;
        }
        buffer ++;
    }
    keyboard_route(keyboard_buffer);
#else
    if (kbd->report_id != 0xff) {
        if (kbd->report_id != buffer[0]) {
            log_error("report_id = %x buffer[0] = %x", kbd->report_id, buffer[0]);
            return;
        }
        buffer++;
    }
    u8 keyboard_buffer[8];
    memset(keyboard_buffer, 0, sizeof(keyboard_buffer));

    keyboard_buffer[0] = *buffer;
    buffer ++;

    if (*buffer == 0) {
        buffer ++;
    }
    keyboard_buffer[1] = 0;

    /* memcpy(&keyboard_buffer[2], buffer, 6); */
    u8 pos = 2;
    for (int i = pos; i < 8; i++) {
        if (*buffer) {
            keyboard_buffer[pos] = *buffer;
            pos++;
        }
        buffer++;
    }
    keyboard_route(keyboard_buffer);
#endif
}

static void hid_route(const struct hid_device_t *hid, const u8 *buffer)
{
    for (int i = 0; i < hid->report_count; i++) {
        if (hid->report_list[i].usage == 0x02) {
            hid_convert_mouse(&hid->report_list[i], buffer);
        } else if ((hid->report_list[i].usage == 0x06) ||
                   (hid->report_list[i].usage == 0x07)) {
            hid_convert_krbd(&hid->report_list[i], buffer);
        } else {
            r_printf("usage %x", hid->report_list[i].usage);
        }
    }
}
static void hid_isr(struct usb_interface_info *usb_if, u32 ep)
{
    u8 buffer[64] = {0};
    struct usb_host_device *host_dev = usb_if->dev.hid->parent;
    usb_dev usb_id = host_device2id(host_dev);
    u32 target_ep = usb_if->dev.hid->ep_pair[ep];
    u32 rx_len = usb_h_ep_read_async(usb_id, ep, target_ep, buffer, 64, USB_ENDPOINT_XFER_INT, 0);

    if (rx_len) {
        hid_route(usb_if->dev.hid, buffer);
    }
    /* printf_buf(buffer, rx_len);    */

    usb_h_ep_read_async(usb_id, ep, target_ep, buffer, 8, USB_ENDPOINT_XFER_INT, 1);
}
void hid_process(u32 id)
{
    struct usb_host_device *host_dev = host_id2device(id);
    u8 report[256 + 2];
    u8 ep_pair[4];

    for (u8 i = 0; i < MAX_HOST_INTERFACE; i++) {
        struct usb_interface_info  *usb_if = host_dev->interface_info[i];
        log_info("parent %x hid @ interface %d usb_if %x hid %x",
                 host_dev, i, usb_if, usb_if ? usb_if->dev.hid : 0);
        if (usb_if &&
            (usb_if->ctrl->interface_class == USB_CLASS_HID)) {
            hid_set_idle(host_dev, i);
            memset(report, 0, sizeof(report));
            hid_get_report(host_dev, report, i, 0xff);
            printf_buf(report, 256);
            hid_report_parse(usb_if->dev.hid, report, 256);
            memcpy(ep_pair, usb_if->dev.hid->ep_pair, 4);

            if (usb_if->dev.hid->report_count == 0) {
                continue;
            }

            for (int i = 0; i < usb_if->dev.hid->bNumEndpoints; i++) {

                u32 host_ep = usb_get_ep_num(id, USB_DIR_IN, USB_ENDPOINT_XFER_INT);

                ASSERT(host_ep != -1, "ep not enough");

                u32 target_ep = ep_pair[i];

                usb_if->dev.hid->ep_pair[host_ep] = target_ep;

                log_info("D2H ep: %x --> %x interval %d",
                         target_ep, host_ep, interval[id][target_ep]);

                usb_h_set_ep_isr(host_dev, host_ep | USB_DIR_IN, hid_isr, usb_if);

                u8 *ep_buffer = usb_h_get_ep_buffer(id, host_ep | USB_DIR_OUT);
                usb_h_ep_config(id,  host_ep | USB_DIR_IN, USB_ENDPOINT_XFER_INT, 1,
                                interval[id][target_ep], ep_buffer, 64);
                int r = usb_h_ep_read_async(id, host_ep, target_ep, NULL, 0, USB_ENDPOINT_XFER_INT, 1);
            }
        } else {
            if (usb_if) {
                log_error("error hid class %x", usb_if->ctrl->interface_class);
            }
        }
    }

}

#endif
