/*
 * Copyright (c) 2015, Freescale Semiconductor, Inc.
 * Copyright 2016 - 2018 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "usb_host_config.h"
#include "usb_host.h"
#include "usb_host_hid.h"
#include "host_hid_generic.h"
#include "app.h"
#include "fsl_gpio.h"
#include "display_support.h"
#include "board.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/

#define VIDPID_KEDEI     0x0eef0005
#define VIDPID_NEWHAVEN  0x04610022

#define COMBINE_VID_PID(__vid, __pid)  (((__vid)<<16)|(__pid))


typedef struct
{
    uint16_t x;
    uint16_t y;
    bool pressed;
} touch_data_t;

/*******************************************************************************
 * Prototypes
 ******************************************************************************/

/*!
 * @brief process hid data and print generic data.
 *
 * @param genericInstance   hid generic instance pointer.
 */
static void USB_HostHidGenericProcessBuffer(usb_host_hid_generic_instance_t *genericInstance);

/*!
 * @brief host hid generic control transfer callback.
 *
 * This function is used as callback function for control transfer .
 *
 * @param param      the host hid generic instance pointer.
 * @param data       data buffer pointer.
 * @param dataLength data length.
 * @status           transfer result status.
 */
static void USB_HostHidControlCallback(void *param, uint8_t *data, uint32_t dataLength, usb_status_t status);

/*!
 * @brief host hid generic interrupt in transfer callback.
 *
 * This function is used as callback function when call USB_HostHidRecv .
 *
 * @param param      the host hid generic instance pointer.
 * @param data       data buffer pointer.
 * @param dataLength data length.
 * @status           transfer result status.
 */
static void USB_HostHidInCallback(void *param, uint8_t *data, uint32_t dataLength, usb_status_t status);

// default implementation of VidPidFunction_t, can be overridden with a call to BOARD_RegisterUSBTouchCallbacks()
static bool is_supported_device(uint16_t vid, uint16_t pid);

// default implementation of ExtractFunction_t, can be overridden with a call to BOARD_RegisterUSBTouchCallbacks()
static bool extract_scaled_touch_event(uint32_t vidpid, const uint8_t* buff, uint32_t len, uint16_t* x, uint16_t* y, bool* pressed);

void BOARD_MIPI_PANEL_TOUCH_IRQ_HANDLER(void);

/*******************************************************************************
 * Variables
 ******************************************************************************/

usb_host_hid_generic_instance_t g_HostHidGeneric; /* hid generic instance */

USB_DMA_NONINIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE)
uint8_t s_GenericInBuffer[HID_GENERIC_IN_BUFFER_SIZE]; /*!< use to receive report descriptor and data */

static touch_data_t fingers[1] = {0};

static VidPidFunction_t vpFunc = is_supported_device;
static ExtractFunction_t eFunc = extract_scaled_touch_event;

/*******************************************************************************
 * Code
 ******************************************************************************/

status_t BOARD_HDMI_Touch_GetData(int* x, int* y)
{
    if (fingers[0].pressed) {
        /* Return the prescaled coordinates */
        *x = (int)fingers[0].x;
        *y = (int)fingers[0].y;
        return kStatus_Success;
    }
    return kStatus_Fail;
}

void BOARD_RegisterUSBTouchCallbacks(VidPidFunction_t vp, ExtractFunction_t e)
{
    vpFunc = vp;
    eFunc = e;
}

