/**
 * FreeRDP: A Remote Desktop Protocol Implementation
 * RemoteFX USB Redirection
 *
 * Copyright 2012 Atrust corp.
 * Copyright 2012 Alfred Liu <alfred.liu@atruscorp.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <winpr/crt.h>
#include <winpr/cmdline.h>

#include <freerdp/addin.h>

#include "urbdrc_types.h"
#include "urbdrc_main.h"

#include "libusb_udevice.h"

int libusb_debug;

#define BASIC_STATE_FUNC_DEFINED(_arg, _type) \
	static _type udevman_get_##_arg (IUDEVMAN* idevman) \
	{ \
		UDEVMAN * udevman = (UDEVMAN *) idevman; \
		return udevman->_arg; \
	} \
	static void udevman_set_##_arg (IUDEVMAN* idevman, _type _t) \
	{ \
		UDEVMAN * udevman = (UDEVMAN *) idevman; \
		udevman->_arg = _t; \
	}

#define BASIC_STATE_FUNC_REGISTER(_arg, _man) \
	_man->iface.get_##_arg = udevman_get_##_arg; \
	_man->iface.set_##_arg = udevman_set_##_arg

typedef struct _UDEVMAN UDEVMAN;

struct _UDEVMAN
{
	IUDEVMAN iface;

	IUDEVICE* idev; /* iterator device */
	IUDEVICE* head; /* head device in linked list */
	IUDEVICE* tail; /* tail device in linked list */

	UINT32 defUsbDevice;
	UINT16 flags;
	int device_num;
	int sem_timeout;

	pthread_mutex_t devman_loading;
	sem_t sem_urb_lock;
};
typedef UDEVMAN* PUDEVMAN;

static void udevman_rewind(IUDEVMAN* idevman)
{
	UDEVMAN* udevman = (UDEVMAN*) idevman;
	udevman->idev = udevman->head;
}

static int udevman_has_next(IUDEVMAN* idevman)
{
	UDEVMAN* udevman = (UDEVMAN*) idevman;

	if (udevman->idev == NULL)
		return 0;
	else
		return 1;
}

static IUDEVICE* udevman_get_next(IUDEVMAN* idevman)
{
	UDEVMAN* udevman = (UDEVMAN*) idevman;
	IUDEVICE* pdev;
	pdev = udevman->idev;
	udevman->idev = (IUDEVICE*)((UDEVICE*) udevman->idev)->next;
	return pdev;
}

static IUDEVICE* udevman_get_udevice_by_addr(IUDEVMAN* idevman, int bus_number, int dev_number)
{
	IUDEVICE* pdev;
	idevman->loading_lock(idevman);
	idevman->rewind(idevman);

	while (idevman->has_next(idevman))
	{
		pdev = idevman->get_next(idevman);

		if ((pdev->get_bus_number(pdev) == bus_number) && (pdev->get_dev_number(pdev) == dev_number))
		{
			idevman->loading_unlock(idevman);
			return pdev;
		}
	}

	idevman->loading_unlock(idevman);
	WLog_WARN(TAG, "bus:%d dev:%d not exist in udevman",
	          bus_number, dev_number);
	return NULL;
}

static int udevman_register_udevice(IUDEVMAN* idevman, int bus_number, int dev_number,
                                    int UsbDevice, UINT16 idVendor, UINT16 idProduct, int flag)
{
	UDEVMAN* udevman = (UDEVMAN*) idevman;
	IUDEVICE* pdev = NULL;
	IUDEVICE** devArray;
	int i, num, addnum = 0;
	pdev = (IUDEVICE*) udevman_get_udevice_by_addr(idevman, bus_number, dev_number);

	if (pdev != NULL)
		return 0;

	if (flag == UDEVMAN_FLAG_ADD_BY_ADDR)
	{
		pdev = udev_new_by_addr(bus_number, dev_number);

		if (pdev == NULL)
			return 0;

		pdev->set_UsbDevice(pdev, UsbDevice);
		idevman->loading_lock(idevman);

		if (udevman->head == NULL)
		{
			/* linked list is empty */
			udevman->head = pdev;
			udevman->tail = pdev;
		}
		else
		{
			/* append device to the end of the linked list */
			udevman->tail->set_p_next(udevman->tail, pdev);
			pdev->set_p_prev(pdev, udevman->tail);
			udevman->tail = pdev;
		}

		udevman->device_num += 1;
		idevman->loading_unlock(idevman);
	}
	else if (flag == UDEVMAN_FLAG_ADD_BY_VID_PID)
	{
		addnum = 0;
		/* register all device that match pid vid */
		num = udev_new_by_id(idVendor, idProduct, &devArray);

		for (i = 0; i < num; i++)
		{
			pdev = devArray[i];

			if (udevman_get_udevice_by_addr(idevman,
			                                pdev->get_bus_number(pdev), pdev->get_dev_number(pdev)) != NULL)
			{
				zfree(pdev);
				continue;
			}

			pdev->set_UsbDevice(pdev, UsbDevice);
			idevman->loading_lock(idevman);

			if (udevman->head == NULL)
			{
				/* linked list is empty */
				udevman->head = pdev;
				udevman->tail = pdev;
			}
			else
			{
				/* append device to the end of the linked list */
				udevman->tail->set_p_next(udevman->tail, pdev);
				pdev->set_p_prev(pdev, udevman->tail);
				udevman->tail = pdev;
			}

			udevman->device_num += 1;
			idevman->loading_unlock(idevman);
			addnum++;
		}

		zfree(devArray);
		return addnum;
	}
	else
	{
		WLog_ERR(TAG,  "udevman_register_udevice: function error!!");
		return 0;
	}

	return 1;
}

