// 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 "HAL/RunnableThread.h"
#include "SkyPortalFigure.h"
#include <Async/TaskGraphInterfaces.h>
#include <Async/Async.h>


void USkyPortalSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{

	Super::Initialize(Collection);
	FigureArray.SetNum(16);
	// Create UFigureData objects dynamically
	for (uint8 i = 0; i < FigureArray.Num(); i++)
	{
		FigureArray[i] = NewObject<UFigureData>(this);
	}
	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';
	unsigned char* output;
	do {
		PortalHandle->Write(&command);
		output = PortalHandle->Read();
	} while (output[0] != 'R');

	unsigned char _PortalId[2] = { output[1],output[2] };
	uint16_t BE_PortalId = (_PortalId[0] << 8) | _PortalId[1];  // Big-endian format
	uint32 BEU_PortalId = (_PortalId[0] << 8) | _PortalId[1];  // Big-endian format unsigned
	uint16_t LE_PortalId = (_PortalId[1] << 8) | _PortalId[0]; //little endian
	//uint32 LEU_PortalId = (_PortalId[1] << 8) | _PortalId[0]; //little endian unsigned
	PortalId = BEU_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);
}

SKYPORTAL_API bool USkyPortalSubsystem::RestartRunner()
{
	if (StatusChecker)
	{
		StatusChecker->Stop();
		StatusCheckerThread->WaitForCompletion();

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

	StatusChecker = new FPortalStatusChecker(this, RunnerInterval);  // Check every 10 milliseconds
	StatusCheckerThread = FRunnableThread::Create(StatusChecker, TEXT("PortalStatusCheckerThread"), 0, TPri_AboveNormal);

	UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Initialized"));
	return true;
}




#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




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


bool USkyPortalSubsystem::PortalConnect()
{
	if (StatusChecker)
	{
		StatusChecker->Stop();
		StatusCheckerThread->WaitForCompletion();

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

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

		Sleep(500);
		ChangePortalColor(FLinearColor(0.5, 0.5, 0.5));

		// Start the status checker thread
		StatusChecker = new FPortalStatusChecker(this, RunnerInterval);
		StatusCheckerThread = FRunnableThread::Create(StatusChecker, TEXT("PortalStatusCheckerThread"), 0, TPri_AboveNormal);

		return true;
	}
	return false;
}

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

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

void USkyPortalSubsystem::PortalAnalyzeAsync(const uint8 Index)
 {
	 bShouldPauseRunner = true;
	 UE_LOG(LogSkyportalIO, Log, TEXT("Starting async figure analysis for index %d..."), Index);

	 // Run the PortalAnalyze function asynchronously on a background thread
	 AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, Index]()
	 {
		 UE_LOG(LogSkyportalIO, Verbose, TEXT("Async Task started for figure index %d"), Index);

		 // Perform the blocking operation on the background thread
		 PortalAnalyze(Index);

		 // Once done, switch back to the main game thread
		 AsyncTask(ENamedThreads::GameThread, [this, Index]()
		 {
			 // This will be executed on the game thread after the async task is complete
			 bShouldPauseRunner = false;
			 UE_LOG(LogSkyportalIO, Log, TEXT("Figure analysis complete for index: %d"), Index);
		 });
	 });
 }

 void USkyPortalSubsystem::PortalAnalyze(const uint8 index)
 {
	
	 UE_LOG(LogSkyportalIO, Log, TEXT("Reading figure..."));
	 if (FigureArray.IsValidIndex(index) && StatusData.StatusArray[index] == EFigureStatus::PRESENT) {
		 if (IsValid(PortalHandle)) {
			 FigureArray[index] = PortalHandle->ReadFigureBlocks(index);
			 UE_LOG(LogSkyportalIO, Log, TEXT("Figure ID : %d"), FigureArray[index]->GetFigureID());
			 return;
		 }
		 UE_LOG(LogSkyportalIO, Error, TEXT("PortalHandle unreachable"));
		 return;
	 }
	 UE_LOG(LogSkyportalIO, Error, TEXT("Error in FigureArray"));
	 return;
	 

 }

 void USkyPortalSubsystem::GetFigureArray() {
	FigureArray;
	for (int i = 0; 16; i++) {
		FigureArray[i] = PortalHandle->ReadFigureBlocks(i);
	}
	return;
}