#include "EmoShapeSubSystem/EmoShapeJson/Public/ESRequestJSON.h"
#include "EmoShapeSubSystem.h"
#include "EmoShapeSubSystem/EmoShapeJson/Public/ESJsonLibrary.h"
#include "EmoShapeSubSystem/EmoShapeJson/Public/ESJsonObject.h"
#include "EmoShapeSubSystem/EmoShapeJson/Public/ESJsonValue.h"
#include "Runtime/Json/Public/Dom/JsonValue.h"
#include "Serialization/JsonSerializer.h"

template <class T>
void FEmoShapeJsonLatentAction<T>::Cancel()
{
	UObject *Obj = Request.Get();
	if (Obj != nullptr)
	{
		((UESRequestJSON*)Obj)->Cancel();
	}
}

UESRequestJSON::UESRequestJSON(const class FObjectInitializer& PCIP)
  : Super(PCIP),
	BinaryContentType(TEXT("application/octet-stream"))
{
	RequestVerb = EEmoShapeIORequestVerb::GET;
	RequestContentType = EEmoShapeIORequestContentType::x_www_form_urlencoded_url;
	bShouldHaveBinaryResponse = false;
	OnProcessURLCompleteCallback = nullptr;

	ResetData();
}

UESRequestJSON* UESRequestJSON::ConstructRequest(UObject* WorldContextObject)
{
	return NewObject<UESRequestJSON>();
}

UESRequestJSON* UESRequestJSON::ConstructRequestExt(UObject* WorldContextObject, EEmoShapeIORequestVerb Verb,
	EEmoShapeIORequestContentType ContentType)
{
	UESRequestJSON* Request = ConstructRequest(WorldContextObject);

	Request->SetVerb(Verb);
	Request->SetContentType(ContentType);

	return Request;
}

void UESRequestJSON::SetVerb(EEmoShapeIORequestVerb Verb)
{
	RequestVerb = Verb;
}

void UESRequestJSON::SetCustomVerb(FString Verb)
{
	CustomVerb = Verb;
}

void UESRequestJSON::SetContentType(EEmoShapeIORequestContentType ContentType)
{
	RequestContentType = ContentType;
}

void UESRequestJSON::SetBinaryContentType(const FString& ContentType)
{
	BinaryContentType = ContentType;
}

void UESRequestJSON::SetBinaryRequestContent(const TArray<uint8>& Content)
{
	RequestBytes = Content;
}

void UESRequestJSON::SetHeader(const FString& HeaderName, const FString& HeaderValue)
{
	RequestHeaders.Add(HeaderName, HeaderValue);
}

void UESRequestJSON::ResetData()
{
	ResetRequestData();
	ResetResponseData();
}

void UESRequestJSON::ResetRequestData()
{
	if (RequestJsonObj != nullptr)
	{
		RequestJsonObj->Reset();
	}
	else
	{
		RequestJsonObj = NewObject<UESJsonObject>();
	}

	HttpRequest = FHttpModule::Get().CreateRequest();
}

void UESRequestJSON::ResetResponseData()
{
	if (ResponseJsonObj != nullptr)
	{
		ResponseJsonObj->Reset();
	}
	else
	{
		ResponseJsonObj = NewObject<UESJsonObject>();
	}

	ResponseHeaders.Empty();
	ResponseCode = -1;

	bIsValidJsonResponse = false;
}

void UESRequestJSON::Cancel()
{
	ContinueAction = nullptr;

	ResetResponseData();
}

UESJsonObject* UESRequestJSON::GetRequestObject()
{
	return RequestJsonObj;
}

void UESRequestJSON::SetRequestObject(UESJsonObject* JsonObject)
{
	RequestJsonObj = JsonObject;
}

UESJsonObject* UESRequestJSON::GetResponseObject()
{
	return ResponseJsonObj;
}

void UESRequestJSON::SetResponseObject(UESJsonObject* JsonObject)
{
	ResponseJsonObj = JsonObject;
}

FString UESRequestJSON::GetURL()
{
	return HttpRequest->GetURL();
}

EEmoShapeIORequestStatus UESRequestJSON::GetStatus()
{
	return EEmoShapeIORequestStatus((uint8)HttpRequest->GetStatus());
}

int32 UESRequestJSON::GetResponseCode()
{
	return ResponseCode;
}

FString UESRequestJSON::GetResponseHeader(const FString HeaderName)
{
	FString Result;

	FString* Header = ResponseHeaders.Find(HeaderName);
	if (Header != nullptr)
	{
		Result = *Header;
	}

	return Result;
}

TArray<FString> UESRequestJSON::GetAllResponseHeaders()
{
	TArray<FString> Result;
	for (TMap<FString, FString>::TConstIterator It(ResponseHeaders); It; ++It)
	{
		Result.Add(It.Key() + TEXT(": ") + It.Value());
	}
	return Result;
}

