#include "WingParameterEditor.h"
#include "Materials/MaterialInstanceConstant.h"
#include "WingUtils.h"
#include "WingTokenizer.h"
#include "WingProperty.h"
#include "WingServer.h"
#include "Engine/Font.h"
#include "Engine/TextureCollection.h"
#include "Misc/DefaultValueHelper.h"
#include "SparseVolumeTexture/SparseVolumeTexture.h"
#include "VT/RuntimeVirtualTexture.h"

FWingParameterEditor::GetterAndSetter::GetterAndSetter(FName N, GetterFunc G, SetterFunc S)
{
	Property = FWingParameterEditor::StaticStruct()->FindPropertyByName(N);
	Getter = G;
	Setter = S;
}

FWingParameterEditor::GetterAndSetterMap FWingParameterEditor::CalcGetterAndSetterMap()
{
	using Editor = FWingParameterEditor;
	using Value = FMaterialParameterValue;
	using Type = EMaterialParameterType;

	GetterAndSetterMap M;
	
	M.Add(Type::Scalar, GetterAndSetter(
		GET_MEMBER_NAME_CHECKED(FWingParameterEditor, Scalar),
		[](Editor &E)->Value { return Value(E.Scalar); },
		[](Editor &E, const Value &V) { E.Scalar = V.AsScalar(); }));

	M.Add(Type::Vector, GetterAndSetter(
		GET_MEMBER_NAME_CHECKED(FWingParameterEditor, Vector),
		[](Editor &E)->Value { return Value(E.Vector); },
		[](Editor &E, const Value &V) { E.Vector = V.AsLinearColor(); }));

	M.Add(Type::DoubleVector, GetterAndSetter(
		GET_MEMBER_NAME_CHECKED(FWingParameterEditor, DoubleVector),
		[](Editor &E)->Value { return Value(E.DoubleVector); },
		[](Editor &E, const Value &V) { E.DoubleVector = V.AsVector4d(); }));

	M.Add(Type::Texture, GetterAndSetter(
		GET_MEMBER_NAME_CHECKED(FWingParameterEditor, Texture),
		[](Editor &E)->Value { return Value(E.Texture); },
		[](Editor &E, const Value &V) { E.Texture = V.Texture; }));

	M.Add(Type::TextureCollection, GetterAndSetter(
		GET_MEMBER_NAME_CHECKED(FWingParameterEditor, TextureCollection),
		[](Editor &E)->Value { return Value(E.TextureCollection); },
		[](Editor &E, const Value &V) { E.TextureCollection = V.TextureCollection; }));

	M.Add(Type::Font, GetterAndSetter(
		GET_MEMBER_NAME_CHECKED(FWingParameterEditor, Font),
		[](Editor &E)->Value { return Value(E.Font.Font, E.Font.Page); },
		[](Editor &E, const Value &V) { E.Font.Font = V.Font.Value; E.Font.Page = V.Font.Page; }));

	M.Add(Type::RuntimeVirtualTexture, GetterAndSetter(
		GET_MEMBER_NAME_CHECKED(FWingParameterEditor, RuntimeVirtualTexture),
		[](Editor &E)->Value { return Value(E.RuntimeVirtualTexture); },
		[](Editor &E, const Value &V) { E.RuntimeVirtualTexture = V.RuntimeVirtualTexture; }));

	M.Add(Type::SparseVolumeTexture, GetterAndSetter(
		GET_MEMBER_NAME_CHECKED(FWingParameterEditor, SparseVolumeTexture),
		[](Editor &E)->Value { return Value(E.SparseVolumeTexture); },
		[](Editor &E, const Value &V) { E.SparseVolumeTexture = V.SparseVolumeTexture; }));

	M.Add(Type::StaticSwitch, GetterAndSetter(
		GET_MEMBER_NAME_CHECKED(FWingParameterEditor, StaticSwitch),
		[](Editor &E)->Value { return Value(E.StaticSwitch); },
		[](Editor &E, const Value &V) { E.StaticSwitch = V.AsStaticSwitch(); }));

	M.Add(Type::StaticComponentMask, GetterAndSetter(
		GET_MEMBER_NAME_CHECKED(FWingParameterEditor, StaticComponentMask),
		[](Editor &E)->Value { return Value(E.StaticComponentMask); },
		[](Editor &E, const Value &V) { E.StaticComponentMask = V.AsStaticComponentMask(); }));

	return M;
}

const FWingParameterEditor::GetterAndSetterMap &FWingParameterEditor::GetGetterAndSetterMap()
{
	static GetterAndSetterMap TheMap = CalcGetterAndSetterMap();
	return TheMap;
}

FName FWingParameterEditor::TypeToName(EMaterialParameterType Type)
{
	const GetterAndSetter *GS = GetGetterAndSetterMap().Find(Type);
	if (GS == nullptr) return FName(TEXT("unknown"));
	return GS->Property->GetFName();
}

