/**
  * 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_HID) && CONFIG_EXAMPLE_USBD_HID
#include <platform_stdlib.h>
#include "usbd.h"
#include "usbd_hid.h"
#include "osdep_service.h"
#include "example_usbd_hid.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_HID_HOTPLUG						1

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

// Thread priorities
#define CONFIG_USBD_HID_INIT_THREAD_PRIORITY		5
#define CONFIG_USBD_HID_ISR_THREAD_PRIORITY			7
#define CONFIG_USBD_HID_HOTPLUG_THREAD_PRIORITY		8 // Should be higher than CONFIG_USBD_HID_ISR_THREAD_PRIORITY

// Send mouse data through monitor.
#define CONFIG_USBD_HID_MOUSE_CMD					1

// Send hid device data table. Once connected to PC, cursor of PC will do process according to array mdata[].
#define CONFIG_USBD_HID_CONSTANT_DATA				1
#define CONFIG_USBD_HID_CONSTANT_LOOP				10

/* 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.
} usbd_hid_mouse_data_t;

typedef struct {
	u8 func_cmd;			//left & right : alt win shift alt command
	u8 padding;				//constant panding
	u8 cmd[6];				//6 command key values
} usbd_hid_keyboard_data_t;

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

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

static void hid_cb_init(void);
static void hid_cb_deinit(void);
static void hid_cb_setup(void);
static void hid_cb_transmitted(u8 status);
#if USBD_HID_DEVICE_TYPE == USBD_HID_KEYBOARD_DEVICE
static void hid_cb_received(u8 *buf, u32 len);
#endif
static void hid_cb_status_changed(u8 status);

#if CONFIG_USBD_HID_MOUSE_CMD
#if USBD_HID_DEVICE_TYPE == USBD_HID_MOUSE_DEVICE
static u32 hid_cmd_mouse_data(u16 argc, u8  *argv[]);
#endif
#endif

static void hid_send_device_data(void *data);

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

static _sema hid_connect_sema;
static _sema hid_transmit_sema;

#if CONFIG_USBD_HID_HOTPLUG
static u8 hid_attach_status;
static _sema hid_attach_status_changed_sema;
#endif

#if CONFIG_USBD_HID_CONSTANT_DATA
#if USBD_HID_DEVICE_TYPE == USBD_HID_MOUSE_DEVICE
static usbd_hid_mouse_data_t mdata[] = {
	{0,   0,   0,  50,   0,   0},	//move the cursor 50 pixels to the right
	{0,   0,   0,   0,  50,   0},	//move the cursor down 50 pixels
	{0,   0,   0, -50,   0,   0},	//move the cursor 50 pixels to the left
	{0,   0,   0,   0, -50,   0},	//move the cursor up 50 pixels
	{0,   0,   0,   0,   0,   5},	//scroll up for 5 units
	{0,   0,   0,   0,   0,  -5},	//scroll down for 5 units
	{0,   0,   1,   0,   0,   0},	//middle button pressed
	{0,   0,   0,   0,   0,   0},	//middle button released
	{0,   1,   0,   0,   0,   0},	//right button pressed
	{0,   0,   0,   0,   0,   0},	//right button released
	{0,   0,   0,  -5,   0,   0},	//move the cursor 5 pixels to the left
	{1,   0,   0,   0,   0,   0},	//left button pressed
	{0,   0,   0,   0,   0,   0},	//left button released
};
#else
usbd_hid_keyboard_data_t mdata[] = {
	{0,   0,   {0,	0,	0,	0,	0,	0} },	//all released

	{0,   0,   {4,	0,	0,	0,	0,	0} },	//enter a pressed
	{0,   0,   {0,	0,	0,	0,	0,	0} },	//enter a released

	{2,   0,   {4,	0,	0,	0,	0,	0} },	//left Shift pressed + enter a pressed = A
	{0,   0,   {0,	0,	0,	0,	0,	0} },	//left Shift & a released
};
#endif
#endif

#if CONFIG_USBD_HID_MOUSE_CMD
#if USBD_HID_DEVICE_TYPE == USBD_HID_MOUSE_DEVICE
/*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_hid_mouse_data_cmd[] = {
	{(const u8 *)"mouse",		8, hid_cmd_mouse_data,		NULL},
};
#endif
#endif  //CONFIG_USBD_HID_MOUSE_CMD

static usbd_config_t hid_cfg = {
	.speed = CONFIG_USBD_HID_SPEED,
	.dma_enable = 0U,
	.isr_priority = CONFIG_USBD_HID_ISR_THREAD_PRIORITY,
};

static usbd_hid_usr_cb_t hid_usr_cb = {
	.init = hid_cb_init,
	.deinit = hid_cb_deinit,
	.setup = hid_cb_setup,
	.transmitted = hid_cb_transmitted,
#if USBD_HID_DEVICE_TYPE == USBD_HID_KEYBOARD_DEVICE
	.received = hid_cb_received,
#endif
	.status_changed = hid_cb_status_changed,
};

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

static void hid_cb_init(void)
{
	printf("[HID] User callback: hid device init\n");
}

static void hid_cb_deinit(void)
{
	printf("[HID] User callback: hid device deinit\n");
}

static void hid_cb_setup(void)
{
	rtw_up_sema(&hid_connect_sema);
}

static void hid_cb_transmitted(u8 status)
{
	UNUSED(status);
	//printf("User callback: transmit status %d\n", status);
	rtw_up_sema(&hid_transmit_sema);
}

#if USBD_HID_DEVICE_TYPE == USBD_HID_KEYBOARD_DEVICE
static void hid_cb_received(u8 *buf, u32 len)
{
	UNUSED(buf);
	u32 i = 0;
	printf("[HID] User callback: receive len =%ld\n", len);
	for (i = 0; i < len ; i++) {
		printf(" 0x%x ", buf[i]);
		if ((i + 1) % 10 == 0) {
			printf("\n");
		}
	}
}
#endif

static void hid_cb_status_changed(u8 status)
{
	printf("\n[HID] USB status changed: %d\n", status);
#if CONFIG_USBD_HID_HOTPLUG
	hid_attach_status = status;
	rtw_up_sema(&hid_attach_status_changed_sema);
#endif
}

#if CONFIG_USBD_HID_MOUSE_CMD
#if USBD_HID_DEVICE_TYPE == USBD_HID_MOUSE_DEVICE
static u32 hid_cmd_mouse_data(u16 argc, u8  *argv[])
{
	usbd_hid_mouse_data_t data;

	if (argc == 0U) {
		printf("[HID] 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("[HID] Send HID mouse data\n");

	hid_send_device_data(&data);

	return HAL_OK;
}
#endif
#endif  //CONFIG_USBD_HID_MOUSE_CMD

/*brief: send device data.(wrapper function usbd_hid_send_data())*/
static void hid_send_device_data(void *pdata)
{
#if USBD_HID_DEVICE_TYPE == USBD_HID_MOUSE_DEVICE
	u8 byte[4];
	usbd_hid_mouse_data_t *data = (usbd_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] |= USBD_HID_MOUSE_BUTTON_LEFT;
	}
	if (data->right != 0) {
		byte[0] |= USBD_HID_MOUSE_BUTTON_RIGHT;
	}
	if (data->middle != 0) {
		byte[0] |= USBD_HID_MOUSE_BUTTON_MIDDLE;
	}

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

	usbd_hid_send_data(byte, 4);
