// A lot of this code is made because of the work of https://github.com/capull0/SkyDumper and https://github.com/silicontrip/SkyReader

#include "SkyPortalSubsystem.h"
#include "Engine/Engine.h"
#include "SkyPortal.h"

#include "HAL/RunnableThread.h"
#include "Misc/ScopeLock.h"


void USkyPortalSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{

	Super::Initialize(Collection);
	// Start the status checker thread
	StatusChecker = new FPortalStatusChecker(this, 0.1f);  // Check every 50 milliseconds
	StatusCheckerThread = FRunnableThread::Create(StatusChecker, TEXT("PortalStatusCheckerThread"), 0, TPri_AboveNormal);

	UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Initialized"));

}

void USkyPortalSubsystem::Deinitialize()
{
	// Stop the thread when the subsystem is deinitialized
	if (StatusChecker)
	{
		StatusChecker->Stop();
		StatusCheckerThread->WaitForCompletion();

		delete StatusCheckerThread;
		delete StatusChecker;
		StatusChecker = nullptr;
		StatusCheckerThread = nullptr;
	}

	// Disconnect portal
	if (PortalHandle) {
		PortalHandle->Close();
	}
	hid_exit();


	UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Deinitialized"));
	Super::Deinitialize();
}


void USkyPortalSubsystem::PortalActivate(const bool bShouldActivate)
{
	FWriteBlock command;

	memset(command.data, 0, write_buf_size);
	command.data[1] = 'A';
	command.data[2] = bShouldActivate;
	uint8* output;
	do {
		PortalHandle->Write(&command);
		output = PortalHandle->Read();
	} while (output[0] != 'A');

}



void USkyPortalSubsystem::PortalReady()
{
	FWriteBlock command;

	memset(command.data, 0, write_buf_size); //maybe not needed here
	command.data[1] = 'R';
	uint8* output;
	do {
		PortalHandle->Write(&command);
		output = PortalHandle->Read();
	} while (output[0] != 'R');

	uint16 _PortalId = ((output[1] << 8) + output[2]);
	PortalId = _PortalId; //Need a conversion as uint16 is not supported in BP

}

FPortalStatusData USkyPortalSubsystem::PortalStatus() {
	FWriteBlock command;

	memset(command.data, 0, write_buf_size); //maybe not needed here
	command.data[1] = 'S';
	uint8* output;

	do {
		PortalHandle->Write(&command);
		output = PortalHandle->Read();
	} while (output[0] != 'S');

	

	return ParsePortalStatus(output);
}




#pragma region Color functions

void USkyPortalSubsystem::ChangePortalColor(const FLinearColor& Color)
{

	unsigned char r = FMath::Clamp(Color.R * 100, 0.0f, 255.0f);
	unsigned char g = FMath::Clamp(Color.G * 100, 0.0f, 255.0f);
	unsigned char b = FMath::Clamp(Color.B * 100, 0.0f, 255.0f);

	FWriteBlock command;

	memset(command.data, 0, write_buf_size);

	command.data[1] = 'C';
	command.data[2] = r; // R
	command.data[3] = g; // G
	command.data[4] = b; // B

	// no response for this one.
	PortalHandle->Write(&command);
}

void USkyPortalSubsystem::ChangePortalColorSide(const FLinearColor& Color, const EPortalSide PortalSide, const float BlendTime)
{

	unsigned char r = FMath::Clamp(Color.R * 100, 0, 255);
	unsigned char g = FMath::Clamp(Color.G * 100, 0, 255);
	unsigned char b = FMath::Clamp(Color.B * 100, 0, 255);
	EPortalSide _portalside;
	FWriteBlock command;

	memset(command.data, 0, write_buf_size);

	if (PortalSide == EPortalSide::BOTH) {
		_portalside = EPortalSide::LEFT;
	}
	else {
		_portalside = PortalSide;
	}

	switch (_portalside) {
	case EPortalSide::LEFT:
		command.data[1] = 'J';
		command.data[2] = 0x00;
	case EPortalSide::RIGHT:
		command.data[1] = 'J';
		command.data[2] = 0x02;
	case EPortalSide::TRAP:
		command.data[1] = 'L';
		command.data[2] = 0x01;
		command.data[3] = FMath::Max3(r, g, b); // calculate brightness
		PortalHandle->Write(&command); //since it's a color command for trap, only 3 bytes are needed, no response.
		return;
	}

	command.data[3] = r; // R
	command.data[4] = g; // G
	command.data[5] = b; // B

	//Convert the time in millisecond into two bytes
	uint16_t _time = BlendTime;
	uint8_t _time_low = _time & 0xFF;       // Get the low byte by masking the least significant 8 bits
	uint8_t _time_high = (_time >> 8) & 0xFF; // Get the high byte extracted by shifting the bits 8 positions to the right and masking the result
	command.data[6] = _time_low;
	command.data[7] = _time_high;

	uint8* output;
	do {
		PortalHandle->Write(&command);
		output = PortalHandle->Read();
	} while (output[0] != 'J');

	if (PortalSide == EPortalSide::BOTH) {
		ChangePortalColorSide(Color, EPortalSide::RIGHT, BlendTime); // send a second command for the right side
	}
}
#pragma endregion