bool FWingParameterEditor::NameToType(FName Name, EMaterialParameterType &Result, WingOut Errors)
{
	for (const auto &Pair : GetGetterAndSetterMap())
	{
		if (Pair.Value.Property->GetName() == Name)
		{
			Result = Pair.Key; return true;
		}
	}
	Errors.Printf(TEXT("ERROR: '%s' is not a valid EMaterialParameterType\n"), *Name.ToString());
	Result = EMaterialParameterType::None;
	return false;
}

FWingParameterEditor::InfoMetaMap FWingParameterEditor::GetMaterialParameters(UMaterialInterface* Material)
{
	InfoMetaMap Result;
	if (!Material) return Result;
	for (int I = 0; I < int(EMaterialParameterType::Num); I++)
	{
		EMaterialParameterType T = (EMaterialParameterType)I;
		TMap<FMaterialParameterInfo, Metadata> Temp;
		Material->GetAllParametersOfType(T, Temp);
		for (const auto &KV : Temp)
		{
			check(KV.Value.Value.Type == T);
			Result.Add(Info(KV.Key, T), KV.Value);
		}
	}
	return Result;
}

bool FWingParameterEditor::ReportBadType(EMaterialParameterType Type, WingOut Errors)
{
	Errors.Printf(TEXT("Material parameter type #%d is not a valid type.\n"), int(Type));
	return false;
}

void FWingParameterEditor::ReportBadToken(WingTokenizer &Tok, const TCHAR *Expected, WingOut Errors)
{
	FString Whole = Tok.GetRange(FName(), 1000);
	Tok.SaveCursor(FName());
	FString Next = Tok.GetRange(FName(), 1);
	Errors.Printf(TEXT("Parsing %s, near %s: expected %s"), *Whole, *Next, Expected);
}

FString FWingParameterEditor::StringID(const Info &ID)
{
	FString TypeName = TypeToName(ID.Type).ToString();
	FString Ext = WingUtils::ExternalizeID(ID.Name);
	if (ID.Association != EMaterialParameterAssociation::GlobalParameter)
	{
		const TCHAR *Prefix = TEXT("Layer");
		if (ID.Association == EMaterialParameterAssociation::BlendParameter)
			Prefix = TEXT("Blend");
		return FString::Printf(TEXT("%s:%s:%d:%s"), *TypeName, Prefix, ID.Index, *Ext);
	}
	else
	{
		return FString::Printf(TEXT("%s:%s"), *TypeName, *Ext);
	}
}

bool FWingParameterEditor::ParseID(const FString& IDString, Info& ID, WingOut Errors)
{
	WingTokenizer Tok(IDString);

	ID.Type =  EMaterialParameterType::None;
	ID.Association = EMaterialParameterAssociation::GlobalParameter;
	ID.Index = INDEX_NONE;
	ID.Name = FName();

	// Parse the type.
	const TCHAR *GoodTypes = TEXT("Scalar, Vector, DoubleVector, Texture, TextureCollection, Font, "
		"RuntimeVirtualTexture, SparseVolumeTexture, StaticSwitch, or StaticComponentMask");
	if (!Tok.TokenIs(Tok.Identifier))
		{ ReportBadToken(Tok, GoodTypes, Errors); return false; }
	EMaterialParameterType TypeValue;
	if (!NameToType(Tok.NextName(), TypeValue, Errors)) return false;
	if (TypeValue >= EMaterialParameterType::Num)
		{ ReportBadToken(Tok, GoodTypes, Errors); return false; }
	ID.Type = TypeValue;
	Tok.Advance();

	// A colon after the type.
	if (!Tok.TokenIs(':')) { ReportBadToken(Tok, TEXT("colon"), Errors); return false; }
	Tok.Advance();

	// Parse the Layer or blend prefix.
	if (Tok.TokenIs(TEXT("Layer"), ':') || Tok.TokenIs(TEXT("Blend"), ':'))
	{
		if (Tok.NextName() == TEXT("Layer"))
			ID.Association = EMaterialParameterAssociation::LayerParameter;
		else
			ID.Association = EMaterialParameterAssociation::BlendParameter;
		Tok.Advance();
		Tok.Advance();

		if ((!Tok.TokenIs(Tok.Identifier)) ||
			(!LexTryParseString(ID.Index, *Tok.NextName().ToString())))
			{ ReportBadToken(Tok, TEXT("integer"), Errors); return false; }
		Tok.Advance();

		if ((ID.Index < 0)||(ID.Index > 50))
		{
			Errors.Printf(TEXT("Layer/Blend index outside of reasonable range\n"));
			return false;
		}

		if (!Tok.TokenIs(':'))
			{ ReportBadToken(Tok, TEXT("colon"), Errors); return false; }
		Tok.Advance();
	}

	// Parse the parameter name
	if (!Tok.TokenIs(Tok.Identifier))
		{ ReportBadToken(Tok, TEXT("parameter name"), Errors); return false; }
	ID.Name = Tok.NextName();
	Tok.Advance();

	if (!Tok.TokenIs(0))
		{ ReportBadToken(Tok, TEXT("end of input"), Errors); return false; }
	return true;
}

