// Project Sword & Gun Copyright © 2021 Kevin Poretti


#include "Characters/SNGCharacterBase.h"

#include "Components/CapsuleComponent.h"
#include "GameFramework/PawnMovementComponent.h"
#include "Kismet/KismetMathLibrary.h"
#include "SwordNGun/SwordNGun.h"

// Sets default values
ASNGCharacterBase::ASNGCharacterBase(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer.SetDefaultSubobjectClass<USNGCharacterMovementComponent>(ACharacter::CharacterMovementComponentName))
{
 	// By default calls Tick() every frame.
	PrimaryActorTick.bCanEverTick = true;

	StateMachineComponent = CreateDefaultSubobject<USNGStateMachineComponent>(TEXT("StateMachineComponent"));
	HealthComponent = CreateDefaultSubobject<USNGHealthComponent>(TEXT("HealthComponent"));
	
	GetCapsuleComponent()->SetCollisionResponseToChannel(COLLISION_RANGED_WEAPON, ECR_Ignore);
	GetCapsuleComponent()->SetCollisionResponseToChannel(COLLISION_MELEE_WEAPON, ECR_Ignore);
	
	EnableAllActions();
	
	AController* MyController = GetController();
	if(MyController)
	{
		GetController()->SetIgnoreMoveInput(false);
	}
}

void ASNGCharacterBase::SetupRotateTowardsTarget(FRotator TargetRotation, float DegreesPerSecond, float MaxRotation)
{
	RotateTimeElapsed = 0.0f;
	InitialRot = GetActorRotation();
	this->TargetRot = TargetRotation;
	DegPerSecond = DegreesPerSecond;
	this->MaxRot = MaxRotation;

	RotateTime = FMath::Min(FMath::Abs(TargetRotation.Yaw - InitialRot.Yaw), MaxRotation) / DegreesPerSecond;
}

void ASNGCharacterBase::RotateTowardsTarget(float DeltaTime)
{
	if(RotateTimeElapsed < RotateTime)
	{
		SetActorRotation(UKismetMathLibrary::RInterpTo_Constant(GetActorRotation(), TargetRot,
		DeltaTime, DegPerSecond));

		RotateTimeElapsed += DeltaTime;
	}
}

void ASNGCharacterBase::SetIgnoreMovementInput(bool IsIgnored)
{
	AController* MyController = GetController();
	if(MyController)
	{
		GetController()->SetIgnoreMoveInput(IsIgnored);
	}
}

void ASNGCharacterBase::DisableAllActions()
{
	bCanMove = false;
	bCanJump = false;
	bCanDodge = false;
	bCanAttack = false;
}

void ASNGCharacterBase::EnableAllActions()
{
	bCanMove = true;
	bCanJump = true;
	bCanDodge = true;
	bCanAttack = true;
}

void ASNGCharacterBase::SetCanMove(bool CanMove)
{
	bCanMove = CanMove;
}

bool ASNGCharacterBase::CanMove()
{
	return bCanMove;
}

void ASNGCharacterBase::SetCanCharacterJump(bool CanJump)
{
	bCanJump = CanJump;
}

bool ASNGCharacterBase::CanCharacterJump()
{
	return bCanJump;
}

void ASNGCharacterBase::SetCanAttack(bool CanAttack)
{
	bCanAttack = CanAttack;
}

bool ASNGCharacterBase::CanAttack()
{
	return bCanAttack;
}

void ASNGCharacterBase::SetCanDodge(bool CanDodge)
{
	bCanDodge = CanDodge;
}

bool ASNGCharacterBase::CanDodge()
{
	return bCanDodge;
}

void ASNGCharacterBase::ApplyHitStop(float HitStopTime, float HitSlowTime)
{
	CurrHitStopTime = HitStopTime;
	CurrHitSlowTime = HitSlowTime;
	bIsInHitStop = true;
}

void ASNGCharacterBase::PushEvent_Implementation(FSNGEvent Event)
{
	if(StateMachineComponent)
	{
		StateMachineComponent->PushEvent(Event);
	}
}

ASNGWeaponBase* ASNGCharacterBase::GetCurrentWeapon_Implementation()
{
	return nullptr;
}

