#include "quakedef.h"

#define MAX_RI_DEVICE_SIZE 128
#define INIT_RIBUFFER_SIZE (sizeof(RAWINPUTHEADER)+sizeof(RAWMOUSE))

#define RI_RAWBUTTON_MASK 0x000003E0
#define RI_INVALID_POS    0x80000000

// raw input dynamic functions
typedef INT (WINAPI *pGetRawInputDeviceList)(OUT PRAWINPUTDEVICELIST pRawInputDeviceList, IN OUT PINT puiNumDevices, IN UINT cbSize);
typedef INT(WINAPI *pGetRawInputData)(IN HRAWINPUT hRawInput, IN UINT uiCommand, OUT LPVOID pData, IN OUT PINT pcbSize, IN UINT cbSizeHeader);
typedef INT(WINAPI *pGetRawInputDeviceInfoA)(IN HANDLE hDevice, IN UINT uiCommand, OUT LPVOID pData, IN OUT PINT pcbSize);
typedef BOOL (WINAPI *pRegisterRawInputDevices)(IN PCRAWINPUTDEVICE pRawInputDevices, IN UINT uiNumDevices, IN UINT cbSize);

pGetRawInputDeviceList _GRIDL;
pGetRawInputData _GRID;
pGetRawInputDeviceInfoA _GRIDIA;
pRegisterRawInputDevices _RRID;

mouse_t *currentmouse;
int rawmicecount;
RAWINPUT *raw;
int ribuffersize;

cvar_t m_rawinput = {"m_rawinput", "0", true};
cvar_t m_rawinputrdp = {"m_rawinputrdp", "0", true};

/*void IN_RawInput_DeRegister(void);
int IN_RawInput_Register(void);
void IN_RawInput_DeInit(void);
*/
void Raw_VarsRegister(void)
{
	Cvar_RegisterVariable (&m_rawinput);
	Cvar_RegisterVariable (&m_rawinputrdp);
}

void IN_RawInput_DeRegister(void)
{
	RAWINPUTDEVICE Rid;

	// deregister raw input
	Rid.usUsagePage = 0x01; 
	Rid.usUsage = 0x02; 
	Rid.dwFlags = RIDEV_REMOVE;
	Rid.hwndTarget = NULL;

	(*_RRID)(&Rid, 1, sizeof(Rid));
}

void IN_RawInput_DeInit(void)
{
	if (rawmicecount < 1)
		return;

	IN_RawInput_DeRegister();

	//free(currentmouse);

	// dealloc mouse structure
	rawmicecount = 0;
}

// raw input registration functions
int IN_RawInput_Register(void)
{
	// This function registers to receive the WM_INPUT messages
	RAWINPUTDEVICE Rid; // Register only for mouse messages from wm_input.  

	//register to get wm_input messages
	Rid.usUsagePage = 0x01; 
	Rid.usUsage = 0x02; 
	Rid.dwFlags = RIDEV_NOLEGACY; // adds HID mouse and also ignores legacy mouse messages
	Rid.hwndTarget = NULL;

	// Register to receive the WM_INPUT message for any change in mouse (buttons, wheel, and movement will all generate the same message)
	if (!(*_RRID)(&Rid, 1, sizeof(Rid)))
		return 1;

	return 0;
}

int IN_RawInput_IsRDPMouse(char *cDeviceString)
{
	char cRDPString[] = "\\??\\Root#RDP_MOU#";
	int i;

	if (strlen(cDeviceString) < strlen(cRDPString)) {
		return 0;
	}

	for (i = strlen(cRDPString) - 1; i >= 0; i--)
	{
		if (cDeviceString[i] != cRDPString[i])
			return 0;
	}

	return 1; // is RDP mouse
}

