/*
 * win_QrfeHidPort.cpp
 *
 *  Created on: 30.10.2008
 *      Author: stefan.detter
 */

#include "../inc/Win_QrfeHidDevice.h"

#include <QrfeSleeper>

Win_QrfeHidDevice::Win_QrfeHidDevice(QObject* parent)
	:QrfeHidBase(parent)
	,QrfeTraceModule("Win_QrfeHidDevice")
{
	resetDeviceData();
}

Win_QrfeHidDevice::Win_QrfeHidDevice(QString devicePath, QObject* parent)
	:QrfeHidBase(devicePath, parent)
	,QrfeTraceModule("Win_QrfeHidDevice")
{
	resetDeviceData();
}

Win_QrfeHidDevice::~Win_QrfeHidDevice()
{
	close();
}


void Win_QrfeHidDevice::resetDeviceData()
{
	// Reset the device handle
	m_handle = INVALID_HANDLE_VALUE;

 	// Set all buffer lengths to 0
	m_InputReportBufferLength = 0;
	m_OutputReportBufferLength = 0;
	m_FeatureReportBufferLength = 0;

	m_getReportTimeout = 500;
	m_setReportTimeout = 3000;

	// Obtain the OS version
	OSVERSIONINFO osVer;
	osVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	GetVersionEx(&osVer);

	// Only set the max report requests if we are using 2K or later
	if ((osVer.dwPlatformId == 2) && (osVer.dwMajorVersion == 5)) {
		if (osVer.dwMinorVersion >= 1) {
			// XP or later supports 512 input reports
			m_MaxReportRequest = MAX_REPORT_REQUEST_XP;
		} else if (osVer.dwMinorVersion == 0) {
			// 2K supports 200 input reports
			m_MaxReportRequest = MAX_REPORT_REQUEST_2K;
		}
	} else {
		// Otherwise set the max reports to 0, and this will
		// only allow te default
		m_MaxReportRequest = 0;
	}
}

bool Win_QrfeHidDevice::open( OpenMode mode )
{
	m_lastError = HID_DEVICE_SUCCESS;

	// Ensure that the we don't already have an open device
	if (isOpen()){
		m_lastError = HID_DEVICE_ALREADY_OPENED;
		return false;
	}

	// Begin to look for the device if it is not opened
	HANDLE hHidDeviceHandle = openDevice(m_devicePath);

	// Check that the device is opened and the handle is valid
	if (hHidDeviceHandle == INVALID_HANDLE_VALUE){
		m_lastError = HID_DEVICE_NOT_FOUND;
		return false;
	}


	PHIDP_PREPARSED_DATA preparsedData;
	if (!HidD_GetPreparsedData(hHidDeviceHandle, &preparsedData)){
		CloseHandle(hHidDeviceHandle);
		m_lastError = HID_DEVICE_CANNOT_GET_HID_INFO;
		return false;
	}

	HIDP_CAPS capabilities;
	// Used the preparsed data structure to get the device capabilities
	if (!HidP_GetCaps(preparsedData, &capabilities)) {
		CloseHandle(hHidDeviceHandle);
		m_lastError = HID_DEVICE_CANNOT_GET_HID_INFO;
		return false;
	}

	// Allocate memory for the input, output and feature reports
	if (capabilities.InputReportByteLength)
		m_InputReportBufferLength = capabilities.InputReportByteLength;
	if (capabilities.OutputReportByteLength)
		m_OutputReportBufferLength = capabilities.OutputReportByteLength;
	if (capabilities.FeatureReportByteLength)
		m_FeatureReportBufferLength = capabilities.FeatureReportByteLength;
	HidD_GetNumInputBuffers(hHidDeviceHandle, (PULONG) (&m_MaxReportRequest));

	// Cleanup the preparesed data
	HidD_FreePreparsedData(preparsedData);

	// Set the member variables to an opened device and handle
	m_deviceOpened = true;
	m_handle = hHidDeviceHandle;

	m_reader = new Win_QrfeHidDeviceReaderThread(this);
	m_reader->start(QThread::HighestPriority);

	return QIODevice::open(mode);
}

bool Win_QrfeHidDevice::open( QString devicePath, OpenMode mode )
{
	m_devicePath = devicePath;
	return open(mode);
}