#else
	usbd_hid_keyboard_data_t *data = (usbd_hid_keyboard_data_t *)pdata;
	usbd_hid_send_data((u8 *)data, 8);
#endif
}

#if CONFIG_USBD_HID_HOTPLUG
static void hid_hotplug_thread(void *param)
{
	int ret = 0;

	UNUSED(param);

	for (;;) {
		if (rtw_down_sema(&hid_attach_status_changed_sema)) {
			if (hid_attach_status == USBD_ATTACH_STATUS_DETACHED) {
				printf("\n[HID] USB DETACHED\n");
				usbd_hid_deinit();
				usbd_deinit();

				rtw_mdelay_os(100);
				printf("\n[HID] Free heap size: 0x%lx\n", rtw_getFreeHeapSize());

				ret = usbd_init(&hid_cfg);
				if (ret != 0) {
					printf("\n[HID] Fail to re-init USBD driver\n");
					break;
				}
				ret = usbd_hid_init(512, &hid_usr_cb);
				if (ret != 0) {
					printf("\n[HID] Fail to re-init USB HID class\n");
					usbd_deinit();
					break;
				}
			} else if (hid_attach_status == USBD_ATTACH_STATUS_ATTACHED) {
				printf("\n[HID] USB ATTACHED\n");
			} else {
				printf("\n[HID] USB INIT\n");
			}
		}
	}

	rtw_thread_exit();
}
#endif // CONFIG_USBD_HID_HOTPLUG