boolean IN_RawInput_Init(void)
{
	  // "0" to exclude, "1" to include
	PRAWINPUTDEVICELIST pRawInputDeviceList;
	int inputdevices, i, j, mtemp;
	char dname[MAX_RI_DEVICE_SIZE];

	// Return 0 if rawinput is not available
	HMODULE user32 = LoadLibrary("user32.dll");

	if (!user32)
	{
		Con_SafePrintf("Raw input: unable to load user32.dll\n");
		return false;
	}
	_RRID = (pRegisterRawInputDevices)GetProcAddress(user32,"RegisterRawInputDevices");
	if (!_RRID)
	{
		Con_SafePrintf("Raw input: function RegisterRawInputDevices could not be registered\n");
		return false;
	}
	_GRIDL = (pGetRawInputDeviceList)GetProcAddress(user32,"GetRawInputDeviceList");
	if (!_GRIDL)
	{
		Con_SafePrintf("Raw input: function GetRawInputDeviceList could not be registered\n");
		return false;
	}
	_GRIDIA = (pGetRawInputDeviceInfoA)GetProcAddress(user32,"GetRawInputDeviceInfoA");
	if (!_GRIDIA)
	{
		Con_SafePrintf("Raw input: function GetRawInputDeviceInfoA could not be registered\n");
		return false;
	}
	_GRID = (pGetRawInputData)GetProcAddress(user32,"GetRawInputData");
	if (!_GRID)
	{
		Con_SafePrintf("Raw input: function GetRawInputData could not be registered\n");
		return false;
	}

	rawmicecount = 0;
	currentmouse = NULL;
	raw = NULL;
	ribuffersize = 0;

	// 1st call to GetRawInputDeviceList: Pass NULL to get the number of devices.
	if ((*_GRIDL)(NULL, &inputdevices, sizeof(RAWINPUTDEVICELIST)) != 0)
	{
		Con_SafePrintf("Raw input: unable to count raw input devices\n");
		return false;
	}

	// Allocate the array to hold the DeviceList
	pRawInputDeviceList = malloc(sizeof(RAWINPUTDEVICELIST) * inputdevices);

	// 2nd call to GetRawInputDeviceList: Pass the pointer to our DeviceList and GetRawInputDeviceList() will fill the array
	if ((*_GRIDL)(pRawInputDeviceList, &inputdevices, sizeof(RAWINPUTDEVICELIST)) == -1)
	{
		Con_SafePrintf("Raw input: unable to get raw input device list\n");
		return false;
	}

	// Loop through all devices and count the mice
	for (i = 0, mtemp = 0; i < inputdevices; i++)
	{
		if (pRawInputDeviceList[i].dwType == RIM_TYPEMOUSE) 
		{
			j = MAX_RI_DEVICE_SIZE;

			// Get the device name and use it to determine if it's the RDP Terminal Services virtual device.
			if ((*_GRIDIA)(pRawInputDeviceList[i].hDevice, RIDI_DEVICENAME, dname, &j) < 0)
				dname[0] = 0;

			if (!(m_rawinputrdp.value) && IN_RawInput_IsRDPMouse(dname)) // use rdp mouse (cvar)
				continue;

			// advance temp device count
			mtemp++;
		}
	}

	// exit out if no devices found
	if (!mtemp)
	{
		Con_SafePrintf("Raw input: no usable device found\n");
		return false;
	}

	// Loop again and bind devices
	currentmouse = malloc(sizeof(mouse_t) * mtemp);
	for (i = 0; i < inputdevices; i++)
	{
		if (pRawInputDeviceList[i].dwType == RIM_TYPEMOUSE)
		{
			j = MAX_RI_DEVICE_SIZE;

			// Get the device name and use it to determine if it's the RDP Terminal Services virtual device.
			if ((*_GRIDIA)(pRawInputDeviceList[i].hDevice, RIDI_DEVICENAME, dname, &j) < 0)
				dname[0] = 0;

			if (!(m_rawinputrdp.value) && IN_RawInput_IsRDPMouse(dname)) // use rdp mouse (cvar)
				continue;

			// print pretty message about the mouse
			dname[MAX_RI_DEVICE_SIZE - 1] = 0;
			for (mtemp = strlen(dname); mtemp >= 0; mtemp--)
			{
				if (dname[mtemp] == '#')
				{
					dname[mtemp + 1] = 0;
					break;
				}
			}
			//Con_Printf("Raw input: [%i] %s\n", i, dname);

			// set handle
			currentmouse[rawmicecount].handles.rawinputhandle = pRawInputDeviceList[i].hDevice;
			currentmouse[rawmicecount].numbuttons = 10;
			currentmouse[rawmicecount].pos[0] = RI_INVALID_POS;
			rawmicecount++;
			//Con_Printf("rawmicecount: [%i]\n", rawmicecount);
		}
	}

   
	// free the RAWINPUTDEVICELIST
	free(pRawInputDeviceList);

	// alloc raw input buffer
	raw = malloc(INIT_RIBUFFER_SIZE);
	ribuffersize = INIT_RIBUFFER_SIZE;

	if (developer.value) Con_Printf("Found %i point devices for raw input\n", rawmicecount);

	return true; // success
}

