// Fill out your copyright notice in the Description page of Project Settings.


#include "ESSubSystem.h"
#include <string>
#include "EmoShapeSubSystem/EmoShapeJson/Public/ESIOJConvert.h"
#include "EmoShapeSubSystem/EmoShapeJson/Public/ESJsonObject.h"
#include "HttpModule.h"
#include "HttpStruct.h"
#include "JsonObjectConverter.h"
#include "SharedMemoryController.h"
#include "Common/UdpSocketBuilder.h"
#include "Interfaces/IHttpResponse.h"
#include "Misc/Base64.h"

DEFINE_LOG_CATEGORY(EmoShapeCategory);

void UESSubSystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);

	//Important to set this here so we can reuse this array in every recieve
	ReceivedData.Init(0, 300);    
	Http = &FHttpModule::Get();
   

	//Reflection Hack
	PseudoReflectionMap.Add("ResponseNone", &UESSubSystem::PostResponse);
	PseudoReflectionMap.Add("ResponseText", &UESSubSystem::PostResponseText);
	PseudoReflectionMap.Add("ResponseBuffer", &UESSubSystem::PostResponseBuffer);
	PseudoReflectionMap.Add("ResponseLogin", &UESSubSystem::PostResponseLogin);
	PseudoReflectionMap.Add("ResponseSER", &UESSubSystem::PostResponseSER);
	PseudoReflectionMap.Add("ResponseHeartBeat", &UESSubSystem::PostResponseHeartBeat);
}

bool UESSubSystem::StartUDPReceiver(const FString& YourChosenSocketName, const FString& TheIP, const int32 ThePort)
{
	UE_LOG(EmoShapeCategory, Display, TEXT("Started UDP"));

	FIPv4Address address;
	FIPv4Address::Parse(TheIP, address);

	const FIPv4Endpoint Endpoint(address, ThePort);
	BufferSize = 2 * 1024 * 1024;

	ListenSocket = FUdpSocketBuilder(*YourChosenSocketName)
		.AsNonBlocking()
		.AsReusable()
		.BoundToEndpoint(Endpoint)
		.WithReceiveBufferSize(BufferSize);
	;

	FTimespan ThreadWaitTime = FTimespan::FromMilliseconds(100);
	UDPReceiver = new FUdpSocketReceiver(ListenSocket, ThreadWaitTime, TEXT("UDPTest"));    
	UDPReceiver->OnDataReceived().BindUObject(this, &UESSubSystem::Recv);    
	UDPReceiver->Start();    

	return true;
}

void UESSubSystem::CloseReceiver()
{
	UDPReceiver->Stop();      
	UDPReceiver->~FUdpSocketReceiver();  
	delete UDPReceiver;   
	UDPReceiver = nullptr;
}

void UESSubSystem::Recv(const FArrayReaderPtr& ArrayReaderPtr, const FIPv4Endpoint& EndPt)
{
	UE_LOG(EmoShapeCategory, Display, TEXT("Received Packet"));
	if (ArrayReaderPtr->Num() > int32(10)) {
		UE_LOG(LogTemp, Warning, TEXT("Good Packet"));
		const std::string cstr(reinterpret_cast<const char*>(ArrayReaderPtr->GetData()), ArrayReaderPtr->Num());
		UdpText = FString(cstr.c_str());
		CloseReceiver();      
	}   
}

void UESSubSystem::StartUDP()
{
	ListenSocket = NULL;
	UE_LOG(EmoShapeCategory, Display, TEXT("Start"));
	StartUDPReceiver("Test", "255.255.255.255", 2626);
}

void UESSubSystem::Connect(const FString& ipAddress)
{
	if (Socket) {
		ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(Socket);        
		Socket = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateSocket(NAME_Stream, TEXT("default"), false);
	}
    
	FIPv4Address ip;
	FIPv4Address::Parse(ipAddress, ip);
	const TSharedRef<FInternetAddr> addr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
	bool isValid;
	addr->SetIp(*ipAddress, isValid);
	addr->SetPort(Port);
	UE_LOG(EmoShapeCategory, Display, TEXT("Is Connected %s"), *FString(ipAddress));
	Socket->SetSendBufferSize(BufferSize, BufferSize);
	Socket->SetReceiveBufferSize(BufferSize, BufferSize);
	Socket->SetNonBlocking(false);//This make it do not choke the game thread 
	bConnected = Socket->Connect(*addr);    
	Socket->SetNonBlocking(true);//This make it do not choke the game thread   
	UE_LOG(EmoShapeCategory, Display, TEXT("Is Connected %s"), bConnected ? "True" : "False");
}

void UESSubSystem::ConnectAlternative(const FString& ipAddress)
{
	if (Socket) {
		ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(Socket);        
		Socket = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateSocket(NAME_Stream, TEXT("default"), false);
	}
	FIPv4Address ip;
	FIPv4Address::Parse(ipAddress, ip);
	TSharedRef<FInternetAddr> addr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
	bool isValid;
	addr->SetIp(*ipAddress, isValid);
	addr->SetPort(Port);
	Socket->SetSendBufferSize(BufferSize, BufferSize);
	Socket->SetReceiveBufferSize(BufferSize, BufferSize);
	Socket->SetNonBlocking(true);//This make it do not choke the game thread pretty cool   
	Socket->Connect(*addr); 
}