// default implementation of ExtractFunction_t, can be overridden with a call to BOARD_RegisterUSBTouchCallbacks()
static bool extract_scaled_touch_event(uint32_t vidpid, const uint8_t* buff, uint32_t len, uint16_t* x, uint16_t* y, bool* pressed)
{
    if (vidpid == VIDPID_KEDEI) {
        if (len == 11) {
            if (buff[0] == 1) { // report ID
                *pressed = buff[1];
                uint32_t x_tmp = buff[4] + (buff[5]<<8);
                uint32_t y_tmp = buff[6] + (buff[7]<<8);

                /* Scale from the native 1024x600 to the current resolution */
                *x = (uint16_t)((DEMO_PANEL_WIDTH*x_tmp)/1024);
                *y = (uint16_t)((DEMO_PANEL_HEIGHT*y_tmp)/600);
                return true;
            }
        }
    } else if (vidpid == VIDPID_NEWHAVEN) {
        if (len == 5) {
            *pressed = buff[0];
            uint32_t x_tmp = buff[1] + (buff[2]<<8);
            uint32_t y_tmp = buff[3] + (buff[4]<<8);

            /* Scale from the native 10000x10000 to the current resolution */
            *x = (uint16_t)((DEMO_PANEL_WIDTH*x_tmp)/10000);
            *y = (uint16_t)((DEMO_PANEL_HEIGHT*y_tmp)/10000);
            return true;
        }
    }
    return false;
}

static void USB_HostHidGenericProcessBuffer(usb_host_hid_generic_instance_t *genericInstance)
{
    /* Toggle LED to help see if display is generating touch events */
    GPIO_PinWrite(GPIO9, 15, (GPIO_PinRead(GPIO9, 15)==0) ? 1 : 0);

    if ((eFunc != NULL) && eFunc(genericInstance->vidpid,
                                 genericInstance->genericInBuffer,
                                 genericInstance->bytesInBuffer,
                                 &(fingers[0].x), &(fingers[0].y), &(fingers[0].pressed))) {

        /* Call the interrupt handler as if it was a normal GPIO interrupt.
           This is needed to get the Qt runtime to continue to detect touches. */
        BOARD_MIPI_PANEL_TOUCH_IRQ_HANDLER();
        //if (fingers[0].pressed) {
        //    usb_echo("   TOUCH at x=%3u, y=%3u\r\n", fingers[0].x, fingers[0].y);
        //} else {
        //    usb_echo("NO TOUCH\r\n");
        //}
    }
}

// default implementation of VidPidFunction_t, can be overridden with a call to BOARD_RegisterUSBTouchCallbacks()
static bool is_supported_device(uint16_t vid, uint16_t pid)
{
    if ((pid == 0x0005) && (vid == 0x0eef)) {
        /* Found KEDEI Touch Screen */
        return true;
    } else if ((pid == 0x0022) && (vid == 0x0461)) {
        /* Found NewHaven Touch Screen */
        return true;
    }
    return false;
}

static void USB_HostHidControlCallback(void *param, uint8_t *data, uint32_t dataLength, usb_status_t status)
{
    usb_host_hid_generic_instance_t *genericInstance = (usb_host_hid_generic_instance_t *)param;

    if (genericInstance->runWaitState == kUSB_HostHidRunWaitSetInterface) /* set interface finish */
    {
        genericInstance->runState = kUSB_HostHidRunSetInterfaceDone;
    }
    else if (genericInstance->runWaitState == kUSB_HostHidRunWaitSetIdle) /* hid set idle finish */
    {
        genericInstance->runState = kUSB_HostHidRunSetIdleDone;
    }
    else if (genericInstance->runWaitState ==
             kUSB_HostHidRunWaitGetReportDescriptor) /* hid get report descriptor finish */
    {
        genericInstance->runState = kUSB_HostHidRunGetReportDescriptorDone;
        genericInstance->bytesInBuffer = dataLength;
    }
    else if (genericInstance->runWaitState == kUSB_HostHidRunWaitGetFeatureReport) /* hid get feature report finish */
    {
        genericInstance->runState = kUSB_HostHidRunGetFeatureReportDone;
    }
    else
    {
    }
}

