
#include "AnimQueue.h"
#include "Common.h"
#include "StreamBuffer.h"
#include "UtilityLibrary.h"
#include "GameFramework/Actor.h"
#include "Components/MeshComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "AssetLookup.h"
#include "Materials/MaterialInstanceDynamic.h"
#include <iostream>


////////////////////////////////////////////////////////////
//
// An animation step that doesn't actually store the step,
// it just contains a pointer to the string.
//
////////////////////////////////////////////////////////////

struct FlxAnimationStepView {
	int64 Hash;
	std::string_view Body;

	FlxAnimationStepView() : Hash(0), Body("") {}
	FlxAnimationStepView(int64 h, std::string_view b) : Hash(h), Body(b) {}
};

////////////////////////////////////////////////////////////
//
// FlxAnimationField
//
// A single field from an animation step: a variable name, a
// persistent flag, a type, and value storage.
// 
// IMPORTANT: Stores a string_view, not a string, so the lifetime
// of this field is only as long as the step you're parsing.
//
// Boolean values are stored in X as 1 or 0. Double values
// are stored in X.
//
////////////////////////////////////////////////////////////

struct FlxAnimationField {
	std::string_view Name;
	bool Persistent;
	LuaValueType Type;
	double X, Y, Z;
	std::string_view S;
};

////////////////////////////////////////////////////////////
//
// FlxAnimationStepDecoder
//
// Stream reader for a single animation step. Reads one
// FlxAnimationField at a time until EOF.
//
////////////////////////////////////////////////////////////

class FlxAnimationStepDecoder {
private:
	FlxStreamBuffer Decoder;

public:
	// Initialize from an encoded step body.
	//
	FlxAnimationStepDecoder(std::string_view body) : Decoder(body) {}

	// Return true if the parser has reached EOF.
	//
	bool AtEOF() { return Decoder.empty(); }

	// Read one field.
	//
	FlxAnimationField ReadField();

	// Convert an animation step to a debug string.
	//
	static FString DebugString(bool finished, int64 hash, std::string_view body);
};

////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////
//
// FlxAnimQueueDecoder
//
// Stream reader for animation queues.  Reads one
// FlxAnimationStepView at a time until EOF.
//
////////////////////////////////////////////////////////////

class FlxAnimQueueDecoder {
private:
	FlxStreamBuffer Decoder;

	// Read from the header immediately on
	// construction.
	//
	int SizeLimit;
	int ActualSize;

public:
	// Initialize with an encoded animation queue.
	//
	FlxAnimQueueDecoder(std::string_view s);

	// Get the size limit of the animation queue.
	//
	int GetSizeLimit() const { return SizeLimit; }

	// Get the actual size of the animation queue.
	//
	int GetActualSize() const { return ActualSize; }

	// Return true if the parser has reached EOF.
	//
	bool AtEOF() { return Decoder.empty(); }

	// Read one animation step.
	//
	FlxAnimationStepView ReadStep();

	// Peek at the hash of the next step.
	//
	int64 PeekHash();

	// Convert an AnimQueue to an FString.
	//
	// static FString DebugString(std::string_view s);
};

FlxAnimationStep::FlxAnimationStep(int64 hash, std::string_view body) {
	Finished = false;
	Hash = hash;
	Body.SetNum(body.size());
	memcpy(Body.GetData(), body.data(), body.size());
	Blueprint = UlxAnimationStepLibrary::AnimationStepGetString(*this, "bp");
}

static void ClearProperties(const FString& prefix, UObject* obj, FProperty *except) {
	UClass* uclass = obj->GetClass();
	FName prefixlo(prefix);
	FName prefixhi(prefix + TEXT("\xFFFF"));
	for (TFieldIterator<FProperty> It(uclass); It; ++It)
	{
		FProperty* fprop = *It;
		if (fprop == except) continue;
		bool match1 = (fprop->GetFName().Compare(prefixlo) > 0);
		bool match2 = (fprop->GetFName().Compare(prefixhi) < 0);
		if (match1 && match2) {
			uint8* pptr = fprop->ContainerPtrToValuePtr<uint8>(obj);
			fprop->ClearValue(pptr);
		}
	}
}