// ---------------------- START NEW FUNCTIONS ------------------------

void UESSubSystem::CustomWave(FString type, int32 apex, int32 curve, int32 level, int32 duration, int32 origin) {
	FString JsonString;
	TSharedRef<TJsonWriter<TCHAR>> JsonWriter = TJsonWriterFactory<TCHAR>::Create(&JsonString);
	JsonWriter->WriteObjectStart();
	JsonWriter->WriteValue("type", type);
	JsonWriter->WriteValue("apex", FString::FromInt(apex));
	JsonWriter->WriteValue("curve", FString::FromInt(curve));
	JsonWriter->WriteValue("level", FString::FromInt(level));
	JsonWriter->WriteValue("duration", FString::FromInt(duration));
	JsonWriter->WriteValue("origin", FString::FromInt(origin));

	JsonWriter->WriteObjectEnd();
	JsonWriter->Close();

	TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = Http->CreateRequest();
	Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
	Request->SetHeader(TEXT("User-Agent"), TEXT("X-UnrealEngine-Agent"));
	Request->SetHeader(TEXT("Authorization"), FString("Bearer ") + Token);

	if (AzureKey == "__test_key") {
		Request->SetHeader(TEXT("Origin"), TEXT("https://metasoul.live"));
		Request->SetHeader(TEXT("Referer"), TEXT("https://metasoul.live"));
	}

	Request->SetURL("https://emohuman.com/custom_wave");
	Request->SetVerb("PUT");
	Request->SetContentAsString(JsonString);

	Send(Request);
	UE_LOG(LogTemp, Warning, TEXT("Request Sent"));
}

void UESSubSystem::Login() {
	FString JsonString;
	TSharedRef<TJsonWriter<TCHAR>> JsonWriter = TJsonWriterFactory<TCHAR>::Create(&JsonString);
	JsonWriter->WriteObjectStart();
	JsonWriter->WriteValue("secret", MetaSoulSecret);

	if (Epu_ID != "") {
		JsonWriter->WriteValue("epuid", Epu_ID);
	}

	JsonWriter->WriteObjectEnd();
	JsonWriter->Close();

	TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = Http->CreateRequest();
	Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
	Request->SetHeader(TEXT("User-Agent"), TEXT("X-UnrealEngine-Agent"));
	Request->SetURL("http://metasoulfunctions.azurewebsites.net/api/login");
	Request->OnProcessRequestComplete().BindUObject(this, &UESSubSystem::PostResponseLoginWeb);
	Request->SetVerb("POST");
	Request->SetContentAsString(JsonString);

	Send(Request);
	UE_LOG(LogTemp, Warning, TEXT("Request Sent"));
}

void UESSubSystem::Logout() {
	FString JsonString;
	TSharedRef<TJsonWriter<TCHAR>> JsonWriter = TJsonWriterFactory<TCHAR>::Create(&JsonString);
	JsonWriter->WriteObjectStart();
	JsonWriter->WriteObjectEnd();
	JsonWriter->Close();

	TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = Http->CreateRequest();
	Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
	Request->SetHeader(TEXT("User-Agent"), TEXT("X-UnrealEngine-Agent"));
	Request->SetHeader(TEXT("Authorization"), FString("Bearer ") + Token);
	Request->SetURL("http://emohuman.com/logout");
	Request->OnProcessRequestComplete().BindUObject(this, &UESSubSystem::PostResponseLogout);
	Request->SetVerb("POST");
	Request->SetContentAsString(JsonString);

	Send(Request);
	UE_LOG(LogTemp, Warning, TEXT("Request Sent"));
}

void UESSubSystem::PostResponseLogout(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	UE_LOG(EmoShapeCategory, Display, TEXT("Response Code %d"), Response->GetResponseCode());
	if (Response->GetResponseCode() == 200)
	{
		bConnected = false;
		onLogout.Broadcast();
	}
}

void UESSubSystem::PostResponseLoginWeb(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	UE_LOG(EmoShapeCategory, Display, TEXT("Response Code %d"), Response->GetResponseCode());
	if (Response->GetResponseCode() == 200)
	{
		bConnected = true;
		TSharedPtr<FJsonValue> JsonValue;
		TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
		if (FJsonSerializer::Deserialize(Reader, JsonValue)) {
			Epu_ID = JsonValue->AsObject()->GetStringField("epuid");
			Token = JsonValue->AsObject()->GetStringField("token");	
			ChatHistory = JsonValue->AsObject()->GetStringField("preamble");

			onLogin.Broadcast();

			UE_LOG(LogTemp, Warning, TEXT("Token: %s"), *Token);
		}
		else {
			onConnectionFail.Broadcast();
		}
	}
	else {
		onConnectionFail.Broadcast();
	}
}

