// Project Sword & Gun Copyright © 2021 Kevin Poretti


#include "Characters/SNGProtagonist.h"

#include "DrawDebugHelpers.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet/KismetSystemLibrary.h"
#include "SwordNGun/SwordNGun.h"

static int32 DebugProtagonistDrawing = 0;
FAutoConsoleVariableRef CVARDebugProtagonistDrawing(TEXT("SNG.DebugProtagonist"),
                        DebugProtagonistDrawing, 
                        TEXT("Draw Debug Information for Protagonist"), 
                        ECVF_Cheat);

ASNGProtagonist::ASNGProtagonist(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	PrimaryActorTick.bCanEverTick = true;
	
	SpringArmComponent = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComponent"));
	SpringArmComponent->SetupAttachment(RootComponent);
	
	CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
	CameraComponent->SetupAttachment(SpringArmComponent);

	WeaponInventoryComponent = CreateDefaultSubobject<USNGWeaponInventoryComponent>(TEXT("WeaponInventoryComponent"));
	
	bIsMeleeAttackComboReset = false;
	bDoesMovementInputCancelAnimMontage = true;

	AimFOV = 60.0f;
	AimSpeed = 20.0f;
	DefaultFOV = CameraComponent->FieldOfView;
	
	SoftLockHalfAngle = 45.0f;
	SoftLockRange = 200.0f;
	NumSoftLockLineTraces = 4;

	DegreesPerSecond = 720.0f;
	MaxRotation = 180.0f;

	MaxNumAirDashes = 2;
}

void ASNGProtagonist::BeginPlay()
{
	Super::BeginPlay();

	EquipWeapon(WeaponInventoryComponent->SetCurrentRangedWeapon(0), GunHandSocket);
	
	EquipWeapon(WeaponInventoryComponent->SetCurrentMeleeWeapon(0), SwordHandSocket);

	CurrNumAirDashes = 0;
}

void ASNGProtagonist::MoveForward(const float Value)
{
	LastForwardAxis = Value;

	// If the protag starts moving when a recovery anim montage is playing we want to cancel out of it
	if(!IsMoveInputIgnored() && !FMath::IsNearlyZero(Value) && bDoesMovementInputCancelAnimMontage)
	{
		UAnimMontage* CurrMontage = GetMesh()->GetAnimInstance()->GetCurrentActiveMontage();
		if(CurrMontage)
		{
			GetMesh()->GetAnimInstance()->Montage_Stop(0.1f, CurrMontage);
		}
	}
	
	FRotator ViewRotation = GetController()->GetControlRotation();
	FRotator ViewRotationYaw(0.0f, ViewRotation.Yaw, 0.0f);
	AddMovementInput(UKismetMathLibrary::GetForwardVector(ViewRotationYaw), Value);
}

void ASNGProtagonist::MoveRight(const float Value)
{
	LastRightAxis = Value;

	// If the protag starts moving when a recovery anim montage is playing we want to cancel out of it
	if(!IsMoveInputIgnored() && !FMath::IsNearlyZero(Value) && bDoesMovementInputCancelAnimMontage)
	{
		UAnimMontage* CurrMontage = GetMesh()->GetAnimInstance()->GetCurrentActiveMontage();
		if(CurrMontage)
		{
			GetMesh()->GetAnimInstance()->Montage_Stop(0.1f, CurrMontage);
		}
	}
	
	FRotator ViewRotation = GetController()->GetControlRotation();
	FRotator ViewRotationYaw(0.0f, ViewRotation.Yaw, 0.0f);
	AddMovementInput(UKismetMathLibrary::GetRightVector(ViewRotationYaw), Value);
}

void ASNGProtagonist::DoJump()
{
	UAnimMontage* CurrMontage = GetMesh()->GetAnimInstance()->GetCurrentActiveMontage();
	if(CurrMontage)
	{
		GetMesh()->GetAnimInstance()->Montage_Stop(0.1f, CurrMontage);
	}
	
	Jump();
}

/*
float ASNGProtagonist::Dash()
{
	float AnimationDuration = 0.0f;
	
	FVector InputDirection = GetInputAsWorldDirection();
	// is there is no movement input then roll forward, otherwise roll in the movement input direction 
	if(!InputDirection.IsNearlyZero())
	{
		SetActorRotation(UKismetMathLibrary::MakeRotFromX(InputDirection));
	}			

	if(DashMontage)
	{
		AnimationDuration = PlayAnimMontage(DashMontage);
	}
	else
	{
		UE_LOG(LogTemp, Error, TEXT("SNGProtagonist :: No dash animation montage set for character"));
	}

	SetIgnoreMovementInput(true);
	
	return AnimationDuration;
}
*/

