#include <stdio.h>

#include "usbd_ioreq.h"
#include "usbd_desc.h"
#include "usbd_req.h"
#include "usbd_pyb_core.h"

#define USB_PYB_CONFIG_DESC_SIZ (34)

#define HID_MOUSE_REPORT_DESC_SIZE (74)

#define HID_DESCRIPTOR_TYPE (0x21)
#define HID_REPORT_DESC     (0x22)

#define HID_IN_EP           (0x81)
#define HID_IN_PACKET       (4) /* maximum, and actual, packet size */

/*********************************************
   PYB Device library callbacks
 *********************************************/

static uint8_t usbd_pyb_Init   (void *pdev, uint8_t cfgidx);
static uint8_t usbd_pyb_DeInit (void *pdev, uint8_t cfgidx);
static uint8_t usbd_pyb_Setup  (void *pdev, USB_SETUP_REQ *req);
static uint8_t usbd_pyb_DataIn (void *pdev, uint8_t epnum);

/*********************************************
   PYB specific management functions
 *********************************************/

static uint8_t *usbd_pyb_GetCfgDesc(uint8_t speed, uint16_t *length);

__ALIGN_BEGIN static uint8_t USBD_HID_AltSet __ALIGN_END = 0;
__ALIGN_BEGIN static uint8_t USBD_HID_Protocol __ALIGN_END = 0;
__ALIGN_BEGIN static uint8_t USBD_HID_IdleState __ALIGN_END = 0;

/* PYB interface class callbacks structure */
USBD_Class_cb_TypeDef USBD_PYB_HID_cb =
{
    usbd_pyb_Init,
    usbd_pyb_DeInit,
    usbd_pyb_Setup,
    NULL, // EP0_TxSent
    NULL, // usbd_pyb_EP0_RxReady,
    usbd_pyb_DataIn,
    NULL, // usbd_pyb_DataOut,
    NULL, // usbd_pyb_SOF,
    NULL, // IsoINIncomplete
    NULL, // IsoOUTIncomplete
    usbd_pyb_GetCfgDesc,
    // for OTG_HS support need to add other cfg desc here
};

/* USB PYB device Configuration Descriptor */
__ALIGN_BEGIN static uint8_t usbd_pyb_CfgDesc[USB_PYB_CONFIG_DESC_SIZ] __ALIGN_END =
{
    //--------------------------------------------------------------------------
    // Configuration Descriptor
    0x09,   // bLength: Configuration Descriptor size
    USB_CONFIGURATION_DESCRIPTOR_TYPE,      // bDescriptorType: Configuration
    LOBYTE(USB_PYB_CONFIG_DESC_SIZ),        // wTotalLength: no of returned bytes
    HIBYTE(USB_PYB_CONFIG_DESC_SIZ),
    0x01,   // bNumInterfaces: 1 interfaces
    0x01,   // bConfigurationValue: Configuration value
    0x04,   // iConfiguration: Index of string descriptor describing the configuration
    0x80,   // bmAttributes: bus powered; 0xc0 for self powered
    0xfa,   // bMaxPower: in units of 2mA

    //--------------------------------------------------------------------------
    // Interface Descriptor
    0x09,   // bLength: Interface Descriptor size
    USB_INTERFACE_DESCRIPTOR_TYPE,      // bDescriptorType: interface descriptor
    0x00,   // bInterfaceNumber: Number of Interface
    0x00,   // bAlternateSetting: Alternate setting
    0x01,   // bNumEndpoints
    0x03,   // bInterfaceClass: HID Class
    0x01,   // bInterfaceSubClass: 0=no boot, 1=BOOT
    0x02,   // nInterfaceProtocol: 0=none, 1=keyboard, 2=mouse
    0x00,   // iInterface:

    // Descriptor of Joystick Mouse HID
    0x09,   // bLength: HID Descriptor size
    HID_DESCRIPTOR_TYPE, // bDescriptorType: HID
    0x11,   // bcdHID: HID Class Spec release number, low byte
    0x01,   // bcdHID: high byte
    0x00,   // bCountryCode: Hardware target country (0=unsupported)
    0x01,   // bNumDescriptors: Number of HID class descriptors to follow
    HID_REPORT_DESC,            // bDescriptorType: report
    HID_MOUSE_REPORT_DESC_SIZE, // wItemLength: Total length of Report descriptor
    0x00,

    // Endpoint IN descriptor
    0x07,                           // bLength: Endpoint descriptor length
    USB_ENDPOINT_DESCRIPTOR_TYPE,   // bDescriptorType: Endpoint descriptor type
    HID_IN_EP,                      // bEndpointAddress: IN, address of HID
    0x03,                           // bmAttributes: Interrupt endpoint type
    LOBYTE(HID_IN_PACKET),          // wMaxPacketSize
    HIBYTE(HID_IN_PACKET),
    0x0a,                           // bInterval: polling interval, units of 1ms
};