void UESSubSystem::AudioData(FString text, FString voice_name, bool generateEmo, FString chat_history, int32 coef) {
	UE_LOG(LogTemp, Warning, TEXT("AudioData Token: %s"), *Token);

	FString JsonString;
	TSharedRef<TJsonWriter<TCHAR>> JsonWriter = TJsonWriterFactory<TCHAR>::Create(&JsonString);
	JsonWriter->WriteObjectStart();
	JsonWriter->WriteValue("coef", FString::FromInt(coef));
	JsonWriter->WriteValue("voice_name", voice_name);
	JsonWriter->WriteValue("text", text);
	if (OpenAIKey != "") {
		JsonWriter->WriteValue("apiKey", OpenAIKey);
	}
	else {
		JsonWriter->WriteValue("apiKey", true);
	}
	if (AzureKey != "__test_key") {
		JsonWriter->WriteValue("azure_key", AzureKey);
		JsonWriter->WriteValue("azure_region", AzureKeyRegion);
	}

	if (chat_history != "") {
		JsonWriter->WriteValue("chat_history", chat_history);
	}

	JsonWriter->WriteValue("generateEmo", generateEmo);

	JsonWriter->WriteObjectEnd();
	JsonWriter->Close();

	TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = Http->CreateRequest();
	Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
	Request->SetHeader(TEXT("User-Agent"), TEXT("X-UnrealEngine-Agent"));
	Request->SetHeader(TEXT("Authorization"), FString("Bearer ") + Token);

	if (AzureKey == "__test_key") {
		Request->SetHeader(TEXT("Origin"), TEXT("https://metasoul.live"));
		Request->SetHeader(TEXT("Referer"), TEXT("https://metasoul.live"));
	}

	Request->SetURL("http://metasoulfunctions.azurewebsites.net/api/audio_data");
	Request->OnProcessRequestComplete().BindUObject(this, &UESSubSystem::PostResponseAudioData);
	Request->SetVerb("POST");
	Request->SetContentAsString(JsonString);

	Send(Request);
	UE_LOG(LogTemp, Warning, TEXT("Request Sent"));
}

void UESSubSystem::PostResponseAudioData(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	UE_LOG(EmoShapeCategory, Display, TEXT("PostResponseAudioData Response Code %d"), Response->GetResponseCode());
	if (Response->GetResponseCode() == 200)
	{
		bConnected = true;
		TSharedPtr<FJsonValue> JsonValue;
		TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
		if (FJsonSerializer::Deserialize(Reader, JsonValue)) {
			uint64 duration = JsonValue->AsObject()->GetIntegerField("privAudioDuration");
			FString reply = JsonValue->AsObject()->GetStringField("reply");

			TArray<uint8> audio_data;
			auto audio_data_field = JsonValue->AsObject()->GetObjectField("privAudioData")->GetArrayField("data");


			for (auto wav_integer : audio_data_field) {
				audio_data.Push(wav_integer.Get()->AsNumber());
			}

			auto visemes = JsonValue->AsObject()->GetArrayField("viseme");

			TArray<FViseme> blueprint_visemes;

			for (auto viseme : visemes) {
				auto object = viseme.Get()->AsObject().Get();
				FViseme bp_viseme;

				bp_viseme.animation = object->GetStringField("privAnimation");
				bp_viseme.audio_offset = object->GetIntegerField("privAudioOffset");
				bp_viseme.viseme_id = object->GetIntegerField("privVisemeId");

				blueprint_visemes.Push(bp_viseme);
			}

			onAudioData.Broadcast(audio_data, duration, blueprint_visemes, reply);

			UE_LOG(LogTemp, Warning, TEXT("Duration: %d"), duration);
			UE_LOG(LogTemp, Warning, TEXT("Audio length: %d"), audio_data.Num());
			UE_LOG(LogTemp, Warning, TEXT("Visemes: %d"), visemes.Num());
		}
	}
	else {
		onConnectionFail.Broadcast();
	}
}

TArray<FViseme> UESSubSystem::parseVisems(const TArray<FViseme>& visems) {
	TArray<FViseme> result;

	for (int i = 0; i < visems.Num(); i++) {
		if (i == 0) {
			result.Push(visems[i]);
			continue;
		}

		int32 duration = visems[i].audio_offset - visems[i - 1].audio_offset;
		FViseme viseme = visems[i];

		viseme.audio_offset = duration;

		result.Push(viseme);
	}

	return result;
}

// ---------------------- END OF NEW FUNCTIONS ------------------------

void UESSubSystem::CheckConnection()
{
	if (Socket->GetConnectionState() == ESocketConnectionState::SCS_Connected) {
		bConnected = true;
	}

	UE_LOG(EmoShapeCategory, Display, TEXT("Is Connected %s"), bConnected ? "True" : "False");
}

void UESSubSystem::StartEPU(FString method, FString key)
{
	if (key == "") {
        
		UE_LOG(EmoShapeCategory, Display, TEXT("No Key please check the key is correct and try again"));
		return;
	}
	Message = "[EPUID]@>EPUInit " + method + " " + key + " \r\n";
	LongLineSend(Message);
}

void UESSubSystem::StartEPUProcess(FString path, bool silent)
{
	if(silent)
	{
		ProcessHandle = FPlatformProcess::CreateProc(*path, TEXT("-u"), true, false, false, nullptr, 0, nullptr, nullptr);
	}
	else
	{
		ProcessHandle = FPlatformProcess::CreateProc(*path, TEXT(""), true, false, false, nullptr, 0, nullptr, nullptr);
	}
}

void UESSubSystem::StopEPUProcess()
{
	FPlatformProcess::TerminateProc(ProcessHandle);    
}