void Win_QrfeHidDevice::close()
{
	QIODevice::close();
	// This function will close the HID Device and then calls ResetDeviceData
	// to reinitialize all of the members after the close completes

	m_lastError = HID_DEVICE_SUCCESS;

	// Check to see if the device is opened, otherwise return an error code
	if (!isOpen()){
		m_lastError = HID_DEVICE_NOT_OPENED;
		return;
	}

	// Check that we have valid handle values, otherwise return an error code
	if ((m_handle != INVALID_HANDLE_VALUE) && (m_handle != NULL))
		CloseHandle(m_handle);
	else{
		m_lastError = HID_DEVICE_HANDLE_ERROR;
		return;
	}

	delete m_reader;

	// Reset the device data
	resetDeviceData();

	return;
}

bool Win_QrfeHidDevice::isOpen()
{
	// Check if a device is opened, and the handle is valid
	if (m_deviceOpened && (m_handle != INVALID_HANDLE_VALUE) && (m_handle != NULL))
		return true;

	if (m_deviceOpened)
		resetDeviceData();

	return false;
}

qint64 Win_QrfeHidDevice::bytesAvailable () const
{
	return m_readFifo.bytesAvailable();
}

qint64 Win_QrfeHidDevice::bytesToWrite () const
{
	return 0;
}

bool Win_QrfeHidDevice::isSequential () const
{
	return true;
}

qint64 Win_QrfeHidDevice::readData ( char * data, qint64 maxSize )
{
	if(!isOpen())
		return -1;

	return m_readFifo.read(data, maxSize);
}

qint64 Win_QrfeHidDevice::writeData ( const char * data, qint64 maxSize )
{
	if(!isOpen())
		return 0;
	
	bool ret = setReportInterrupt(QByteArray(data, maxSize));
	if(ret)
		return maxSize;
	else
		return 0;
}

void Win_QrfeHidDevice::timeouts(quint32 &getReportTimeout, quint32 &setReportTimeout)
{
	// Will get the timeouts for Get/SetReport
	getReportTimeout = m_getReportTimeout;
	setReportTimeout = m_setReportTimeout;
}

void Win_QrfeHidDevice::setTimeouts(quint32 getReportTimeout, quint32 setReportTimeout)
{
	// Will set the timeouts for Get/SetReport
	m_getReportTimeout = getReportTimeout;
	m_setReportTimeout = setReportTimeout;
}

bool Win_QrfeHidDevice::flush()
{
	return HidD_FlushQueue(m_handle);
}

QString Win_QrfeHidDevice::getSerialString()
{
	// This function will obtain the serial string of a device by it's index within it's VID
	// and PID. So if only 1 device is connected with VID 10C4, 9999, it's index is 0. If three
	// devices are connected with VID 10C4, 9999 are connected, they would be referenced as
	// device 0, 1, and 2

	QString serialString = "HID";
	m_lastError = HID_DEVICE_NOT_FOUND;

	HANDLE hHidDeviceHandle = INVALID_HANDLE_VALUE;
	if(!isOpen())
	{
		// Begin to look for the device if it is not opened
		hHidDeviceHandle = openDevice(m_devicePath);
	}
	else
	{
		hHidDeviceHandle = m_handle;
	}


	// Check that the device is opened and the handle is valid
	if (hHidDeviceHandle == INVALID_HANDLE_VALUE){
		m_lastError = HID_DEVICE_NOT_FOUND;
		return serialString;
	}

	wchar_t serialBuf[512];
	// Obtain the serial number
	if (HidD_GetSerialNumberString(hHidDeviceHandle, serialBuf, 512))
	{
		serialString = QString::fromWCharArray(serialBuf);
		// Return success
		m_lastError = HID_DEVICE_SUCCESS;
	}

	if(!isOpen())
	{
		CloseHandle(hHidDeviceHandle);
	}

	return serialString;
}



bool Win_QrfeHidDevice::setFeatureReport(QByteArray buffer)
{
	m_lastError = HID_DEVICE_SUCCESS;

	// Check to see that the device is opened
	if (!isOpen())
	{
		m_lastError = HID_DEVICE_NOT_OPENED;
		return false;
	}

	if (!HidD_SetFeature(m_handle, buffer.data(), buffer.size()))
	{
		m_lastError = HID_DEVICE_TRANSFER_FAILED;
		return false;
	}

	return true;
}