float ASNGProtagonist::MeleeAttack(FMeleeAttackData MeleeAttackData)
{
	GetClosestSoftLockedActor();
	if(ClosestActor)
	{
		SetupRotateTowardsTarget(
			UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), ClosestActor->GetActorLocation()),
			DegreesPerSecond, MaxRotation);
	}
	
	float AnimationDuration = 0.0f;

	// probably want to do an assert here instead??
	if(MeleeAttackData.AttackMontage)
	{
		AnimationDuration = PlayAnimMontage(MeleeAttackData.AttackMontage);

		SetIgnoreMovementInput(true);
	}
	else
	{
		UE_LOG(LogTemp, Error, TEXT("SNGProtagonist :: No attack animation montage set for character"));
	}
	
	return AnimationDuration;
}

bool ASNGProtagonist::IsMeleeAttackComboReset()
{
	return bIsMeleeAttackComboReset;
}

void ASNGProtagonist::SetMeleeAttackCombo(bool IsComboReset)
{
	bIsMeleeAttackComboReset = IsComboReset;
}

bool ASNGProtagonist::DoesMovementInputCancelAnimMontage()
{
	return bDoesMovementInputCancelAnimMontage;
}

void ASNGProtagonist::SetDoesMovementInputCancelAnimMontage(bool DoesMovementCancelMontage)
{
	bDoesMovementInputCancelAnimMontage = DoesMovementCancelMontage;
}

void ASNGProtagonist::DoAirDash()
{
	CurrNumAirDashes++;
	UE_LOG(LogTemp, Log, TEXT("SNGProtagonist :: CurrNumAirDashes %d"), CurrNumAirDashes);
}

void ASNGProtagonist::ResetAirDashCount()
{
	CurrNumAirDashes = 0;
	UE_LOG(LogTemp, Log, TEXT("SNGProtagonist :: Resetting Air Dashes"), CurrNumAirDashes);
}

bool ASNGProtagonist::CanAirDash() const
{
	return CurrNumAirDashes < MaxNumAirDashes;
}

void ASNGProtagonist::CycleRangedWeapon()
{
	WeaponInventoryComponent->GetCurrentRangedWeapon()->SetUnequipped();
	int32 CurrWeaponIdx = WeaponInventoryComponent->GetCurrentRangedWeaponIndex();
	int32 NumWeapons = WeaponInventoryComponent->GetNumRangedWeapons();
	int32 NextWeaponIdx = ++CurrWeaponIdx % NumWeapons;
	EquipWeapon(WeaponInventoryComponent->SetCurrentRangedWeapon(NextWeaponIdx), GunHandSocket);
}

void ASNGProtagonist::CycleMeleeWeapon()
{
	WeaponInventoryComponent->GetCurrentMeleeWeapon()->SetUnequipped();
	int32 CurrWeaponIdx = WeaponInventoryComponent->GetCurrentMeleeWeaponIndex();
	int32 NumWeapons = WeaponInventoryComponent->GetNumMeleeWeapons();
	int32 NextWeaponIdx = ++CurrWeaponIdx % NumWeapons;
	EquipWeapon(WeaponInventoryComponent->SetCurrentMeleeWeapon(NextWeaponIdx), SwordHandSocket);
}

void ASNGProtagonist::EquipWeapon(ASNGWeaponBase* WeaponToEquip, FName HandSocket)
{
	WeaponToEquip->SetEquipped();
	WeaponToEquip->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, HandSocket);
}

ASNGWeaponBase* ASNGProtagonist::GetCurrentWeapon_Implementation()
{
	return WeaponInventoryComponent->GetCurrentMeleeWeapon();
}

/*
FVector ASNGProtagonist::GetInputAsWorldDirection(float ForwardAxisValue, float RightAxisValue)
{
	AController* MyController = GetController();
	if(MyController == nullptr)
	{
		return FVector::ZeroVector;
	}
	
	FRotator ViewRotation = MyController->GetControlRotation();
	FRotator ViewRotationYaw(0.0f, ViewRotation.Yaw, 0.0f);
	
	FVector ForwardInput = UKismetMathLibrary::GetForwardVector(ViewRotationYaw) * ForwardAxisValue;

	FVector RightInput = UKismetMathLibrary::GetRightVector(ViewRotationYaw) * RightAxisValue;

	return ForwardInput + RightInput;
}
*/