static void USB_HostHidInCallback(void *param, uint8_t *data, uint32_t dataLength, usb_status_t status)
{
    usb_host_hid_generic_instance_t *genericInstance = (usb_host_hid_generic_instance_t *)param;

    if (genericInstance->runWaitState == kUSB_HostHidRunWaitDataReceived)
    {
        if (status == kStatus_USB_Success)
        {
            genericInstance->runState = kUSB_HostHidRunDataReceived; /* go to process data */
            genericInstance->bytesInBuffer = dataLength;
        }
        else
        {
            if (genericInstance->deviceState == kStatus_DEV_Attached)
            {
                genericInstance->runState = kUSB_HostHidRunPrimeDataReceive; /* go to prime next receiving */
            }
        }
    }
}

void USB_HostHidGenericTask(void *param)
{
    usb_host_hid_descriptor_t *hidDescriptor;
    uint32_t hidReportLength = 0;
    uint8_t *descriptor;
    uint32_t endPosition;
    usb_host_hid_generic_instance_t *genericInstance = (usb_host_hid_generic_instance_t *)param;

    /* device state changes, process once for each state */
    if (genericInstance->deviceState != genericInstance->prevState)
    {
        genericInstance->prevState = genericInstance->deviceState;
        switch (genericInstance->deviceState)
        {
            case kStatus_DEV_Idle:
                break;

            case kStatus_DEV_Attached: /* deivce is attached and numeration is done */
                genericInstance->runState = kUSB_HostHidRunSetInterface;
                if (USB_HostHidInit(genericInstance->deviceHandle, &genericInstance->classHandle) !=
                    kStatus_USB_Success)
                {
                    usb_echo("host hid class initialize fail\r\n");
                }
                //else
                //{
                //    usb_echo("hid generic attached\r\n");
                //}
                genericInstance->sendIndex = 0;
                break;

            case kStatus_DEV_Detached: /* device is detached */
                genericInstance->deviceState = kStatus_DEV_Idle;
                genericInstance->runState    = kUSB_HostHidRunIdle;
                USB_HostHidDeinit(genericInstance->deviceHandle, genericInstance->classHandle);
                genericInstance->classHandle = NULL;
                usb_echo("hid generic detached\r\n");
                break;

            default:
                break;
        }
    }

    /* run state */
    switch (genericInstance->runState)
    {
        case kUSB_HostHidRunIdle:
            break;

        case kUSB_HostHidRunSetInterface: /* 1. set hid interface */
            genericInstance->runWaitState = kUSB_HostHidRunWaitSetInterface;
            genericInstance->runState     = kUSB_HostHidRunIdle;
            if (USB_HostHidSetInterface(genericInstance->classHandle, genericInstance->interfaceHandle, 0,
                                        USB_HostHidControlCallback, genericInstance) != kStatus_USB_Success)
            {
                usb_echo("set interface error\r\n");
            }
            break;

        case kUSB_HostHidRunSetInterfaceDone: /* 2. hid set idle */
            genericInstance->inMaxPacketSize =
                USB_HostHidGetPacketsize(genericInstance->classHandle, USB_ENDPOINT_INTERRUPT, USB_IN);

            /* first: set idle */
            genericInstance->runWaitState = kUSB_HostHidRunWaitSetIdle;
            genericInstance->runState     = kUSB_HostHidRunIdle;
            if (USB_HostHidSetIdle(genericInstance->classHandle, 0, 0, USB_HostHidControlCallback, genericInstance) !=
                kStatus_USB_Success)
            {
                usb_echo("Error in USB_HostHidSetIdle\r\n");
            }
            break;

        case kUSB_HostHidRunSetIdleDone: /* 3. hid get report descriptor */
            /* get report descriptor's length */
            hidDescriptor = NULL;
            descriptor    = (uint8_t *)((usb_host_interface_t *)genericInstance->interfaceHandle)->interfaceExtension;
            endPosition   = (uint32_t)descriptor +
                          ((usb_host_interface_t *)genericInstance->interfaceHandle)->interfaceExtensionLength;

            while ((uint32_t)descriptor < endPosition)
            {
                if (*(descriptor + 1) == USB_DESCRIPTOR_TYPE_HID) /* descriptor type */
                {
                    hidDescriptor = (usb_host_hid_descriptor_t *)descriptor;
                    break;
                }
                else
                {
                    descriptor = (uint8_t *)((uint32_t)descriptor + (*descriptor)); /* next descriptor */
                }
            }

            if (hidDescriptor != NULL)
            {
                usb_host_hid_class_descriptor_t *hidClassDescriptor;
                hidClassDescriptor = (usb_host_hid_class_descriptor_t *)&(hidDescriptor->bHidDescriptorType);
                for (uint8_t index = 0; index < hidDescriptor->bNumDescriptors; ++index)
                {
                    hidClassDescriptor += index;
                    if (hidClassDescriptor->bHidDescriptorType == USB_DESCRIPTOR_TYPE_HID_REPORT)
                    {
                        hidReportLength =
                            (uint16_t)USB_SHORT_FROM_LITTLE_ENDIAN_ADDRESS(hidClassDescriptor->wDescriptorLength);
                        break;
                    }
                }
            }
            if (hidReportLength > HID_GENERIC_IN_BUFFER_SIZE)
            {
                usb_echo("hid buffer is too small\r\n");
                genericInstance->runState = kUSB_HostHidRunIdle;
                return;
            }

            genericInstance->runWaitState = kUSB_HostHidRunWaitGetReportDescriptor;
            genericInstance->runState     = kUSB_HostHidRunIdle;
            /* second: get report descriptor */
            USB_HostHidGetReportDescriptor(genericInstance->classHandle, genericInstance->genericInBuffer,
                                           hidReportLength, USB_HostHidControlCallback, genericInstance);
            break;

        case kUSB_HostHidRunGetReportDescriptorDone: /* 4. hid get feature report */
            genericInstance->runWaitState = kUSB_HostHidRunWaitGetFeatureReport;
            genericInstance->runState     = kUSB_HostHidRunIdle;

            USB_HostHidGetReport(genericInstance->classHandle, 2, 3,
                                 genericInstance->genericInBuffer, HID_GENERIC_IN_BUFFER_SIZE,
                                 USB_HostHidControlCallback, genericInstance);
            break;

        case kUSB_HostHidRunGetFeatureReportDone: /* 5. start to receive data */
            genericInstance->runWaitState = kUSB_HostHidRunWaitDataReceived;
            genericInstance->runState     = kUSB_HostHidRunIdle;
            if (USB_HostHidRecv(genericInstance->classHandle, genericInstance->genericInBuffer,
                                genericInstance->inMaxPacketSize, USB_HostHidInCallback,
                                genericInstance) != kStatus_USB_Success)
            {
                usb_echo("Error in USB_HostHidRecv\r\n");
            }
            break;

        case kUSB_HostHidRunDataReceived: /* process received data and receive next data */
            USB_HostHidGenericProcessBuffer(genericInstance);

            genericInstance->runWaitState = kUSB_HostHidRunWaitDataReceived;
            genericInstance->runState     = kUSB_HostHidRunIdle;
            if (USB_HostHidRecv(genericInstance->classHandle, genericInstance->genericInBuffer,
                                genericInstance->inMaxPacketSize, USB_HostHidInCallback,
                                genericInstance) != kStatus_USB_Success)
            {
                usb_echo("Error in USB_HostHidRecv\r\n");
            }
            break;

        case kUSB_HostHidRunPrimeDataReceive: /* receive data */
            genericInstance->runWaitState = kUSB_HostHidRunWaitDataReceived;
            genericInstance->runState     = kUSB_HostHidRunIdle;
            if (USB_HostHidRecv(genericInstance->classHandle, genericInstance->genericInBuffer,
                                genericInstance->inMaxPacketSize, USB_HostHidInCallback,
                                genericInstance) != kStatus_USB_Success)
            {
                usb_echo("Error in USB_HostHidRecv\r\n");
            }
            break;

        default:
            break;
    }
}

