#include "EmoShapeSubSystem/EmoShapeJson/Public/ESJsonValue.h"
#include "EmoShapeSubSystem.h"
#include "EmoShapeSubSystem/EmoShapeJson/Public/ESIOJConvert.h"
#include "EmoShapeSubSystem/EmoShapeJson/Public/ESJsonObject.h"
#include "Misc/Base64.h"

#if PLATFORM_WINDOWS
#pragma region FJsonValueBinary
#endif

bool FJsonValueBinary::IsBinary(const TSharedPtr<FJsonValue>& InJsonValue)
{
	if (!InJsonValue.IsValid())
	{
		return false;
	}
	//use our hackery to determine if we got a binary string
	bool IgnoreBool;
	return !InJsonValue->TryGetBool(IgnoreBool);
}

TArray<uint8> FJsonValueBinary::AsBinary(const TSharedPtr<FJsonValue>& InJsonValue)
{
	if (FJsonValueBinary::IsBinary(InJsonValue))
	{
		TSharedPtr<FJsonValueBinary> BinaryValue = StaticCastSharedPtr<FJsonValueBinary>(InJsonValue);
		return BinaryValue->AsBinary();
	}
	else if (InJsonValue->Type == EJson::String)
	{
		//If we got a string that isn't detected as a binary via socket.io protocol hack
		//then we need to decode this string as base 64
		TArray<uint8> DecodedArray;
		bool bDidDecodeCorrectly = FBase64::Decode(InJsonValue->AsString(), DecodedArray);
		if (!bDidDecodeCorrectly)
		{
			UE_LOG(LogEmoShape, Warning, TEXT("FJsonValueBinary::AsBinary couldn't decode %s as a binary."), *InJsonValue->AsString());
		}
		return DecodedArray;
	}
	else
	{
		TArray<uint8> EmptyArray;
		return EmptyArray;
	}
}

UESJsonValue::UESJsonValue(const class FObjectInitializer& PCIP)
	: Super(PCIP)
{

}

UESJsonValue* UESJsonValue::ConstructJsonValueNumber(UObject* WorldContextObject, float Number)
{
	TSharedPtr<FJsonValue> NewVal = MakeShareable(new FJsonValueNumber(Number));

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

	return NewValue;
}

UESJsonValue* UESJsonValue::ConstructJsonValueString(UObject* WorldContextObject, const FString& StringValue)
{
	TSharedPtr<FJsonValue> NewVal = MakeShareable(new FJsonValueString(StringValue));

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

	return NewValue;
}

UESJsonValue* UESJsonValue::ConstructJsonValueBool(UObject* WorldContextObject, bool InValue)
{
	TSharedPtr<FJsonValue> NewVal = MakeShareable(new FJsonValueBoolean(InValue));

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

	return NewValue;
}

UESJsonValue* UESJsonValue::ConstructJsonValueArray(UObject* WorldContextObject,
	const TArray<UESJsonValue*>& InArray)
{
	// Prepare data array to create new value
	TArray< TSharedPtr<FJsonValue> > ValueArray;
	for (auto InVal : InArray)
	{
		ValueArray.Add(InVal->GetRootValue());
	}

	TSharedPtr<FJsonValue> NewVal = MakeShareable(new FJsonValueArray(ValueArray));

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

	return NewValue;
}

UESJsonValue* UESJsonValue::ConstructJsonValueObject(UESJsonObject* JsonObject, UObject* WorldContextObject)
{
	TSharedPtr<FJsonValue> NewVal = MakeShareable(new FJsonValueObject(JsonObject->GetRootObject()));

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

	return NewValue;
}

UESJsonValue* UESJsonValue::ConstructJsonValueBinary(UObject* WorldContextObject, TArray<uint8> ByteArray)
{
	TSharedPtr<FJsonValue> NewVal = MakeShareable(new FJsonValueBinary(ByteArray));

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

	return NewValue;
}

UESJsonValue* UESJsonValue::ConstructJsonValue(UObject* WorldContextObject, const TSharedPtr<FJsonValue>& InValue)
{
	TSharedPtr<FJsonValue> NewVal = InValue;

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

	return NewValue;
}

UESJsonValue* UESJsonValue::ValueFromJsonString(UObject* WorldContextObject, const FString& StringValue)
{
	TSharedPtr<FJsonValue> NewVal = UESIOJConvert::JsonStringToJsonValue(StringValue);

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

	return NewValue;
}

TSharedPtr<FJsonValue>& UESJsonValue::GetRootValue()
{
	return JsonVal;
}

void UESJsonValue::SetRootValue(TSharedPtr<FJsonValue>& JsonValue)
{
	JsonVal = JsonValue;
}

