// Modifications Copyright 2022-current EmoShape. All Rights Reserved


// Copyright 2016 Vladimir Alyamkin. All Rights Reserved.

#pragma once
#include "ESIOJConvert.h"
#include "ESIOTypes.h"
#include "ESJsonObject.h"
#include "ESJsonValue.h"
#include "UObject/Object.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "ESJsonLibrary.generated.h"

class UESRequestJSON;
class UESIOJConvert;
class UESJsonObject;
class UESJsonValue;
DECLARE_DYNAMIC_DELEGATE_OneParam(FEmoShapeJsonCallDelegate, UESRequestJSON*, Request);

USTRUCT()
struct FEmoShapeCallResponse
{
	GENERATED_USTRUCT_BODY()
	
	UPROPERTY()
	UESRequestJSON* Request;
	
	UPROPERTY()
	UObject* WorldContextObject;
	
	UPROPERTY()
	FEmoShapeJsonCallDelegate Callback;
	
	FDelegateHandle CompleteDelegateHandle;
	FDelegateHandle FailDelegateHandle;
	
	FEmoShapeCallResponse()
		: Request(nullptr)
		, WorldContextObject(nullptr)
	{
	}
	
};

UCLASS()
class EMOSHAPESUBSYSTEM_API UESJsonLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()
	