void UESSubSystem::PauseEPU()
{
	Message = "@>pause "+ CustomLineBreaker;
	Socket->Send(reinterpret_cast<uint8*>(TCHAR_TO_UTF8(*Message)), sizeof(Message), BytesSent);
}

void UESSubSystem::CloseEPU()
{
	Message = "[EPUID]@>EPUClose" + CustomLineBreaker;    
	LongLineSend(Message);  
}

void UESSubSystem::SendCustomMessage(FString Msg, bool end)
{
	Msg += CustomLineBreaker;
	if (Msg.Len() > 16) {
		LongLineSend(Msg);
	}
	else {
       
		Socket->Send(reinterpret_cast<uint8*>(TCHAR_TO_UTF8(*Msg)), sizeof(Msg), BytesSent);
	}
}

void UESSubSystem::LongLineSend(FString& Msg)
{
	TArray<TCHAR> Msgs = Msg.GetCharArray();
	UE_LOG(EmoShapeCategory, Display, TEXT("Size is %s"), *Msg);
	Msg = "";
	UE_LOG(EmoShapeCategory, Display, TEXT("Size is %d"), Msgs.Num());
	for (int32 i = 0; i < Msgs.Num(); i++) {
		Msg += Msgs[i];
		if (i != 0 && (i+1) % 16 == 0) {
			UE_LOG(LogTemp, Warning, TEXT("Size is %s"), *Msg);
			Socket->Send(reinterpret_cast<uint8*>(TCHAR_TO_UTF8(*Msg)), sizeof(Msg), BytesSent);
			Msg = "";
		}
	}    
	Socket->Send(reinterpret_cast<uint8*>(TCHAR_TO_UTF8(*Msg)), sizeof(Msg), BytesSent);
}

void UESSubSystem::ResumeEPU()
{
	Message = "@>resume " + CustomLineBreaker;
	Socket->Send(reinterpret_cast<uint8*>(TCHAR_TO_UTF8(*Message)), sizeof(Message), BytesSent);
}

void UESSubSystem::ResetEPU()
{
	Message = "@>reset " + CustomLineBreaker;
	Socket->Send(reinterpret_cast<uint8*>(TCHAR_TO_UTF8(*Message)), sizeof(Message), BytesSent);
}

void UESSubSystem::SendMessage(FString message)
{
	if (bTalkmode) {
		Message = "@>robot " + message + CustomLineBreaker;
	}
	else {
		Message = "@>user " + message + CustomLineBreaker;
	}

	LongLineSend(Message);
	UE_LOG(EmoShapeCategory, Display, TEXT("My message is %s"), *Message);
}

void UESSubSystem::SendBroadcast(FString message)
{
	Message = "[EPUID]@>transfer " + message + " <@" + CustomLineBreaker;
	LongLineSend(Message);
	UE_LOG(EmoShapeCategory, Display, TEXT("My message is %s"), *Message);
}

void UESSubSystem::SendTextToSpeech(FString message)
{
	Message = "[EPUID]@>synth " + message + CustomLineBreaker;
	LongLineSend(Message);
	UE_LOG(EmoShapeCategory, Display, TEXT("My message is %s"), *Message);
}

void UESSubSystem::SetSenses(bool robot, FString value)
{
	if (robot) {
		Message = "@>robot"+ CustomLineBreaker;
	}
	else {
		Message = "@>user" + CustomLineBreaker;
	}
	Socket->Send(reinterpret_cast<uint8*>(TCHAR_TO_UTF8(*Message)), sizeof(Message), BytesSent);
	Message = "@>sens " + value + CustomLineBreaker;
	LongLineSend(Message);  
}

void UESSubSystem::SetPersistence(bool robot, FString value)
{
	if (robot) {
		Message = "@>robot" + CustomLineBreaker;
	}
	else {
		Message = "@>user" + CustomLineBreaker;
	}
	Socket->Send(reinterpret_cast<uint8*>(TCHAR_TO_UTF8(*Message)), sizeof(Message), BytesSent);
	Message = "@>time " + value + CustomLineBreaker;
	LongLineSend(Message);
}

void UESSubSystem::SetObjectiveAppraisal(bool robot)
{
	if (robot) {
		Message = "@>robot" + CustomLineBreaker;
	}
	else {
		Message = "@>user" + CustomLineBreaker;
	}
	Socket->Send(reinterpret_cast<uint8*>(TCHAR_TO_UTF8(*Message)), sizeof(Message), BytesSent);
	Message = "@>objective_on " + CustomLineBreaker;
	LongLineSend(Message);
}

void UESSubSystem::SetSimbolicLearning(FString message)
{
	Message = "@>robot " + message + CustomLineBreaker;
	LongLineSend(Message);
}

void UESSubSystem::SaveSimbolicLearning(FString word)
{
	Message = "@>robot " + word + CustomLineBreaker;
	LongLineSend(Message);
}

void UESSubSystem::EraseLexic()
{
	Message = "@>lexic_erase"+ CustomLineBreaker;
	LongLineSend(Message);
}

void UESSubSystem::InjectEmotion(FString emotion, FString level, FString duration, FString origin_id, FString apex,
	FString curve)
{
	//maybe I'll put this in format, but I think this way is clearer for everyone, you can change this if you want.
	Message = "[EPUID]@>wave " + emotion.ToLower() + "," + level + "," + duration + "," + origin_id + "," + apex + "," + curve + CustomLineBreaker;
	LongLineSend(Message);
}

