/************************************************************
* Copyright (C) 2006-2007 Masahiko SAWAI All Rights Reserved. 
************************************************************/

#include "wiiremote_impl.h"

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

#include <tchar.h>
#include <windows.h>

#ifdef _WIN64 /* winddk's build didn't like not having it defined */
#define _SIZE_T_DEFINED
#endif

#include <setupapi.h>

#ifdef __GNUC__
#include <ddk/hidsdi.h>
#else
#include <hidsdi.h> 
#endif /* __GNUC__ */

/************************************************************
#type definitions
************************************************************/
struct wrmt_wiiremote_impl
{
	LPTSTR device_path;
	HANDLE hid_handle;
	OVERLAPPED hid_overlapped;
	unsigned char input_buffer[WRMT_BUFFER_SIZE];
	unsigned char output_buffer[WRMT_BUFFER_SIZE];
} ;

#define WRMT_Impl_Invaliant(self) \
{ \
	assert((self) != NULL); \
	assert((self)->device_path != NULL); \
}

/************************************************************
#private variables
************************************************************/

static
WRMT_WiiRemoteImpl 
wiiRemoteImplList[WRMT_MAX_DEVICES];

static
int 
wiiRemoteImplList_index = 0;

/************************************************************
#private functions
************************************************************/
static
BOOL
IsOpenableWiiRemote(LPCTSTR device_path)
{
	int result = FALSE;
	HANDLE handle;
	HIDD_ATTRIBUTES attr;
	assert(device_path != NULL);

	handle = CreateFile(device_path,
		GENERIC_READ|GENERIC_WRITE,
		FILE_SHARE_READ|FILE_SHARE_WRITE,
		NULL, OPEN_EXISTING, 0, NULL);
	if( handle != INVALID_HANDLE_VALUE )
	{
		if (HidD_GetAttributes(handle, &attr) )
		{
			if (attr.VendorID == WRMT_VENDOR_ID &&
				attr.ProductID == WRMT_PRODUCT_ID)
			{
				result = TRUE;
			}
		}
		CloseHandle(handle);
	}

	assert(result == TRUE || result == FALSE);
	return result;
}

static
void
WRMT_WiiRemoteImpl_InitWithDevicePath(WRMT_WiiRemoteImpl *self, LPTSTR device_path)
{
	self->device_path = device_path;
	self->hid_handle = NULL;
	self->hid_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	self->hid_overlapped.Offset = 0;
	self->hid_overlapped.OffsetHigh = 0;
	WRMT_Impl_Invaliant(self);
}

static
void
WRMT_WiiRemoteImpl_Finalize(WRMT_WiiRemoteImpl *self)
{
	WRMT_Impl_Invaliant(self);
	WRMT_WiiRemoteImpl_Close(self);
	free(self->device_path);
	self->device_path = NULL;
	CloseHandle(self->hid_overlapped.hEvent);
	self->hid_overlapped.hEvent = NULL;
}

static
WRMT_IOReturn
WRMT_WiiRemoteImpl_ReadFromDevice(WRMT_WiiRemoteImpl *self, int timeout_msec)
{
	WRMT_IOReturn result = WRMT_IO_SUCCESS;
	DWORD size;
	PDWORD psize = &size;
	LPOVERLAPPED lpOverlapped;
	WRMT_Impl_Invaliant(self);

/*	memset(self->input_buffer, 0, sizeof(unsigned char) * WRMT_BUFFER_SIZE); */

	lpOverlapped = &(self->hid_overlapped);
	if(ReadFile(self->hid_handle, self->input_buffer, WRMT_BUFFER_SIZE, psize, lpOverlapped) == 0)
	{
		if(GetLastError() == ERROR_IO_PENDING)
		{
			DWORD waitReturn;

			waitReturn = WaitForSingleObject(lpOverlapped->hEvent, timeout_msec);
			if(waitReturn == WAIT_OBJECT_0)
			{
				GetOverlappedResult(self->hid_handle, lpOverlapped, 
					psize, FALSE);
			}
			else if(waitReturn == WAIT_TIMEOUT)
			{
				CancelIo(self->hid_handle);
				result = WRMT_IO_TIMEOUT;
			}
			else if(waitReturn == WAIT_FAILED)
			{
				WRMT_SetError("Read Wait Failed.");
				result = WRMT_IO_ERROR;
			}
		}
		else
		{
			WRMT_SetError("Read Failed.");
			result = WRMT_IO_ERROR;
		}
	}
	ResetEvent(lpOverlapped->hEvent);

	WRMT_Impl_Invaliant(self);
	assert( result == WRMT_IO_ERROR ||
		result == WRMT_IO_SUCCESS ||
		result == WRMT_IO_TIMEOUT);
	return result;
}