bool Win_QrfeHidDevice::getFeatureReport(QByteArray &buffer, quint32 buffSize)
{
	m_lastError = HID_DEVICE_SUCCESS;

	// Check to see that the device is opened
	if (!isOpen())
	{
		m_lastError = HID_DEVICE_NOT_OPENED;
		return false;
	}

	buffer.clear();
	char* buff = new char[buffSize];
	memset(buff, 0, buffSize);
	buff[0] = buffer.at(0);
	if (!HidD_GetFeature(m_handle, buff, buffSize))
	{
		delete buff;
		m_lastError = HID_DEVICE_TRANSFER_FAILED;
		return false;
	}
	buffer = QByteArray(buff, buffSize);
	delete buff;

	return true;
}

bool Win_QrfeHidDevice::setReportInterrupt(QByteArray buffer)
{
	m_lastError = HID_DEVICE_SUCCESS;

	if (buffer.size() > m_OutputReportBufferLength)
	{
		m_lastError = HID_DEVICE_INVALID_BUFFER_SIZE;
		return false;
	}

	// Check to see that the device is opened
	if (!isOpen())
	{
		m_lastError = HID_DEVICE_NOT_OPENED;
		return false;
	}

	quint32 bytesWritten = 0;
	OVERLAPPED o = {0};

	o.hEvent = CreateEvent(NULL, false, false, NULL);

	// Try to write the file
	if (!WriteFile(m_handle, buffer.data(), buffer.size(), (LPDWORD)&bytesWritten, &o))
	{
		// If the write fails, see if it is because IO is pending
		if (GetLastError() == ERROR_IO_PENDING)
		{
			//If there is still data to be written, wait on the event for 3 seconds
			DWORD waitStatus = WaitForSingleObject(o.hEvent, m_setReportTimeout);

			// If the object is signaled, then get the overlapped result, the write succeeded
			// Otherwise determine if the error was a timeout, or another error
			if (waitStatus == WAIT_OBJECT_0)
			{
				GetOverlappedResult(m_handle, &o, (LPDWORD)&bytesWritten, false);
			}
			else if (waitStatus == WAIT_TIMEOUT)
			{
				m_lastError = HID_DEVICE_TRANSFER_TIMEOUT;
				CancelIo(m_handle);
				if (GetOverlappedResult(m_handle, &o, (LPDWORD)&bytesWritten, false))
				{
					m_lastError = HID_DEVICE_SUCCESS;
				} else
				{
					return false;
				}
			}
			else
			{
				m_lastError = HID_DEVICE_TRANSFER_FAILED;
				CancelIo(m_handle);
				return false;
			}
		}
		else
		{
			m_lastError = HID_DEVICE_TRANSFER_FAILED;
			return false;
		}
	}

	CloseHandle(o.hEvent);

	return true;
}

bool Win_QrfeHidDevice::getReportInterrupt(QByteArray &buffer, WORD numReports)
{
	m_lastError = HID_DEVICE_SUCCESS;
	quint32 bytesRead = 0;

	if (numReports > m_MaxReportRequest)
	{
		m_lastError = HID_DEVICE_INVALID_BUFFER_SIZE;
	}

	// Check to see that the device is opened
	if (!isOpen())
	{
		m_lastError = HID_DEVICE_NOT_OPENED;
		return false;
	}

	OVERLAPPED o = {0};

	o.hEvent = CreateEvent(NULL, false, false, NULL);

	// Clear out the report buffer, and set the head to the report ID
	QByteArray buff(m_InputReportBufferLength * numReports, (char) 0 );
	//char* buff = new char[m_InputReportBufferLength * numReports];
	//memset(buff, 0, m_InputReportBufferLength * numReports);

	// Try to read input data
	if (!ReadFile(m_handle, buff.data(), m_InputReportBufferLength * numReports, (LPDWORD)&bytesRead, &o))
	{
		// If the read fails, see if it is because IO is pending
		if (GetLastError() == ERROR_IO_PENDING)
		{
			// If there is still data to read, wait on the event object for 3 seconds
			DWORD waitStatus = WaitForSingleObject(o.hEvent, m_getReportTimeout);

			// If the object is signaled, then get the overlapped result, the read succeeded
			// Otherwise determine if the error was a timeout, or another error
			if (waitStatus == WAIT_OBJECT_0)
			{

				GetOverlappedResult(m_handle, &o, (LPDWORD)&bytesRead, false);
			}
			else if (waitStatus == WAIT_TIMEOUT)
			{
				m_lastError = HID_DEVICE_TRANSFER_TIMEOUT;
				CancelIo(m_handle);
				if (GetOverlappedResult(m_handle, &o, (LPDWORD)&bytesRead, false))
				{
					m_lastError = HID_DEVICE_SUCCESS;
				} else
				{
					return false;
				}
			}
			else
			{
				m_lastError = HID_DEVICE_TRANSFER_FAILED;
				CancelIo(m_handle);
				return false;
			}
		}
		else
		{
			m_lastError = HID_DEVICE_TRANSFER_FAILED;
			return false;
		}
	}

	CloseHandle(o.hEvent);


	// If the read succeeded, then send the number of bytes read back
	buffer = QByteArray(buff.data(), bytesRead);

	return true;
}

