#include "fsl_device_registers.h"
#include "fsl_debug_console.h"
#include "pin_mux.h"
#include "clock_config.h"
#include "board.h"

#include "usb_device_config.h"
#include "usb.h"
#include "usb_device.h"
#include "usb_device_class.h"
#include "usb_device_cdc_acm.h"
#include "usb_device_ch9.h"

#include "usb_phy.h"

#include "usb_device_descriptor.h"

#include "usb_application.h"

#define HS_CDC_VCOM_BULK_OUT_PACKET_SIZE 512

usb_status_t USB_DeviceCdcVcomCallback(class_handle_t handle, uint32_t event, void *param);
usb_status_t USB_DeviceCallback(usb_device_handle handle, uint32_t event, void *param);

extern usb_device_class_struct_t g_UsbDeviceInterface0CicVcomConfig;
extern usb_device_endpoint_struct_t g_UsbDeviceInterface1DicVcomSetting0DefaultEndpoints[];

USB_DMA_NONINIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE) static usb_cdc_acm_info_t s_usbCdcAcmInfo;

/* Data buffer for receiving and sending*/
USB_DMA_NONINIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE) static uint8_t s_currRecvBuf[DATA_BUFF_SIZE];
USB_DMA_NONINIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE) static uint8_t s_currSendBuf[DATA_BUFF_SIZE];

USB_DMA_INIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE)
static uint8_t s_lineCoding[LINE_CODING_SIZE] = {
    /* E.g. 0x00,0xC2,0x01,0x00 : 0x0001C200 is 115200 bits per second */
    (LINE_CODING_DTERATE >> 0U) & 0x000000FFU,
    (LINE_CODING_DTERATE >> 8U) & 0x000000FFU,
    (LINE_CODING_DTERATE >> 16U) & 0x000000FFU,
    (LINE_CODING_DTERATE >> 24U) & 0x000000FFU,
    LINE_CODING_CHARFORMAT,
    LINE_CODING_PARITYTYPE,
    LINE_CODING_DATABITS};

static usb_cdc_vcom_struct_t s_cdcVcom;

static usb_device_class_config_struct_t s_cdcAcmConfig[1] = {{
    .classCallback = USB_DeviceCdcVcomCallback,
    .classHandle = 0,
    .classInfomation = &g_UsbDeviceInterface0CicVcomConfig
}};

static usb_device_class_config_list_struct_t s_cdcAcmConfigList = {
    .config = s_cdcAcmConfig,
    .deviceCallback = USB_DeviceCallback,
    .count = 1
};

void USB_OTG1_IRQHandler(void) {
    USB_DeviceEhciIsrFunction(s_cdcVcom.deviceHandle);
}

void USB_DeviceClockInit(void) {
    usb_phy_config_struct_t phy_config = {
        .D_CAL = 0x0CU, // BOARD_USB_PHY_D_CAL
        .TXCAL45DP = 0x06U,
        .TXCAL45DM = 0x06U
    };

    USB_EhciPhyInit(CONTROLLER_ID, 24000000UL, &phy_config);
}

void USB_DeviceApplicationInit(void) {
    USB_DeviceClockInit();

    s_cdcVcom.speed = USB_SPEED_HIGH;
    s_cdcVcom.attach = 0;
    s_cdcVcom.cdcAcmHandle = (class_handle_t)NULL;
    s_cdcVcom.deviceHandle = NULL;

    if(kStatus_USB_Success != USB_DeviceClassInit(CONTROLLER_ID, &s_cdcAcmConfigList, &s_cdcVcom.deviceHandle)) {
        PRINTF("DeviceClass init failed.\r\n");
    } else {
        s_cdcVcom.cdcAcmHandle = s_cdcAcmConfigList.config->classHandle;
    }

    uint8_t usbDeviceEhciIrq[] = USBHS_IRQS;
    uint8_t irqNumber = usbDeviceEhciIrq[CONTROLLER_ID - kUSB_ControllerEhci0];

    NVIC_SetPriority(irqNumber, 6U);
    EnableIRQ(irqNumber);

    SDK_DelayAtLeastUs(5000, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY);

    USB_DeviceRun(s_cdcVcom.deviceHandle);
}

usb_status_t USB_DeviceCdcVcomCallback(class_handle_t handle, uint32_t event, void *param) {
    PRINTF("USB_DeviceCdcVcomCallback 0x%x\r\n", event);

    usb_status_t error = kStatus_USB_Error;
    uint32_t len;
    uint8_t *uartBitmap;
    usb_cdc_acm_info_t *acmInfo = &s_usbCdcAcmInfo;
    usb_device_cdc_acm_request_param_struct_t *acmReqParam;
    usb_device_endpoint_callback_message_struct_t *epCbParam;
    acmReqParam = (usb_device_cdc_acm_request_param_struct_t *)param;
    epCbParam   = (usb_device_endpoint_callback_message_struct_t *)param;

    switch(event) {
        case kUSB_DeviceCdcEventSetLineCoding: {
            if(acmReqParam->isSetup == 1) {
                *(acmReqParam->buffer) = s_lineCoding;
            } else {
                *(acmReqParam->length) = 0;
            }
        } break;
    }

    return kStatus_USB_Success;
}

usb_status_t USB_DeviceCallback(usb_device_handle handle, uint32_t event, void *param) {
    PRINTF("USB_DeviceCallback 0x%x\r\n", event);

    usb_status_t error = kStatus_USB_Error;
    uint16_t *temp16   = (uint16_t *)param;
    uint8_t *temp8     = (uint8_t *)param;

    switch(event) {

        // 0x01
        case kUSB_DeviceEventBusReset: {
            s_cdcVcom.attach = 0;
            s_cdcVcom.currentConfiguration = 0;
            if(USB_DeviceClassGetSpeed(CONTROLLER_ID, &s_cdcVcom.speed) == kStatus_USB_Success) {
                USB_DeviceSetSpeed(handle, s_cdcVcom.speed);
            }
        } break;

        // 0x09
        case kUSB_DeviceEventSetConfiguration: {
            if((*temp8) == 0) {
                s_cdcVcom.attach = 0;
                s_cdcVcom.currentConfiguration = 0U;
            } else if((*temp8) == USB_COMPOSITE_CONFIGURATION_INDEX) {
                s_cdcVcom.attach = 1;
                s_cdcVcom.currentConfiguration = *temp8;

                USB_DeviceCdcAcmRecv(s_cdcVcom.cdcAcmHandle, USB_INTERFACE_1_DIC_VCOM_SETTING_0_EP_1_BULK_OUT, s_currRecvBuf,
                                     g_UsbDeviceInterface1DicVcomSetting0DefaultEndpoints[0].maxPacketSize);
            }
        } break;

        // 0xb
        case kUSB_DeviceEventGetDeviceDescriptor: {
            if(param) {
                error = USB_DeviceGetDeviceDescriptor(handle, (usb_device_get_device_descriptor_struct_t *)param);
            }
        } break;

        // 0xc
        case kUSB_DeviceEventGetConfigurationDescriptor: {
            if(param) {
                error = USB_DeviceGetConfigurationDescriptor(handle,
                    (usb_device_get_configuration_descriptor_struct_t *)param);
            }
        } break;

        // 0xd
        case kUSB_DeviceEventGetStringDescriptor: {
            if (param) {
                error = USB_DeviceGetStringDescriptor(handle, (usb_device_get_string_descriptor_struct_t *)param);
            }
       } break;
        default:
        break;
    }
    return kStatus_USB_Success;
}