// ©2022 Batty Bovine Productions, LLC. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"

#include "Components/ActorComponent.h"
#include "ComboInputAssets.h"
#include "Events/ComboActionDelegateBinding.h"

#include "ComboManagerComponent.generated.h"

DECLARE_LOG_CATEGORY_EXTERN(LogComboManagerComponent, Log, All);

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FComboActionHandlerDelegate, const class UComboAction *, ComboAction, const bool, Activated);
DECLARE_DYNAMIC_DELEGATE_TwoParams(FComboActionHandlerDynamicSignature, const class UComboAction *, ComboAction, const EComboActionTriggerEvent &, TriggerEvent);


struct COMBOINPUT_API FComboDelegateKey
{
public:
	FComboDelegateKey(const class UComboAction *ComboAction, const EComboActionTriggerEvent &TriggerEvent)
	{
		this->Key = FName(ComboAction->GetName() + TEXT("_") + StaticEnum<EComboActionTriggerEvent>()->GetDisplayNameTextByIndex((uint8)TriggerEvent).ToString());
	}
	FName Key;
};

USTRUCT(BlueprintType)
struct COMBOINPUT_API FComboOffsetMap
{
	GENERATED_BODY()
public:
	UPROPERTY(BlueprintReadOnly, EditAnywhere)	TObjectPtr<const class UComboInputAsset>	ComboInput;
	UPROPERTY(BlueprintReadOnly, EditAnywhere)	TObjectPtr<const class UComboAction>		ComboAction;
};

UCLASS(BlueprintType, ClassGroup=(Input), meta=(BlueprintSpawnableComponent))
class COMBOINPUT_API UComboManagerComponent : public UActorComponent
{
	GENERATED_BODY()

public:
	UComboManagerComponent();

	UFUNCTION(BlueprintCallable)
		void HandleComboInput(const class UComboInputAsset *Input, const EComboActionTriggerEvent &TriggerEvent);

	void BindAction(UObject *ObjectToBindTo, FName FunctionName, const class UComboAction *ComboAction, const EComboActionTriggerEvent &TriggerEvent)
	{
		FComboActionHandlerDynamicSignature Delegate;
		Delegate.BindUFunction(ObjectToBindTo, FunctionName);
		const FComboDelegateKey Key(ComboAction, TriggerEvent);
		this->ComboActionEventBindings.Emplace(Key.Key, MoveTemp(Delegate));
	}

protected:
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly)
		TObjectPtr<const class UComboSequenceNode> DefaultStartNode;

	// This input will be recognised as an offset input, which cancels a node transition
	// if activated early enough in the transition. This locks the current place in the
	// combo and allows this combo string to be continued after this input has executed.
	// The associated combo action will then be activated.
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly)
		FComboOffsetMap OffsetMap;

	// A list of default input->action mappings. If you wish for an action to be handled
	// outside the scope of a combo sequence node, it should go here.
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly)
		TMap<TObjectPtr<const class UComboInputAsset>, TObjectPtr<const class UComboAction>> FallbackActions;

	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly)
		TMap<TObjectPtr<const class UComboInputAsset>, float> DEBUG__UnlockTimers;

	UPROPERTY(BlueprintReadWrite, BlueprintAssignable)
		FComboActionHandlerDelegate OnUnhandledAction;

private:
	void ActivateComboAction(const class UComboInputAsset *Input);
	void ReleaseComboAction(const class UComboInputAsset *Input);

	void BeginNodeTransition(const class UComboSequenceNode *NextNode);
	void FinishTransition();
	void ResetCombo();

	void BroadcastDelegates(const class UComboAction *ComboAction, const EComboActionTriggerEvent &TriggerEvent);

	void DEBUG__UnlockAction(TObjectPtr<const class UComboInputAsset> Unlock);

	TObjectPtr<const class UComboSequenceNode> ActiveNode = nullptr;
	TObjectPtr<const class UComboSequenceNode> PreviousNode = nullptr;

	TObjectPtr<const class UComboAction> LastComboAction = nullptr;

	TMap<FName, FComboActionHandlerDynamicSignature> ComboActionEventBindings;

	TObjectPtr<class UInputBufferComponent> AttachedInputBuffer;

	FTimerHandle FinishTransitionTimer;
	FTimerHandle DEBUG__ResetComboTimer;

	TMap<TObjectPtr<const class UComboInputAsset>, FTimerHandle> DEBUG__TimerHandles;
};
