// Copyright CodeSpartan, 2022. All rights reserved.

#include "MmoUtilities.h"
#include "Engine/Engine.h"
#include "Online.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Net/OnlineEngineInterface.h"
#include "Kismet/GameplayStatics.h"
#include "GameInstanceCpp.h"
#include "Misc/CommandLine.h"
#include "Serialization/MemoryWriter.h"
#include "Serialization/MemoryReader.h"
#include "Serialization/ObjectAndNameAsStringProxyArchive.h"
#include "UObject/TopLevelAssetPath.h"
#include "Blueprint/BlueprintExceptionInfo.h"
#include "JsonObjectConverter.h"

#define LOCTEXT_NAMESPACE "UMmoUtilities"

bool UMmoUtilities::InPieWorld(UObject* WorldContextObject)
{
	if (!WorldContextObject) return false;

	const UWorld* World = GEngine->GetWorldFromContextObjectChecked(WorldContextObject);
	if (!World) return false;

	return (World->WorldType == EWorldType::PIE);
}

int32 UMmoUtilities::GetPieWindowId(UObject* WorldContextObject)
{
	if (!WorldContextObject) return -1;

	const UWorld* World = GEngine->GetWorldFromContextObjectChecked(WorldContextObject);
	if (!World) return -1;

	// this used to be GPlayInEditorID... a possible alternative is GEngine->GetWorldContextFromWorld(World)->PIEInstance;
	return UE::GetPlayInEditorID();
}

int32 UMmoUtilities::GetServerPort(UObject* WorldContextObject)
{
	if (!WorldContextObject) return -1;

	const UWorld* World = GEngine->GetWorldFromContextObjectChecked(WorldContextObject);
	if (!World) return -1;

	return World->URL.Port;
}

bool UMmoUtilities::CheckCommandLineParam(FString Param)
{
	return FParse::Param(FCommandLine::Get(), *Param);
}

bool UMmoUtilities::GetCommandLineValue(FString Param, FString & Value)
{
	return FParse::Value(FCommandLine::Get(), *Param, Value);
}

FString UMmoUtilities::GetSteamSessionTicket()
{
	IOnlineIdentityPtr identity = Online::GetIdentityInterface();

	if (!identity.IsValid())
	{
		return TEXT("identity invalid: no session ticket");
	}

	return identity->GetAuthToken(0);
}

FString UMmoUtilities::GetSteamUserId()
{
	IOnlineIdentityPtr identity = Online::GetIdentityInterface();

	if (!identity.IsValid())
	{
		return TEXT("identity invalid: no user id");
	}

	return identity->GetUniquePlayerId(0).Get()->ToString();
}

bool UMmoUtilities::IsWithSteam()
{
	FString ossName = UOnlineEngineInterface::Get()->GetDefaultOnlineSubsystemName().ToString();
	// Possible values are: "NULL", "STEAM"
	return ossName.Equals(TEXT("Steam"), ESearchCase::IgnoreCase);
}

void UMmoUtilities::LogString(const UObject* WorldContextObject, const FString& InString, const LogVerbosity Verbosity, bool bPrintToScreen, bool bPrintToLog, FLinearColor TextColor, float Duration, const FName Key)
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) // Do not Print in Shipping or Test
	const UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if (World == nullptr) return;
	const UGameInstance* GI = UGameplayStatics::GetGameInstance(World);
	if (const auto GICpp = Cast<UGameInstanceCpp>(GI))
	{
		if (Verbosity >= GICpp->LogVerbosity)
			UKismetSystemLibrary::PrintString(World, InString, bPrintToScreen, bPrintToLog, TextColor, Duration, Key);
	}
#endif
}

TArray<uint8> UMmoUtilities::SerializeObject(UObject* Obj)
{
	TArray<uint8> result;
	FMemoryWriter MemWriter(result);
	FObjectAndNameAsStringProxyArchive Ar(MemWriter, true);

	// Find only variables with UPROPERTY(SaveGame)
	Ar.ArIsSaveGame = true;
	// No diff, serializes all variables even if they're at their default value
	// This allows us to change the defaults and not worry about incorrect deserialization later
	Ar.ArNoDelta = true;
	// Converts Object's SaveGame UPROPERTIES into binary array
	Obj->Serialize(Ar);

	return result;
}

