#include "SkyPortalIO.h"

DEFINE_LOG_CATEGORY(LogHIDApi);
DEFINE_LOG_CATEGORY(LogSkyportalIO);


//Constructor
USkyPortalIO::USkyPortalIO()
{
	bPortalReady = OpenPortalHandle();


}

bool USkyPortalIO::OpenPortalHandle() {
	//reset
	if (PortalDevice) {
		hid_close(PortalDevice);
		bPortalReady = false;
	}
	/*
		Declare two pointers to hold information about HID devices.
		"list" will point to the head of the linked list of devices,
		"attributes" will be used to iterate through the list.
	*/
	struct hid_device_info* list, * attributes;

	list = hid_enumerate(0x0, 0x0);
	// If `list` is NULL, that means no devices were found or there was an error.
	// In this case, print an error message and terminate the program.
	if (!list) {
		UE_LOG(LogHIDApi, Error, TEXT("No devices found"));
		// Get the error message from the HIDAPI
		HidError = hid_error(NULL);
		UE_LOG(LogHIDApi, Error, TEXT("%s"), *HidError);
		return false;
	}
	attributes = list;




	// Iterate through the linked list of devices
	int vendorCount = sizeof(VendorIds) / sizeof(VendorIds[0]);
	int productCount = sizeof(ProductIds) / sizeof(ProductIds[0]);

	while (attributes) {
		// Check if the devices match any of vendor_id and product_id 
		for (int i = 0; i < vendorCount; i++) {
			for (int j = 0; j < productCount; j++) {
				if (attributes->vendor_id == VendorIds[i] && attributes->product_id == ProductIds[j]) {
					UE_LOG(LogHIDApi, Display, TEXT("Portal found"));
					UE_LOG(LogHIDApi, Log, TEXT("Vendor ID: 0x%x, Product ID: 0x%x"), attributes->vendor_id, attributes->product_id);

					PortalDevice = hid_open(attributes->vendor_id, attributes->product_id, NULL);
					if (PortalDevice) {
						UE_LOG(LogHIDApi, Display, TEXT("Successful connection to Portal."));

						// Free the device list
						hid_free_enumeration(list);
						return true;
					}

					break;
				}
			}
		}

		// Move to the next device in the list
		attributes = attributes->next;
	}

	// Free the device list
	hid_free_enumeration(list);
	UE_LOG(LogHIDApi, Error, TEXT("No Portals found"));
	return false;
}

// write function need to be different on windows, as hid_write doesn't work with the way windows handle I/O
#if PLATFORM_WINDOWS

#include <Windows.h>

#define HID_CTL_CODE(id)    \
        CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_NEITHER, FILE_ANY_ACCESS)
#define HID_IN_CTL_CODE(id)  \
        CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_IN_DIRECT, FILE_ANY_ACCESS)
#define IOCTL_HID_SET_OUTPUT_REPORT             HID_IN_CTL_CODE(101)

struct hid_device_ {
	HANDLE device_handle;
	BOOL blocking;
	USHORT output_report_length;
	unsigned char* write_buf;
	size_t input_report_length;
	USHORT feature_report_length;
	unsigned char* feature_buf;
	wchar_t* last_error_str;
	BOOL read_pending;
	char* read_buf;
	OVERLAPPED ol;
	OVERLAPPED write_ol;
	struct hid_device_info* device_info;
};


void USkyPortalIO::Write(FWriteBlock* pb)
{
	if (!ensure(PortalDevice)) {
		UE_LOG(LogSkyportalIO, Error, TEXT("No Portal found"));
		return;
	}
	BOOL res;

	OVERLAPPED ol;
	memset(&ol, 0, sizeof(ol));

	DWORD bytes_returned;

	res = DeviceIoControl(PortalDevice->device_handle,
		IOCTL_HID_SET_OUTPUT_REPORT,
		(unsigned char*)pb->data, write_buf_size,
		(unsigned char*)pb->data, write_buf_size,
		&bytes_returned, &ol);
	ensureMsgf(res, TEXT("Unable to write to Portal"));
	pb->BytesTransferred = bytes_returned;

}