FPortalStatusData USkyPortalSubsystem::OLDParsePortalStatus(const FWriteBlock& ResponseBlock)
{
	FPortalStatusData PortalStatusData;

	// 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 |= ResponseBlock.data[1]; // 1st byte
	FigureStatusArray |= (ResponseBlock.data[2] << 8);  // 2nd byte
	FigureStatusArray |= (ResponseBlock.data[3] << 16); // 3rd byte
	FigureStatusArray |= (ResponseBlock.data[4] << 24); // 4th byte

	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;
			break;
		case 0b01:
			FigureStatus = EFigureStatus::PRESENT;
			break;
		case 0b11:
			FigureStatus = EFigureStatus::ADDED;
			break;
		case 0b10:
			FigureStatus = EFigureStatus::REMOVED;
			break;
		default:
			FigureStatus = EFigureStatus::NOT_PRESENT;  // Default case
			break;
		}

		// Add to the array of figure statuses
		//PortalStatusData.StatusArray.Insert(FigureStatus, i);
		tempArray[i] = FigureStatus;
	}
	PortalStatusData.StatusArray.SetNum(0);
	PortalStatusData.StatusArray.Append(tempArray);
	// The next byte is the response counter
	PortalStatusData.Counter = ResponseBlock.data[5];

	// The last byte is the boolean indicating whether the portal is ready
	PortalStatusData.bIsReady = ResponseBlock.data[6] != 0;  // 0 means not ready, non-zero means ready

	return PortalStatusData;
}

void USkyPortalSubsystem::Sleep(int sleepMs) {
	FPlatformProcess::Sleep(sleepMs * 0.0001);
}


bool USkyPortalSubsystem::PortalConnect()
{
	PortalHandle = NewObject<USkyPortalIO>();
	if (IsValid(PortalHandle)) {
		UE_LOG(LogSkyportalIO, Log, TEXT("Portal connected: "));
		PortalReady();
		PortalActivate(1);

		Sleep(500);
		ChangePortalColor(FLinearColor(0.5, 0.5, 0.5));
		return true;
	}
	return false;
}

bool USkyPortalSubsystem::IsPortalReady()
{
	if (PortalHandle) {
		return PortalHandle->bPortalReady;
	}
	//maybe should send a status request ?
	return false;
}

SKYPORTAL_API void USkyPortalSubsystem::PortalMusic(const USoundWave* Sound)
{
	return ;
}


/*
FigureDataBlock USkyPortalSubsystem::ReadFigureBlocks(uint8 FigureIndex)
{
	FigureDataBlock FigureData;
	FigureData.error = false;  // Initialize error flag

	FWriteBlock req, res;

	// Loop over all 64 blocks
	for (uint8 BlockIndex = 0; BlockIndex < FIGURE_TOTAL_BLOCKS; ++BlockIndex)
	{
		// Prepare the request buffer
		memset(command.data, 0, rw_buf_size);
		command.data[1] = 'Q';            // Command character
		command.data[2] = FigureIndex;     // Figure index (0x00 to 0x0F)
		command.data[3] = BlockIndex;      // Block index (0x00 to 0x3F)

		// Send the request
		Write(&req);

		// Read the response
		int BuffResponse = hid_read_timeout(PortalDevice, res.buf, rw_buf_size, TIMEOUT);

		// Check if the response was received successfully
		if (BuffResponse < rw_buf_size)
		{
			FigureData.error = true;  // Mark error flag
			UE_LOG(LogSkyportalIO, Warning, TEXT("Error receiving data for block %d of figure %d"), BlockIndex, FigureIndex);
			break;
		}

		// Parse the response status
		uint8 StatusByte = res.buf[2];
		uint8 ReturnedBlockIndex = res.buf[3];

		// Check for errors in the status byte
		if (StatusByte == 0x01)  // Error in response
		{
			FigureData.error = true;
			UE_LOG(LogSkyportalIO, Warning, TEXT("Error reading block %d for figure %d"), BlockIndex, FigureIndex);
			break;
		}
		else if (StatusByte != (0x10 + 'Q'))  // Unexpected status byte
		{
			FigureData.error = true;
			UE_LOG(LogSkyportalIO, Warning, TEXT("Unexpected status byte 0x%02X for block %d"), StatusByte, BlockIndex);
			break;
		}

		// Verify that the block index matches the requested block
		if (ReturnedBlockIndex != BlockIndex)
		{
			FigureData.error = true;
			UE_LOG(LogSkyportalIO, Warning, TEXT("Mismatched block index. Expected %d, got %d"), BlockIndex, ReturnedBlockIndex);
			break;
		}

		// Copy block data into FigureData
		memcpy(FigureData.blockdata[BlockIndex], &res.buf[4], FIGURE_BLOCK_SIZE);
	}

	return FigureData;  // Return the complete figure data
}
*/