/************************************************************
#public functions
************************************************************/

/**
* This function ignore inqiury_length_in_sec.
**/
int
WRMT_Impl_Init(int inqiury_length_in_sec)
{
	int result = 0;
	GUID guid;
	HDEVINFO hDevInfo;
	DWORD device_index;

	HidD_GetHidGuid(&guid);
	hDevInfo = SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_DEVICEINTERFACE|DIGCF_PRESENT);
	if (hDevInfo != NULL)
	{
		for (device_index = 0;;device_index++)
		{
			SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
			PSP_DEVICE_INTERFACE_DATA lpDeviceInterfaceData;
			PSP_DEVICE_INTERFACE_DETAIL_DATA lpDeviceInterfaceDetail;
			DWORD size;
			BOOL rc;

			memset(&deviceInterfaceData, 0, sizeof(SP_DEVICE_INTERFACE_DATA));
			deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
			lpDeviceInterfaceData = &deviceInterfaceData;

			rc = SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &guid, device_index, lpDeviceInterfaceData);
			if (rc == FALSE)
			{
				if(GetLastError() != ERROR_NO_MORE_ITEMS)
				{
					result = -1;
				}
				break;
			}

			 /* get required size */
			SetupDiGetDeviceInterfaceDetail( hDevInfo,
				lpDeviceInterfaceData, NULL, 0, &size, NULL);

			 /* alloc buffer size */
			lpDeviceInterfaceDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(size);
			lpDeviceInterfaceDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

			 /* get device detail data */
			rc = SetupDiGetDeviceInterfaceDetail( hDevInfo,
				lpDeviceInterfaceData, lpDeviceInterfaceDetail,
				size, &size, NULL);
			if (rc == FALSE)
			{
				result = -1;
				break;
			}

			if (IsOpenableWiiRemote(lpDeviceInterfaceDetail->DevicePath) == TRUE)
			{
				size_t length;
				LPTSTR devicePathBuffer;

				length = _tcslen(lpDeviceInterfaceDetail->DevicePath);
				devicePathBuffer = (LPTSTR)malloc(sizeof(TCHAR) * (length + 1));
				_tcscpy(devicePathBuffer, lpDeviceInterfaceDetail->DevicePath);

				WRMT_WiiRemoteImpl_InitWithDevicePath(
					&(wiiRemoteImplList[wiiRemoteImplList_index]),
					devicePathBuffer);
				wiiRemoteImplList_index++;
			}

			free(lpDeviceInterfaceDetail);
			if (wiiRemoteImplList_index >= WRMT_MAX_DEVICES) break;
		}
		SetupDiDestroyDeviceInfoList(hDevInfo);
	}

	assert(result == 0 || result == -1);
	return result;
}

void
WRMT_Impl_Quit()
{
	int i;
	for (i = 0;i < wiiRemoteImplList_index;i++)
	{
		WRMT_WiiRemoteImpl_Finalize(&(wiiRemoteImplList[i]));
	}
	wiiRemoteImplList_index = 0;
}

void
WRMT_Impl_Sleep(int ms)
{
/*	Sleep(ms); */
	SleepEx(ms, TRUE);
}

