
#include "FormatDataLibrary.h"
#include "Editor.h"
#include "EdGraphSchema_K2.h"
#include "Internationalization/TextFormatter.h"
#include "Layout/Geometry.h"
#include "Widgets/Layout/Anchors.h"
#include "Common.h"
#include "Engine/GameViewportClient.h"
#include "Slate/SGameLayerManager.h"
#include "Kismet/KismetTextLibrary.h"
#include "UObject/UObjectIterator.h"

void UlxFormatDataLibrary::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);
	ScanForConverters();
}

void UlxFormatDataLibrary::ScanForConverters()
{
	Converters.Empty();
	for (TObjectIterator<UClass> It; It; ++It)
	{
		UClass* Class = *It;
		for (TFieldIterator<UFunction> FuncIt(Class, EFieldIteratorFlags::ExcludeSuper); FuncIt; ++FuncIt)
		{
			UFunction* Function = *FuncIt;

			// Must have an AutoConvertedValue parameter.
			if (Function->FindPropertyByName(TEXT("AutoConvertedValue")) == nullptr) continue;

			// Must have a Name parameter that is a string.
			FProperty* NameProp = Function->FindPropertyByName(TEXT("Name"));
			if (NameProp == nullptr) continue;
			if (CastField<FStrProperty>(NameProp) == nullptr) continue;

			// Must return FFormatArgumentData.
			FStructProperty* ReturnProp = CastField<FStructProperty>(Function->GetReturnProperty());
			if (ReturnProp == nullptr) continue;
			if (ReturnProp->Struct->GetFName() != TEXT("FormatArgumentData")) continue;

			// Must have exactly three properties: AutoConvertedValue, Name, and ReturnValue.
			int PropCount = 0;
			for (TFieldIterator<FProperty> PropIt(Function); PropIt; ++PropIt) PropCount++;
			if (PropCount != 3) continue;

			Converters.Add(Function);
		}
	}
	for (UFunction* Func : Converters)
	{
		UE_LOG(LogLuprexIntegration, Display, TEXT("FormatData converter: %s::%s"), *Func->GetOuterUClass()->GetName(), *Func->GetName());
	}
}

UFunction* UlxFormatDataLibrary::GetConverterForPinType(const UEdGraphSchema_K2 *Schema, const FEdGraphPinType& PinType, bool AllowWild)
{
	// Special case: wildcard pins are unconnected pins.
	if ((PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard) && AllowWild)
	{
		return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataBlank));
	}

	// Scan the cached converter list for a matching type.
	UlxFormatDataLibrary* FormatDataLib = GEditor->GetEditorSubsystem<UlxFormatDataLibrary>();
	for (UFunction* Function : FormatDataLib->Converters)
	{
		FProperty* ValueProperty = Function->FindPropertyByName(TEXT("AutoConvertedValue"));
		FEdGraphPinType ValuePinType;
		if (!Schema->ConvertPropertyToPinType(ValueProperty, ValuePinType)) continue;
		if (!Schema->ArePinTypesEquivalent(PinType, ValuePinType)) continue;
		return Function;
	}

	// A general handler for Enums.
	if ((PinType.PinCategory == UEdGraphSchema_K2::PC_Byte) && (nullptr != Cast<const UEnum>(PinType.PinSubCategoryObject)))
	{
		return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataEnum));
	}

	// A case for subclasses of 'Object' which are not exactly 'Object'.
	if (PinType.PinCategory == UEdGraphSchema_K2::PC_Object)
	{
		return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataObject));
	}

	return nullptr;
}

