/**
  * This module is a confidential and proprietary property of RealTek and
  * possession or use of this module requires written permission of RealTek.
  *
  * Copyright(c) 2021, Realtek Semiconductor Corporation. All rights reserved.
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------ */

#include <platform_opts.h>
#if defined(CONFIG_EXAMPLE_USBD_COMPOSITE) && CONFIG_EXAMPLE_USBD_COMPOSITE
#include "usbd.h"
#include "usbd_composite.h"
#include "osdep_service.h"

/* Private defines -----------------------------------------------------------*/

// This configuration is used to enable a thread to check hotplug event
// and reset USB stack to avoid memory leak, only for example.
#define CONFIG_USBD_COMPOSITE_HOTPLUG							1

// USB speed
#ifdef CONFIG_USB_FS
#define CONFIG_USBD_COMPOSITE_SPEED								USB_SPEED_FULL
#else
#define CONFIG_USBD_COMPOSITE_SPEED								USB_SPEED_HIGH
#endif

#define CONFIG_USBD_COMPOSITE_HID_CDC_ACM_BULK_IN_XFER_SIZE		2048U
#define CONFIG_USBD_COMPOSITE_HID_CDC_ACM_BULK_OUT_XFER_SIZE	2048U
#define CONFIG_USBD_COMPOSITE_HID_HID_INTR_IN_XFER_SIZE			512U

// Thread priorities
#define CONFIG_USBD_COMPOSITE_INIT_THREAD_PRIORITY				5
#define CONFIG_USBD_COMPOSITE_ISR_THREAD_PRIORITY				7
#define CONFIG_USBD_COMPOSITE_HOTPLUG_THREAD_PRIORITY			8 // Should be higher than CONFIG_USBD_COMPOSITE_ISR_THREAD_PRIORITY

/* Private types -------------------------------------------------------------*/

typedef struct {
	u8 left;			//left button. 0: release, 1: press
	u8 right;			//right button. 0: release, 1: press
	u8 middle;			//wheel button. 0: release, 1: press
	char x_axis;		//x-axis pixels. relative value from -127 to 127, positive for right and negative for left
	char y_axis;		//y-axis pixels. relative value from -127 to 127, positive for up and negative for down
	char wheel;			//scrolling units. relative value from -127 to 127, positive for up and negative for down.
} composite_hid_mouse_data_t;

/* Private macros ------------------------------------------------------------*/

/* Private function prototypes -----------------------------------------------*/

static u8 composite_cdc_acm_cb_init(void);
static u8 composite_cdc_acm_cb_deinit(void);
static u8 composite_cdc_acm_cb_setup(usb_setup_req_t *req, u8 *buf);
static u8 composite_cdc_acm_cb_received(u8 *buf, u32 Len);

static void composite_hid_send_device_data(void *pdata);
static u8 composite_hid_cb_setup(usb_setup_req_t *req, u8 *buf);

static void composite_cb_status_changed(u8 status);

/* Private variables ---------------------------------------------------------*/

static usbd_config_t composite_cfg = {
	.speed = CONFIG_USBD_COMPOSITE_SPEED,
	.dma_enable = 0U,
	.isr_priority = CONFIG_USBD_COMPOSITE_ISR_THREAD_PRIORITY,
	.intr_use_ptx_fifo = 0U,
	.nptx_max_epmis_cnt = 100U,
};

static usbd_composite_cdc_acm_usr_cb_t composite_cdc_acm_usr_cb = {
	.init = composite_cdc_acm_cb_init,
	.deinit = composite_cdc_acm_cb_deinit,
	.setup = composite_cdc_acm_cb_setup,
	.received = composite_cdc_acm_cb_received
};

static usbd_composite_cdc_acm_line_coding_t composite_cdc_acm_line_coding;

static u16 composite_cdc_acm_ctrl_line_state;

static u16 composite_hid_protocol;

static u16 composite_hid_idle_state;

static usbd_composite_hid_usr_cb_t composite_hid_usr_cb = {
	.setup = composite_hid_cb_setup,
};

static usbd_composite_cb_t composite_cb = {
	.status_changed = composite_cb_status_changed,
};

#if CONFIG_USBD_COMPOSITE_HOTPLUG
static u8 composite_attach_status;
static _sema composite_attach_status_changed_sema;
#endif

