#pragma once

#include "CoreMinimal.h"
#include "MaterialTypes.h"
#include "WingBasics.h"
#include "WingParameterEditor.generated.h"

class UMaterialInterface;
class UMaterialInstanceConstant;
struct WingTokenizer;

USTRUCT()
struct FWingFontAndPage
{
	GENERATED_BODY()
public:
	UPROPERTY(EditAnywhere)
	UFont *Font;

	UPROPERTY(EditAnywhere)
	int32 Page;
};


// FWingParameterEditor is a tool for modifying material
// parameters in material instances.  Transferring values
// into UPROPERTY fields makes it possible to use GetText
// and SetText to parse them.
// 
USTRUCT()
struct FWingParameterEditor
{
	GENERATED_BODY()
private:
	UPROPERTY(EditAnywhere)
	float Scalar;

	UPROPERTY(EditAnywhere)
	FLinearColor Vector;

	UPROPERTY(EditAnywhere)
	FVector4d DoubleVector;

	UPROPERTY(EditAnywhere)
	UTexture *Texture;

	UPROPERTY(EditAnywhere)
	UTextureCollection *TextureCollection;

	UPROPERTY(EditAnywhere)
	FWingFontAndPage Font;

	UPROPERTY(EditAnywhere)
	URuntimeVirtualTexture *RuntimeVirtualTexture;

	UPROPERTY(EditAnywhere)
	USparseVolumeTexture *SparseVolumeTexture;

	UPROPERTY(EditAnywhere)
	bool StaticSwitch;

	UPROPERTY(EditAnywhere)
	FStaticComponentMaskValue StaticComponentMask;

public:
	// Info struct is a FMaterialParameterInfo plus a type field.
	struct Info : public FMaterialParameterInfo
	{
		EMaterialParameterType Type;

		Info() : FMaterialParameterInfo(), Type(EMaterialParameterType::None) {}
		Info(const FMaterialParameterInfo &I, EMaterialParameterType T) : FMaterialParameterInfo(I), Type(T) {}
		friend FORCEINLINE bool operator==(const Info& Lhs, const Info& Rhs)
		{
			return Lhs.Name.IsEqual(Rhs.Name) && Lhs.Association == Rhs.Association && Lhs.Index == Rhs.Index && Lhs.Type == Rhs.Type;
		}
		friend FORCEINLINE bool operator!=(const Info& Lhs, const Info& Rhs)
		{
			return !operator==(Lhs, Rhs);
		}
		friend FORCEINLINE uint32 GetTypeHash(const Info& Value)
		{
			return HashCombine(HashCombine(HashCombine(GetTypeHash(NameToScriptName(Value.Name)), Value.Index), (uint32)Value.Association), (uint32)Value.Type);
		}
	};

	using Metadata = FMaterialParameterMetadata;
	using InfoMetaMap = TMap<Info, Metadata>;

	// Convert a type to a name.
	//
	static FName TypeToName(EMaterialParameterType Type);

	// Convert a name to a type, if possible.
	//
	static bool NameToType(FName Name, EMaterialParameterType &Result, WingOut Errors);

	// Get all the material parameters of the specified material including
	// both parameters that have been overridden and those that haven't.
	//
	static InfoMetaMap GetMaterialParameters(UMaterialInterface* Material);

	// Find a material parameter in the InfoMap.
	// If it doesn't exist, or report an error and return nullptr.
	//
	static Metadata *FindParameter(InfoMetaMap &Map, const Info &ID, WingOut Errors);

		// If the material uses custom primitive data, print an error
	// and return false.
	//
	static bool CheckNoCustomPrimitiveData(const Info &ID, const Metadata &Data, WingOut Errors);

	// Convert an ID in string form into an ID in info form.
	//
	static bool ParseID(const FString& Str, Info& ID, WingOut Errors);

	// Convert an ID in Info form into an ID in string form.
	//
	static FString StringID(const Info& ID);

	// Add a material parameter override to a material instance.
	//
	static bool AddOverride(
		const Info &ID, UMaterialInstanceConstant *MI, const FString &StrVal, WingOut Errors);

	// Remove a material parameter override from a material instance.
	// If there is no override, print an error and return false.
	//
	static bool RemoveOverride(const Info &ID, UMaterialInstanceConstant *MI, WingOut Errors);

	// Print out a material parameter with ID.
	//
	static void Print(const Info &Info, const Metadata &Meta);

	// Print out a map full of material parameters.  If Headings is
	// true, the parameters are grouped by inherited/overridden.
	//
	static void PrintAll(const InfoMetaMap &Params, bool Headings);

private:
	using GetterFunc = TFunction<FMaterialParameterValue(FWingParameterEditor &E)>;
	using SetterFunc = TFunction<void(FWingParameterEditor &E, const FMaterialParameterValue &V)>;
	
	struct GetterAndSetter
	{
		FProperty *Property;
		GetterFunc Getter;
		SetterFunc Setter;

		GetterAndSetter(FName N, GetterFunc G, SetterFunc S);
	};

	using GetterAndSetterMap = TMap<EMaterialParameterType, GetterAndSetter>;

	static void Add(GetterAndSetterMap &M,
			EMaterialParameterType Type, FName Name, GetterFunc G, SetterFunc S);
	
	static GetterAndSetterMap CalcGetterAndSetterMap();
	static const GetterAndSetterMap &GetGetterAndSetterMap();
	
private:
	static void ReportBadToken(WingTokenizer &Tok, const TCHAR *Expected, WingOut Errors);
	static bool ReportBadType(EMaterialParameterType Type, WingOut Errors);
};