usb_status_t USB_HostHidGenericEvent(usb_device_handle deviceHandle,
                                     usb_host_configuration_handle configurationHandle,
                                     uint32_t eventCode)
{
    uint32_t pid;
    uint32_t vid;
    usb_host_configuration_t *configuration;
    usb_host_interface_t *interface;
    uint32_t infoValue;
    usb_status_t status = kStatus_USB_Success;
    uint8_t interfaceIndex;
    uint8_t id;

    switch (eventCode)
    {
        case kUSB_HostEventAttach:
            /* judge whether is configurationHandle supported */
            configuration = (usb_host_configuration_t *)configurationHandle;
            for (interfaceIndex = 0; interfaceIndex < configuration->interfaceCount; ++interfaceIndex)
            {
                interface = &configuration->interfaceList[interfaceIndex];
                id        = interface->interfaceDesc->bInterfaceClass;
                if (id != USB_HOST_HID_CLASS_CODE)
                {
                    continue;
                }
                id = interface->interfaceDesc->bInterfaceSubClass;
                if ((id != USB_HOST_HID_SUBCLASS_CODE_NONE) && (id != USB_HOST_HID_SUBCLASS_CODE_BOOT))
                {
                    continue;
                }
                USB_HostHelperGetPeripheralInformation(deviceHandle, kUSB_HostGetDevicePID, &pid);
                USB_HostHelperGetPeripheralInformation(deviceHandle, kUSB_HostGetDeviceVID, &vid);
                if ((vpFunc != NULL) && vpFunc(vid, pid))
                {
                    /* Found an acceptable Touch Screen */
                    if (g_HostHidGeneric.deviceState == kStatus_DEV_Idle)
                    {
                        /* the interface is supported by the application */
                        g_HostHidGeneric.genericInBuffer  = s_GenericInBuffer;
                        g_HostHidGeneric.deviceHandle     = deviceHandle;
                        g_HostHidGeneric.interfaceHandle  = interface;
                        g_HostHidGeneric.configHandle     = configurationHandle;
                        g_HostHidGeneric.vidpid           = COMBINE_VID_PID(vid, pid);
                        return kStatus_USB_Success;
                    }
                    else
                    {
                        continue;
                    }
                }
            }
            status = kStatus_USB_NotSupported;
            break;

        case kUSB_HostEventNotSupported:
            break;

        case kUSB_HostEventEnumerationDone:
            if (g_HostHidGeneric.configHandle == configurationHandle)
            {
                if ((g_HostHidGeneric.deviceHandle != NULL) && (g_HostHidGeneric.interfaceHandle != NULL))
                {
                    /* the device enumeration is done */
                    if (g_HostHidGeneric.deviceState == kStatus_DEV_Idle)
                    {
                        g_HostHidGeneric.deviceState = kStatus_DEV_Attached;

                        USB_HostHelperGetPeripheralInformation(deviceHandle, kUSB_HostGetDevicePID, &infoValue);
                        usb_echo("hid touch attached:pid=0x%x", infoValue);
                        USB_HostHelperGetPeripheralInformation(deviceHandle, kUSB_HostGetDeviceVID, &infoValue);
                        usb_echo("vid=0x%x ", infoValue);
                        USB_HostHelperGetPeripheralInformation(deviceHandle, kUSB_HostGetDeviceAddress, &infoValue);
                        usb_echo("address=%d\r\n", infoValue);
                    }
                    else
                    {
                        usb_echo("not idle generic instance\r\n");
                        status = kStatus_USB_Error;
                    }
                }
            }
            break;

        case kUSB_HostEventDetach:
            if (g_HostHidGeneric.configHandle == configurationHandle)
            {
                /* the device is detached */
                g_HostHidGeneric.configHandle = NULL;
                if (g_HostHidGeneric.deviceState != kStatus_DEV_Idle)
                {
                    g_HostHidGeneric.deviceState = kStatus_DEV_Detached;
                }
            }
            break;

        default:
            break;
    }
    return status;
}
