#pragma once

#include <string_view>
#include "CoreMinimal.h"
#include "CoreUObject.h"
#include "Containers/Deque.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "AnimQueue.generated.h"

////////////////////////////////////////////////////////////
//
// FlxAnimationStep
//
// A single animation step.  The key-value pairs
// are stored in an encoded form, which is not
// directly accessible to blueprints.  To read
// them, use UnpackAnimationStep or the
// AnimationStepGetXXX functions.
//
////////////////////////////////////////////////////////////

USTRUCT(BlueprintType)
struct INTEGRATION_API FlxAnimationStep {
	GENERATED_BODY()

public:
	UPROPERTY()
	bool Finished;

	// The hash of the animation step, a 63-bit
	// unique identifier.
	//
	UPROPERTY()
	int64 Hash;

	// The Body contains all the key-value pairs
	// in an encoded form.  To obtain these in a
	// useful form, use UnpackAnimationStep or the
	// AnimationStepGetXXX functions.
	//
	UPROPERTY()
	TArray<uint8> Body;

	UPROPERTY()
	FString Blueprint;

	FlxAnimationStep() : Finished(false), Hash(0), Body(), Blueprint() {}
	FlxAnimationStep(int64 h, std::string_view b);

	// Auto-Execute
	//
	// These functions automatically update certain
	// actor properties:
	//
	// AutoUpdateXYZ     - uses 'xyz' keyword
	// AutoUpdateFacing  - uses 'facing' keyword
	// AutoUpdatePlane   - uses 'plane' keyword
	//
	void AutoUpdateXYZ(AActor *actor) const;
	void AutoUpdateFacing(AActor *actor) const;
	void AutoUpdatePlane(FName *plane) const;
};

////////////////////////////////////////////////////////////
//
// UlxAnimationStepLibrary
//
// Blueprint function library for reading and
// applying animation steps.
//
////////////////////////////////////////////////////////////

UCLASS()
class INTEGRATION_API UlxAnimationStepLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()

public:
	UFUNCTION(BlueprintPure, Category = "Luprex|Animation Step")
	static FString AnimationStepDebugString(const FlxAnimationStep& step);

	// Stores the key-value pairs in properties of the
	// target object.
	//
	// The prefix parameter is prepended to the property
	// names.  For example, if the pairs are "color=blue,
	// speed=20" and the prefix is "aq", then "aq Color" is
	// set to "blue" and "aq Speed" is set to 20.  A
	// property "aq Animation Step" is also written,
	// containing the entire animation step.
	//
	// If a key has no corresponding property, it is
	// silently ignored.  If a property has no corresponding
	// key, it is cleared.
	//
	// Returns bChanged=true if the hash of the "aq
	// Animation Step" property has changed. Returns the
	// Action string from action=xxx.
	//
	UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "target"), Category = "Luprex|Animation Step")
	static void UnpackAnimationStep(bool &bChanged, FString &Action, const FlxAnimationStep& step, UObject* target, const FString& VariableNamePrefix = TEXT("aq"));

	UFUNCTION(BlueprintPure, Category = "Luprex|Animation Step")
	static bool AnimationStepEqual(const FlxAnimationStep &StepA, const FlxAnimationStep &StepB);

	UFUNCTION(BlueprintPure, Category = "Luprex|Animation Step")
	static bool AnimationStepIsIdle(const FlxAnimationStep &step);

	UFUNCTION(BlueprintPure, Category = "Luprex|Animation Step")
	static FVector AnimationStepGetVector(const FlxAnimationStep& step, const FString& name);

	UFUNCTION(BlueprintPure, Category = "Luprex|Animation Step")
	static double AnimationStepGetFloat(const FlxAnimationStep& step, const FString& name);

	UFUNCTION(BlueprintPure, Category = "Luprex|Animation Step")
	static FString AnimationStepGetString(const FlxAnimationStep& step, const FString& name);

	UFUNCTION(BlueprintPure, Category = "Luprex|Animation Step")
	static FName AnimationStepGetName(const FlxAnimationStep& step, const FString& name);

	UFUNCTION(BlueprintPure, Category = "Luprex|Animation Step")
	static bool AnimationStepGetBool(const FlxAnimationStep& step, const FString& name);

	UFUNCTION(BlueprintPure, meta = (BlueprintAutocast), Category = "Luprex|Animation Step")
    static int64 AnimationStepID(const FlxAnimationStep& step) { return step.Hash; }

	// Using mat_xxxx values from the animation step, update the actor's
	// material parameters. Doing this may involve creating or replacing
	// dynamic material instances for the actor.
	//
	UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "actor"), Category = "Luprex|Animation Step")
	static void AnimationStepApplyMaterials(const FlxAnimationStep& step, AActor* actor);

	// Loads a new static mesh, using the animation step to choose it.
	// If the chosen mesh is already installed, no changes are made.
	// Looks for 'mesh=name' first; if not found and FallbackToBP is
	// true, looks for 'bp=name'.  If no mesh is successfully loaded,
	// uses the Fallback mesh.  Failed loads are logged once.
	//
	UFUNCTION(BlueprintCallable, Category = "Luprex|Animation Step")
	static void AnimationStepApplyStaticMesh(const FlxAnimationStep& step, bool FallbackToBP,
		UStaticMeshComponent* MeshComp, UStaticMesh* Fallback = nullptr);

	// Loads a new skeletal mesh, using the animation step to choose it.
	// If the chosen mesh is already installed, no changes are made.
	// Looks for 'mesh=name' first; if not found and FallbackToBP is
	// true, looks for 'bp=name'.  If no mesh is successfully loaded,
	// uses the Fallback mesh.  Failed loads are logged once.
	//
	UFUNCTION(BlueprintCallable, Category = "Luprex|Animation Step")
	static void AnimationStepApplySkeletalMesh(const FlxAnimationStep& step, bool FallbackToBP,
		USkeletalMeshComponent* MeshComp, USkeletalMesh* Fallback = nullptr);

	// Allows you to pass an animation step to Format Message.
	//
	UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Animation Step")
	static FFormatArgumentData FormatArgumentDataAnimationStep(const FlxAnimationStep &AutoConvertedValue, const FString &Name);
};