static int udevman_unregister_udevice(IUDEVMAN* idevman, int bus_number, int dev_number)
{
	UDEVMAN* udevman = (UDEVMAN*) idevman;
	UDEVICE* pdev, * dev;
	int ret = 0, err = 0;
	dev = (UDEVICE*) udevman_get_udevice_by_addr(idevman, bus_number, dev_number);
	idevman->loading_lock(idevman);
	idevman->rewind(idevman);

	while (idevman->has_next(idevman) != 0)
	{
		pdev = (UDEVICE*) idevman->get_next(idevman);

		if (pdev == dev) /* device exists */
		{
			/* set previous device to point to next device */
			if (dev->prev != NULL)
			{
				/* unregistered device is not the head */
				pdev = dev->prev;
				pdev->next = dev->next;
			}
			else
			{
				/* unregistered device is the head, update head */
				udevman->head = (IUDEVICE*)dev->next;
			}

			/* set next device to point to previous device */

			if (dev->next != NULL)
			{
				/* unregistered device is not the tail */
				pdev = (UDEVICE*)dev->next;
				pdev->prev = dev->prev;
			}
			else
			{
				/* unregistered device is the tail, update tail */
				udevman->tail = (IUDEVICE*)dev->prev;
			}

			udevman->device_num--;
			break;
		}
	}

	idevman->loading_unlock(idevman);

	if (dev)
	{
		/* reset device */
		if (err != LIBUSB_ERROR_NO_DEVICE)
		{
			ret = libusb_reset_device(dev->libusb_handle);

			if (ret < 0)
			{
				WLog_ERR(TAG, "libusb_reset_device: ERROR!!ret:%d", ret);
			}
		}

		/* release all interface and  attach kernel driver */
		dev->iface.attach_kernel_driver((IUDEVICE*)dev);

		if (dev->request_queue) zfree(dev->request_queue);

		/* free the config descriptor that send from windows */
		msusb_msconfig_free(dev->MsConfig);
		libusb_close(dev->libusb_handle);
		libusb_close(dev->hub_handle);
		sem_destroy(&dev->sem_id);

		/* free device info */
		if (dev->devDescriptor)
			zfree(dev->devDescriptor);

		if (dev)
			zfree(dev);

		return 1; /* unregistration successful */
	}

	/* if we reach this point, the device wasn't found */
	return 0;
}

static void udevman_parse_device_addr(char* str, int* id1, int* id2, char sign)
{
	char s1[8];
	char* s2;
	ZeroMemory(s1, sizeof(s1));
	s2 = (strchr(str, sign)) + 1;
	strncpy(s1, str, strlen(str) - (strlen(s2) + 1));
	*id1 = strtol(s1, NULL, 0);
	*id2 = strtol(s2, NULL, 0);
}

static void udevman_parse_device_pid_vid(char* str, int* id1, int* id2, char sign)
{
	char s1[8];
	char* s2;
	ZeroMemory(s1, sizeof(s1));
	s2 = (strchr(str, sign)) + 1;
	strncpy(s1, str, strlen(str) - (strlen(s2) + 1));
	*id1 = strtol(s1, NULL, 16);
	*id2 = strtol(s2, NULL, 16);
}

static int udevman_check_device_exist_by_id(IUDEVMAN* idevman, UINT16 idVendor, UINT16 idProduct)
{
	if (libusb_open_device_with_vid_pid(NULL, idVendor, idProduct))
		return 1;

	return 0;
}

static int udevman_is_auto_add(IUDEVMAN* idevman)
{
	UDEVMAN* udevman = (UDEVMAN*) idevman;
	return (udevman->flags & UDEVMAN_FLAG_ADD_BY_AUTO) ? 1 : 0;
}