void UESSubSystem::AskForEmotionalBuffer()
{
	Message = "@>buffer" + CustomLineBreaker;
	LongLineSend(Message);
}

void UESSubSystem::BroadcastMessageToAllClients(FString message)
{
	Message = "[EPUID]@>transfer " + message + " <@"+ CustomLineBreaker;
	LongLineSend(Message);
}

void UESSubSystem::EnableDisableToneOfVoiceDetection(bool on)
{
	if (on)
	{
		Message = "@>tov_on"+ CustomLineBreaker;
	}
	else
	{
		Message = "@>tov_off"+ CustomLineBreaker;
	}
	Socket->Send(reinterpret_cast<uint8*>(TCHAR_TO_UTF8(*Message)), sizeof(Message), BytesSent);
}

void UESSubSystem::EnableDisableFaceTracking(bool on)
{
	if (on)
	{
		Message = "@>ft_on" + CustomLineBreaker;
	}
	else
	{
		Message = "@>ft_off" + CustomLineBreaker;
	}
	Socket->Send(reinterpret_cast<uint8*>(TCHAR_TO_UTF8(*Message)), sizeof(Message), BytesSent);
}

void UESSubSystem::AddRemoveUserForFaceTracking(FString username, bool add)
{
	if (username == "") return;
	if (add)
	{
		Message = "@>ft_user_add " + username + CustomLineBreaker;
	}
	else
	{
		Message = "@>ft_user_rm " + username + CustomLineBreaker;
	}
	LongLineSend(Message);
}

void UESSubSystem::SetSpeechRecognition(bool on)
{
	if (on)
	{
		Message = "@>asr_on" + CustomLineBreaker;
	}
	else
	{
		Message = "@>asr_off" + CustomLineBreaker;
	}
	LongLineSend(Message);
}

void UESSubSystem::GetCurrentFaceTrackingUser()
{
	Message = "@>detected_user" + CustomLineBreaker;
	LongLineSend(Message);
}

void UESSubSystem::StartSharedMem()
{
	SharedMem = true;
	Message = Epu_ID+"@>shmem_start"+ CustomLineBreaker;
	LongLineSend(Message);
}

void UESSubSystem::StopSharedMem()
{
	SharedMem = false;
	Message = Epu_ID+"@>shmem_stop"+ CustomLineBreaker;
	LongLineSend(Message);
}

void UESSubSystem::GetSupportedLanguages()
{
	Message = "[EPUID]@>supported_lang" + CustomLineBreaker;
	LongLineSend(Message);
}

void UESSubSystem::SetLanguages(FString index)
{
	Message = "[EPUID]@> " + index + CustomLineBreaker;
	LongLineSend(Message);
}

void UESSubSystem::StopConnection()
{
	Message = "[EPUID]@>EPUClose" + CustomLineBreaker;
	LongLineSend(Message); 
	bConnected = false;  
}

FString UESSubSystem::UTF8toString(const TArray<uint8>& BinaryArray)
{
	const std::string cstr(reinterpret_cast<const char*>(BinaryArray.GetData()), BinaryArray.Num());  
	return FString(cstr.c_str());
}

void UESSubSystem::UpdateEmotionTMap(TArray<FString> mArray)
{
	//This is to be sure the recv got the emotional buffer not something else.

	if (mArray.Num() == 123) {
		MEmotions.Add(TEXT("channelEXCITED"), mArray[4]);
		MEmotions.Add(TEXT("channelCONFIDENT"), mArray[14]);
		MEmotions.Add(TEXT("channelHAPPY"), mArray[24]);        
		MEmotions.Add(TEXT("channelTRUST"), mArray[34]);
		MEmotions.Add(TEXT("channelDESIRE"), mArray[44]);
		MEmotions.Add(TEXT("channelFEAR"), mArray[54]);
		MEmotions.Add(TEXT("channelSURPRISE"), mArray[64]);
		MEmotions.Add(TEXT("channelINATTENTION"), mArray[74]);
		MEmotions.Add(TEXT("channelSAD"), mArray[84]);
		MEmotions.Add(TEXT("channelREGRET"), mArray[94]);
		MEmotions.Add(TEXT("channelDISGUST"), mArray[104]);
		MEmotions.Add(TEXT("channelANGER"), mArray[114]);
		TArray<FString> Array;
		mArray[122].ParseIntoArray(Array, TEXT("C"), true);
		MEmotions.Add(TEXT("channelPAINPLEASURE"), Array[0]);
        
		//  UE_LOG(LogTemp, Warning, TEXT("Good Array %s"), *mEmotions["channelExcited"]);

	}
	else {
		UE_LOG(EmoShapeCategory, Display, TEXT("Bad Array"));
		//maybe I'll implement here to handle console commands, because that array will be the console commands if it get here.

	}
}

void UESSubSystem::ReadMDADSharedMemory()
{
	if (SharedMem == true) {
		UpdateEmotionTMap(SharedMemoryController::OpenMDADSharedMemory());
		//  UE_LOG(LogTemp, Warning, TEXT("Testing Shared Memory"));
	}
}