static u32 composite_cmd_mouse_data(u16 argc, u8  *argv[])
{
	composite_hid_mouse_data_t data;

	if (argc == 0U) {
		printf("[USB] Invalid arguments, usage:\n"
			   "mouse <left> [<right> <middle> <x_axis> <y_axis> <wheel>]\n");
		return HAL_ERR_PARA;
	}

	memset((void *)&data, 0, sizeof(data));

	if (argc > 0) {
		data.left = _strtoul((const char *)(argv[0]), (char **)NULL, 10);
	}

	if (argc > 1) {
		data.right = _strtoul((const char *)(argv[1]), (char **)NULL, 10);
	}

	if (argc > 2) {
		data.middle = _strtoul((const char *)(argv[2]), (char **)NULL, 10);
	}

	if (argc > 3) {
		data.x_axis = _strtoul((const char *)(argv[3]), (char **)NULL, 10);
	}

	if (argc > 4) {
		data.y_axis = _strtoul((const char *)(argv[4]), (char **)NULL, 10);
	}

	if (argc > 5) {
		data.wheel = _strtoul((const char *)(argv[5]), (char **)NULL, 10);
	}

	printf("[USB] Send HID mouse data\n");

	composite_hid_send_device_data(&data);

	return HAL_OK;
}

/*exmaple cmd: mouse  0   0   0   50   0   0
	left button release,
	right button release,
	middle button release,
	x_axis: move the cursor 50 pixels to the right,
	y_axos: no movement,
	wheel: no scrolling.
*/
CMD_TABLE_DATA_SECTION
const COMMAND_TABLE  usbd_composite_mouse_data_cmd[] = {
	{(const u8 *)"mouse",		8, composite_cmd_mouse_data,		NULL},
};

/* Private functions ---------------------------------------------------------*/

/**
  * @brief  Initializes the CDC media layer
  * @param  None
  * @retval Status
  */
static u8 composite_cdc_acm_cb_init(void)
{
	usbd_composite_cdc_acm_line_coding_t *lc = &composite_cdc_acm_line_coding;

	lc->bitrate = 150000;
	lc->format = 0x00;
	lc->parity_type = 0x00;
	lc->data_type = 0x08;

	return HAL_OK;
}

/**
  * @brief  DeInitializes the CDC media layer
  * @param  None
  * @retval Status
  */
static u8 composite_cdc_acm_cb_deinit(void)
{
	return HAL_OK;
}

/**
  * @brief  Data received over USB OUT endpoint are sent over CDC interface through this function.
  * @param  Buf: RX buffer
  * @param  Len: RX data length (in bytes)
  * @retval Status
  */
static u8 composite_cdc_acm_cb_received(u8 *buf, u32 len)
{
	//printf("\n[USB] RX data0=0x%02x,len=%d bytes\n", buf[0], len);
	return usbd_composite_cdc_acm_transmit(buf, len);
}

/**
  * @brief  Handle the CDC class control requests
  * @param  cmd: Command code
  * @param  buf: Buffer containing command data (request parameters)
  * @param  len: Number of data to be sent (in bytes)
  * @retval Status
  */