void ASNGCharacterBase::ReceiveHit(FSNGHitData const& HitData, AController* EventInstigator, AActor* DamageCauser)
{
	AActor* Causer = EventInstigator->GetPawn();

	if(HitData.HitstopApplicationType == ESNGHitstopApplicationType::Both ||
		HitData.HitstopApplicationType == ESNGHitstopApplicationType::ReceiverOnly)
	{
		ApplyHitStop(HitData.StopTime, HitData.SlowTime);
	}
	
	FVector AdjustedLaunchDir;
	switch (HitData.LaunchDirectionType)
	{
		case ESNGLaunchDirectionType::UseCauserRotation:
			{
				FRotator CauserRotationYaw(0.0f, Causer->GetActorRotation().Yaw, 0.0f);
				AdjustedLaunchDir = CauserRotationYaw.RotateVector(HitData.LaunchDirection);
			}
			break;
		case ESNGLaunchDirectionType::UseLookAtRotation:
			{
				FRotator LookAtRotation = UKismetMathLibrary::FindLookAtRotation(Causer->GetActorLocation(), GetActorLocation());
				FRotator LookAtRotationYaw(0.0f, LookAtRotation.Yaw, 0.0f);
				AdjustedLaunchDir = LookAtRotationYaw.RotateVector(HitData.LaunchDirection);
			}
			break;
		default:
			{
				AdjustedLaunchDir = HitData.LaunchDirection;
			}
			break;
	}

	switch (HitData.LaunchType)
	{
		case ESNGLaunchType::OnlyInAir:
			{
				bool IsInAir = GetMovementComponent()->IsFalling();
				if(IsInAir)
				{
					AdjustedLaunchDir *= HitData.LaunchForce;
					LaunchCharacter(AdjustedLaunchDir, true, true);
				}
				else
				{
					AdjustedLaunchDir = FVector(AdjustedLaunchDir.X, AdjustedLaunchDir.Y, 0.0f);
					LaunchCharacter(AdjustedLaunchDir, true, true);
				}
			}
			break;
		case ESNGLaunchType::Always:
			{
				AdjustedLaunchDir *= HitData.LaunchForce;
				LaunchCharacter(AdjustedLaunchDir, true, true);
			}
			break;
		default:
			{
				
			}
			break;
	}
}

float ASNGCharacterBase::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator,
                                    AActor* DamageCauser)
{
	Super::TakeDamage(Damage, DamageEvent, EventInstigator, DamageCauser);
	
	if(HealthComponent)
	{
		HealthComponent->SubtractHealth(Damage);
	}

	if (DamageEvent.IsOfType(FSNGDamageEvent::ClassID))
	{
		// point damage event, pass off to helper function
		FSNGDamageEvent* const MyDamageEvent = (FSNGDamageEvent*) &DamageEvent;
		ReceiveHit(MyDamageEvent->HitData, EventInstigator, DamageCauser);

		FSNGEvent Event;
		Event.EventType = ESNGEventType::Damage;
		Event.DamageData.HitData = MyDamageEvent->HitData;
		Event.DamageData.EventInstigator = EventInstigator;
		Event.DamageData.DamageCauser = DamageCauser;
		Event.DamageData.SurfaceType = MyDamageEvent->SurfaceType;
		Event.DamageData.ImpactPoint = MyDamageEvent->ImpactPoint;
		PushEvent(Event);
	}
	
	return Damage;
}

void ASNGCharacterBase::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);
	
	RotateTowardsTarget(DeltaSeconds);
	
	if(bIsInHitStop)
	{
		CurrHitStopTime = FMath::Clamp(CurrHitStopTime - GetWorld()->GetDeltaSeconds(), 0.0f, CurrHitStopTime);
		if(CurrHitStopTime > 0.0f)
		{
			CustomTimeDilation = 0.0f;
		}
		else
		{
			CustomTimeDilation = 0.01;
			CurrHitSlowTime = FMath::Clamp(CurrHitSlowTime - GetWorld()->GetDeltaSeconds(), 0.0f, CurrHitSlowTime);
			if(CurrHitSlowTime == 0.0f)
			{
				CustomTimeDilation = 1.0f;
				bIsInHitStop = false;
			}
		}
	}
}

void ASNGCharacterBase::PostInitializeComponents()
{
	Super::PostInitializeComponents();

	SNGCharacterMovementComponent = Cast<USNGCharacterMovementComponent>(Super::GetMovementComponent());
}