void UESSubSystem::ReadGPTSharedMemory()
{
	if (SharedMem == true)
	{     
		GptData = SharedMemoryController::OpenGPTSharedMemory();
		//  UE_LOG(LogTemp, Warning, TEXT("Testing Shared Memory"));
	}
}

void UESSubSystem::ReadVisemeSharedMemory()
{
	if (SharedMem == true)
	{
		SharedMemoryController::OpenVisemeSharedMemory(VisemeOffset, VisemeID);     
		// UE_LOG(LogTemp, Warning, TEXT("Viseme Size %d"), VisemeOffset.Num());
	}
}

void UESSubSystem::ReceiveMessage()
{
	ReceivedData.Init(0, 512);
	Socket->Recv(ReceivedData.GetData(), ReceivedData.Num(), BytesSent);
}

void UESSubSystem::SendPut(FString PathSuffix, UESJsonObject* Json, PossibleResponses functionName)
{
	const FString ContentJsonString =  Json->EncodeJsonToSingleString();
	TSharedRef<IHttpRequest,ESPMode::ThreadSafe> Request = PutRequest(PathSuffix, ContentJsonString);
	UE_LOG(LogTemp, Warning, TEXT("Json %s"), *ContentJsonString);    
	Request->OnProcessRequestComplete().BindUObject(this, GetResponse(*UEnum::GetValueAsName(functionName).ToString())); 
	UE_LOG(LogTemp, Warning, TEXT("URL %s"), *Request->GetURL());    
	Send(Request); 
	UE_LOG(LogTemp, Warning, TEXT("Request Sent"));
}

void UESSubSystem::SendPost(FString PathSuffix, UESJsonObject* Json, PossibleResponses functionName)
{
	const FString ContentJsonString =  Json->EncodeJsonToSingleString();
	TSharedRef<IHttpRequest,ESPMode::ThreadSafe> Request = PostRequest(PathSuffix, ContentJsonString);
	UE_LOG(LogTemp, Warning, TEXT("Json %s"), *ContentJsonString);    
	Request->OnProcessRequestComplete().BindUObject(this, GetResponse(*UEnum::GetValueAsName(functionName).ToString())); 
	UE_LOG(LogTemp, Warning, TEXT("URL %s"), *Request->GetURL());    
	Send(Request); 
	UE_LOG(LogTemp, Warning, TEXT("Request Sent"));
}

void UESSubSystem::SendGet(FString PathSuffix, PossibleResponses functionName)
{
	TSharedRef<IHttpRequest,ESPMode::ThreadSafe> Request = GetRequest(PathSuffix);    
	Request->OnProcessRequestComplete().BindUObject(this, GetResponse(*UEnum::GetValueAsName(functionName).ToString())); 
	UE_LOG(LogTemp, Warning, TEXT("URL %s"), *Request->GetURL());    
	Send(Request); 
	UE_LOG(LogTemp, Warning, TEXT("Request Sent"));
}

void UESSubSystem::PostSER(FString Pathsuffix, FSecretParam Secret_Param, TArray<uint8> audio)
{
	// prepare json data
	FString JsonString;
	TSharedRef<TJsonWriter<TCHAR>> JsonWriter = TJsonWriterFactory<TCHAR>::Create(&JsonString);
	FString name = "chunk.wav";
	JsonWriter->WriteObjectStart();    
	JsonWriter->WriteValue("fileToUpload", name);
	JsonWriter->WriteValue("wavData", FBase64::Encode(audio));
	JsonWriter->WriteObjectEnd();
	JsonWriter->Close();
    
	TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = Http->CreateRequest();
	Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
	Request->SetHeader(TEXT("User-Agent"), TEXT("X-UnrealEngine-Agent"));    
	Request->SetURL("http://localhost:2424/api?secret="+Secret_Param.secret);
	Request->OnProcessRequestComplete().BindUObject(this, &UESSubSystem::PostResponseSER);    
	Request->SetVerb("POST");
	Request->SetContentAsString(JsonString);    
 
	Send(Request);  
	UE_LOG(LogTemp, Warning, TEXT("Request Sent"));
}

void UESSubSystem::PostResponseLogin(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	UE_LOG(EmoShapeCategory, Display, TEXT("Response Code %d"), Response->GetResponseCode());
	if (Response->GetResponseCode() == 200)
	{
		bConnected = true;
		TSharedPtr<FJsonValue> JsonValue;
		TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
		if (FJsonSerializer::Deserialize(Reader, JsonValue)) {
			// Get the value of the json object by field name
			Epu_ID = JsonValue->AsObject()->GetStringField("epuid");
		}
	}
}


auto UESSubSystem::GetResponse(
	FString name) -> void(UESSubSystem::*)(TSharedPtr<IHttpRequest, ESPMode::ThreadSafe>, TSharedPtr<IHttpResponse,
	ESPMode::ThreadSafe>, bool)
{
	return PseudoReflectionMap[name];
}

void UESSubSystem::PostResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	if(Response.IsValid())
	{
		UE_LOG(EmoShapeCategory, Display, TEXT("Response Code %d"), Response->GetResponseCode());   
		FString Json = Response->GetContentAsString();   
		UE_LOG(EmoShapeCategory, Display, TEXT("Full message: %s"), *Json);
	}
}