FWingParameterEditor::Metadata *FWingParameterEditor::FindParameter(
		InfoMetaMap &Map, const Info &ID, WingOut Errors)
{
	Metadata* Found = Map.Find(ID);
	if (!Found)
	{
		Errors.Printf(TEXT("No parameter '%s' in this material"), *StringID(ID));
		return nullptr;
	}
	return Found;
}

bool FWingParameterEditor::CheckNoCustomPrimitiveData(
	const Info &ID, const Metadata &Data, WingOut Errors)
{
	if (Data.PrimitiveDataIndex != INDEX_NONE)
	{
		Errors.Printf(
			TEXT("Parameter '%s' uses custom primitive data, which cannot be set on a material instance"),
			 	*StringID(ID));
		return false;
	}
	return true;
}

bool FWingParameterEditor::AddOverride(
	const Info &ID, UMaterialInstanceConstant *MI, const FString &StrVal, WingOut Errors)
{
	// Verify that the parameter type is valid.
	const GetterAndSetter *GS = GetGetterAndSetterMap().Find(ID.Type);
	if (GS == nullptr) return ReportBadType(ID.Type, Errors);

	// Find the existing parameter.
	TMap<FMaterialParameterInfo, FMaterialParameterMetadata> Lookup;
	MI->GetAllParametersOfType(ID.Type, Lookup);
	FMaterialParameterMetadata *Meta = Lookup.Find(ID);
	if (Meta == nullptr)
	{
		Errors.Printf(TEXT("No parameter named %s to override.\n"), *StringID(ID));
		return false;
	}
	check(Meta->Value.Type == ID.Type);

	// If the parameter involves the use of custom primitive data, we
	// cannot meaningfully override it.
	if (!CheckNoCustomPrimitiveData(ID, *Meta, Errors)) return false;
	
	// Parse the value string.
	FWingParameterEditor Editor;
	if (!FWingProperty(nullptr, &Editor, GS->Property, true).SetText(StrVal, Errors)) return false;
	Meta->Value = GS->Getter(Editor);

	// Apply the update.
	FMaterialInstanceParameterUpdateContext Update(MI);
	Update.SetParameterValueEditorOnly(ID, *Meta);
	return true;
}

bool FWingParameterEditor::RemoveOverride(
		const Info &ID, UMaterialInstanceConstant *MI, WingOut Errors)
{
	InfoMetaMap AllParams = GetMaterialParameters(MI);
	Metadata *Target = FindParameter(AllParams, ID, Errors);
	if (Target == nullptr) return false;
	if (!Target->bOverride)
	{
		Errors.Printf(TEXT("Parameter '%s' is inherited and not overridden"), *StringID(ID));
		return false;
	}

	FMaterialInstanceParameterUpdateContext Update(MI, EMaterialInstanceClearParameterFlag::All);
	for (const auto &[Info, Meta] : AllParams)
	{
		if (!Meta.bOverride) continue;
		if (Info == ID) continue;
		Update.SetParameterValueEditorOnly(Info, Meta);
	}
	return true;
}


void FWingParameterEditor::Print(const Info &ID, const Metadata &Meta)
{
	const GetterAndSetter *GS = GetGetterAndSetterMap().Find(ID.Type);
	if ((GS == nullptr) || (ID.Type != Meta.Value.Type))
	{
		WingOut::Stdout.Printf(TEXT("    corrupted parameter: %s\n"),
				*StringID(ID));
		return;
	}
	FWingParameterEditor Editor;
	GS->Setter(Editor, Meta.Value);
	FString StrVal = FWingProperty(nullptr, &Editor, GS->Property, false).GetText();
	WingOut::Stdout.Printf(TEXT("    %s %s\n"),
			*StringID(ID), *StrVal);
}

void FWingParameterEditor::PrintAll(const InfoMetaMap &Params, bool Headings)
{
	// Overridden parameters first.
	bool bHasOverrides = false;
	for (auto& [Info, Meta] : Params)
	{
		if (!Meta.bOverride) continue;
		if (Headings && !bHasOverrides) 
			{ WingOut::Stdout.Print(TEXT("\nOverridden Parameters:\n")); bHasOverrides = true; }
		FWingParameterEditor::Print(Info, Meta);
	}

	// Inherited (non-overridden) parameters.
	bool bHasInherited = false;
	for (auto& [Info, Meta] : Params)
	{
		if (Meta.bOverride) continue;
		if (Headings && !bHasInherited)
			{ WingOut::Stdout.Print(TEXT("\nInherited Parameters (not overridden):\n")); bHasInherited = true; }
		FWingParameterEditor::Print(Info, Meta);
	}
}