#pragma optimize("", off)

static bool SetProperty(const FString& prefix, UObject* obj, const FlxAnimationField& field) {
	UClass* uclass = obj->GetClass();
	FString sname(field.Name.size(), (const UTF8CHAR*)field.Name.data());
	FName nname(prefix + sname);
	switch (field.Type) {
		case LuaValueType::STRING: {
			FStrProperty* fprop = FindFProperty<FStrProperty>(uclass, nname);
			if (fprop == nullptr) return false;
			FString* pptr = fprop->ContainerPtrToValuePtr<FString>(obj);
			*pptr = FString(field.S.size(), (const UTF8CHAR*)field.S.data());
			return true;
		}
		case LuaValueType::TOKEN: {
			FNameProperty* fprop = FindFProperty<FNameProperty>(uclass, nname);
			if (fprop == nullptr) return false;
			FName* pptr = fprop->ContainerPtrToValuePtr<FName>(obj);
			*pptr = FName(field.S.size(), (const UTF8CHAR*)field.S.data(), FNAME_Add);
			return true;
		}
		case LuaValueType::NUMBER: {
			FDoubleProperty* fprop = FindFProperty<FDoubleProperty>(uclass, nname);
			if (fprop == nullptr) return false;
			double* pptr = fprop->ContainerPtrToValuePtr<double>(obj);
			*pptr = field.X;
			return true;
		}
		case LuaValueType::BOOLEAN: {
			FBoolProperty* fprop = FindFProperty<FBoolProperty>(uclass, nname);
			if (fprop == nullptr) return false;
			uint8* pptr = fprop->ContainerPtrToValuePtr<uint8>(obj);
			fprop->SetPropertyValue(pptr, (field.X == 1.0));
			return true;
		}
		case LuaValueType::VECTOR: {
			FStructProperty* fprop = FindFProperty<FStructProperty>(uclass, nname);
			if (fprop == nullptr) return false;
			if (fprop->Struct != TBaseStructure<FVector>::Get()) return false;
			FVector* pptr = fprop->ContainerPtrToValuePtr<FVector>(obj);
			*pptr = FVector(field.X, field.Y, field.Z);
			return true;
		}
	}
	return false;
}

static FStructProperty *FindAnimationStepProperty(UClass *uclass, const FString &prefix) {
	FName nname(prefix + TEXT("Animation Step"));
	FStructProperty* fprop = FindFProperty<FStructProperty>(uclass, nname);
	if (fprop == nullptr) return nullptr;
	if (fprop->Struct != TBaseStructure<FlxAnimationStep>::Get()) return nullptr;
	return fprop;
}

static FInt64Property *FindAnimationIDProperty(UClass *uclass, const FString &prefix) {
	FName nname(prefix + TEXT("Animation ID"));
	FInt64Property* fprop = FindFProperty<FInt64Property>(uclass, nname);
	return fprop;
}

