#include "EmoShapeSubSystem/EmoShapeJson/Public/ESJsonLibrary.h"
#include "EmoShapeSubSystem/EmoShapeJson/Public/ESRequestJSON.h"
#include "EmoShapeSubSystem.h"
#include "EmoShapeSubSystem/EmoShapeWebUtility/Public/EmoShapeLambdaRunnable.h"

FString UESJsonLibrary::PercentEncode(const FString& Source)
{
	FString OutText = Source;

	OutText = OutText.Replace(TEXT(" "), TEXT("%20"));
	OutText = OutText.Replace(TEXT("!"), TEXT("%21"));
	OutText = OutText.Replace(TEXT("\""), TEXT("%22"));
	OutText = OutText.Replace(TEXT("#"), TEXT("%23"));
	OutText = OutText.Replace(TEXT("$"), TEXT("%24"));
	OutText = OutText.Replace(TEXT("&"), TEXT("%26"));
	OutText = OutText.Replace(TEXT("'"), TEXT("%27"));
	OutText = OutText.Replace(TEXT("("), TEXT("%28"));
	OutText = OutText.Replace(TEXT(")"), TEXT("%29"));
	OutText = OutText.Replace(TEXT("*"), TEXT("%2A"));
	OutText = OutText.Replace(TEXT("+"), TEXT("%2B"));
	OutText = OutText.Replace(TEXT(","), TEXT("%2C"));
	OutText = OutText.Replace(TEXT("/"), TEXT("%2F"));
	OutText = OutText.Replace(TEXT(":"), TEXT("%3A"));
	OutText = OutText.Replace(TEXT(";"), TEXT("%3B"));
	OutText = OutText.Replace(TEXT("="), TEXT("%3D"));
	OutText = OutText.Replace(TEXT("?"), TEXT("%3F"));
	OutText = OutText.Replace(TEXT("@"), TEXT("%40"));
	OutText = OutText.Replace(TEXT("["), TEXT("%5B"));
	OutText = OutText.Replace(TEXT("]"), TEXT("%5D"));
	OutText = OutText.Replace(TEXT("{"), TEXT("%7B"));
	OutText = OutText.Replace(TEXT("}"), TEXT("%7D"));

	return OutText;
}

FString UESJsonLibrary::Base64Encode(const FString& Source)
{
	return FBase64::Encode(Source);
}

FString UESJsonLibrary::Base64EncodeBytes(const TArray<uint8>& Source)
{
	return FBase64::Encode(Source);
}

bool UESJsonLibrary::Base64Decode(const FString& Source, FString& Dest)
{
	return FBase64::Decode(Source, Dest);
}

bool UESJsonLibrary::Base64DecodeBytes(const FString& Source, TArray<uint8>& Dest)
{
	return FBase64::Decode(Source, Dest);
}

bool UESJsonLibrary::StringToJsonValueArray(const FString& JsonString, TArray<UESJsonValue*>& OutJsonValueArray)
{
	TArray < TSharedPtr<FJsonValue>> RawJsonValueArray;
	TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(*JsonString);
	FJsonSerializer::Deserialize(Reader, RawJsonValueArray);

	for (auto Value : RawJsonValueArray)
	{
		auto SJsonValue = NewObject<UESJsonValue>();
		SJsonValue->SetRootValue(Value);
		OutJsonValueArray.Add(SJsonValue);
	}

	return OutJsonValueArray.Num() > 0;
}

UESJsonValue* UESJsonLibrary::Conv_ArrayToJsonValue(const TArray<UESJsonValue*>& InArray)
{
	return UESJsonValue::ConstructJsonValueArray(nullptr, InArray);
}

UESJsonValue* UESJsonLibrary::Conv_JsonObjectToJsonValue(UESJsonObject* InObject)
{
	return UESJsonValue::ConstructJsonValueObject(InObject, nullptr);
}

UESJsonValue* UESJsonLibrary::Conv_BytesToJsonValue(const TArray<uint8>& InBytes)
{
	return UESJsonValue::ConstructJsonValueBinary(nullptr, InBytes);
}

UESJsonValue* UESJsonLibrary::Conv_StringToJsonValue(const FString& InString)
{
	return UESJsonValue::ConstructJsonValueString(nullptr, InString);
}

UESJsonValue* UESJsonLibrary::Conv_IntToJsonValue(int32 InInt)
{
	TSharedPtr<FJsonValue> NewVal = MakeShareable(new FJsonValueNumber(InInt));

	UESJsonValue* NewValue = NewObject<UESJsonValue>();
	NewValue->SetRootValue(NewVal);

	return NewValue;
}

UESJsonValue* UESJsonLibrary::Conv_FloatToJsonValue(float InFloat)
{
	return UESJsonValue::ConstructJsonValueNumber(nullptr, InFloat);
}