WRMT_IOReturn
WRMT_Impl_Poll(int *updated_device_index_pointer)
{
	WRMT_IOReturn result = WRMT_IO_TIMEOUT;
	int i;

	for (i = 0;i < wiiRemoteImplList_index;i++)
	{
		WRMT_WiiRemoteImpl *wiiRemoteImpl = &(wiiRemoteImplList[i]);
		if (WRMT_WiiRemoteImpl_IsOpened(wiiRemoteImpl))
		{
			WRMT_IOReturn rc;
			rc = WRMT_WiiRemoteImpl_ReadFromDevice(wiiRemoteImpl, 0);
			if (rc != WRMT_IO_TIMEOUT)
			{
				result = rc;
				if (rc == WRMT_IO_SUCCESS &&
					updated_device_index_pointer != NULL)
				{
					*updated_device_index_pointer = i;
				}
				break;
			}
		}
	}

	return result;
}

int
WRMT_Impl_GetNumWiiRemote()
{
	assert(wiiRemoteImplList_index >= 0 &&
		wiiRemoteImplList_index <= WRMT_MAX_DEVICES);
	return wiiRemoteImplList_index;
}

WRMT_WiiRemoteImpl *
WRMT_Impl_GetWiiRemoteAt(int device_index)
{
	WRMT_WiiRemoteImpl *result = NULL;
	assert(device_index >= 0 && device_index < wiiRemoteImplList_index);

	result = &(wiiRemoteImplList[device_index]);

	assert(result != NULL);
	return result;
}


WRMT_IOReturn
WRMT_WiiRemoteImpl_Open(WRMT_WiiRemoteImpl *self)
{
	WRMT_IOReturn result = WRMT_IO_ERROR;
	HANDLE handle;
	WRMT_Impl_Invaliant(self);

	handle = CreateFile(self->device_path,
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE, 
		NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
	if(handle == INVALID_HANDLE_VALUE)
	{
		WRMT_SetError("Device Open Failed.");
		return result;
	}

	self->hid_handle = handle;
	result = WRMT_IO_SUCCESS;

	WRMT_Impl_Invaliant(self);
	return result;
}

int
WRMT_WiiRemoteImpl_IsOpened(WRMT_WiiRemoteImpl *self)
{
	int result = 0;
	WRMT_Impl_Invaliant(self);

	if (self->hid_handle != NULL) result = 1;

	WRMT_Impl_Invaliant(self);

	return result;
}

void
WRMT_WiiRemoteImpl_Close(WRMT_WiiRemoteImpl *self)
{
	WRMT_Impl_Invaliant(self);

	if (self->hid_handle != NULL)
	{
		CloseHandle(self->hid_handle);
		self->hid_handle = NULL;
	}

	WRMT_Impl_Invaliant(self);
}

unsigned char *
WRMT_WiiRemoteImpl_GetOutputBuffer(WRMT_WiiRemoteImpl *self)
{
	WRMT_Impl_Invaliant(self);
	return self->output_buffer;
}

unsigned char *
WRMT_WiiRemoteImpl_GetInputBuffer(WRMT_WiiRemoteImpl *self)
{
	WRMT_Impl_Invaliant(self);
	return self->input_buffer;
}

WRMT_IOReturn
WRMT_WiiRemoteImpl_OutputToDevice(WRMT_WiiRemoteImpl *self)
{
	WRMT_IOReturn result = WRMT_IO_ERROR;
	BOOL rc;
	DWORD written_size;
	WRMT_Impl_Invaliant(self);
	assert( (self->output_buffer[0] >= WRMT_OUTPUT_REPORT_ID_FIRST) &&
		(self->output_buffer[0] <= WRMT_OUTPUT_REPORT_ID_LAST) );

	rc = WriteFile(self->hid_handle, 
		self->output_buffer,
		WRMT_BUFFER_SIZE,
		&written_size,
		&(self->hid_overlapped));

	if (rc && written_size == WRMT_BUFFER_SIZE)
	{
		result = WRMT_IO_SUCCESS;
	}

	WRMT_Impl_Invaliant(self);
	assert(result == WRMT_IO_SUCCESS ||
		result == WRMT_IO_ERROR);
	return result;
}