void UlxAnimationStepLibrary::UnpackAnimationStep(bool &bChanged, FString &Action, const FlxAnimationStep& step, UObject* target, const FString& prefix) {
	bChanged = false;
	Action = TEXT("");

	if (prefix.IsEmpty())
	{
		UE_LOG(LogBlueprint, Error, TEXT("UnpackAnimationStep: You may not pass an empty string for prefix"));
		return;
	}

	UClass* uclass = target->GetClass();
	std::string_view body((const char*)(step.Body.GetData()), step.Body.Num());
	FlxAnimationStepDecoder decoder(body);

	FStructProperty* stepproperty = FindAnimationStepProperty(uclass, prefix);
	if (stepproperty == nullptr) {
		UE_LOG(LogBlueprint, Error, TEXT("UnpackAnimationStep: Target object does not have a variable named: '%s Animation Step'"), *prefix);
		return;
	}

	FInt64Property* idproperty = FindAnimationIDProperty(uclass, prefix);
	if (idproperty == nullptr) {
		UE_LOG(LogBlueprint, Error, TEXT("UnpackAnimationStep: Target object does not have a variable named: '%s Animation ID'"), *prefix);
		return;
	}

	FlxAnimationStep* stepstorage = stepproperty->ContainerPtrToValuePtr<FlxAnimationStep>(target);
	int64 oldhash = stepstorage->Hash;

	FlxAnimationField actionfield;
	actionfield.Name = "action";
	actionfield.Persistent = false;
	actionfield.Type = LuaValueType::STRING;
	actionfield.S = "unknown";

	// Decode everything.  If an action field is found, save it for later.
	ClearProperties(prefix, target, stepproperty);
	while (!decoder.AtEOF()) {
		FlxAnimationField field = decoder.ReadField();
		if ((field.Type == LuaValueType::STRING) && (field.Name == "action")) {
			actionfield.S = field.S;
			continue;
		}
		if (step.Finished && !field.Persistent) continue;
		SetProperty(prefix, target, field);
	}

	// Store the action field.
	if (step.Finished) actionfield.S = "idle";
	SetProperty(prefix, target, actionfield);

	// Store the whole step.
	*stepstorage = step;
	
	// Store the ID
	int64 *idstorage = idproperty->ContainerPtrToValuePtr<int64>(target);
	*idstorage = step.Hash;

	// Return the correct values.
	Action = FString(actionfield.S.size(), (const UTF8CHAR*)actionfield.S.data());
	bChanged = (step.Hash != oldhash);
}

FString UlxAnimationStepLibrary::AnimationStepDebugString(const FlxAnimationStep& step) {
	std::string_view body((const char*)(step.Body.GetData()), step.Body.Num());
	return FlxAnimationStepDecoder::DebugString(step.Finished, step.Hash, body);
}

static FlxAnimationField FindAnimationFieldLL(const FlxAnimationStep& step, std::string_view name) {
	std::string_view body((const char*)(step.Body.GetData()), step.Body.Num());
	FlxAnimationStepDecoder decoder(body);
	FlxAnimationField result;
	while (!decoder.AtEOF()) {
		result = decoder.ReadField();
		if (result.Name == name) {
			return result;
		}
	}
	result.Type = LuaValueType::UNINITIALIZED;
	return result;
}

static FlxAnimationField FindAnimationField(const FlxAnimationStep& step, const FString& name) {
	FTCHARToUTF8 utf8name(name);
	std::string_view uname(utf8name.Get(), utf8name.Length());
	return FindAnimationFieldLL(step, uname);
}

void FlxAnimationStep::AutoUpdateXYZ(AActor *actor) const {
	FlxAnimationField xyz = FindAnimationFieldLL(*this, "xyz");
	if (xyz.Type == LuaValueType::VECTOR) {
		actor->SetActorLocation(FVector(xyz.X, xyz.Y, xyz.Z));
	}
}

void FlxAnimationStep::AutoUpdateFacing(AActor *actor) const {
	FlxAnimationField facing = FindAnimationFieldLL(*this, "facing");
	if (facing.Type == LuaValueType::NUMBER) {
		actor->SetActorRotation(FRotator(0, facing.X, 0));
	}
}

void FlxAnimationStep::AutoUpdatePlane(FName *planep) const {
	FlxAnimationField plane = FindAnimationFieldLL(*this, "plane");
	if (plane.Type == LuaValueType::STRING) {
		FString pname(plane.S.size(), (const UTF8CHAR*)(plane.S.data()));
		*planep = FName(pname);
	}
}

bool UlxAnimationStepLibrary::AnimationStepEqual(const FlxAnimationStep &stepa, const FlxAnimationStep &stepb) {
	return (stepa.Finished == stepb.Finished) && (stepa.Hash == stepb.Hash);
}

bool UlxAnimationStepLibrary::AnimationStepIsIdle(const FlxAnimationStep &step) {
	return step.Finished;
}

FVector UlxAnimationStepLibrary::AnimationStepGetVector(const FlxAnimationStep& step, const FString& name) {
	FlxAnimationField field = FindAnimationField(step, name);
	if (field.Type != LuaValueType::VECTOR) return FVector(0, 0, 0);
	return FVector(field.X, field.Y, field.Z);
}

