#include "usbd_hid.h"
#include "usbd_desc.h"
#include "usbd_ctlreq.h"


static uint8_t  USBD_HID_Init (USBD_HandleTypeDef *pdev, uint8_t cfgidx);
static uint8_t  USBD_HID_DeInit (USBD_HandleTypeDef *pdev, uint8_t cfgidx);
static uint8_t  USBD_HID_Setup (USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req);
static uint8_t  *USBD_HID_GetCfgDesc (uint16_t *length); 
static uint8_t  *USBD_HID_GetDeviceQualifierDesc (uint16_t *length); 
static uint8_t  USBD_HID_DataIn (USBD_HandleTypeDef *pdev, uint8_t epnum); 
static uint8_t  USBD_HID_DataOut (USBD_HandleTypeDef *pdev, uint8_t epnum);

USBD_ClassTypeDef  USBD_HID = 
{
    USBD_HID_Init,
    USBD_HID_DeInit,
    USBD_HID_Setup,
    NULL, /* EP0_TxSent */  
    NULL, /* EP0_RxReady */
    USBD_HID_DataIn,
    USBD_HID_DataOut,
    NULL, /* SOF */
    NULL,
    NULL,      
    USBD_HID_GetCfgDesc,
    USBD_HID_GetCfgDesc, 
    USBD_HID_GetCfgDesc,
    USBD_HID_GetDeviceQualifierDesc,
};

/* USB HID device configuration descriptor */
__ALIGN_BEGIN static uint8_t USBD_HID_CfgDesc[USB_HID_CONFIG_DESC_SIZ]  __ALIGN_END =
{
    0x09, /* bLength: Configuration Descriptor size */
    USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
    USB_HID_CONFIG_DESC_SIZ,
    /* wTotalLength: Bytes returned */
    0x00,
    0x01,         /*bNumInterfaces: 1 interface*/
    0x01,         /*bConfigurationValue: Configuration value*/
    0x00,         /*iConfiguration: Index of string descriptor describing
                    the configuration*/
    0xE0,         /*bmAttributes: bus powered and Support Remote Wake-up */
    0x32,         /*MaxPower 100 mA: this current is used for detecting Vbus*/

    /* Interface descriptor */
    /* 09 */
    0x09,         /*bLength: Interface Descriptor size*/
    USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/
    0x00,         /*bInterfaceNumber: Number of Interface*/
    0x00,         /*bAlternateSetting: Alternate setting*/
    HID_NUM_EP,

    0x03,         /*bInterfaceClass: HID*/

    0x01,         /*bInterfaceSubClass : 1=BOOT, 0=no boot*/
    0x01,         /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
    0,            /*iInterface: Index of string descriptor*/
    /* HID descriptor */
    /* 18 */
    0x09,         /*bLength: HID Descriptor size*/
    HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/
    0x11,         /*bcdHID: HID Class Spec release number*/
    0x01,
    0x00,         /*bCountryCode: Hardware target country*/
    0x01,         /*bNumDescriptors: Number of HID class descriptors to follow*/
    0x22,         /*bDescriptorType*/
    HID_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/
    0x00,
    /* HID endpoint descriptor */
    /* 27 */
    0x07,          /*bLength: Endpoint Descriptor size*/
    USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/

    HID_EPIN_ADDR,     /*bEndpointAddress: Endpoint Address (IN)*/
    0x03,          /*bmAttributes: Interrupt endpoint*/
    HID_EPIN_SIZE, /*wMaxPacketSize: 4 Byte max */
    0x00,
    HID_FS_BINTERVAL,          /*bInterval: Polling Interval (10 ms)*/
    /* 34 */
#if   HID_LED_SUPPORT
    0x07,          /*bLength: Endpoint Descriptor size*/
    USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/

    HID_EPOUT_ADDR,     /*bEndpointAddress: Endpoint Address (IN)*/
    0x03,          /*bmAttributes: Interrupt endpoint*/
    HID_EPOUT_SIZE, /*wMaxPacketSize: 4 Byte max */
    0x00,
    HID_FS_BINTERVAL,          /*bInterval: Polling Interval (10 ms)*/
#endif
    /* 41 */

} ;

/* USB HID device Configuration Descriptor */
__ALIGN_BEGIN static uint8_t USBD_HID_Desc[USB_HID_DESC_SIZ]  __ALIGN_END  =
{
    /* 18 */
    0x09,         /*bLength: HID Descriptor size*/
    HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/
    0x11,         /*bcdHID: HID Class Spec release number*/
    0x01,
    0x00,         /*bCountryCode: Hardware target country*/
    0x01,         /*bNumDescriptors: Number of HID class descriptors to follow*/
    0x22,         /*bDescriptorType*/
    HID_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/
    0x00,
};