void IN_RawInput_MouseRead(HANDLE in_device_handle)
{
	int i = 0, tbuttons, j;
	int dwSize;

	//if (!m_rawinput.value)
	//	return;

	// get raw input
	if ((*_GRID)((HRAWINPUT)in_device_handle, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER)) == -1) 
	{
		Con_Printf("Raw input: unable to add to get size of raw input header.\n");
		return;
	}

	if (dwSize > ribuffersize)
	{
		ribuffersize = dwSize;
		raw = realloc(raw, dwSize);
	}
		
	if ((*_GRID)((HRAWINPUT)in_device_handle, RID_INPUT, raw, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize ) {
		Con_Printf("Raw input: unable to add to get raw input header.\n");
		return;
	} 

	// find mouse in our mouse list
	for (; i < rawmicecount; i++)
	{
		if (currentmouse[i].handles.rawinputhandle == raw->header.hDevice)
			break;
	}

	if (i == rawmicecount) // we're not tracking this mouse
		return;

	// movement
	if (raw->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE)
	{
		if (currentmouse[i].pos[0] != RI_INVALID_POS)
		{
			currentmouse[i].delta[0] += raw->data.mouse.lLastX - currentmouse[i].pos[0];
			currentmouse[i].delta[1] += raw->data.mouse.lLastY - currentmouse[i].pos[1];
		}
		currentmouse[i].pos[0] = raw->data.mouse.lLastX;
		currentmouse[i].pos[1] = raw->data.mouse.lLastY;
	}
	else // RELATIVE
	{
		currentmouse[i].delta[0] += raw->data.mouse.lLastX;
		currentmouse[i].delta[1] += raw->data.mouse.lLastY;
		currentmouse[i].pos[0] = RI_INVALID_POS;
	}

	// buttons
	if (raw->data.mouse.buttondata.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) 
		Key_Event(K_MOUSE1, true);
	if (raw->data.mouse.buttondata.usButtonFlags & RI_MOUSE_BUTTON_1_UP)   
		Key_Event(K_MOUSE1, false);
	if (raw->data.mouse.buttondata.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) 
		Key_Event(K_MOUSE2, true);
	if (raw->data.mouse.buttondata.usButtonFlags & RI_MOUSE_BUTTON_2_UP)   
		Key_Event(K_MOUSE2, false);
	if (raw->data.mouse.buttondata.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) 
		Key_Event(K_MOUSE3, true);
	if (raw->data.mouse.buttondata.usButtonFlags & RI_MOUSE_BUTTON_3_UP)   
		Key_Event(K_MOUSE3, false);
	if (raw->data.mouse.buttondata.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) 
		Key_Event(K_MOUSE4, true);
	if (raw->data.mouse.buttondata.usButtonFlags & RI_MOUSE_BUTTON_4_UP)   
		Key_Event(K_MOUSE4, false);
	if (raw->data.mouse.buttondata.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) 
		Key_Event(K_MOUSE5, true);
	if (raw->data.mouse.buttondata.usButtonFlags & RI_MOUSE_BUTTON_5_UP)   
		Key_Event(K_MOUSE5, false);
	// mouse wheel
	if (raw->data.mouse.buttondata.usButtonFlags & RI_MOUSE_WHEEL)
	{      // If the current message has a mouse_wheel message
		if ((SHORT)raw->data.mouse.buttondata.usButtonData > 0) 
		{
			Key_Event(K_MWHEELUP, true);
			Key_Event(K_MWHEELUP, false);
		}
		if ((SHORT)raw->data.mouse.buttondata.usButtonData < 0)
		{
			Key_Event(K_MWHEELDOWN, true);
			Key_Event(K_MWHEELDOWN, false);
		}
	}

	// extra buttons
	tbuttons = raw->data.mouse.ulRawButtons & RI_RAWBUTTON_MASK;
	for (j=6 ; j<currentmouse[i].numbuttons ; j++)
	{
		if ( (tbuttons & (1<<j)) && !(currentmouse[i].buttons & (1<<j)) )
		{
			Key_Event (K_MOUSE1 + j, true);
		}

		if ( !(tbuttons & (1<<j)) && (currentmouse[i].buttons & (1<<j)) )
		{
			Key_Event (K_MOUSE1 + j, false);
		}

	}

	currentmouse[i].buttons &= ~RI_RAWBUTTON_MASK;
	currentmouse[i].buttons |= tbuttons;
}