#include <Windows.h>
#include <SetupAPI.h>
#include <hidsdi.h>

#include <string>
#include <list>

#include "local.h"
#include "tstring.h"

unsigned int getAllHidDevicePaths(std::list<std::tstring> &paths)
{
	paths.clear();

	GUID guid;
	HidD_GetHidGuid(&guid);
	// 渡されたGUIDに該当するデバイス情報を取得
	HDEVINFO deviceInfo;
	deviceInfo = SetupDiGetClassDevs(&guid, NULL, NULL, (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));
	if (deviceInfo != INVALID_HANDLE_VALUE)
	{
		SP_DEVICE_INTERFACE_DATA deviceInfoData;
		deviceInfoData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
		// インターフェースを列挙
		for (DWORD index = 0; SetupDiEnumDeviceInterfaces(deviceInfo, 0, &guid, index, &deviceInfoData); ++index) {
			DWORD requiredLength;
			// デバイスパスを含む構造体のサイズを取得
			SetupDiGetDeviceInterfaceDetail(deviceInfo, &deviceInfoData, NULL, 0, &requiredLength, NULL);
			// 取得したサイズを元にバッファを確保
			LPBYTE deviceDetailDataBuffer = new BYTE[requiredLength];
			PSP_DEVICE_INTERFACE_DETAIL_DATA deviceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)deviceDetailDataBuffer;
			deviceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
			// デバイスパスを取得
			SetupDiGetDeviceInterfaceDetail(deviceInfo, &deviceInfoData, deviceDetailData, requiredLength, &requiredLength, NULL);
			// デバイスパスをコピー
			paths.push_back(deviceDetailData->DevicePath);
			// バッファ削除
			delete[] deviceDetailDataBuffer;	// == deviceDetailData
		}
		// 使用したデバイス情報の破棄
		SetupDiDestroyDeviceInfoList(deviceInfo);
	}

	return paths.size();
}

unsigned int findHidDeviceByID(std::list<std::tstring> &paths, USHORT vid, USHORT pid)
{
	for (auto it = paths.begin(); it != paths.end(); ) {
		HANDLE h = CreateFile(it->c_str(), // デバイスパス文字列
			(GENERIC_READ | GENERIC_WRITE), (FILE_SHARE_READ | FILE_SHARE_WRITE),
			nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
		if (h != INVALID_HANDLE_VALUE) {
			HIDD_ATTRIBUTES attributes; // デバイスの基本情報
			if (HidD_GetAttributes(h, &attributes)) {
				// attributes が取得できた
				if (attributes.VendorID != vid || attributes.ProductID != pid) {
					it = paths.erase(it);	// VID, PID が違う
				}
				else {
					++it;	// 目的の VID, PID を見つけた
				}
			}
			else {
				it = paths.erase(it);	// アトリビュートが得られない
			}
			CloseHandle(h);
		}
		else {
			it = paths.erase(it);		// オープンできない
		}
	}
	return paths.size();
}

bool getHidDeviceInfo(std::tstring &path, std::wstring &manufacture, std::wstring &product)
{
	bool ret = false;

	HANDLE h = CreateFile(path.c_str(), // デバイスパス文字列
		(GENERIC_READ | GENERIC_WRITE), (FILE_SHARE_READ | FILE_SHARE_WRITE),
		nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
	
	if (h != INVALID_HANDLE_VALUE) {
	
		HIDD_ATTRIBUTES attributes; // デバイスの基本情報
		if (HidD_GetAttributes(h, &attributes)) {
			
			// USB の規格上文字列は最大 255 バイト。unicode なので 126 文字+null が最大と考えていい
			wchar_t wstr[256];	// 一応256文字確保する

			memset(wstr, '\0', sizeof(wstr));
			if (HidD_GetManufacturerString(h, wstr, sizeof(wstr))) {	// バイトでサイズ指定する
				manufacture = wstr;
			}

			memset(wstr, '\0', sizeof(wstr));
			if (HidD_GetProductString(h, wstr, sizeof(wstr))) {
				product = wstr;
			}
			ret = true;
		}
		CloseHandle(h);
	}

	return ret;
}

bool getHidDeviceCapabilities(std::tstring &path, USHORT *psendBufLen, USHORT *precvBufLen)
{
	bool ret = false;

	HANDLE h = CreateFile(path.c_str(), // デバイスパス文字列
		(GENERIC_READ | GENERIC_WRITE), (FILE_SHARE_READ | FILE_SHARE_WRITE),
		nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);

	if (h != INVALID_HANDLE_VALUE) {

		PHIDP_PREPARSED_DATA preparsedData; // HID Report Descriptor 情報
		if (HidD_GetPreparsedData(h, &preparsedData)) {

			// preparsedData が取得できた
			HIDP_CAPS capabilities; // デバイスが送受信するレポートに関する基本情報
			if (HIDP_STATUS_SUCCESS == HidP_GetCaps(preparsedData, &capabilities)) {
				// capabilities が取得できた
				//capabilities.UsagePage
				//capabilities.Usage
				*psendBufLen = capabilities.OutputReportByteLength;
				*precvBufLen = capabilities.InputReportByteLength;

				ret = true;
			}
			
			// 開放する
			HidD_FreePreparsedData(preparsedData);
		}
		CloseHandle(h);
	}

	return ret;
}

bool rwHidDevice(std::tstring &path, uint8_t *wbuf, unsigned int wsize, uint8_t *rbuf, unsigned int *rsize)
{
	bool ret = false;

	if (wbuf != nullptr && rbuf != nullptr) {

		HANDLE h = CreateFile(path.c_str(), // デバイスパス文字列
			(GENERIC_READ | GENERIC_WRITE), (FILE_SHARE_READ | FILE_SHARE_WRITE),
			nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
		
		if (h != INVALID_HANDLE_VALUE) {
			
			DWORD len = 0;
			WriteFile(h, wbuf, wsize, &len, nullptr);
			
			len = 0;
			ReadFile(h, rbuf, *rsize, &len, nullptr);
			*rsize = len;
			
			CloseHandle(h);
			ret = true;
		}
	}
	return ret;
}