/* USB Standard Device Descriptor */
__ALIGN_BEGIN static uint8_t USBD_HID_DeviceQualifierDesc[USB_LEN_DEV_QUALIFIER_DESC]  __ALIGN_END =
{
    USB_LEN_DEV_QUALIFIER_DESC,
    USB_DESC_TYPE_DEVICE_QUALIFIER,
    0x00,
    0x02,
    0x00,
    0x00,
    0x00,
    0x40,
    0x01,
    0x00,
};

__ALIGN_BEGIN static uint8_t HID_ReportDesc[HID_REPORT_DESC_SIZE]  __ALIGN_END =
{
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x06,                    // USAGE (Keyboard)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x85, 0x01,  /*   Report ID  */
    0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
    0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)
    0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x08,                    //   REPORT_COUNT (8)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)
    0x95, 0x05,                    //   REPORT_COUNT (5)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x05, 0x08,                    //   USAGE_PAGE (LEDs)
    0x19, 0x01,                    //   USAGE_MINIMUM (Num Lock)
    0x29, 0x05,                    //   USAGE_MAXIMUM (Kana)
    0x91, 0x02,                    //   OUTPUT (Data,Var,Abs)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x75, 0x03,                    //   REPORT_SIZE (3)
    0x91, 0x03,                    //   OUTPUT (Cnst,Var,Abs)
    0x95, 0x06,                    //   REPORT_COUNT (6)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x65,                    //   LOGICAL_MAXIMUM (101)
    0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
    0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))
    0x29, 0x65,                    //   USAGE_MAXIMUM (Keyboard Application)
    0x81, 0x00,                    //   INPUT (Data,Ary,Abs)
    0xc0,                          // End Collection
    //
    0x05, 0x0C, /*		Usage Page (Consumer Devices)		*/
    0x09, 0x01, /*		Usage (Consumer Control)			*/
    0xA1, 0x01, /*		Collection (Application)			*/
    0x85, 0x02,	/*		Report ID=2							*/
    0x05, 0x0C, /*		Usage Page (Consumer Devices)		*/
    0x15, 0x00, /*		Logical Minimum (0)					*/
    0x25, 0x01, /*		Logical Maximum (1)					*/
    0x75, 0x01, /*		Report Size (1)						*/
    0x95, 0x07, /*		Report Count (7)					*/
    0x09, 0xB5, /*		Usage (Scan Next Track)				*/
    0x09, 0xB6, /*		Usage (Scan Previous Track)			*/
    0x09, 0xB7, /*		Usage (Stop)						*/
    0x09, 0xCD, /*		Usage (Play / Pause)				*/
    0x09, 0xE2, /*		Usage (Mute)						*/
    0x09, 0xE9, /*		Usage (Volume Up)					*/
    0x09, 0xEA, /*		Usage (Volume Down)					*/
    0x81, 0x02, /*		Input (Data, Variable, Absolute)	*/
    0x95, 0x01, /*		Report Count (1)					*/
    0x81, 0x01, /*		Input (Constant)					*/
    0xC0,              // End Collection

    // how to format the 3 byte report
    // byte 0 report ID = 0x02 (VOLUME_REPORT)
    // byte 1 media code  for ex VOL_UP 0xE9 , VOL_DONW 0xEA ... etc
    // byte 2  0x00
    // a second  report with 0 code shal be send to avoid "key repaeat"
}; 

uint32_t  nOutData;
uint8_t OutDataBuffer[HID_EPOUT_SIZE]; // local copy for user (usb fly at same time)
uint8_t OutData[HID_EPOUT_SIZE]; // live usb buffer


static uint8_t  USBD_HID_Init (USBD_HandleTypeDef *pdev, uint8_t cfgidx)
{
    USBD_LL_OpenEP(pdev, HID_EPIN_ADDR, USBD_EP_TYPE_INTR, HID_EPIN_SIZE);  
#if HID_LED_SUPPORT
    USBD_LL_OpenEP(pdev, HID_EPOUT_ADDR, USBD_EP_TYPE_INTR, HID_EPOUT_SIZE);
#endif

    pdev->pClassData = USBD_malloc(sizeof (USBD_HID_HandleTypeDef));

    if(pdev->pClassData == NULL)
        return 1;

#if HID_LED_SUPPORT
    USBD_LL_PrepareReceive(pdev, HID_EPOUT_ADDR, OutData, HID_EPOUT_SIZE);
#endif
    ((USBD_HID_HandleTypeDef *)pdev->pClassData)->state = HID_IDLE;
    return 0;
}

static uint8_t  USBD_HID_DeInit (USBD_HandleTypeDef *pdev, 
        uint8_t cfgidx)
{
    USBD_LL_CloseEP(pdev, HID_EPIN_ADDR);
    USBD_LL_CloseEP(pdev, HID_EPOUT_ADDR);

    if(pdev->pClassData != NULL) {
        USBD_free(pdev->pClassData);
        pdev->pClassData = NULL;
    } 

    return USBD_OK;
}