double UlxAnimationStepLibrary::AnimationStepGetFloat(const FlxAnimationStep& step, const FString& name) {
	FlxAnimationField field = FindAnimationField(step, name);
	if (field.Type != LuaValueType::NUMBER) return 0.0;
	return field.X;
}

FString UlxAnimationStepLibrary::AnimationStepGetString(const FlxAnimationStep& step, const FString& name) {
	if (step.Finished && (name == TEXT("action"))) {
		return TEXT("idle");
	}
	FlxAnimationField field = FindAnimationField(step, name);
	if (field.Type != LuaValueType::STRING) return TEXT("");
	return FString(field.S.size(), (const UTF8CHAR*)(field.S.data()));
}

FName UlxAnimationStepLibrary::AnimationStepGetName(const FlxAnimationStep& step, const FString& name) {
	FlxAnimationField field = FindAnimationField(step, name);
	if (field.Type != LuaValueType::TOKEN) return FName();
	return FName(field.S.size(), (const UTF8CHAR*)(field.S.data()), FNAME_Add);
}

bool UlxAnimationStepLibrary::AnimationStepGetBool(const FlxAnimationStep& step, const FString& name) {
	FlxAnimationField field = FindAnimationField(step, name);
	if (field.Type != LuaValueType::BOOLEAN) return false;
	return field.X == 1.0;
}


FlxAnimationField FlxAnimationStepDecoder::ReadField() {
	FlxAnimationField result;
	result.Name = Decoder.read_string_view();
	result.Persistent = Decoder.read_bool();
	result.Type = (LuaValueType)Decoder.read_uint8();
	switch (result.Type) {
		case LuaValueType::STRING: {
			result.S = Decoder.read_string_view();
			break;
		}
		case LuaValueType::TOKEN: {
			result.S = Decoder.read_string_view();
			break;
		}
		case LuaValueType::NUMBER: {
			result.X = Decoder.read_double();
			break;
		}
		case LuaValueType::BOOLEAN: {
			result.X = Decoder.read_bool() ? 1.0 : 0.0;
			break;
		}
		case LuaValueType::VECTOR: {
			result.X = Decoder.read_double();
			result.Y = Decoder.read_double();
			result.Z = Decoder.read_double();
			break;
		}
		default: {
			Decoder.read_bytes(Decoder.fill());
			result.Type = LuaValueType::UNINITIALIZED;
			break;
		}
	}
	return result;
}

FString FlxAnimationStepDecoder::DebugString(bool finished, int64 hash, std::string_view body) {
	FString result;
	FlxAnimationStepDecoder decoder(body);
	result.Appendf(TEXT("Hash=%016llx"), hash);
	if (finished) {
		result.Appendf(TEXT(" finished"));
	}
	while (!decoder.AtEOF()) {
		FlxAnimationField field = decoder.ReadField();
		result.Append(TEXT(" "));
		result.Append(FString(field.Name.size(), (const UTF8CHAR*)field.Name.data()));
		result.Append(field.Persistent ? TEXT("=") : TEXT(":"));
		switch (field.Type) {
		case LuaValueType::STRING:
			result.Append(FString(field.S.size(), (const UTF8CHAR*)field.S.data()));
			break;
		case LuaValueType::TOKEN:
			result.Appendf(TEXT("[%s]"), *FString(field.S.size(), (const UTF8CHAR*)field.S.data()));
			break;
		case LuaValueType::NUMBER:
			result.Appendf(TEXT("%lf"), field.X);
			break;
		case LuaValueType::BOOLEAN:
			result.Append((field.X) == 1.0 ? TEXT("true") : TEXT("false"));
			break;
		case LuaValueType::VECTOR:
			result.Appendf(TEXT("%lf,%lf,%lf"), field.X, field.Y, field.Z);
			break;
		}
	}
	return result;
}

FlxAnimationStepView FlxAnimQueueDecoder::ReadStep() {
	FlxAnimationStepView result;
	result.Hash = Decoder.read_int64();
	result.Body = Decoder.read_string_view();
	return result;
}

int64 FlxAnimQueueDecoder::PeekHash() {
	int64_t read_count = Decoder.total_reads();
	int64 result = Decoder.read_int64();
	Decoder.unread_to(read_count);
	return result;
}