void UESSubSystem::PostResponseBuffer(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	UE_LOG(EmoShapeCategory, Display, TEXT("Response Code %d"), Response->GetResponseCode());    
	if (!ResponseIsValid(Response, bWasSuccessful)) return;    
	FString Json = Response->GetContentAsString();   
	UE_LOG(EmoShapeCategory, Display, TEXT("Full message: %s"), *Json);
}

void UESSubSystem::PostResponseText(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	UE_LOG(EmoShapeCategory, Display, TEXT("Response Code %d"), Response->GetResponseCode());    
	if (!ResponseIsValid(Response, bWasSuccessful))
	{
		FString Json = Response->GetContentAsString();   
		UE_LOG(EmoShapeCategory, Display, TEXT("Full message: %s"), *Json);
	}
	else
	{
		FString Json = Response->GetContentAsString();   
		UE_LOG(EmoShapeCategory, Display, TEXT("Full message: %s"), *Json);
	}   
}



void UESSubSystem::PostResponseSER(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	UE_LOG(EmoShapeCategory, Display, TEXT("Response Code %d"), Response->GetResponseCode());    
	if (!ResponseIsValid(Response, bWasSuccessful))
	{
		FString Json = Response->GetContentAsString();   
		UE_LOG(EmoShapeCategory, Display, TEXT("Full message: %s"), *Json);
		HandleSERResponse(Json);
	}
	else
	{
		FString Json = Response->GetContentAsString();   
		UE_LOG(EmoShapeCategory, Display, TEXT("Full message: %s"), *Json);
	}   
}

void UESSubSystem::PostResponseHeartBeat(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	UE_LOG(EmoShapeCategory, Display, TEXT("Response Code %d"), Response->GetResponseCode());    
	if (!ResponseIsValid(Response, bWasSuccessful))
	{
		if(Response->GetResponseCode() != 200)
		{
			const auto BPJsonObject = NewObject<UESJsonObject>();                             
			FSecretParam params;        
			params.secret =  HeartBeatParam;
			HeartBeatParam = "";
			const auto JsonObject = UESIOJConvert::ToJsonObject(FSecretParam::StaticStruct(), &params, true);
			BPJsonObject->SetRootObject(JsonObject);
       
			SendPut("logout_all", BPJsonObject, PossibleResponses::ResponseText);
			bConnected = false;
            
		}        
	}
}

void UESSubSystem::ConvertSERToEmotions(TArray<int> SEREmotionalValues)
{
	TArray<int> adjustedValues;
    for(int i = 0; i< SEREmotionalValues.Num(); i++)
    {
        adjustedValues.Add(SEREmotionalValues[i] / 100); 
    }
    //   const auto serEmotionBaseline = EpuController::instance()->settings()->serEmotionBaseline();
    double diff = 0.0;
    FString sendEmos;
    int highestValue = 0;
    int currentEmotionIndex = -1;
    float serEmotionBaseline = 10;
    int emoValue = 0;
    for (int i = 0; i < 5; ++i) {
        switch (i) {
            
        case 0:
            if (SEREmotionalValues[i] > serEmotionBaseline) {
                emoValue = FCString::Atoi(*MEmotions["channelDISGUST"]) * adjustedValues[i];
                InjectEmotion("Disgust", FString::FromInt(emoValue),"3","0","1","2");  
              
            }
            break;
        case 1:
        case 2:
            if ((SEREmotionalValues[i-1] > serEmotionBaseline) &&
                    (SEREmotionalValues[i]  > serEmotionBaseline)) {
                diff = FCString::Atoi(*MEmotions["channelHAPPY"]) * adjustedValues[i-1] - FCString::Atoi(*MEmotions["channelSAD"]) * adjustedValues[i];
                if (0 <= diff) {
                    emoValue = static_cast<int>(diff);
                    InjectEmotion("Happy", FString::FromInt(emoValue),"3","0","1","2");  
                 
                } else {
                    emoValue = static_cast<int>(-diff);                    
                    InjectEmotion("SAD", FString::FromInt(emoValue),"3","0","1","2");                  
                }
                    }
            ++i;
            break;
        case 3:
        case 4:
            if ((SEREmotionalValues[i-1] > serEmotionBaseline) &&
                    (SEREmotionalValues[i]  > serEmotionBaseline)) {
                diff = FCString::Atoi(*MEmotions["channelANGER"]) * adjustedValues[i-1] - FCString::Atoi(*MEmotions["channelFEAR"]) * adjustedValues[i];
                if (0 <= diff) {
                    emoValue = static_cast<int>(diff);
                    InjectEmotion("Anger", FString::FromInt(emoValue),"3","0","1","2");  
                 
                } else {
                    emoValue = static_cast<int>(-diff);                    
                    InjectEmotion("Fear", FString::FromInt(emoValue),"3","0","1","2");                     
                }
                    }
            ++i;
            break;
        default:
            ;
        }
    }
}

void UESSubSystem::HandleSERResponse(FString text)
{
	auto Json = NewObject<UESJsonObject>();
	Json->DecodeJson(text);
	TArray<int> values;
	values.Add(Json->GetNumberField("Disgust"));
	values.Add(Json->GetNumberField("Joy"));
	values.Add(Json->GetNumberField("Sadness"));
	values.Add(Json->GetNumberField("Anger"));
	values.Add(Json->GetNumberField("Fear"));

	//If you want to convert to Emotions or something else
	//ConvertSERToEmotions(values);
}