ELogVerbosity::Type UlxFormatDataLibrary::ConvertElxFormatLogVerbosity(ElxFormatLogVerbosity Verbosity) {
	switch (Verbosity) {
		case ElxFormatLogVerbosity::Error:				return ELogVerbosity::Error;
		case ElxFormatLogVerbosity::Warning:				return ELogVerbosity::Warning;
		case ElxFormatLogVerbosity::Display:				return ELogVerbosity::Display;
		case ElxFormatLogVerbosity::Log:					return ELogVerbosity::Log;
		case ElxFormatLogVerbosity::ThrottledDisplay:	return ELogVerbosity::Display;
		case ElxFormatLogVerbosity::ThrottledLog:		return ELogVerbosity::Log;
		case ElxFormatLogVerbosity::Verbose:				return ELogVerbosity::Verbose;
		case ElxFormatLogVerbosity::VeryVerbose:			return ELogVerbosity::VeryVerbose;
		case ElxFormatLogVerbosity::Fatal:				return ELogVerbosity::Fatal;
	}
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataBool(bool AutoConvertedValue, const FString &Name)
{
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Text;
	Result.ArgumentName = Name;
	Result.ArgumentValue = UKismetTextLibrary::Conv_BoolToText(AutoConvertedValue);
	return Result;
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataByte(uint8 AutoConvertedValue, const FString &Name)
{
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Int;
	Result.ArgumentName = Name;
	Result.ArgumentValueInt = AutoConvertedValue;
	return Result;
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataInt(int AutoConvertedValue, const FString &Name)
{
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Int;
	Result.ArgumentName = Name;
	Result.ArgumentValueInt = AutoConvertedValue;
	return Result;
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataInt64(int64 AutoConvertedValue, const FString &Name)
{
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Int;
	Result.ArgumentName = Name;
	Result.ArgumentValueInt = AutoConvertedValue;
	return Result;
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataFloat(float AutoConvertedValue, const FString &Name)
{
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Float;
	Result.ArgumentName = Name;
	Result.ArgumentValueFloat = AutoConvertedValue;
	return Result;
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataDouble(double AutoConvertedValue, const FString &Name)
{
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Double;
	Result.ArgumentName = Name;
	Result.ArgumentValueDouble = AutoConvertedValue;
	return Result;
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataText(FText AutoConvertedValue, const FString &Name)
{
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Text;
	Result.ArgumentName = Name;
	Result.ArgumentValue = AutoConvertedValue;
	return Result;
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataString(FString AutoConvertedValue, const FString &Name)
{
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Text;
	Result.ArgumentName = Name;
	Result.ArgumentValue = UKismetTextLibrary::Conv_StringToText(AutoConvertedValue);
	return Result;
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataName(FName AutoConvertedValue, const FString &Name)
{
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Text;
	Result.ArgumentName = Name;
	Result.ArgumentValue = UKismetTextLibrary::Conv_NameToText(AutoConvertedValue);
	return Result;
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataKey(FKey AutoConvertedValue, const FString &Name)
{
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Text;
	Result.ArgumentName = Name;
	Result.ArgumentValue = UKismetTextLibrary::Conv_NameToText(AutoConvertedValue.GetFName());
	return Result;
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataGender(ETextGender AutoConvertedValue, const FString &Name)
{
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Gender;
	Result.ArgumentName = Name;
	Result.ArgumentValueGender = AutoConvertedValue;
	return Result;
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataObject(UObject *AutoConvertedValue, const FString &Name)
{
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Text;
	Result.ArgumentName = Name;
	Result.ArgumentValue = UKismetTextLibrary::Conv_ObjectToText(AutoConvertedValue);
	return Result;
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataVector(const FVector &AutoConvertedValue, const FString &Name)
{
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Text;
	Result.ArgumentName = Name;
	Result.ArgumentValue = UKismetTextLibrary::Conv_VectorToText(AutoConvertedValue);
	return Result;
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataVector2D(const FVector2D &AutoConvertedValue, const FString &Name)
{
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Text;
	Result.ArgumentName = Name;
	Result.ArgumentValue = UKismetTextLibrary::Conv_Vector2dToText(AutoConvertedValue);
	return Result;
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataRotator(const FRotator &AutoConvertedValue, const FString &Name)
{
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Text;
	Result.ArgumentName = Name;
	Result.ArgumentValue = UKismetTextLibrary::Conv_RotatorToText(AutoConvertedValue);
	return Result;
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataTransform(const FTransform &AutoConvertedValue, const FString &Name)
{
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Text;
	Result.ArgumentName = Name;
	Result.ArgumentValue = UKismetTextLibrary::Conv_TransformToText(AutoConvertedValue);
	return Result;
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataGeometry(const FGeometry &AutoConvertedValue, const FString &Name)
{
	FVector2D UL = AutoConvertedValue.GetAbsolutePosition();
	FVector2D LR = AutoConvertedValue.GetAbsolutePositionAtCoordinates(FVector2f(1.0f, 1.0f));
	if (GEngine && GEngine->GameViewport)
	{
		TSharedPtr<IGameLayerManager> GameLayerManager = GEngine->GameViewport->GetGameLayerManager();
		if (GameLayerManager.IsValid())
		{
			const FGeometry ViewportGeometry = GameLayerManager->GetViewportWidgetHostGeometry();
			const FVector2D ViewportLocalSize = FVector2D(ViewportGeometry.GetLocalSize());
			FVector2D ViewportPixelSize;
			GEngine->GameViewport->GetViewportSize(ViewportPixelSize);
			if (ViewportLocalSize.X > 0.0 && ViewportLocalSize.Y > 0.0)
			{
				const FVector2D PixelScale = ViewportPixelSize / ViewportLocalSize;
				UL = ViewportGeometry.AbsoluteToLocal(UL) * PixelScale;
				LR = ViewportGeometry.AbsoluteToLocal(LR) * PixelScale;
			}
		}
	}
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Text;
	Result.ArgumentName = Name;
	Result.ArgumentValue = FText::Format(
		INVTEXT("UL={0},{1} LR={2},{3}"),
		FText::AsNumber(UL.X), FText::AsNumber(UL.Y),
		FText::AsNumber(LR.X), FText::AsNumber(LR.Y));
	return Result;
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataAnchors(const FAnchors &AutoConvertedValue, const FString &Name)
{
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Text;
	Result.ArgumentName = Name;
	Result.ArgumentValue = FText::Format(
		INVTEXT("Anchors(Min={0},{1} Max={2},{3})"),
		FText::AsNumber(AutoConvertedValue.Minimum.X), FText::AsNumber(AutoConvertedValue.Minimum.Y),
		FText::AsNumber(AutoConvertedValue.Maximum.X), FText::AsNumber(AutoConvertedValue.Maximum.Y));
	return Result;
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataBlank(const FString &Name)
{
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Text;
	Result.ArgumentName = Name;
	Result.ArgumentValue = FText();
	return Result;
}

FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataEnum(uint8 Value, const FString &Name, const UObject *PinSubCategoryObject)
{
	const UEnum *Enum = Cast<const UEnum>(PinSubCategoryObject);
	FFormatArgumentData Result;
	if (Enum == nullptr)
	{
		Result.ArgumentValueType = EFormatArgumentType::Int;
		Result.ArgumentName = Name;
		Result.ArgumentValueInt = Value;
	}
	else
	{
		Result.ArgumentValueType = EFormatArgumentType::Text;
		Result.ArgumentName = Name;
		Result.ArgumentValue = FText::Format(INVTEXT("<{0}>"), Enum->GetDisplayNameTextByValue(Value));
	}
	return Result;
}

FText UlxFormatDataLibrary::FormatMessageInternal(const FString &InPattern, TArray<FFormatArgumentData> InArgs)
{
	FText InPatternText(FText::FromString(InPattern));
	return FTextFormatter::Format(MoveTemp(InPatternText), MoveTemp(InArgs), false, false);
}

void UlxFormatDataLibrary::FormatLogMessageInternal(UObject *Context, ElxFormatLogVerbosity Verbosity, const FString &InPattern, TArray<FFormatArgumentData> InArgs)
{
	// For throttled verbosity levels, suppress repeated messages with the
	// same format pattern.  We key on the blueprint name + format pattern,
	// and allow at most one message per second per key.
	//
	if ((Verbosity == ElxFormatLogVerbosity::ThrottledDisplay) || (Verbosity == ElxFormatLogVerbosity::ThrottledLog))
	{
		static TMap<FString, double> LastLogTime;
		double Now = FPlatformTime::Seconds();
		FString Key = Context->GetClass()->GetName() + TEXT("::") + InPattern;
		double &Last = LastLogTime.FindOrAdd(Key, 0.0);
		if (Now - Last < 2.0)
		{
			return;
		}
		Last = Now;
	}

	// Generate the formatted string.
	//
	FText Message = FormatMessageInternal(InPattern, MoveTemp(InArgs));
	FString MessageString = Message.ToString();

	// Get the blueprint name.
	//
	// Normally, the log function expects you to pass in a filename, and a log
	// category name.  We use the blueprint name for both.
	//
	// Using the blueprint name as a log category name is not technically
	// correct.  However, there is no correct way to create log categories
	// from inside of blueprints.  Doing it this way at least produces a reasonable
	// message inside the log.  What doesn't work correctly is the log message
	// suppression system.  Ie, console commands like 'log <category> verbose'
	// don't have any effect here.  The design of the log message suppression
	// system is such that there just is no reasonable way to hook into it from
	// inside of blueprints.
	//
	FString BlueprintNameString = Context->GetClass()->GetName();
	auto BlueprintNameAnsi = StringCast<ANSICHAR>(*BlueprintNameString);
	FLogCategoryName BlueprintNameLogCategory(Context->GetClass()->GetFName());

	// Output to Log
	//
	ELogVerbosity::Type VerbosityValue = ConvertElxFormatLogVerbosity(Verbosity);
	if (VerbosityValue <= ELogVerbosity::COMPILED_IN_MINIMUM_VERBOSITY)
	{
		FMsg::Logf(BlueprintNameAnsi.Get(), 0, BlueprintNameLogCategory, VerbosityValue, TEXT("%s"), *MessageString);
	}
}