#else
void USkyPortalIO::Write(FWriteBlock* pb) {

	if (!ensure(PortalDevice)) {
		UE_LOG(LogSkyportalIO, Error, TEXT("No Portal found"));
		return;
	}

	pb->data[0] = 0; // Use report 0

	res = hid_write(PortalDevice, pb->data, write_buf_size);
	ensureMsgf(res != -1, TEXT("Unable to write to Portal, %s"), hid_error(PortalDevice));
	pb->BytesTransferred = res;
}
#endif

void USkyPortalIO::WriteRaw(const TArray<uint8>* block)
{
	;
	int res = hid_write(PortalDevice, block->GetData(), block->GetAllocatedSize()); //return the number of byte written
	if (res == -1)
	{
		UE_LOG(LogSkyportalIO, Error, TEXT("Unable to write raw data to Portal. error:\n %s"), hid_error(PortalDevice));
		return;
	}
}

uint8* USkyPortalIO::Read()
{
	uint8* output = new byte[0x20];
	hid_read(PortalDevice, output, 0x20);
	return output;
}

TArray<uint8> USkyPortalIO::QueryBlock(uint8 characterIndex, uint8 block)
{
	return TArray<uint8>();
}

void USkyPortalIO::Close() {
	if (PortalDevice) {
		hid_close(PortalDevice);
	}
	bPortalReady = false;
}


/* TODO: Should not be here
bool USkyPortalIO::IsFalsePositive() const
{
	int dif = FMath::Abs(CurrentStatusData.Counter - OldStatusData.Counter);
	return (dif <= 2 || dif >= 254);

}
*/

EPortalCommand GetPortalCommandFromChar(unsigned char Char)
{
	switch (Char)
	{
	case 'A':
		return EPortalCommand::A;
	case 'C':
		return EPortalCommand::C;
	case 'J':
		return EPortalCommand::J;
	case 'L':
		return EPortalCommand::L;
	case 'M':
		return EPortalCommand::M;
	case 'Q':
		return EPortalCommand::Q;
	case 'R':
		return EPortalCommand::R;
	case 'S':
		return EPortalCommand::S;
	default:
		// Handle the case when the character doesn't match any enum
		// Return a default or invalid value, or handle the error
		UE_LOG(LogSkyportalIO, Warning, TEXT("Invalid character for Portal Command: %c"), TCHAR(Char));
		return EPortalCommand::S;  // 'S' for Status as a default
	}

}

FPortalStatusData ParsePortalStatus(const uint8* StatusResponse)
{
		FPortalStatusData result;
		result.Counter = StatusResponse[5];
		result.bIsReady = (StatusResponse[6] == 0x01);

		// Parse the figure status array (little-endian 32-bit integer)
		uint32 FigureStatusArray = 0;
		// Reading the 32-bit integer (character status array) from the buffer
		FigureStatusArray |= StatusResponse[1]; // 1st byte
		FigureStatusArray |= (StatusResponse[2] << 8);  // 2nd byte
		FigureStatusArray |= (StatusResponse[3] << 16); // 3rd byte
		FigureStatusArray |= (StatusResponse[4] << 24); // 4th byte

		bool bChangeBitsSet = false;

		TStaticArray<EFigureStatus, 16> tempArray;
		// For each of the 16 entries, extract the 2-bit status and map it to EFigureStatus
		for (int32 i = 0; i < 16; ++i)
		{
			uint8 StatusBits = (FigureStatusArray >> (i * 2)) & 0b11;  // Extract 2 bits
			EFigureStatus FigureStatus;

			switch (StatusBits)
			{
			case 0b00:
				FigureStatus = EFigureStatus::NOT_PRESENT;
			case 0b01:
				FigureStatus = EFigureStatus::PRESENT;
			case 0b11:
				FigureStatus = EFigureStatus::ADDED;
			case 0b10:
				FigureStatus = EFigureStatus::REMOVED;
			default:
				FigureStatus = EFigureStatus::NOT_PRESENT;  // Default case
			}

			// Add to the array of figure statuses
			//PortalStatusData.StatusArray.Insert(FigureStatus, i);
			tempArray[i] = FigureStatus;
		}
		result.StatusArray.SetNum(0);
		result.StatusArray.Append(tempArray);

		return result;
	}