static IUDEVICE* udevman_get_udevice_by_UsbDevice_try_again(IUDEVMAN* idevman, UINT32 UsbDevice)
{
	UDEVICE* pdev;
	idevman->loading_lock(idevman);
	idevman->rewind(idevman);

	while (idevman->has_next(idevman))
	{
		pdev = (UDEVICE*) idevman->get_next(idevman);

		if (pdev->UsbDevice == UsbDevice)
		{
			idevman->loading_unlock(idevman);
			return (IUDEVICE*) pdev;
		}
	}

	idevman->loading_unlock(idevman);
	return NULL;
}

static IUDEVICE* udevman_get_udevice_by_UsbDevice(IUDEVMAN* idevman, UINT32 UsbDevice)
{
	UDEVICE* pdev;
	idevman->loading_lock(idevman);
	idevman->rewind(idevman);

	while (idevman->has_next(idevman))
	{
		pdev = (UDEVICE*) idevman->get_next(idevman);

		if (pdev->UsbDevice == UsbDevice)
		{
			idevman->loading_unlock(idevman);
			return (IUDEVICE*) pdev;
		}
	}

	idevman->loading_unlock(idevman);
	/* try again */
	pdev = (UDEVICE*) idevman->get_udevice_by_UsbDevice_try_again(idevman, UsbDevice);

	if (pdev)
	{
		return (IUDEVICE*) pdev;
	}

	WLog_ERR(TAG, "0x%"PRIx32" ERROR!!", UsbDevice);
	return NULL;
}

static void udevman_loading_lock(IUDEVMAN* idevman)
{
	UDEVMAN* udevman = (UDEVMAN*) idevman;
	pthread_mutex_lock(&udevman->devman_loading);
}

static void udevman_loading_unlock(IUDEVMAN* idevman)
{
	UDEVMAN* udevman = (UDEVMAN*) idevman;
	pthread_mutex_unlock(&udevman->devman_loading);
}

static void udevman_wait_urb(IUDEVMAN* idevman)
{
	UDEVMAN* udevman = (UDEVMAN*) idevman;
	sem_wait(&udevman->sem_urb_lock);
}

static void udevman_push_urb(IUDEVMAN* idevman)
{
	UDEVMAN* udevman = (UDEVMAN*) idevman;
	sem_post(&udevman->sem_urb_lock);
}

BASIC_STATE_FUNC_DEFINED(defUsbDevice, UINT32)
BASIC_STATE_FUNC_DEFINED(device_num, int)
BASIC_STATE_FUNC_DEFINED(sem_timeout, int)

static void udevman_free(IUDEVMAN* idevman)
{
	UDEVMAN* udevman = (UDEVMAN*) idevman;
	pthread_mutex_destroy(&udevman->devman_loading);
	sem_destroy(&udevman->sem_urb_lock);
	libusb_exit(NULL);

	/* free udevman */

	if (udevman)
		zfree(udevman);
}

static void udevman_load_interface(UDEVMAN* udevman)
{
	/* standard */
	udevman->iface.free = udevman_free;
	/* manage devices */
	udevman->iface.rewind = udevman_rewind;
	udevman->iface.get_next = udevman_get_next;
	udevman->iface.has_next = udevman_has_next;
	udevman->iface.register_udevice = udevman_register_udevice;
	udevman->iface.unregister_udevice = udevman_unregister_udevice;
	udevman->iface.get_udevice_by_UsbDevice = udevman_get_udevice_by_UsbDevice;
	udevman->iface.get_udevice_by_UsbDevice_try_again =
	    udevman_get_udevice_by_UsbDevice_try_again;
	/* Extension */
	udevman->iface.check_device_exist_by_id = udevman_check_device_exist_by_id;
	udevman->iface.isAutoAdd = udevman_is_auto_add;
	/* Basic state */
	BASIC_STATE_FUNC_REGISTER(defUsbDevice, udevman);
	BASIC_STATE_FUNC_REGISTER(device_num, udevman);
	BASIC_STATE_FUNC_REGISTER(sem_timeout, udevman);
	/* control semaphore or mutex lock */
	udevman->iface.loading_lock = udevman_loading_lock;
	udevman->iface.loading_unlock = udevman_loading_unlock;
	udevman->iface.push_urb = udevman_push_urb;
	udevman->iface.wait_urb = udevman_wait_urb;
}