void UESRequestJSON::ProcessURL(const FString& Url)
{
	HttpRequest->SetURL(Url);

	ProcessRequest();
}

void UESRequestJSON::ApplyURL(const FString& Url, UESJsonObject*& Result, UObject* WorldContextObject,
	FLatentActionInfo LatentInfo)
{
	HttpRequest->SetURL(Url);

	// Prepare latent action
	if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
	{
		FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
		FEmoShapeJsonLatentAction<UESJsonObject*> *Kont = LatentActionManager.FindExistingAction<FEmoShapeJsonLatentAction<UESJsonObject*>>(LatentInfo.CallbackTarget, LatentInfo.UUID);


		if (Kont != nullptr)
		{
			Kont->Cancel();
			LatentActionManager.RemoveActionsForObject(LatentInfo.CallbackTarget);
		}

		LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, ContinueAction = new FEmoShapeJsonLatentAction<UESJsonObject*>(this, Result, LatentInfo));
	}

	ProcessRequest();
}

void UESRequestJSON::ProcessRequest()
{
	// Set verb
	switch (RequestVerb)
	{
	case EEmoShapeIORequestVerb::GET:
		HttpRequest->SetVerb(TEXT("GET"));
		break;

	case EEmoShapeIORequestVerb::POST:
		HttpRequest->SetVerb(TEXT("POST"));
		break;

	case EEmoShapeIORequestVerb::PUT:
		HttpRequest->SetVerb(TEXT("PUT"));
		break;
			
	case EEmoShapeIORequestVerb::DEL:
		HttpRequest->SetVerb(TEXT("DELETE"));
		break;

	case EEmoShapeIORequestVerb::CUSTOM:
		HttpRequest->SetVerb(CustomVerb);
		break;

	default:
		break;
	}

	// Set content-type
	switch (RequestContentType)
	{
	case EEmoShapeIORequestContentType::x_www_form_urlencoded_url:
	{
		HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/x-www-form-urlencoded"));

		FString UrlParams = "";
		uint16 ParamIdx = 0;

		// Loop through all the values and prepare additional url part
		for (auto RequestIt = RequestJsonObj->GetRootObject()->Values.CreateIterator(); RequestIt; ++RequestIt)
		{
			FString Key = RequestIt.Key();
			FString Value = RequestIt.Value().Get()->AsString();

			if (!Key.IsEmpty() && !Value.IsEmpty())
			{
				UrlParams += ParamIdx == 0 ? "?" : "&";
				UrlParams += UESJsonLibrary::PercentEncode(Key) + "=" + UESJsonLibrary::PercentEncode(Value);
			}

			ParamIdx++;
		}

		// Apply params
		HttpRequest->SetURL(HttpRequest->GetURL() + UrlParams);

		UE_LOG(LogEmoShape, Log, TEXT("Request (urlencoded): %s %s %s"), *HttpRequest->GetVerb(), *HttpRequest->GetURL(), *UrlParams);

		break;
	}
	case EEmoShapeIORequestContentType::x_www_form_urlencoded_body:
	{
		HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/x-www-form-urlencoded"));

		FString UrlParams = "";
		uint16 ParamIdx = 0;

		// Loop through all the values and prepare additional url part
		for (auto RequestIt = RequestJsonObj->GetRootObject()->Values.CreateIterator(); RequestIt; ++RequestIt)
		{
			FString Key = RequestIt.Key();
			FString Value = RequestIt.Value().Get()->AsString();

			if (!Key.IsEmpty() && !Value.IsEmpty())
			{
				UrlParams += ParamIdx == 0 ? "" : "&";
				UrlParams += UESJsonLibrary::PercentEncode(Key) + "=" + UESJsonLibrary::PercentEncode(Value);
			}

			ParamIdx++;
		}

		// Apply params
		HttpRequest->SetContentAsString(UrlParams);

		UE_LOG(LogEmoShape, Log, TEXT("Request (url body): %s %s %s"), *HttpRequest->GetVerb(), *HttpRequest->GetURL(), *UrlParams);

		break;
	}
	case EEmoShapeIORequestContentType::binary:
	{
		HttpRequest->SetHeader(TEXT("Content-Type"), BinaryContentType);
		HttpRequest->SetContent(RequestBytes);

		UE_LOG(LogEmoShape, Log, TEXT("Request (binary): %s %s"), *HttpRequest->GetVerb(), *HttpRequest->GetURL());

		break;
	}
	case EEmoShapeIORequestContentType::json:
	{
		HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));

		// Serialize data to json string
		FString OutputString;
		TSharedRef< TJsonWriter<> > Writer = TJsonWriterFactory<>::Create(&OutputString);
		FJsonSerializer::Serialize(RequestJsonObj->GetRootObject().ToSharedRef(), Writer);

		// Set Json content
		HttpRequest->SetContentAsString(OutputString);

		UE_LOG(LogEmoShape, Log, TEXT("Request (json): %s %s %s"), *HttpRequest->GetVerb(), *HttpRequest->GetURL(), *OutputString);

		break;
	}

	default:
		break;
	}

	// Apply additional headers
	for (TMap<FString, FString>::TConstIterator It(RequestHeaders); It; ++It)
	{
		HttpRequest->SetHeader(It.Key(), It.Value());
	}
	
	// Bind event
	if (bShouldHaveBinaryResponse)
	{
		HttpRequest->OnProcessRequestComplete().BindUObject(this, &UESRequestJSON::OnProcessRequestCompleteBinaryResult);
	}
	else
	{
		HttpRequest->OnProcessRequestComplete().BindUObject(this, &UESRequestJSON::OnProcessRequestComplete);
	}

	// Execute the request
	HttpRequest->ProcessRequest();
}