public:
	/** Applies percent-encoding to text */
	UFUNCTION(BlueprintCallable, Category = "EmoShapeJson|Utility")
		static FString PercentEncode(const FString& Source);

	/**
	 * Encodes a FString into a Base64 string
	 *
	 * @param Source	The string data to convert
	 * @return			A string that encodes the binary data in a way that can be safely transmitted via various Internet protocols
	 */
	UFUNCTION(BlueprintPure, Category = "EmoShapeJson|Utility", meta = (DisplayName = "Base64 Encode (String)"))
		static FString Base64Encode(const FString& Source);

	/**
	 * Encodes a Byte array into a Base64 string
	 *
	 * @param Source	The string data to convert
	 * @return			A string that encodes the binary data in a way that can be safely transmitted via various Internet protocols
	 */
	UFUNCTION(BlueprintPure, Category = "EmoShapeJson|Utility", meta = (DisplayName = "Base64 Encode (Bytes)"))
		static FString Base64EncodeBytes(const TArray<uint8>& Source);

	/**
	 * Decodes a Base64 string into a FString
	 *
	 * @param Source	The stringified data to convert
	 * @param Dest		The out buffer that will be filled with the decoded data
	 * @return			True if the buffer was decoded, false if it failed to decode
	 */
	UFUNCTION(BlueprintPure, Category = "EmoShapeJson|Utility", meta = (DisplayName = "Base64 Decode (To String)"))
		static bool Base64Decode(const FString& Source, FString& Dest);


	/**
	 * Decodes a Base64 string into a Byte array
	 *
	 * @param Source	The stringified data to convert
	 * @param Dest		The out buffer that will be filled with the decoded data
	 * @return			True if the buffer was decoded, false if it failed to decode
	 */
	UFUNCTION(BlueprintPure, Category = "EmoShapeJson|Utility", meta = (DisplayName = "Base64 Decode (To Bytes)"))
		static bool Base64DecodeBytes(const FString& Source, TArray<uint8>& Dest);

	//////////////////////////////////////////////////////////////////////////
	// Easy URL processing

	/**
	* Decodes a json string into an array of stringified json Values
	*
	* @param JsonString				Input stringified json
	* @param OutJsonValueArray		The decoded Array of JsonValue
	*/
	UFUNCTION(BlueprintPure, Category = "EmoShapeJson|Utility")
		static bool StringToJsonValueArray(const FString& JsonString, TArray<UESJsonValue*>& OutJsonValueArray);

	/**
	* Uses the reflection system to convert an unreal struct into a JsonObject
	*
	* @param AnyStruct		The struct you wish to convert
	* @return				Converted Json Object
	*/
	UFUNCTION(BlueprintPure, Category = "EmoShapeSocketIOFunctions", CustomThunk, meta = (CustomStructureParam = "AnyStruct"))
		static UESJsonObject* StructToJsonObject(TFieldPath<FProperty> AnyStruct);

	//Convert property into c++ accessible form
	DECLARE_FUNCTION(execStructToJsonObject)
	{
		//Get properties and pointers from stack
		Stack.Step(Stack.Object, NULL);
		FStructProperty* StructProperty = CastField<FStructProperty>(Stack.MostRecentProperty);
		void* StructPtr = Stack.MostRecentPropertyAddress;

		// We need this to wrap up the stack
		P_FINISH;

		auto BPJsonObject = NewObject<UESJsonObject>();

		auto JsonObject = UESIOJConvert::ToJsonObject(StructProperty->Struct, StructPtr, true);
		BPJsonObject->SetRootObject(JsonObject);

		*(UESJsonObject**)RESULT_PARAM = BPJsonObject;
	}

	UFUNCTION(BlueprintPure, CustomThunk, meta = (DisplayName = "To JsonValue (Struct)", BlueprintAutocast, CustomStructureParam = "AnyStruct"), Category = "Utilities|EmoShapeSocketIO")
	static UESJsonValue* StructToJsonValue(TFieldPath<FProperty> AnyStruct);

	DECLARE_FUNCTION(execStructToJsonValue)
	{
		//Get properties and pointers from stack
		Stack.Step(Stack.Object, NULL);
		FStructProperty* StructProperty = CastField<FStructProperty>(Stack.MostRecentProperty);
		void* StructPtr = Stack.MostRecentPropertyAddress;

		// We need this to wrap up the stack
		P_FINISH;

		auto JsonObject = UESIOJConvert::ToJsonObject(StructProperty->Struct, StructPtr, true);

		TSharedPtr<FJsonValue> JsonValue = MakeShareable(new FJsonValueObject(JsonObject));
		UESJsonValue* BPJsonValue = NewObject<UESJsonValue>();
		BPJsonValue->SetRootValue(JsonValue);
		
		*(UESJsonValue**)RESULT_PARAM = BPJsonValue;
	}


	/**
	* Uses the reflection system to fill an unreal struct from data defined in JsonObject.
	*
	* @param JsonObject		The source JsonObject for properties to fill
	* @param AnyStruct		The struct you wish to fill
	* @return				Whether all properties filled correctly
	*/
	UFUNCTION(BlueprintCallable, Category = "EmoShapeSocketIOFunctions", CustomThunk, meta = (CustomStructureParam = "AnyStruct"))
	static bool JsonObjectToStruct(UESJsonObject* JsonObject, TFieldPath<FProperty> AnyStruct);

	DECLARE_FUNCTION(execJsonObjectToStruct)
	{
		//Get properties and pointers from stack
		P_GET_OBJECT(UESJsonObject, JsonObject);

		Stack.Step(Stack.Object, NULL);
		FStructProperty* StructProperty = CastField<FStructProperty>(Stack.MostRecentProperty);
		void* StructPtr = Stack.MostRecentPropertyAddress;

		P_FINISH;

		//Pass in the reference to the json object
		bool Success = UESIOJConvert::JsonObjectToUStruct(JsonObject->GetRootObject(), StructProperty->Struct, StructPtr, true);

		*(bool*)RESULT_PARAM = Success;
	}

	//Convenience - Saving/Loading structs from files
	UFUNCTION(BlueprintCallable, Category = "EmoShapeSocketIOFunctions", CustomThunk, meta = (CustomStructureParam = "AnyStruct"))
	static bool SaveStructToJsonFile(TFieldPath<FProperty> AnyStruct, const FString& FilePath);

	UFUNCTION(BlueprintCallable, Category = "EmoShapeSocketIOFunctions", CustomThunk, meta = (CustomStructureParam = "AnyStruct"))
	static bool LoadJsonFileToStruct(const FString& FilePath, TFieldPath<FProperty> AnyStruct);

	//custom thunk needed to handle wildcard structs
	DECLARE_FUNCTION(execSaveStructToJsonFile)
	{
		Stack.StepCompiledIn<FStructProperty>(NULL);
		FStructProperty* StructProp = CastField<FStructProperty>(Stack.MostRecentProperty);
		void* StructPtr = Stack.MostRecentPropertyAddress;

		FString FilePath;
		Stack.StepCompiledIn<FStrProperty>(&FilePath);
		P_FINISH;

		P_NATIVE_BEGIN;
		*(bool*)RESULT_PARAM = UESIOJConvert::ToJsonFile(FilePath, StructProp->Struct, StructPtr);
		P_NATIVE_END;
	}

	//custom thunk needed to handle wildcard structs
	DECLARE_FUNCTION(execLoadJsonFileToStruct)
	{
		FString FilePath;
		Stack.StepCompiledIn<FStrProperty>(&FilePath);
		Stack.StepCompiledIn<FStructProperty>(NULL);
		FStructProperty* StructProp = CastField<FStructProperty>(Stack.MostRecentProperty);
		void* StructPtr = Stack.MostRecentPropertyAddress;
		P_FINISH;

		P_NATIVE_BEGIN;
		*(bool*)RESULT_PARAM = UESIOJConvert::JsonFileToUStruct(FilePath, StructProp->Struct, StructPtr, true);
		P_NATIVE_END;
	}

	//Conversion Nodes - comments added for blueprint hover with compact nodes

	//To JsonValue (Array)
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To JsonValue (Array)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|EmoShapeSocketIO")
	static UESJsonValue* Conv_ArrayToJsonValue(const TArray<UESJsonValue*>& InArray);

	//To JsonValue (JsonObject)
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To JsonValue (JsonObject)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|EmoShapeSocketIO")
	static UESJsonValue* Conv_JsonObjectToJsonValue(UESJsonObject* InObject);

	//To JsonValue (Bytes)
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To JsonValue (Bytes)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|EmoShapeSocketIO")
	static UESJsonValue* Conv_BytesToJsonValue(const TArray<uint8>& InBytes);

	//To JsonValue (String)
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To JsonValue (String)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|EmoShapeSocketIO")
	static UESJsonValue* Conv_StringToJsonValue(const FString& InString);

	//To JsonValue (Integer)
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To JsonValue (Integer)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|EmoShapeSocketIO")
	static UESJsonValue* Conv_IntToJsonValue(int32 InInt);

	//To JsonValue (Float)
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To JsonValue (Float)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|EmoShapeSocketIO")
	static UESJsonValue* Conv_FloatToJsonValue(float InFloat);

	//To JsonValue (Bool)
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To JsonValue (Bool)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|EmoShapeSocketIO")
	static UESJsonValue* Conv_BoolToJsonValue(bool InBool);

	//To String (JsonValue) - doesn't autocast due to get display name
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To String (JsonValue)", BlueprintAutocast), Category = "Utilities|EmoShapeSocketIO")
	static FString Conv_EmoShapeJsonValueToString(class UESJsonValue* InValue);

	//To Integer (JsonValue)
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To Integer (JsonValue)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|EmoShapeSocketIO")
	static int32 Conv_JsonValueToInt(class UESJsonValue* InValue);

	//To Float (JsonValue)
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To Float (JsonValue)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|EmoShapeSocketIO")
	static float Conv_JsonValueToFloat(class UESJsonValue* InValue);

	//To Bool (JsonValue)
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To Bool (JsonValue)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|EmoShapeSocketIO")
	static bool Conv_JsonValueToBool(class UESJsonValue* InValue);

	//To Bytes (JsonValue)
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To Bytes (JsonValue)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|EmoShapeSocketIO")
	static TArray<uint8> Conv_JsonValueToBytes(class UESJsonValue* InValue);

	//To String (JsonObject) - doesn't autocast due to get display name
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To String (JsonObject)", BlueprintAutocast), Category = "Utilities|EmoShapeSocketIO")
	static FString Conv_EmoShapeJsonObjectToString(class UESJsonObject* InObject);

	//To Object (JsonValue)
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To Object (JsonValue)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|EmoShapeSocketIO")
	static UESJsonObject* Conv_JsonValueToJsonObject(class UESJsonValue* InValue);

public:
	/** Easy way to process http requests */
	UFUNCTION(BlueprintCallable, Category = "EmoShapeJson|Utility", meta = (WorldContext = "WorldContextObject"))
	static void CallURL(UObject* WorldContextObject, const FString& URL, EEmoShapeIORequestVerb Verb, EEmoShapeIORequestContentType ContentType, UESJsonObject* EmoShapeJJson, const FEmoShapeJsonCallDelegate& Callback);

	/** Easy way to fetch resources using get */
	UFUNCTION(BlueprintCallable, Category = "EmoShapeJson|Utility", meta = (Latent, LatentInfo = "LatentInfo", WorldContext = "WorldContextObject"))
	static void GetURLBinary(UObject* WorldContextObject, const FString& URL, EEmoShapeIORequestVerb Verb, EEmoShapeIORequestContentType ContentType, TArray<uint8>& OutResultData, struct FLatentActionInfo LatentInfo);

	/** Called when URL is processed (one for both success/unsuccess events)*/
	static void OnCallComplete(UESRequestJSON* Request);

private:
	static TMap<UESRequestJSON*, FEmoShapeCallResponse> RequestMap;
	
};