void ASNGProtagonist::GetClosestSoftLockedActor()
{
	for(int i = 0; i < SoftLockedActors.Num(); i++)
	{
		float CurrDist = FVector::Distance(GetActorLocation(), SoftLockedActors[i]->GetActorLocation());

		FRotator DeltaRot = UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), SoftLockedActors[i]->GetActorLocation());
		float CurrAngle = FMath::Abs(DeltaRot.Yaw - GetActorRotation().Yaw);

		if(ClosestActor)
		{
			if(CurrAngle <= AngleFromClosestActor && CurrDist <= DistFromClosestActor)
			{
				ClosestActor = SoftLockedActors[i];
				DistFromClosestActor = CurrDist;
				AngleFromClosestActor = CurrAngle;
			}
		}
		else
		{
			ClosestActor = SoftLockedActors[i];
			DistFromClosestActor = CurrDist;
			AngleFromClosestActor = CurrAngle;
		}
	}
}

FVector ASNGProtagonist::GetPawnViewLocation() const
{
	if(CameraComponent)
	{
		return CameraComponent->GetComponentLocation();
	}

	return Super::GetPawnViewLocation();
}

void ASNGProtagonist::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);

	// set field of view depending on whether player is aiming down sights or not
	float TargetFOV = bIsAiming ? AimFOV : DefaultFOV;
	float NewFOV = FMath::FInterpTo(CameraComponent->FieldOfView, TargetFOV, DeltaSeconds, AimSpeed);
	CameraComponent->SetFieldOfView(NewFOV);

    // find enemies to soft lock to when performing melee attacks
	SoftLockedActors.Empty();
	float LineTraceAngleStep = (2 * SoftLockHalfAngle) / NumSoftLockLineTraces;
	float CurrLineTraceAngle = -SoftLockHalfAngle;
	for(int i = 0; i < NumSoftLockLineTraces; i++, CurrLineTraceAngle += LineTraceAngleStep)
	{
		FCollisionQueryParams QueryParams;
		QueryParams.AddIgnoredActor(this);
		QueryParams.AddIgnoredActors(SoftLockedActors);
		QueryParams.bTraceComplex = true;

		FRotator CurrLineTraceRotation(0.0f, GetActorRotation().Yaw + CurrLineTraceAngle, 0.0f);
		FVector CurrLineTraceDir = UKismetMathLibrary::GetForwardVector(CurrLineTraceRotation);

		// why was I using the weapon trace channel for this??
		FHitResult HitResult;
		ETraceTypeQuery TraceType = UEngineTypes::ConvertToTraceType(COLLISION_RANGED_WEAPON);

		/*
		if(GetWorld()->LineTraceSingleByChannel(HitResult, GetActorLocation(),
			GetActorLocation() + (CurrLineTraceDir * SoftLockRange), COLLISION_WEAPON, QueryParams))
			*/
		EDrawDebugTrace::Type DrawDebugTrace = DebugProtagonistDrawing ? EDrawDebugTrace::Type::ForOneFrame : EDrawDebugTrace::Type::None; 
		if(UKismetSystemLibrary::SphereTraceSingle(this, GetActorLocation(), GetActorLocation() + (CurrLineTraceDir * SoftLockRange), 20.0f, TraceType, true,
			SoftLockedActors, DrawDebugTrace, HitResult, true,
		FLinearColor::Red, FLinearColor::Green, -1))
		{
			AActor* HitActor = HitResult.Actor.Get();
			if(HitActor)
			{
				SoftLockedActors.Add(HitActor);
			}
		}

		if(DebugProtagonistDrawing)
		{
			DrawDebugLine(GetWorld(), GetActorLocation(),GetActorLocation() + (CurrLineTraceDir * SoftLockRange), FColor::Red, false, -1, 0, 1.0f);
			DrawDebugSphere(GetWorld(), HitResult.ImpactPoint, 5.0f, 12, FColor::Green, false, -1, 0, 1.0f);
		}
	}
	
	if(DebugProtagonistDrawing)
	{
		FVector ActorLocation = GetActorLocation();
		FVector ActorForward = UKismetMathLibrary::GetForwardVector(GetActorRotation());
		DrawDebugDirectionalArrow(GetWorld(), ActorLocation, ActorLocation + (ActorForward * 100.0f),
            10.0f, FColor::Cyan, false, -1.0f, 0, 2.0f);

		/*
		FVector InputDirection = GetInputAsWorldDirection(LastForwardAxis, LastRightAxis);
		InputDirection.Normalize();
		DrawDebugDirectionalArrow(GetWorld(), ActorLocation, ActorLocation + (InputDirection * 100.0f),
        10.0f, FColor::Magenta, false, -1.0f, 0, 2.0f);
        */
	}
}