FlxAnimQueueDecoder::FlxAnimQueueDecoder(std::string_view queue) : Decoder(queue) {
	SizeLimit = Decoder.read_uint8();
	ActualSize = Decoder.read_uint8();
}

// FString FlxAnimQueueDecoder::DebugString(std::string_view queue) {
// 	FString result;
// 	FlxAnimQueueDecoder decoder(queue);
// 	while (!decoder.AtEOF()) {
// 		FlxAnimationStepView step = decoder.ReadStep();
// 		FString stepdebug = FlxAnimationStepDecoder::DebugString(false, step.Hash, step.Body);
// 		result.Appendf(TEXT("%s\n"), *stepdebug);
// 	}
// 	return result;
// }

FlxAnimTracker::FlxAnimTracker() {
	Clear();
}

void FlxAnimTracker::Clear() {
	AQ.Empty();
	Changed = true;
}

void FlxAnimTracker::FinishedAnimation(int64 hash) {
	for (int i = 0; i < AQ.Num(); i++) {
		if (AQ[i].Hash == hash) {
			AQ[i].Finished = true;
			Changed = true;
		}
	}
}

bool FlxAnimTracker::IsFinished(int64 hash) {
	for (int i = 0; i < AQ.Num(); i++) {
		if (AQ[i].Hash == hash) {
			return AQ[i].Finished;
		}
	}
	return true;
}

void FlxAnimTracker::SkipToEnd() {
	for (int i = 0; i < AQ.Num(); i++) {
		if (!AQ[i].Finished) {
			AQ[i].Finished = true;
			Changed = true;
		}
	}
}

TArray<int64> FlxAnimTracker::GetHashes()
{
	TArray<int64> Result;
	Result.SetNum(AQ.Num());
	for (int i = 0; i < AQ.Num(); i++)
	{
		Result[i] = AQ[i].Hash;
	}
	return Result;
}

const FlxAnimationStep *FlxAnimTracker::FirstUnfinished() const
{
	for (int i = 0; i < AQ.Num(); i++) {
		if (!AQ[i].Finished) return &AQ[i];
	}
	return nullptr;
}

const FlxAnimationStep *FlxAnimTracker::LastFinished() const
{
	for (int i = AQ.Num() - 1; i >= 0; i--) {
		if (AQ[i].Finished) return &AQ[i];
	}
	return nullptr;
}

const FlxAnimationStep *FlxAnimTracker::FindAnimation(int64 hash) const
{
	for (int i = 0; i < AQ.Num(); i++) {
		if (AQ[i].Hash == hash) return &AQ[i];
	}
	return nullptr;
}

void FlxAnimTracker::Update(std::string_view encqueue) {
	// An empty encqueue represents a blank queue: one step with hash=0, empty body.
	//
	if (encqueue.empty()) {
		if (AQ.Num() == 1 && AQ[0].Hash == 0) return;
		Changed = true;
		AQ.Empty();
		AQ.Emplace(0, std::string_view());
		return;
	}

	// If the first hash matches, we don't bother updating at all.
	//
	FlxAnimQueueDecoder decoder(encqueue);
	if (!AQ.IsEmpty()) {
		if (decoder.PeekHash() == AQ.Last().Hash) {
			return;
		}
	}


	// The Changed flag is set whenever there is any change to
	// the animation queue of any kind.
	//
	Changed = true;

	// Build a map indexing the steps in AQ.
	//
	TMap<int64, int32> HashToIndex;
	for (int i = 0; i < AQ.Num(); i++) {
		HashToIndex.Emplace(AQ[i].Hash, i);
	}

	// Search for a Luprex animation record that matches
	// something that we already have.  Yields the sequence
	// number of the most recent matching record.
	//
	// At the same time, push all non-matching records
	// after the matching record onto a stack of new records.
	//
	TArray<FlxAnimationStepView> newsteps;
	int32 matchingindex = -1;
	while (!decoder.AtEOF()) {
		FlxAnimationStepView step = decoder.ReadStep();
		int32* indexp = HashToIndex.Find(step.Hash);
		if (indexp == nullptr) {
			newsteps.Emplace(step);
		} else {
			matchingindex = *indexp;
			break;
		}
	}

	// Remove all animations after the most recent matching
	// record.
	// 
	int32 nremove = (AQ.Num() - (matchingindex + 1));
	check((nremove >= 0) && (nremove <= AQ.Num()));
	for (int32 i = 0; i < nremove; i++) {
		AQ.Pop();
	}

	// Transfer the new animations onto the queue.
	//
	while (!newsteps.IsEmpty()) {
		FlxAnimationStepView step = newsteps.Pop();
		AQ.Emplace(step.Hash, step.Body);
	}

	// If there are too many animations in AQ, discard
	// any very old ones.
	//
	// TODO: this is hardwired to keep 10.  Instead, we
	// should keep the number specified in the queue.
	//
	const int limit = 10;
	if (AQ.Num() > limit)
	{
		int offset = AQ.Num() - limit;
		for (int i = 0; i < limit; i++)
		{
			AQ[i] = AQ[i + offset];
		}
		AQ.SetNum(limit);
	}
}