static u8 composite_cdc_acm_cb_setup(usb_setup_req_t *req, u8 *buf)
{
	u8 ret = HAL_OK;
	usbd_composite_cdc_acm_line_coding_t *lc = &composite_cdc_acm_line_coding;

	switch (req->bRequest) {
	case COMP_CDC_SEND_ENCAPSULATED_COMMAND:
		/* Do nothing */
		break;

	case COMP_CDC_GET_ENCAPSULATED_RESPONSE:
		/* Do nothing */
		break;

	case COMP_CDC_SET_COMM_FEATURE:
		/* Do nothing */
		break;

	case COMP_CDC_GET_COMM_FEATURE:
		/* Do nothing */
		break;

	case COMP_CDC_CLEAR_COMM_FEATURE:
		/* Do nothing */
		break;

	case COMP_CDC_SET_LINE_CODING:
		printf("[USB] COMP_CDC_SET_LINE_CODING\n");
		if (req->wLength == COMP_CDC_ACM_LINE_CODING_SIZE) {
			lc->bitrate = (u32)(buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24));
			lc->format = buf[4];
			lc->parity_type = buf[5];
			lc->data_type = buf[6];
		}
		break;

	case COMP_CDC_GET_LINE_CODING:
		printf("[USB] COMP_CDC_GET_LINE_CODING\n");
		buf[0] = (u8)(lc->bitrate & 0xFF);
		buf[1] = (u8)((lc->bitrate >> 8) & 0xFF);
		buf[2] = (u8)((lc->bitrate >> 16) & 0xFF);
		buf[3] = (u8)((lc->bitrate >> 24) & 0xFF);
		buf[4] = lc->format;
		buf[5] = lc->parity_type;
		buf[6] = lc->data_type;
		break;

	case COMP_CDC_SET_CONTROL_LINE_STATE:
		printf("[USB] COMP_CDC_SET_CONTROL_LINE_STATE\n");
		/*
		wValue:	wValue, Control Signal Bitmap
				D2-15:	Reserved, 0
				D1:	RTS, 0 - Deactivate, 1 - Activate
				D0:	DTR, 0 - Not Present, 1 - Present
		*/
		composite_cdc_acm_ctrl_line_state = req->wValue;
		if (composite_cdc_acm_ctrl_line_state & 0x01) {
			printf("[USB] VCOM port activated\n");
		}
		break;

	case COMP_CDC_SEND_BREAK:
		/* Do nothing */
		break;

	default:
		printf("[USB] Invalid CDC bRequest 0x%02X\n", req->bRequest);
		ret = HAL_ERR_PARA;
		break;
	}

	return ret;
}

static u8 composite_hid_cb_setup(usb_setup_req_t *req, u8 *buf)
{
	u8 ret = HAL_OK;

	switch (req->bRequest) {
	case COMP_HID_SET_PROTOCOL:
		printf("[USB] COMP_HID_SET_PROTOCOL\n");
		composite_hid_protocol = req->wValue;
		break;
	case COMP_HID_GET_PROTOCOL:
		printf("[USB] COMP_HID_GET_PROTOCOL\n");
		buf[0] = (u8)(composite_hid_protocol & 0xFF);
		break;
	case COMP_HID_SET_REPORT:
		printf("[USB] COMP_HID_SET_REPORT\n");
		break;
	case COMP_HID_SET_IDLE:
		printf("[USB] COMP_HID_SET_IDLE\n");
		composite_hid_idle_state = req->wValue >> 8;
		break;
	case COMP_HID_GET_IDLE:
		printf("[USB] COMP_HID_GET_IDLE\n");
		buf[0] = (u8)(composite_hid_idle_state & 0xFF);
		break;
	default:
		printf("[USB] Invalid HID bRequest 0x%02X\n", req->bRequest);
		ret = HAL_ERR_PARA;
		break;
	}

	return ret;
}

/*brief: send device data.(wrapper function usbd_composite_hid_send_data())*/
static void composite_hid_send_device_data(void *pdata)
{
	u8 byte[4];
	composite_hid_mouse_data_t *data = (composite_hid_mouse_data_t *)pdata;

	memset(byte, 0, 4);

	/* mouse protocol:
		BYTE0
			|-- bit7~bit3: RSVD
			|-- bit2: middle button press
			|-- bit1: right button press
			|-- bit0: left button press
		BYTE1: x-axis value, -128~127
		BYTE2: y-axis value, -128~127
		BYTE3: wheel value, -128~127
	*/

	if (data->left != 0) {
		byte[0] |= COMP_HID_MOUSE_BUTTON_LEFT;
	}
	if (data->right != 0) {
		byte[0] |= COMP_HID_MOUSE_BUTTON_RIGHT;
	}
	if (data->middle != 0) {
		byte[0] |= COMP_HID_MOUSE_BUTTON_MIDDLE;
	}

	byte[0] |= COMP_HID_MOUSE_BUTTON_RESERVED;
	byte[1] = data->x_axis;
	byte[2] = data->y_axis;
	byte[3] = data->wheel;

	usbd_composite_hid_send_data(byte, 4);
}

static void composite_cb_status_changed(u8 status)
{
	printf("\n[USB] USB status changed: %d\n", status);
#if CONFIG_USBD_COMPOSITE_HOTPLUG
	composite_attach_status = status;
	rtw_up_sema(&composite_attach_status_changed_sema);
#endif
}

