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

#include "Components/ComboManagerComponent.h"

#include "ComboInputAssets.h"

#include "Components/InputBufferComponent.h"
#include "Engine/LocalPlayer.h"
#include "GameFramework/Character.h"
#include "Kismet/GameplayStatics.h"

DEFINE_LOG_CATEGORY(LogComboManagerComponent);


UComboManagerComponent::UComboManagerComponent()
{
	PrimaryComponentTick.bStartWithTickEnabled = false;
	PrimaryComponentTick.bTickEvenWhenPaused = false;
	PrimaryComponentTick.bCanEverTick = false;
}


void UComboManagerComponent::HandleComboInput(const UComboInputAsset *Input, const EComboActionTriggerEvent &TriggerEvent)
{
	switch (TriggerEvent)
	{
	case EComboActionTriggerEvent::Activated:
		this->ActivateComboAction(Input);
		break;
	case EComboActionTriggerEvent::Released:
		this->ReleaseComboAction(Input);
		break;
	default:
		return;
	}
}

void UComboManagerComponent::ActivateComboAction(const UComboInputAsset *Input)
{
	/************ DEBUG ************/
	for (const TPair<TObjectPtr<const UComboInputAsset>, float> &Pair : this->DEBUG__UnlockTimers)
	{
		if (!this->DEBUG__TimerHandles.Contains(Pair.Key))
		{
			this->DEBUG__TimerHandles.Add(Pair.Key);
		}
		this->GetWorld()->GetTimerManager().ClearTimer(this->DEBUG__TimerHandles[Pair.Key]);
		this->GetWorld()->GetTimerManager().SetTimer(this->DEBUG__TimerHandles[Pair.Key], FTimerDelegate::CreateUObject(this, &UComboManagerComponent::DEBUG__UnlockAction, Pair.Key), Pair.Value, false);
	}
	/********** END DEBUG **********/

	// If we received an offset input, perform the offset here and then leave.
	if (Input == this->OffsetMap.ComboInput)
	{
		this->ActiveNode = this->PreviousNode;
		this->GetWorld()->GetTimerManager().ClearTimer(this->FinishTransitionTimer);
		this->BroadcastDelegates(this->OffsetMap.ComboAction, EComboActionTriggerEvent::Activated);
		UE_LOG(LogComboManagerComponent, Verbose, TEXT("Combo has been offset by %s"), *Input->ComboInputName.ToString());
		return;
	}

	const TObjectPtr<const UComboSequenceNode> CurrentNode = (this->ActiveNode ? this->ActiveNode : this->DefaultStartNode);
	checkf(CurrentNode, TEXT("No combo sequence nodes available."));

	const FComboSequenceAction *ActionData = CurrentNode->ComboBranch.Find(Input);
	// If this node has an action we can activate, then activate it.
	if (ActionData && ActionData->ComboAction)
	{
		this->BeginNodeTransition(ActionData->NextNode);
		this->BroadcastDelegates(ActionData->ComboAction, EComboActionTriggerEvent::Activated);
		UE_LOG(LogComboManagerComponent, Verbose, TEXT("%s activated"), *ActionData->ComboAction->ActionName.ToString());
	}
	// Otherwise, see if we have a fallback we can use.
	else if (const TObjectPtr<const UComboAction> *ComboAction = this->FallbackActions.Find(Input))
	{
		this->ResetCombo();
		this->BroadcastDelegates(*ComboAction, EComboActionTriggerEvent::Activated);
		UE_LOG(LogComboManagerComponent, Verbose, TEXT("Fallback action %s activated"), *(*ComboAction)->ActionName.ToString());
	}
	// If we haven't found an action to perform, end here.
	else
	{
		UE_LOG(LogComboManagerComponent, Verbose, TEXT("No action found for %s"), *Input->ComboInputName.ToString());
	}
}
void UComboManagerComponent::DEBUG__UnlockAction(TObjectPtr<const UComboInputAsset> Unlock)
{
	APlayerController *Controller = UGameplayStatics::GetPlayerController(this, 0);
	if (UInputBufferComponent *InputComponent = Cast<UInputBufferComponent>(Controller->InputComponent))
	{
		InputComponent->UnlockComboInput(Unlock);
	}
}

void UComboManagerComponent::ReleaseComboAction(const class UComboInputAsset *Input)
{
	if (this->LastComboAction)
	{
		this->BroadcastDelegates(this->LastComboAction, EComboActionTriggerEvent::Released);
		this->LastComboAction = nullptr;
	}
}


void UComboManagerComponent::BeginNodeTransition(const UComboSequenceNode *NextNode)
{
	this->PreviousNode = this->ActiveNode;
	this->ActiveNode = NextNode;
	this->GetWorld()->GetTimerManager().SetTimer(this->FinishTransitionTimer, this, &UComboManagerComponent::FinishTransition, 0.5f);

	this->GetWorld()->GetTimerManager().SetTimer(this->DEBUG__ResetComboTimer, this, &UComboManagerComponent::ResetCombo, 1.0f);
}

void UComboManagerComponent::FinishTransition()
{
	this->PreviousNode = this->ActiveNode;
}

void UComboManagerComponent::ResetCombo()
{
	this->GetWorld()->GetTimerManager().ClearTimer(this->DEBUG__ResetComboTimer);
	this->ActiveNode = this->PreviousNode = nullptr;
}


void UComboManagerComponent::BroadcastDelegates(const UComboAction *ComboAction, const EComboActionTriggerEvent &TriggerEvent)
{
	const FComboDelegateKey Key(ComboAction, TriggerEvent);
	if (const FComboActionHandlerDynamicSignature *Binding = this->ComboActionEventBindings.Find(Key.Key))
	{
		Binding->Execute(ComboAction, TriggerEvent);
	}
	else
	{
		this->OnUnhandledAction.Broadcast(ComboAction, (TriggerEvent == EComboActionTriggerEvent::Activated) ? true : false);
	}
	this->LastComboAction = ComboAction;
}