__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE] __ALIGN_END =
{
  0x05,   0x01,
  0x09,   0x02,
  0xA1,   0x01,
  0x09,   0x01,
  
  0xA1,   0x00,
  0x05,   0x09,
  0x19,   0x01,
  0x29,   0x03,
  
  0x15,   0x00,
  0x25,   0x01,
  0x95,   0x03,
  0x75,   0x01,
  
  0x81,   0x02,
  0x95,   0x01,
  0x75,   0x05,
  0x81,   0x01,
  
  0x05,   0x01,
  0x09,   0x30,
  0x09,   0x31,
  0x09,   0x38,
  
  0x15,   0x81,
  0x25,   0x7F,
  0x75,   0x08,
  0x95,   0x03,
  
  0x81,   0x06,
  0xC0,   0x09,
  0x3c,   0x05,
  0xff,   0x09,
  
  0x01,   0x15,
  0x00,   0x25,
  0x01,   0x75,
  0x01,   0x95,
  
  0x02,   0xb1,
  0x22,   0x75,
  0x06,   0x95,
  0x01,   0xb1,
  
  0x01,   0xc0
}; 

/**
  * @brief  usbd_pyb_Init
  *         Initilaize the PYB interface
  * @param  pdev: device instance
  * @param  cfgidx: Configuration index
  * @retval status
  */
static uint8_t usbd_pyb_Init(void *pdev, uint8_t cfgidx) {
    // deinit first to reset
    usbd_pyb_DeInit(pdev, cfgidx);

    // Open EP IN
    DCD_EP_Open(pdev,
                HID_IN_EP,
                HID_IN_PACKET,
                USB_OTG_EP_INT);

    return USBD_OK;
}

/**
  * @brief  usbd_pyb_Init
  *         DeInitialize the CDC layer
  * @param  pdev: device instance
  * @param  cfgidx: Configuration index
  * @retval status
  */
static uint8_t usbd_pyb_DeInit(void *pdev, uint8_t cfgidx) {
    // Close HID EP
    DCD_EP_Close(pdev, HID_IN_EP);

    return USBD_OK;
}

#define HID_REQ_SET_PROTOCOL         (0x0B)
#define HID_REQ_GET_PROTOCOL         (0x03)
#define HID_REQ_SET_IDLE             (0x0A)
#define HID_REQ_GET_IDLE             (0x02)
#define HID_REQ_SET_REPORT           (0x09) // used?
#define HID_REQ_GET_REPORT           (0x01) // used?

/**
  * @brief  usbd_pyb_Setup
  *         Handle the CDC specific requests
  * @param  pdev: instance
  * @param  req: usb requests
  * @retval status
  */