void UMmoUtilities::DeserializeObject(UObject* Obj, const TArray<uint8> Bytes)
{
	FMemoryReader MemReader(Bytes);
	FObjectAndNameAsStringProxyArchive Ar(MemReader, true);

	// Dealing with variables with UPROPERTY(SaveGame)
	Ar.ArIsSaveGame = true;
	// No diff, serializes all variables even if they're at their default value
	// This allows us to change the defaults and not worry about incorrect deserialization later
	Ar.ArNoDelta = true;
	// Convert binary array back into actor's variables
	Obj->Serialize(Ar);
}

FName UMmoUtilities::GetAssetName(const FTopLevelAssetPath& TopLevelAssetPath)
{
	return TopLevelAssetPath.GetAssetName();
}

DEFINE_FUNCTION(UMmoUtilities::execConvertJsonStringToStruct)
{
	P_GET_PROPERTY(FStrProperty, JsonString);

	Stack.StepCompiledIn<FProperty>(nullptr);
	FProperty* OutputValueProp = Stack.MostRecentProperty;
	void* OutputValuePtr = Stack.MostRecentPropertyAddress;

	P_FINISH;

	bool bResult;
	if (!OutputValueProp || !OutputValuePtr)
	{
		FBlueprintExceptionInfo ExceptionInfo(
			EBlueprintExceptionType::AbortExecution,
			LOCTEXT("ConvertJsonStringToStruct_GetInvalidValueWarning", "Failed to resolve the Value for ConvertJsonStringToStruct")
		);

		FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
	}
	else
	{
		// Check that our output pin is indeed a struct
		if (!OutputValueProp->IsA<FStructProperty>()) 
		{
			bResult = false;
			*StaticCast<bool*>(RESULT_PARAM) = bResult;
			return;
		}

		FStructProperty* StructProperty = static_cast<FStructProperty*>(OutputValueProp);

		P_NATIVE_BEGIN;
		FJsonObjectWrapper JsonWrapper;
		bResult = JsonWrapper.JsonObjectFromString(JsonString);
		if (bResult) {			
			bResult = FJsonObjectConverter::JsonObjectToUStruct(JsonWrapper.JsonObject.ToSharedRef(), StructProperty->Struct, OutputValuePtr);
		}
		P_NATIVE_END;

		*StaticCast<bool*>(RESULT_PARAM) = bResult;
	}
}

DEFINE_FUNCTION(UMmoUtilities::execLoadJsonFromStruct)
{

	Stack.StepCompiledIn<FProperty>(nullptr);
	FProperty* InputValueProp = Stack.MostRecentProperty;
	void* InputValuePtr = Stack.MostRecentPropertyAddress;

	PARAM_PASSED_BY_REF(OutJsonWrapper, FStructProperty, FJsonObjectWrapper);

	P_FINISH;

	bool bResult;
	if (!InputValueProp || !InputValuePtr)
	{
		FBlueprintExceptionInfo ExceptionInfo(
			EBlueprintExceptionType::AbortExecution,
			LOCTEXT("LoadJsonFromStruct_GetInvalidInputWarning", "Failed to resolve the Input for LoadJsonFromStruct")
		);

		FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
	}
	else
	{
		// Check that our input pin is indeed a struct
		if (!InputValueProp->IsA<FStructProperty>())
		{
			bResult = false;
			*StaticCast<bool*>(RESULT_PARAM) = bResult;
			return;
		}

		FStructProperty* StructProperty = static_cast<FStructProperty*>(InputValueProp);

		P_NATIVE_BEGIN;
		TSharedRef<FJsonObject> JsonObject = MakeShared<FJsonObject>();
		bResult = FJsonObjectConverter::UStructToJsonObject(StructProperty->Struct, InputValuePtr, JsonObject);
		if (bResult) 
		{
			OutJsonWrapper.JsonObject = JsonObject;
		}
		P_NATIVE_END;

		*StaticCast<bool*>(RESULT_PARAM) = bResult;
	}
}

#undef LOCTEXT_NAMESPACE