UESJsonValue* UESJsonLibrary::Conv_BoolToJsonValue(bool InBool)
{
	return UESJsonValue::ConstructJsonValueBool(nullptr, InBool);
}

FString UESJsonLibrary::Conv_EmoShapeJsonValueToString(UESJsonValue* InValue)
{
	if (InValue)
	{
		return InValue->AsString();
	}

	return TEXT("");
}

int32 UESJsonLibrary::Conv_JsonValueToInt(UESJsonValue* InValue)
{
	if(InValue)
	{
		return (int32)InValue->AsNumber();
	}

	return 0;
}

float UESJsonLibrary::Conv_JsonValueToFloat(UESJsonValue* InValue)
{
	if (InValue)
	{
		return InValue->AsNumber();
	}

	return 0.f;
}

bool UESJsonLibrary::Conv_JsonValueToBool(UESJsonValue* InValue)
{
	if (InValue)
	{
		return InValue->AsBool();
	}

	return false;
}

TArray<uint8> UESJsonLibrary::Conv_JsonValueToBytes(UESJsonValue* InValue)
{
	if (InValue)
	{
		return InValue->AsBinary();
	}

	return TArray<uint8>();
}

TMap<UESRequestJSON*, FEmoShapeCallResponse> UESJsonLibrary::RequestMap;

FString UESJsonLibrary::Conv_EmoShapeJsonObjectToString(UESJsonObject* InObject)
{
	if(InObject)
	{
		return InObject->EncodeJson();
	}

	return "";
}

UESJsonObject* UESJsonLibrary::Conv_JsonValueToJsonObject(UESJsonValue* InValue)
{
	if(InValue)
	{
		return InValue->AsObject();
	}

	return nullptr;
}

void UESJsonLibrary::CallURL(UObject* WorldContextObject, const FString& URL, EEmoShapeIORequestVerb Verb,
	EEmoShapeIORequestContentType ContentType, UESJsonObject* EmoShapeJJson, const FEmoShapeJsonCallDelegate& Callback)
{
	UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
	if (World == nullptr)
	{
		UE_LOG(LogEmoShape, Error, TEXT("UEmoShapeJsonLibrary: Wrong world context"))
		return;
	}

	// Check we have valid data json
	if (EmoShapeJJson == nullptr)
	{
		EmoShapeJJson = UESJsonObject::ConstructJsonObject(WorldContextObject);
	}
	
	UESRequestJSON* Request = NewObject<UESRequestJSON>();
	
	Request->SetVerb(Verb);
	Request->SetContentType(ContentType);
	Request->SetRequestObject(EmoShapeJJson);
	
	FEmoShapeCallResponse Response;
	Response.Request = Request;
	Response.WorldContextObject = WorldContextObject;
	Response.Callback = Callback;
	
	Response.CompleteDelegateHandle = Request->OnStaticRequestComplete.AddStatic(&UESJsonLibrary::OnCallComplete);
	Response.FailDelegateHandle = Request->OnStaticRequestFail.AddStatic(&UESJsonLibrary::OnCallComplete);
	
	RequestMap.Add(Request, Response);
	
	Request->ResetResponseData();
	Request->ProcessURL(URL);
}

void UESJsonLibrary::GetURLBinary(UObject* WorldContextObject, const FString& URL, EEmoShapeIORequestVerb Verb,
	EEmoShapeIORequestContentType ContentType, TArray<uint8>& OutResultData, FLatentActionInfo LatentInfo)
{
	UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
	if (World == nullptr)
	{
		UE_LOG(LogEmoShape, Error, TEXT("UEmoShapeJsonLibrary: Wrong world context"))
		return;
	}

	UESRequestJSON* Request = NewObject<UESRequestJSON>();
	FEmoShapeLatentAction *LatentAction = FEmoShapeLatentAction::CreateLatentAction(LatentInfo, WorldContextObject);

	Request->SetVerb(Verb);
	Request->SetContentType(ContentType);
	Request->bShouldHaveBinaryResponse = true;
	Request->OnProcessURLCompleteCallback = [LatentAction, &OutResultData](TArray<uint8>& ResultData)
	{
		if (LatentAction)
		{
			OutResultData = ResultData;
			LatentAction->Call();	//resume the latent action
		}
	};

	Request->ResetResponseData();
	Request->ProcessURL(URL);

}

void UESJsonLibrary::OnCallComplete(UESRequestJSON* Request)
{
	if (!RequestMap.Contains(Request))
	{
		return;
	}
	
	FEmoShapeCallResponse* Response = RequestMap.Find(Request);
	
	Request->OnStaticRequestComplete.Remove(Response->CompleteDelegateHandle);
	Request->OnStaticRequestFail.Remove(Response->FailDelegateHandle);
	
	Response->Callback.ExecuteIfBound(Request);
	
	Response->WorldContextObject = nullptr;
	Response->Request = nullptr;
	RequestMap.Remove(Request);
}