static uint8_t  USBD_HID_Setup (USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req)
{
    uint16_t len = 0;
    uint8_t  *pbuf = NULL;
    USBD_HID_HandleTypeDef     *hhid = (USBD_HID_HandleTypeDef*) pdev->pClassData;

    switch (req->bmRequest & USB_REQ_TYPE_MASK)
    {
    case USB_REQ_TYPE_CLASS :  
        switch (req->bRequest) {
        case HID_REQ_SET_PROTOCOL:
            hhid->Protocol = (uint8_t)(req->wValue);
            break;

        case HID_REQ_GET_PROTOCOL:
            USBD_CtlSendData (pdev, 
                    (uint8_t *)&hhid->Protocol,
                    1);    
            break;

        case HID_REQ_SET_IDLE:
            hhid->IdleState = (uint8_t)(req->wValue >> 8);
            break;

        case HID_REQ_GET_IDLE:
            USBD_CtlSendData (pdev, 
                    (uint8_t *)&hhid->IdleState,
                    1);        
            break;      

        default:
            USBD_CtlError (pdev, req);
            return USBD_FAIL; 
        }
        break;

    case USB_REQ_TYPE_STANDARD:
        switch (req->bRequest) {
        case USB_REQ_GET_DESCRIPTOR: 
            if( req->wValue >> 8 == HID_REPORT_DESC)
            {
                len = MIN(HID_REPORT_DESC_SIZE , req->wLength);
                pbuf = HID_ReportDesc;
            }
            else if( req->wValue >> 8 == HID_DESCRIPTOR_TYPE)
            {
                pbuf = USBD_HID_Desc;   
                len = MIN(USB_HID_DESC_SIZ , req->wLength);
            }

            USBD_CtlSendData (pdev, 
                    pbuf,
                    len);

            break;

        case USB_REQ_GET_INTERFACE :
            USBD_CtlSendData (pdev,
                    (uint8_t *)&hhid->AltSetting,
                    1);
            break;

        case USB_REQ_SET_INTERFACE :
            hhid->AltSetting = (uint8_t)(req->wValue);
            break;
        }
    }
    return USBD_OK;
}

uint8_t USBD_HID_SendReport (USBD_HandleTypeDef  *pdev, uint8_t *report, uint16_t len)
{
    USBD_HID_HandleTypeDef *hhid = (USBD_HID_HandleTypeDef*)pdev->pClassData;

    if (pdev->dev_state != USBD_STATE_CONFIGURED )
        return USBD_OK;

    if(hhid->state == HID_IDLE)
        return USBD_OK;

    hhid->state = HID_BUSY;
    USBD_LL_Transmit(pdev, HID_EPIN_ADDR, report, len);
    return USBD_OK;
}

uint32_t USBD_HID_GetPollingInterval (USBD_HandleTypeDef *pdev)
{
    uint32_t polling_interval = 0;

    /* HIGH-speed endpoints */
    if(pdev->dev_speed == USBD_SPEED_HIGH)
    {
        /* Sets the data transfer polling interval for high speed transfers. 
           Values between 1..16 are allowed. Values correspond to interval 
           of 2 ^ (bInterval-1). This option (8 ms, corresponds to HID_HS_BINTERVAL */
        polling_interval = (((1 <<(HID_HS_BINTERVAL - 1)))/8);
    }
    else   /* LOW and FULL-speed endpoints */
    {
        /* Sets the data transfer polling interval for low and full 
           speed transfers */
        polling_interval =  HID_FS_BINTERVAL;
    }

    return ((uint32_t)(polling_interval));
}

static uint8_t  *USBD_HID_GetCfgDesc (uint16_t *length)
{
    *length = sizeof (USBD_HID_CfgDesc);
    return USBD_HID_CfgDesc;
}


static uint8_t  USBD_HID_DataIn (USBD_HandleTypeDef *pdev, 
        uint8_t epnum)
{
    /* Ensure that the FIFO is empty before a new transfer, this condition could 
       be caused by  a new transfer before the end of the previous transfer */
    ((USBD_HID_HandleTypeDef *)pdev->pClassData)->state = HID_IDLE;
    return USBD_OK;
}


__weak void USBD_HID_GetReport(uint8_t *buf, int len){
}

static uint8_t  USBD_HID_DataOut (USBD_HandleTypeDef *pdev, uint8_t epnum)
{
    int len;

    nOutData++;
    // data cpy so we can be ready for next usb out and used received data safely
    len = USBD_LL_GetRxDataSize (pdev, epnum);
    memcpy(OutDataBuffer, OutData, len);
    USBD_LL_PrepareReceive(pdev, HID_EPOUT_ADDR, OutData, HID_EPOUT_SIZE);
    USBD_HID_GetReport(OutDataBuffer, len);
    return USBD_OK;
}

static uint8_t  *USBD_HID_GetDeviceQualifierDesc (uint16_t *length)
{
    *length = sizeof (USBD_HID_DeviceQualifierDesc);
    return USBD_HID_DeviceQualifierDesc;
}