void UESRequestJSON::OnProcessRequestComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	// Be sure that we have no data from previous response
	ResetResponseData();

	// Check we have a response and save response code as int32
	if(Response.IsValid())
	{
		ResponseCode = Response->GetResponseCode();
	}

	// Check we have result to process futher
	if (!bWasSuccessful || !Response.IsValid())
	{
		UE_LOG(LogEmoShape, Error, TEXT("Request failed (%d): %s"), ResponseCode, *Request->GetURL());

		// Broadcast the result event
		OnRequestFail.Broadcast(this);
		OnStaticRequestFail.Broadcast(this);

		return;
	}

	// Save response data as a string
	ResponseContent = Response->GetContentAsString();

	// Log response state
	UE_LOG(LogEmoShape, Log, TEXT("Response (%d): %s"), ResponseCode, *ResponseContent);

	// Process response headers
	TArray<FString> Headers = Response->GetAllHeaders();
	for (FString Header : Headers)
	{
		FString Key;
		FString Value;
		if (Header.Split(TEXT(": "), &Key, &Value))
		{
			ResponseHeaders.Add(Key, Value);
		}
	}

	// Try to deserialize data to JSON
	TSharedRef<TJsonReader<TCHAR>> JsonReader = TJsonReaderFactory<TCHAR>::Create(ResponseContent);
	FJsonSerializer::Deserialize(JsonReader, ResponseJsonObj->GetRootObject());

	// Decide whether the request was successful
	bIsValidJsonResponse = bWasSuccessful && ResponseJsonObj->GetRootObject().IsValid();

	// Log errors
	if (!bIsValidJsonResponse)
	{
		if (!ResponseJsonObj->GetRootObject().IsValid())
		{
			// As we assume it's recommended way to use current class, but not the only one,
			// it will be the warning instead of error
			UE_LOG(LogEmoShape, Warning, TEXT("JSON could not be decoded!"));
		}
	}

	// Broadcast the result event
	OnRequestComplete.Broadcast(this);
	OnStaticRequestComplete.Broadcast(this);

	// Finish the latent action
	if (ContinueAction)
	{
		FEmoShapeJsonLatentAction<UESJsonObject*> *K = ContinueAction;
		ContinueAction = nullptr;

		K->Call(ResponseJsonObj);
	}
}

void UESRequestJSON::OnProcessRequestCompleteBinaryResult(FHttpRequestPtr Request, FHttpResponsePtr Response,
	bool bWasSuccessful)
{
	// Be sure that we have no data from previous response
	ResetResponseData();

	// Check we have a response and save response code as int32
	if (Response.IsValid())
	{
		ResponseCode = Response->GetResponseCode();
	}

	// Check we have result to process futher
	if (!bWasSuccessful || !Response.IsValid())
	{
		UE_LOG(LogEmoShape, Error, TEXT("Request failed (%d): %s"), ResponseCode, *Request->GetURL());

		// Broadcast the result event
		OnRequestFail.Broadcast(this);
		OnStaticRequestFail.Broadcast(this);

		return;
	}

	ResultBinaryData = Response->GetContent();

	// Broadcast the result event
	OnRequestComplete.Broadcast(this);
	OnStaticRequestComplete.Broadcast(this);

	if (OnProcessURLCompleteCallback)
	{
		OnProcessURLCompleteCallback(ResultBinaryData);
	}
}

void UESRequestJSON::AddTag(FName Tag)
{
	if (Tag != NAME_None)
	{
		Tags.AddUnique(Tag);
	}
}

int32 UESRequestJSON::RemoveTag(FName Tag)
{
	return Tags.Remove(Tag);
}

bool UESRequestJSON::HasTag(FName Tag) const
{
	return (Tag != NAME_None) && Tags.Contains(Tag);
}