COMMAND_LINE_ARGUMENT_A urbdrc_udevman_args[] =
{
	{ "dbg", COMMAND_LINE_VALUE_FLAG, "", NULL, BoolValueFalse, -1, NULL, "debug" },
	{ "dev", COMMAND_LINE_VALUE_REQUIRED, "<devices>", NULL, NULL, -1, NULL, "device list" },
	{ "id", COMMAND_LINE_VALUE_FLAG, "", NULL, BoolValueFalse, -1, NULL, "FLAG_ADD_BY_VID_PID" },
	{ "addr", COMMAND_LINE_VALUE_FLAG, "", NULL, BoolValueFalse, -1, NULL, "FLAG_ADD_BY_ADDR" },
	{ "auto", COMMAND_LINE_VALUE_FLAG, "", NULL, BoolValueFalse, -1, NULL, "FLAG_ADD_BY_AUTO" },
	{ NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
};

static void urbdrc_udevman_register_devices(UDEVMAN* udevman, char* devices)
{
	char* token;
	int idVendor;
	int idProduct;
	int bus_number;
	int dev_number;
	int success = 0;
	char hardware_id[16];
	char* default_devices = "id";
	UINT32 UsbDevice = BASE_USBDEVICE_NUM;

	if (!devices)
		devices = default_devices;

	/* register all usb devices */
	token = strtok(devices, "#");

	while (token)
	{
		bus_number = 0;
		dev_number = 0;
		idVendor = 0;
		idProduct = 0;
		sprintf_s(hardware_id, ARRAYSIZE(hardware_id), "%s", token);
		token = strtok(NULL, "#");

		if (udevman->flags & UDEVMAN_FLAG_ADD_BY_VID_PID)
		{
			udevman_parse_device_pid_vid(hardware_id, &idVendor, &idProduct, ':');
			success = udevman->iface.register_udevice((IUDEVMAN*) udevman,
			          0, 0, UsbDevice, (UINT16) idVendor, (UINT16) idProduct, UDEVMAN_FLAG_ADD_BY_VID_PID);
		}
		else if (udevman->flags & UDEVMAN_FLAG_ADD_BY_ADDR)
		{
			udevman_parse_device_addr(hardware_id, &bus_number, &dev_number, ':');
			success = udevman->iface.register_udevice((IUDEVMAN*) udevman,
			          bus_number, dev_number, UsbDevice, 0, 0, UDEVMAN_FLAG_ADD_BY_ADDR);
		}

		if (success)
			UsbDevice++;
	}

	udevman->defUsbDevice = UsbDevice;
}

static void urbdrc_udevman_parse_addin_args(UDEVMAN* udevman, ADDIN_ARGV* args)
{
	int status;
	DWORD flags;
	COMMAND_LINE_ARGUMENT_A* arg;
	flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON;
	status = CommandLineParseArgumentsA(args->argc, args->argv,
	                                    urbdrc_udevman_args, flags, udevman, NULL, NULL);
	arg = urbdrc_udevman_args;

	do
	{
		if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
			continue;

		CommandLineSwitchStart(arg)
		CommandLineSwitchCase(arg, "dbg")
		{
			WLog_SetLogLevel(WLog_Get(TAG), WLOG_TRACE);
		}
		CommandLineSwitchCase(arg, "dev")
		{
			urbdrc_udevman_register_devices(udevman, arg->Value);
		}
		CommandLineSwitchCase(arg, "id")
		{
			udevman->flags = UDEVMAN_FLAG_ADD_BY_VID_PID;
		}
		CommandLineSwitchCase(arg, "addr")
		{
			udevman->flags = UDEVMAN_FLAG_ADD_BY_ADDR;
		}
		CommandLineSwitchCase(arg, "auto")
		{
			udevman->flags |= UDEVMAN_FLAG_ADD_BY_AUTO;
		}
		CommandLineSwitchDefault(arg)
		{
		}
		CommandLineSwitchEnd(arg)
	}
	while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
}

#ifdef BUILTIN_CHANNELS
#define freerdp_urbdrc_client_subsystem_entry	libusb_freerdp_urbdrc_client_subsystem_entry
#else
#define freerdp_urbdrc_client_subsystem_entry	FREERDP_API freerdp_urbdrc_client_subsystem_entry
#endif

int freerdp_urbdrc_client_subsystem_entry(PFREERDP_URBDRC_SERVICE_ENTRY_POINTS pEntryPoints)
{
	UDEVMAN* udevman;
	ADDIN_ARGV* args = pEntryPoints->args;
	libusb_init(NULL);
	udevman = (PUDEVMAN) malloc(sizeof(UDEVMAN));

	if (!udevman)
		return -1;

	udevman->device_num = 0;
	udevman->idev = NULL;
	udevman->head = NULL;
	udevman->tail = NULL;
	udevman->sem_timeout = 0;
	udevman->flags = UDEVMAN_FLAG_ADD_BY_VID_PID;
	pthread_mutex_init(&udevman->devman_loading, NULL);
	sem_init(&udevman->sem_urb_lock, 0, MAX_URB_REQUSET_NUM);
	/* load usb device service management */
	udevman_load_interface(udevman);
	/* set debug flag, to enable Debug message for usb data transfer */
	libusb_debug = 10;
	urbdrc_udevman_parse_addin_args(udevman, args);
	pEntryPoints->pRegisterUDEVMAN(pEntryPoints->plugin, (IUDEVMAN*) udevman);
	WLog_DBG(TAG, "UDEVMAN device registered.");
	return 0;
}