bool Win_QrfeHidDevice::setReportControl(QByteArray buffer)
{
	m_lastError = HID_DEVICE_SUCCESS;

	// Check to see that the device is opened
	if (!isOpen())
	{
		m_lastError = HID_DEVICE_NOT_OPENED;
		return false;
	}

	// Call SetOutputReport to send this report buffer over the control pipe
	if (!HidD_SetOutputReport(m_handle, buffer.data(), buffer.size()))
	{
		m_lastError = HID_DEVICE_TRANSFER_FAILED;
		return false;
	}

	return true;
}

bool Win_QrfeHidDevice::getReportControl(QByteArray &buffer)
{
	m_lastError = HID_DEVICE_SUCCESS;

	// Check to see that the device is opened
	if (!isOpen())
	{
		m_lastError = HID_DEVICE_NOT_OPENED;
		return false;
	}

	// Clear out the report buffer, and set the head to the report ID
	unsigned char reportID = buffer.at(0);
	char* buff = new char[m_OutputReportBufferLength];
	memset(buff, 0, m_OutputReportBufferLength);
	buff[0] = reportID;

	// Call GetInputReport to get the requested report buffer over the control pipe
	if (!HidD_GetInputReport(m_handle, buff, m_OutputReportBufferLength))
	{
		delete buff;
		m_lastError = HID_DEVICE_TRANSFER_FAILED;
		return false;
	}

	buffer = QByteArray(buff, m_OutputReportBufferLength);
	delete buff;

	return true;
}


HANDLE Win_QrfeHidDevice::openDevice(QString devicePath)
{
	// This function attempts to open a device. If it succeeds
	// then a handle is returned. If it fails, then INVALID_HANDLE_VALUE is returned

	wchar_t* ba = new wchar_t[devicePath.size() + 1];
	devicePath.toWCharArray(ba);
	ba[devicePath.size()] = 0;

	// Open the device
	HANDLE hHidDeviceHandle = CreateFile(
			(LPCWSTR) ba,
			GENERIC_READ | GENERIC_WRITE,
			NULL,
			NULL,
			OPEN_EXISTING,
			FILE_FLAG_OVERLAPPED,
			NULL);
	delete ba;

	return hHidDeviceHandle;
}

void Win_QrfeHidDevice::_q_emitSignals()
{
	emit readyRead();
}


Win_QrfeHidDeviceReaderThread::Win_QrfeHidDeviceReaderThread(Win_QrfeHidDevice* parent)
	: QThread(parent)
	, m_parent(parent)
{
}

Win_QrfeHidDeviceReaderThread::~Win_QrfeHidDeviceReaderThread()
{
	m_run = false;
	while(!isFinished ())
		QrfeSleeper::MSleep(1);
}

void Win_QrfeHidDeviceReaderThread::run()
{
	m_run = true;

	while(m_run && m_parent->isOpen())
	{
		QByteArray buffer;
		m_parent->m_getReportTimeout = MAXDWORD;
		if(m_parent->getReportInterrupt(buffer, 1))
		{
			m_parent->m_readFifo.write(buffer);
			QMetaObject::invokeMethod(m_parent, "_q_emitSignals", Qt::QueuedConnection);
		}
	}

	return;
}