EESJson::Type UESJsonValue::GetType() const
{
	if (!JsonVal.IsValid())
	{
		return EESJson::None;
	}

	switch (JsonVal->Type)
	{
	case EJson::None:
		return EESJson::None;

	case EJson::Null:
		return EESJson::Null;

	case EJson::String:
		if (FJsonValueBinary::IsBinary(JsonVal))
		{
			return EESJson::Binary;
		}
		else
		{
			return EESJson::String;
		}
	case EJson::Number:
		return EESJson::Number;

	case EJson::Boolean:
		return EESJson::Boolean;

	case EJson::Array:
		return EESJson::Array;

	case EJson::Object:
		return EESJson::Object;

	default:
		return EESJson::None;
	}
}

FString UESJsonValue::GetTypeString() const
{
	if (!JsonVal.IsValid())
	{
		return "None";
	}

	switch (JsonVal->Type)
	{
	case EJson::None:
		return TEXT("None");

	case EJson::Null:
		return TEXT("Null");

	case EJson::String:
		return TEXT("String");

	case EJson::Number:
		return TEXT("Number");

	case EJson::Boolean:
		return TEXT("Boolean");

	case EJson::Array:
		return TEXT("Array");

	case EJson::Object:
		return TEXT("Object");

	default:
		return TEXT("None");
	}
}

bool UESJsonValue::IsNull() const
{
	if (!JsonVal.IsValid())
	{
		return true;
	}

	return JsonVal->IsNull();
}

float UESJsonValue::AsNumber() const
{
	if (!JsonVal.IsValid())
	{
		ErrorMessage(TEXT("Number"));
		return 0.f;
	}

	return JsonVal->AsNumber();
}

FString UESJsonValue::AsString() const
{
	if (!JsonVal.IsValid())
	{
		ErrorMessage(TEXT("String"));
		return FString();
	}

	//Auto-convert non-strings instead of getting directly
	if (JsonVal->Type != EJson::String)
	{
		return EncodeJson();
	}
	else
	{
		return JsonVal->AsString();
	}
}

bool UESJsonValue::AsBool() const
{
	if (!JsonVal.IsValid())
	{
		ErrorMessage(TEXT("Boolean"));
		return false;
	}

	return JsonVal->AsBool();
}

TArray<UESJsonValue*> UESJsonValue::AsArray() const
{
	TArray<UESJsonValue*> OutArray;

	if (!JsonVal.IsValid())
	{
		ErrorMessage(TEXT("Array"));
		return OutArray;
	}

	TArray< TSharedPtr<FJsonValue> > ValArray = JsonVal->AsArray();
	for (auto Value : ValArray)
	{
		UESJsonValue* NewValue = NewObject<UESJsonValue>();
		NewValue->SetRootValue(Value);

		OutArray.Add(NewValue);
	}

	return OutArray;
}

UESJsonObject* UESJsonValue::AsObject()
{
	if (!JsonVal.IsValid())
	{
		ErrorMessage(TEXT("Object"));
		return nullptr;
	}

	TSharedPtr<FJsonObject> NewObj = JsonVal->AsObject();

	UESJsonObject* JsonObj = NewObject<UESJsonObject>();
	JsonObj->SetRootObject(NewObj);

	return JsonObj;
}

TArray<uint8> UESJsonValue::AsBinary()
{
	if (!JsonVal.IsValid())
	{
		ErrorMessage(TEXT("Binary"));
		TArray<uint8> ByteArray;
		return ByteArray;
	}

	//binary object pretending & starts with non-json format? it's our disguise binary
	if (JsonVal->Type == EJson::String)
	{
		//it's a legit binary
		if (FJsonValueBinary::IsBinary(JsonVal))
		{
			//Valid binary available
			return FJsonValueBinary::AsBinary(JsonVal);
		}

		//It's a string, decode as if hex encoded binary
		else
		{
			const FString& HexString = JsonVal->AsString();

			TArray<uint8> ByteArray;
			ByteArray.AddUninitialized(HexString.Len() / 2);

			bool DidConvert = FString::ToHexBlob(HexString, ByteArray.GetData(), ByteArray.Num());

			//Empty our array if conversion failed
			if (!DidConvert)
			{
				ByteArray.Empty();
			}
			return ByteArray;
		}
	}
	//Not a binary nor binary string, return empty array
	else
	{
		//Empty array
		TArray<uint8> ByteArray;
		return ByteArray;
	}
}

FString UESJsonValue::EncodeJson() const
{
	return UESIOJConvert::ToJsonString(JsonVal);
}

void UESJsonValue::ErrorMessage(const FString& InType) const
{
	UE_LOG(LogEmoShape, Error, TEXT("Json Value of type '%s' used as a '%s'."), *GetTypeString(), *InType);
}

#if PLATFORM_WINDOWS
#pragma endregion UEmoShapeJsonValue
#endif