////////////////////////////////////////////////////////////
//
// FlxAnimTracker
//
// Monitors the animation queue for a single tangible.
// Identifies when animations appear or are removed, and
// tracks which ones have been marked as finished.
//
////////////////////////////////////////////////////////////

class FlxAnimTracker {
public:
	// Our copy of the animation queue.  The first element
	// is the oldest item.
	//
	TArray<FlxAnimationStep> AQ;

	// True if something has recently changed.
	//
	bool Changed;

public:
	// Construct a tracker in the empty (Clear) state.
	//
	FlxAnimTracker();

	// Clear everything, reset to initial state.
	//
	void Clear();

	// Update from the specified animation queue. After the
	// update, AQ will be a copy of the queue that was
	// passed in.
	//
	void Update(std::string_view encqueue);

	// Get the current blueprint name.
	//
	FString GetCurrentBlueprintName();

	// Get the current animation step.  This is the step
	// that the blueprint should currently be playing.
	//
	FlxAnimationStep GetCurrentAnimation();

	// Declare that an animation is finished. The blueprint
	// calls this to indicate that it is done playing the
	// specified animation. This causes GetCurrentAnimation
	// to advance to the next step.
	//
	void FinishedAnimation(int64 Hash);

	// Return true if an animation step is marked finished.
	// Also returns true if the step is not found.
	//
	bool IsFinished(int64 Hash);

	// Skip to the end of the animation queue. Equivalent to
	// calling FinishedAnimation on every animation in the
	// queue.
	//
	void SkipToEnd();

	// Get the hashes of all animation steps.
	//
	TArray<int64> GetHashes();

	// Return the first unfinished animation.
	//
	const FlxAnimationStep *FirstUnfinished() const;

	// Return the last finished animation.
	//
	const FlxAnimationStep *LastFinished() const;

	// Return the first animation with the
	// specified hash.
	//
	const FlxAnimationStep *FindAnimation(int64 hash) const;

	// Clear the Changed flag.
	//
	void ClearChanged() { Changed = false; }

	// Get the Changed flag.
	//
	// Set to true whenever the animation queue changes, or
	// when FinishedAnimation marks a step.  Only cleared by
	// ClearChanged.
	//
	bool IsChanged() const { return Changed; }

	// Return a debug string for the entire animation
	// tracker.
	//
	FString DebugString() const;
};