void UESSubSystem::StereoToMono(TArray<uint8> stereoWavBytes, TArray<uint8>& monoWavBytes)
{
	//Change wav headers
	for (int i = 0; i < 44; i++)
	{
		//NumChannels starts from 22 to 24
		if (i == 22)
		{
			short NumChannels = (*(short*)&stereoWavBytes[i]) / 2;
			monoWavBytes.Append((uint8*)&NumChannels, sizeof(NumChannels));
			i++;
		}       
        
		else if (i == 28)
		{
			int ByteRate = (*(int*)&stereoWavBytes[i]) / 4;
			monoWavBytes.Append((uint8*)&ByteRate, sizeof(ByteRate));
			i += 3;
		}
		//BlockAlign starts from 32 to 34
		else if (i == 32)
		{
			short BlockAlign = (*(short*)&stereoWavBytes[i]) / 2;
			monoWavBytes.Append((uint8*)&BlockAlign, sizeof(BlockAlign));
			i++;
		}
		//SubChunkSize starts from 40 to 44
		else if (i == 40)
		{
			int SubChunkSize = (*(int*)&stereoWavBytes[i]) / 2;
			monoWavBytes.Append((uint8*)&SubChunkSize, sizeof(SubChunkSize));
			i += 3;
		}
		else
		{
			monoWavBytes.Add(stereoWavBytes[i]);
		}
	}

	//Copies only the left channel and ignores the right channel
	for (int i = 44; i < stereoWavBytes.Num(); i += 4)
	{
		monoWavBytes.Add(stereoWavBytes[i]);
		monoWavBytes.Add(stereoWavBytes[i+1]);
	}
}

void UESSubSystem::SetAuthorizationHash(FString Hash, TSharedRef<IHttpRequest>& Request)
{
	Request->SetHeader(AuthorizationHeader, Hash);
}

TSharedRef<IHttpRequest, ESPMode::ThreadSafe> UESSubSystem::RequestWithRoute(FString Pathsuffix)
{
	TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = Http->CreateRequest();
	Request->SetURL(ApiBaseUrl+Pathsuffix);
	Request->SetHeader("Content-Type", TEXT("application/json"));
	SetRequestHeaders(Request);
	return Request;
}

void UESSubSystem::SetRequestHeaders(TSharedRef<IHttpRequest, ESPMode::ThreadSafe>& Request)
{
	Request->SetHeader(TEXT("User-Agent"), TEXT("X-UnrealEngine-Agent"));
	Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
	Request->SetHeader(TEXT("Accepts"), TEXT("application/json"));
}

TSharedRef<IHttpRequest, ESPMode::ThreadSafe> UESSubSystem::GetRequest(FString Pathsuffix)
{
	TSharedRef<IHttpRequest,ESPMode::ThreadSafe> Request = RequestWithRoute(Pathsuffix);
	Request->SetVerb("GET");  
	return Request;
}

TSharedRef<IHttpRequest, ESPMode::ThreadSafe> UESSubSystem::PostRequest(FString Pathsuffix, FString ContentJsonString)
{
	TSharedRef<IHttpRequest,ESPMode::ThreadSafe> Request = RequestWithRoute(Pathsuffix);    
	Request->SetVerb("POST");
	uint32 size = ContentJsonString.Len();
	TArray<uint8> data;
	// StringToBytes(ContentJsonString, data.GetData(), size);
	Request->SetContentAsString(ContentJsonString);
    
	return Request;
}

TSharedRef<IHttpRequest, ESPMode::ThreadSafe> UESSubSystem::PutRequest(FString Pathsuffix, FString ContentJsonString)
{
	TSharedRef<IHttpRequest,ESPMode::ThreadSafe> Request = RequestWithRoute(Pathsuffix);    
	Request->SetVerb("PUT");    
	Request->SetContentAsString(ContentJsonString);    
	return Request;
}

void UESSubSystem::Send(TSharedRef<IHttpRequest, ESPMode::ThreadSafe>& Request)
{
	Request->ProcessRequest();
}

bool UESSubSystem::ResponseIsValid(FHttpResponsePtr Response, bool bWasSuccessful)
{
	if (!bWasSuccessful || !Response.IsValid()) return false;
	if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) return true;
	
	UE_LOG(EmoShapeCategory, Display, TEXT("Http Response returned error code: %d"), Response->GetResponseCode());
	return false;
}

template <typename StructType>
void UESSubSystem::GetJsonStringFromStruct(StructType FilledStruct, FString& StringOutput)
{
	FJsonObjectConverter::UStructToJsonObjectString(StructType::StaticStruct(), &FilledStruct, StringOutput, 0, 0);
}

template <typename StructType>
void UESSubSystem::GetStructFromJsonString(FHttpResponsePtr Response, StructType& StructOutput)
{
	StructType StructData;
	FString JsonString = Response->GetContentAsString();
	FJsonObjectConverter::JsonObjectStringToUStruct<StructType>(JsonString, &StructOutput, 0, 0);
}