FString FlxAnimTracker::DebugString() const
{
	FString Result;
	if (Changed)
	{
		Result += TEXT("changed=true ");
	}
	else
	{
		Result += TEXT("changed=false ");
	}
	Result += TEXT("\n");
	for (int i = 0; i < AQ.Num(); i++)
	{
		Result += UlxAnimationStepLibrary::AnimationStepDebugString(AQ[i]);
		Result += TEXT("\n");
	}
	return Result;
}

FString FlxAnimTracker::GetCurrentBlueprintName() {
	for (int i = 0; i < AQ.Num(); i++) {
		if (!AQ[i].Finished) {
			return AQ[i].Blueprint;
		}
	}
	return AQ.Last().Blueprint;
}

FlxAnimationStep FlxAnimTracker::GetCurrentAnimation() {
	FlxAnimationStep result;
	for (int i = 0; i < AQ.Num(); i++) {
		if (!AQ[i].Finished) {
			return AQ[i];
		}
	}
	result = AQ.Last();
	// This next line is a hack.  We need the idle step to have a unique
	// hash.  This is a passable way to get a unique hash value.
	result.Hash += 1;
	return result;
}

void UlxAnimationStepLibrary::AnimationStepApplyMaterials(const FlxAnimationStep& step, AActor* actor) {
	if (actor == nullptr) return;

	// Step 1: Build tables of mat_ parameters (vectors and scalars).
	//
	TMap<FName, FVector> VectorParams;
	TMap<FName, float> ScalarParams;
	{
		std::string_view body((const char*)(step.Body.GetData()), step.Body.Num());
		FlxAnimationStepDecoder decoder(body);
		while (!decoder.AtEOF()) {
			FlxAnimationField field = decoder.ReadField();
			if (field.Name.size() <= 4) continue;
			if (field.Name.substr(0, 4) != "mat_") continue;
			std::string_view suffix = field.Name.substr(4);
			FName paramName(suffix.size(), (const UTF8CHAR*)suffix.data());
			if (field.Type == LuaValueType::VECTOR) {
				VectorParams.Add(paramName, FVector(field.X, field.Y, field.Z));
			} else if (field.Type == LuaValueType::NUMBER) {
				ScalarParams.Add(paramName, (float)field.X);
			}
		}
	}

	// Step 2: Early exit if no mat_ parameters found.
	//
	if (VectorParams.IsEmpty() && ScalarParams.IsEmpty()) return;

	// Step 3: Loop over all mesh components and apply material parameters.
	//
	TInlineComponentArray<UMeshComponent*> MeshComponents;
	actor->GetComponents<UMeshComponent>(MeshComponents);

	for (UMeshComponent* MeshComp : MeshComponents) {
		int32 NumMaterials = MeshComp->GetNumMaterials();
		for (int32 SlotIndex = 0; SlotIndex < NumMaterials; SlotIndex++) {
			UMaterialInterface* CurrentMat = MeshComp->GetMaterial(SlotIndex);
			if (CurrentMat == nullptr) continue;

			// Check if the material has any parameter that doesn't
			// match what was specified.
			//
			bool AnyMismatch = false;
			for (auto& Pair : VectorParams) {
				FLinearColor Compare;
				if (CurrentMat->GetVectorParameterValue(Pair.Key, Compare)) {
					FLinearColor Desired(Pair.Value.X, Pair.Value.Y, Pair.Value.Z);
					if (Compare != Desired) {
						AnyMismatch = true;
					}
				}
			}
			for (auto& Pair : ScalarParams) {
				float Compare;
				if (CurrentMat->GetScalarParameterValue(Pair.Key, Compare)) {
					if (Compare != Pair.Value) {
						AnyMismatch = true;
					}
				}
			}
			if (!AnyMismatch) continue;

			// Strip away any existing dynamic material before creating a new one.
			//
			UMaterialInterface* BaseMat = CurrentMat;
			while (UMaterialInstanceDynamic* MID = Cast<UMaterialInstanceDynamic>(BaseMat)) {
				BaseMat = MID->Parent;
			}
			if (BaseMat != CurrentMat) {
				MeshComp->SetMaterial(SlotIndex, BaseMat);
			}

			// Create the new dynamic material.
			//
			UMaterialInstanceDynamic* MID = MeshComp->CreateDynamicMaterialInstance(SlotIndex);
			for (auto& Pair : VectorParams) {
				FVector& Vec = Pair.Value;
				MID->SetVectorParameterValue(Pair.Key, FLinearColor(Vec.X, Vec.Y, Vec.Z));
			}
			for (auto& Pair : ScalarParams) {
				MID->SetScalarParameterValue(Pair.Key, Pair.Value);
			}
		}
	}
}