static void example_usbd_hid_thread(void *param)
{
	int ret = 0;
	u32 i = 0;
	u32 delaytime = 0;
	u8 array_len = 0 ;
	int loop = 0;

	UNUSED(param);
	printf("[HID] USBD HID demo start\n");

	rtw_init_sema(&hid_connect_sema, 0);
	rtw_init_sema(&hid_transmit_sema, 0);
	rtw_up_sema(&hid_transmit_sema);
#if CONFIG_USBD_HID_HOTPLUG
	rtw_init_sema(&hid_attach_status_changed_sema, 0);
#endif

	ret = usbd_init(&hid_cfg);
	if (ret != 0) {
		printf("[HID] Fail to init USBD controller\n");
		goto example_usbd_hid_device_thread_fail;
	}

	ret = usbd_hid_init(512, &hid_usr_cb);
	if (ret != 0) {
		printf("[HID] Fail to init HID class\n");
		usbd_deinit();
		goto example_usbd_hid_device_thread_fail;
	}

#if CONFIG_USBD_HID_HOTPLUG
	struct task_struct task;
	ret = rtw_create_task(&task, "hid_hotplug_thread", 512, CONFIG_USBD_HID_HOTPLUG_THREAD_PRIORITY, hid_hotplug_thread, NULL);
	if (ret != pdPASS) {
		printf("[HID] Fail to create USBD HID status check thread\n");
		usbd_hid_deinit();
		usbd_deinit();
		goto example_usbd_hid_device_thread_fail;
	}
#endif // CONFIG_USBD_HID_HOTPLUG

	while (usbd_get_status() != USBD_ATTACH_STATUS_ATTACHED) {
		rtw_mdelay_os(100);
	}

#if CONFIG_USBD_HID_CONSTANT_DATA

	rtw_down_sema(&hid_connect_sema);

#if USBD_HID_DEVICE_TYPE == USBD_HID_MOUSE_DEVICE
	array_len = sizeof(mdata) / sizeof(usbd_hid_mouse_data_t);
	delaytime = 1000 ;
	printf("[HID] Constant mouse data transmit test started\n");
#else
	array_len = sizeof(mdata) / sizeof(usbd_hid_keyboard_data_t);
	delaytime = 50 ;
	printf("[HID] Constant keyboard data transmit test started\n");
#endif

	do {
		printf("[HID] Test round %d/%d\n", loop + 1, CONFIG_USBD_HID_CONSTANT_LOOP);
		for (i = 0; i < array_len; i++) {
			rtw_down_sema(&hid_transmit_sema);
			hid_send_device_data(&mdata[i]);
			rtw_mdelay_os(delaytime);
		}
		rtw_mdelay_os(5 * 1000); //next loop
	} while (++loop < CONFIG_USBD_HID_CONSTANT_LOOP);

	printf("[HID] Constant data transmit test done\n");

#endif

	rtw_mdelay_os(100);

example_usbd_hid_device_thread_fail:
#if CONFIG_USBD_HID_HOTPLUG
	rtw_free_sema(&hid_attach_status_changed_sema);
#endif
	rtw_free_sema(&hid_connect_sema);
	rtw_free_sema(&hid_transmit_sema);
	rtw_thread_exit();
}

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

void example_usbd_hid(void)
{
	int ret;
	struct task_struct task;

	ret = rtw_create_task(&task, "example_usbd_hid_thread", 1024, CONFIG_USBD_HID_INIT_THREAD_PRIORITY, example_usbd_hid_thread, NULL);
	if (ret != pdPASS) {
		printf("\nFail to create USBD hid thread\n");
	}
}

#endif