#if CONFIG_USBD_COMPOSITE_HOTPLUG
static void composite_hotplug_thread(void *param)
{
	int ret = 0;

	UNUSED(param);

	for (;;) {
		if (rtw_down_sema(&composite_attach_status_changed_sema)) {
			if (composite_attach_status == USBD_ATTACH_STATUS_DETACHED) {
				printf("\n[USB] USB DETACHED\n");
				usbd_composite_deinit();
				ret = usbd_deinit();
				if (ret != 0) {
					printf("\n[USB] Fail to de-init USBD driver\n");
					break;
				}
				printf("\n[USB] Free heap size: 0x%lx\n", rtw_getFreeHeapSize());
				ret = usbd_init(&composite_cfg);
				if (ret != 0) {
					printf("\n[USB] Fail to re-init USBD driver\n");
					break;
				}
				ret = usbd_composite_init(CONFIG_USBD_COMPOSITE_HID_CDC_ACM_BULK_OUT_XFER_SIZE,
										  CONFIG_USBD_COMPOSITE_HID_CDC_ACM_BULK_IN_XFER_SIZE,
										  &composite_cdc_acm_usr_cb,
										  CONFIG_USBD_COMPOSITE_HID_HID_INTR_IN_XFER_SIZE,
										  &composite_hid_usr_cb,
										  &composite_cb);
				if (ret != 0) {
					printf("\n[USB] Fail to re-init USB composite class\n");
					usbd_deinit();
					break;
				}
			} else if (composite_attach_status == USBD_ATTACH_STATUS_ATTACHED) {
				printf("\n[USB] USB ATTACHED\n");
			} else {
				printf("\n[USB] USB INIT\n");
			}
		}
	}

	rtw_thread_exit();
}
#endif // CONFIG_USBD_COMPOSITE_HOTPLUG

static void example_usbd_composite_thread(void *param)
{
	int ret = 0;
#if CONFIG_USBD_COMPOSITE_HOTPLUG
	struct task_struct check_task;
#endif

	UNUSED(param);

#if CONFIG_USBD_COMPOSITE_HOTPLUG
	rtw_init_sema(&composite_attach_status_changed_sema, 0);
#endif

	ret = usbd_init(&composite_cfg);
	if (ret != HAL_OK) {
		printf("[USB] Fail to init USB device driver\n");
		goto exit_usbd_init_fail;
	}

	ret = usbd_composite_init(CONFIG_USBD_COMPOSITE_HID_CDC_ACM_BULK_OUT_XFER_SIZE,
							  CONFIG_USBD_COMPOSITE_HID_CDC_ACM_BULK_IN_XFER_SIZE,
							  &composite_cdc_acm_usr_cb,
							  CONFIG_USBD_COMPOSITE_HID_HID_INTR_IN_XFER_SIZE,
							  &composite_hid_usr_cb,
							  &composite_cb);
	if (ret != HAL_OK) {
		printf("[USB] Fail to init USB composite class\n");
		goto exit_usbd_composite_init_fail;
	}

#if CONFIG_USBD_COMPOSITE_HOTPLUG
	ret = rtw_create_task(&check_task, "composite_hotplug_thread", 512, CONFIG_USBD_COMPOSITE_HOTPLUG_THREAD_PRIORITY, composite_hotplug_thread,
						  NULL);
	if (ret != pdPASS) {
		printf("[USB] Fail to create status check thread\n");
		goto exit_create_check_task_fail;
	}
#endif

	rtw_mdelay_os(100);

	printf("[USB] Composite demo started\n");

	rtw_thread_exit();

	return;

#if CONFIG_USBD_COMPOSITE_HOTPLUG
exit_create_check_task_fail:
	usbd_composite_deinit();
#endif

exit_usbd_composite_init_fail:
	usbd_deinit();

exit_usbd_init_fail:
#if CONFIG_USBD_COMPOSITE_HOTPLUG
	rtw_free_sema(&composite_attach_status_changed_sema);
#endif
	rtw_thread_exit();
}

/* Exported functions --------------------------------------------------------*/

/**
  * @brief  USB download de-initialize
  * @param  None
  * @retval Result of the operation: 0 if success else fail
  */
void example_usbd_composite(void)
{
	int ret;
	struct task_struct task;

	ret = rtw_create_task(&task, "example_usbd_composite_thread", 1024, CONFIG_USBD_COMPOSITE_INIT_THREAD_PRIORITY, example_usbd_composite_thread, NULL);
	if (ret != pdPASS) {
		printf("[USB] Fail to create composite thread\n");
	}
}

#endif