void UlxAnimationStepLibrary::AnimationStepApplyStaticMesh(const FlxAnimationStep& step, bool FallbackToBP,
		UStaticMeshComponent* MeshComp, UStaticMesh* Fallback) {
	if (MeshComp == nullptr) return;

	FString MeshName = AnimationStepGetString(step, TEXT("mesh"));
	if (MeshName.IsEmpty() && FallbackToBP) {
		MeshName = AnimationStepGetString(step, TEXT("bp"));
	}

	UStaticMesh* NewMesh = nullptr;
	if (!MeshName.IsEmpty()) {
		UlxAssetLookup::LoadStaticMeshAsset(NewMesh, MeshComp, MeshName);
	}
	if (NewMesh == nullptr) NewMesh = Fallback;
	if (NewMesh == nullptr) return;
	if (MeshComp->GetStaticMesh() != NewMesh) {
		MeshComp->SetStaticMesh(NewMesh);
	}
}

void UlxAnimationStepLibrary::AnimationStepApplySkeletalMesh(const FlxAnimationStep& step, bool FallbackToBP,
		USkeletalMeshComponent* MeshComp, USkeletalMesh* Fallback) {
	if (MeshComp == nullptr) return;

	FString MeshName = AnimationStepGetString(step, TEXT("mesh"));
	if (MeshName.IsEmpty() && FallbackToBP) {
		MeshName = AnimationStepGetString(step, TEXT("bp"));
	}

	USkeletalMesh* NewMesh = nullptr;
	if (!MeshName.IsEmpty()) {
		UlxAssetLookup::LoadSkeletalMeshAsset(NewMesh, MeshComp, MeshName);
	}
	if (NewMesh == nullptr) NewMesh = Fallback;
	if (NewMesh == nullptr) return;
	if (MeshComp->GetSkeletalMeshAsset() != NewMesh) {
		MeshComp->SetSkeletalMeshAsset(NewMesh);
	}
}

FFormatArgumentData UlxAnimationStepLibrary::FormatArgumentDataAnimationStep(const FlxAnimationStep &AutoConvertedValue, const FString &Name)
{
	FFormatArgumentData Result;
	Result.ArgumentValueType = EFormatArgumentType::Text;
	Result.ArgumentName = Name;
	Result.ArgumentValue = FText::FromString(AnimationStepDebugString(AutoConvertedValue));
	return Result;
}