static uint8_t usbd_pyb_Setup(void *pdev, USB_SETUP_REQ *req) {
    switch (req->bmRequest & (USB_REQ_TYPE_MASK | USB_REQ_RECIPIENT_MASK)) {

        // Standard Device Request ---------------------------------------------
        case (USB_REQ_TYPE_STANDARD | USB_REQ_RECIPIENT_DEVICE):
            break;

        // Standard Interface Request ------------------------------------------
        case (USB_REQ_TYPE_STANDARD | USB_REQ_RECIPIENT_INTERFACE):
            switch (req->bRequest) {
                case USB_REQ_GET_DESCRIPTOR: // needed for HID; SU 0x81 0x06 0x2200 0x00 request
                    // wIndex & 0xff is the interface
                    if ((req->wIndex & 0xff) == 0) {
                          uint16_t len = 0;
                          uint8_t  *pbuf = NULL;
                          if( req->wValue >> 8 == HID_REPORT_DESC) {
                            len = MIN(HID_MOUSE_REPORT_DESC_SIZE , req->wLength);
                            pbuf = HID_MOUSE_ReportDesc;
                              return USBD_CtlSendData (pdev, pbuf, len);
                          } else if( req->wValue >> 8 == HID_DESCRIPTOR_TYPE) {
                            pbuf = usbd_pyb_CfgDesc + 0x09 + 0x09;
                            len = MIN(0x09 , req->wLength);
                              return USBD_CtlSendData (pdev, pbuf, len);
                          }
                    }

                case USB_REQ_GET_INTERFACE:
                    // wIndex & 0xff is the interface
                    if ((req->wIndex & 0xff) == 0) {
                        return USBD_CtlSendData(pdev, &USBD_HID_AltSet, 1);
                    }

                case USB_REQ_SET_INTERFACE:
                    if ((uint8_t)(req->wValue) < USBD_ITF_MAX_NUM) { // TODO
                        if ((req->wIndex & 0xff) == 0) {
                            USBD_HID_AltSet = req->wValue;
                        }
                        return USBD_OK;
                    }
            }
            break;

        // Standard Endpoint Request -------------------------------------------
        case (USB_REQ_TYPE_STANDARD | USB_REQ_RECIPIENT_ENDPOINT):
            // req->wIndex is the endpoint number, including direction
            break;

        // CDC Class Requests ------------------------------
        case (USB_REQ_TYPE_CLASS | USB_REQ_RECIPIENT_INTERFACE):
            // req->wIndex is the recipient interface number
            if (req->wIndex == 0) {
                // HID component
                switch (req->bRequest) {
                    case HID_REQ_SET_PROTOCOL:
                        USBD_HID_Protocol = req->wValue;
                        return USBD_OK;

                    case HID_REQ_GET_PROTOCOL:
                        return USBD_CtlSendData(pdev, &USBD_HID_Protocol, 1);

                    case HID_REQ_SET_IDLE:
                        USBD_HID_IdleState = (req->wValue >> 8);
                        return USBD_OK;

                    case HID_REQ_GET_IDLE:
                        return USBD_CtlSendData(pdev, &USBD_HID_IdleState, 1);
                }
            }
            break;
    }

    printf("SU %x %x %x %x\n", req->bmRequest, req->bRequest, req->wValue, req->wIndex);

    // invalid command
    USBD_CtlError(pdev, req);
    return USBD_FAIL;
}

/**
  * @brief  usbd_pyb_DataIn
  *         Data sent on non-control IN endpoint
  * @param  pdev: device instance
  * @param  epnum: endpoint number
  * @retval status
  */
static uint8_t usbd_pyb_DataIn(void *pdev, uint8_t epnum) {
    switch (epnum) {
        case (HID_IN_EP & 0x7f):
            /* 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 */
            DCD_EP_Flush(pdev, HID_IN_EP);
            return USBD_OK;
    }

    printf("DI %x\n", epnum);

    return USBD_OK;
}

/**
  * @brief  usbd_pyb_GetCfgDesc 
  *         Return configuration descriptor
  * @param  speed : current device speed
  * @param  length : pointer data length
  * @retval pointer to descriptor buffer
  */
static uint8_t *usbd_pyb_GetCfgDesc(uint8_t speed, uint16_t *length) {
    *length = sizeof(usbd_pyb_CfgDesc);
    return usbd_pyb_CfgDesc;
}

/**
  * @brief  USBD_HID_SendReport
  *         Send HID Report
  * @param  pdev: device instance
  * @param  buff: pointer to report (4 bytes: ?, x, y, ?)
  * @retval status
  */
uint8_t USBD_HID_SendReport(USB_OTG_CORE_HANDLE *pdev, uint8_t *report, uint16_t len) {
    if (pdev->dev.device_status == USB_OTG_CONFIGURED) {
        DCD_EP_Tx(pdev, HID_IN_EP, report, len);
    }
    return USBD_OK;
}
