// Multiplayer By Caleb


#include "GAS/GA_Combo.h"

#include "AbilitySystemBlueprintLibrary.h"
#include "GameplayTagsManager.h"
#include "Abilities/Tasks/AbilityTask_PlayMontageAndWait.h"
#include "Abilities/Tasks/AbilityTask_WaitGameplayEvent.h"
#include "Abilities/Tasks/AbilityTask_WaitInputPress.h"
#include "GAS/UCAbilitySystemStatics.h"

UGA_Combo::UGA_Combo()
{
	AbilityTags.AddTag(UCAbilitySystemStatics::GetBasicAttackAbilityTag());
	BlockAbilitiesWithTag.AddTag(UCAbilitySystemStatics::GetBasicAttackAbilityTag());
}

void UGA_Combo::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
                                const FGameplayAbilityActorInfo* ActorInfo,
                                const FGameplayAbilityActivationInfo ActivationInfo,
                                const FGameplayEventData* TriggerEventData)
{
	Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
	// if (!CommitAbility(Handle, ActorInfo, ActivationInfo)) // this works as well
	if (!K2_CommitAbility())
	{
		K2_EndAbility();
		return;
	}
	if (HasAuthorityOrPredictionKey(ActorInfo, &ActivationInfo))
	{
		const auto PlayComboMontage{
			UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(
				this,
				NAME_None,
				ComboMontage
			)
		};
		PlayComboMontage->OnBlendOut.AddDynamic(this, &UGA_Combo::K2_EndAbility);
		PlayComboMontage->OnCancelled.AddDynamic(this, &UGA_Combo::K2_EndAbility);
		PlayComboMontage->OnCompleted.AddDynamic(this, &UGA_Combo::K2_EndAbility);
		PlayComboMontage->OnInterrupted.AddDynamic(this, &UGA_Combo::K2_EndAbility);
		PlayComboMontage->ReadyForActivation();

		/**
		 * Handle Setting the next combo section in current Montage
		 * This {ChangedEventTag} will mach all the tags under [changed] tag hierchy
		 * this will insure the event is Sents for all the tags(all the animation sections in Montage)
		 ****/
		const auto ChangedEventTag{GetComboChangedEventTag()};
		const auto WaitComboChangeEventTask{
			UAbilityTask_WaitGameplayEvent::WaitGameplayEvent(
				this,
				ChangedEventTag,
				nullptr,
				false,
				false
			)
		};
		WaitComboChangeEventTask->EventReceived.AddDynamic(this, &UGA_Combo::ComboChangedEventReceived);
		WaitComboChangeEventTask->ReadyForActivation();
	}
	if (K2_HasAuthority())
	{
		const auto WaitTargetingEventTask{
			UAbilityTask_WaitGameplayEvent::WaitGameplayEvent(
				this,
				GetComboTargetEventTag()
			)
		};
		WaitTargetingEventTask->EventReceived.AddDynamic(this, &UGA_Combo::DoDamage);
		WaitTargetingEventTask->ReadyForActivation();
	}

	SetupWaitComboInputPressed();
}

void UGA_Combo::ComboChangedEventReceived(FGameplayEventData Data)
{
	const auto EventTag{Data.EventTag};
	if (EventTag == GetComboChangedEventEndTag())
	{
		NextComboName = NAME_None;
		UE_LOG(LogTemp, Warning, TEXT("next combo is cleared"));
		return;
	}

	TArray<FName> TagNames;
	UGameplayTagsManager::Get().SplitGameplayTagFName(EventTag, TagNames);
	NextComboName = TagNames.Last();

	UE_LOG(LogTemp, Warning, TEXT("next combo is: %s"), *NextComboName.ToString());
}

FGameplayTag UGA_Combo::GetComboChangedEventTag()
{
	return FGameplayTag::RequestGameplayTag("ability.combo.change");
}

FGameplayTag UGA_Combo::GetComboChangedEventEndTag()
{
	return FGameplayTag::RequestGameplayTag("ability.combo.change.end");
}

FGameplayTag UGA_Combo::GetComboTargetEventTag()
{
	return FGameplayTag::RequestGameplayTag("ability.combo.damage");
}

void UGA_Combo::SetupWaitComboInputPressed()
{
	const auto WaitInputPress{UAbilityTask_WaitInputPress::WaitInputPress(this)};
	WaitInputPress->OnPress.AddDynamic(this, &UGA_Combo::HandleInputPress);
	WaitInputPress->ReadyForActivation();
}

void UGA_Combo::HandleInputPress(float TimeWaited)
{
	SetupWaitComboInputPressed();
	TryCommitCombo();
}

void UGA_Combo::TryCommitCombo()
{
	const auto OwnerAnimInstance{GetOwnerAnimInstance()};
	if (NextComboName == NAME_None || !OwnerAnimInstance) return;

	const auto SectionNameToChange{OwnerAnimInstance->Montage_GetCurrentSection(ComboMontage)};

	OwnerAnimInstance->Montage_SetNextSection(
		SectionNameToChange,
		NextComboName,
		ComboMontage
	);
}

TSubclassOf<UGameplayEffect> UGA_Combo::GetDamageEffectForCurrentCombo() const
{
	if (const auto OwnerAnimInstance{GetOwnerAnimInstance()})
	{
		const FName CurrentSectionName{OwnerAnimInstance->Montage_GetCurrentSection(ComboMontage)};
		const auto FoundEffectPtr{DamageEffectMap.Find(CurrentSectionName)};

		if (FoundEffectPtr) return *FoundEffectPtr;
	}
	return DefaultEffectClass;
}

void UGA_Combo::DoDamage(FGameplayEventData Data)
{
	auto HitResults{
		GetHitResultFromSweepLocationTargetData(
			Data.TargetData,
			TargetSweepSphereRadius,
			false,
			true
		)
	};

	for (const FHitResult& HitResult : HitResults)
	{
		const auto GameplayEffect{GetDamageEffectForCurrentCombo()};
		const auto AbilityLevel{GetAbilityLevel(GetCurrentAbilitySpecHandle(), GetCurrentActorInfo())};
		const auto EffectSpecHandle{MakeOutgoingGameplayEffectSpec(GameplayEffect, AbilityLevel)};
		const auto AbilityTargetData{UAbilitySystemBlueprintLibrary::AbilityTargetDataFromActor(HitResult.GetActor())};

		auto EffectContextHandle{MakeEffectContext(GetCurrentAbilitySpecHandle(), GetCurrentActorInfo())};
		EffectContextHandle.AddHitResult(HitResult);
		EffectSpecHandle.Data->SetContext(EffectContextHandle);

		const auto ActiveGameplayEffectHandles{
			ApplyGameplayEffectSpecToTarget(
				GetCurrentAbilitySpecHandle(),
				CurrentActorInfo,
				CurrentActivationInfo,
				EffectSpecHandle,
				AbilityTargetData
			)
		};
	}
}
