// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.


#include "Components/OLSLocomotionComponent.h"

#include "Data/OLSEnumLibrary.h"
#include "Interfaces/OLSAnimationInterface.h"
#include "Interfaces/OLSMoveableInterface.h"
#include "Kismet/KismetMathLibrary.h"


UOLSLocomotionComponent::UOLSLocomotionComponent(const FObjectInitializer& objectInitializer)
{
	PrimaryComponentTick.bCanEverTick = true;
}

void UOLSLocomotionComponent::PostInitProperties()
{
	Super::PostInitProperties();

	Gaits.Add(EOLSGait::EWalk, 0.f);
	Gaits.Add(EOLSGait::EJog, 0.f);
	Gaits.Add(EOLSGait::ESprint, 0.f);
}

// Called every frame
void UOLSLocomotionComponent::TickComponent(float deltaTime, ELevelTick tickType,
                                            FActorComponentTickFunction* thisTickFunction)
{
	Super::TickComponent(deltaTime, tickType, thisTickFunction);

	// ...

	UpdateEssentialValues(deltaTime);
	UpdateGait(deltaTime);
}

void UOLSLocomotionComponent::Initialize()
{
	if (const TObjectPtr<APawn> owningPawn = Cast<APawn>(GetOwner()))
	{
		OwningPawn = owningPawn;
		MoveableInterface.SetObject(owningPawn);
		MoveableInterface.SetInterface(Cast<IOLSMoveableInterface>(owningPawn));

		if (const IOLSAnimationInterface* animInterface = Cast<IOLSAnimationInterface>(owningPawn))
		{
			OwningAnimInstance = animInterface->GetAnimInstance();
		}
	}

	MaxSpeed = Gaits.FindChecked(EOLSGait::EJog);

	SetDesiredGait(DesiredGait, true);
	SetRotationMode(DesiredRotationMode, true);
}

EOLSGait UOLSLocomotionComponent::GetAllowedGait() const
{
	if (Stance == EOLSStance::EStanding)
	{
		if (RotationMode != EOLSRotationMode::EAiming)
		{
			if (DesiredGait == EOLSGait::ESprint)
			{
				return CanSprint() ? EOLSGait::ESprint : EOLSGait::EJog;
			}
			return DesiredGait;
		}
	}

	if (DesiredGait == EOLSGait::ESprint)
	{
		return EOLSGait::EJog;
	}

	return DesiredGait;
}

EOLSGait UOLSLocomotionComponent::GetActualGait(const EOLSGait& allowedLocomotionState) const
{
	// This should never happen.
	if (Gaits.IsEmpty())
	{
		return DesiredGait;
	}

	if (allowedLocomotionState == EOLSGait::ESprint)
	{
		return EOLSGait::ESprint;
	}
	else if (allowedLocomotionState == EOLSGait::EJog && CanJog())
	{
		return EOLSGait::EJog;
	}

	return EOLSGait::EWalk;
}

void UOLSLocomotionComponent::UpdateEssentialValues(const float deltaTime)
{
	if (!OwningPawn || !MoveableInterface)
	{
		return;
	}
	
	// These values represent how the capsule is moving as well as how it wants to move, and therefore are essential
	// for any data driven animation system. They are also used throughout the system for various functions,
	// so I found it is easiest to manage them all in one place.
	
	const FVector& currentVel = GetVelocity();
	
	// Set the amount of Acceleration.
	Acceleration = MoveableInterface->GetCurrentAcceleration();

	// Determine if the character is moving by getting it's speed. The Speed equals the length of the horizontal (x y)
	// velocity, so it does not take vertical movement into account. If the character is moving, update the last
	// velocity rotation. This value is saved because it might be useful to know the last orientation of movement
	// even after the character has stopped.
	Speed = currentVel.Size2D();
	
	// Determine if the character has movement input by getting its movement input amount.
	// The Movement Input Amount is equal to the current acceleration divided by the max acceleration so that
	// it has a range of 0-1, 1 being the maximum possible amount of input, and 0 being none.
	// If the character has movement input, update the Last Movement Input Rotation.
	MovementInputAmount = FMath::GetMappedRangeValueClamped(FVector2D(0.f, MoveableInterface->GetMaxAcceleration()),
																 FVector2D(0.f, 1.f),
																 MoveableInterface->GetCurrentAcceleration().Size());
}

void UOLSLocomotionComponent::UpdateGait(const float deltaTime)
{
	const EOLSGait allowedLocomotionState = GetAllowedGait();

	const EOLSGait actualLocomotionState = GetActualGait(allowedLocomotionState);

	SetGait(actualLocomotionState);
}

FVector UOLSLocomotionComponent::GetVelocity() const
{
	return (OwningPawn ? OwningPawn->GetVelocity() : FVector::ZeroVector);
}

float UOLSLocomotionComponent::GetAnimCurve(const FName& curveName) const
{
	return (curveName.IsValid() && OwningAnimInstance ? OwningAnimInstance->GetCurveValue(curveName) : 0.f);
}

const EOLSStance& UOLSLocomotionComponent::GetStance() const
{
	return Stance;
}

void UOLSLocomotionComponent::SetStance(EOLSStance newStance, bool shouldForce)
{
	if (shouldForce || Stance != newStance)
	{
		const EOLSStance prev = Stance;
		Stance = newStance;
		OnStanceChanged(prev);
	}
}

const EOLSGait& UOLSLocomotionComponent::GetGait() const
{
	return Gait;
}

const EOLSGait& UOLSLocomotionComponent::GetDesiredGait() const
{
	return DesiredGait;
}

void UOLSLocomotionComponent::SetGait(EOLSGait newGait, bool shouldForce)
{
	if (shouldForce || Gait != newGait)
	{
		const EOLSGait prev = Gait;
		Gait = newGait;
		OnGaitChanged(prev);

		if (MoveableInterface)
		{
			MoveableInterface->SetMaxWalkSpeed(Gaits.FindChecked(newGait));
		}
	}
}

const EOLSRotationMode& UOLSLocomotionComponent::GetRotationMode() const
{
	return RotationMode;
}

void UOLSLocomotionComponent::SetRotationMode(EOLSRotationMode newRotationMode, bool shouldForce)
{
	if (shouldForce || RotationMode != newRotationMode)
	{
		const EOLSRotationMode prevRotationMode = RotationMode;
		RotationMode = newRotationMode;
		OnRotationModeChanged(prevRotationMode);
	}
}

void UOLSLocomotionComponent::SetDesiredGait(const EOLSGait newGait, const bool shouldForce /* = false */)
{
	if (shouldForce || DesiredGait != newGait)
	{
		const EOLSGait& prevDesiredGait = DesiredGait;
		DesiredGait = newGait;
		OnDesiredGaitChanged(prevDesiredGait);
	}
}

void UOLSLocomotionComponent::SetDesiredStance(const EOLSStance newStance)
{
	DesiredStance = newStance;
}

void UOLSLocomotionComponent::SetDesiredRotationMode(const EOLSRotationMode newRotationMode)
{
	DesiredRotationMode = newRotationMode;
}

void UOLSLocomotionComponent::OnStanceChanged(EOLSStance previousStance)
{
	OnStanceChangedNativeDelegate.Broadcast(previousStance);
}

void UOLSLocomotionComponent::OnDesiredGaitChanged(EOLSGait previousDesiredGait)
{
	OnDesiredGaitChangedNativeDelegate.Broadcast(previousDesiredGait);
}

void UOLSLocomotionComponent::OnGaitChanged(EOLSGait previousGait)
{
	OnGaitChangedNativeDelegate.Broadcast(previousGait);
}

void UOLSLocomotionComponent::OnRotationModeChanged(EOLSRotationMode prevRotationMode)
{
	OnRotationModeChangedNativeDelegate.Broadcast(prevRotationMode);
}

bool UOLSLocomotionComponent::CanSprint() const
{
	if (RotationMode == EOLSRotationMode::EAiming || !MoveableInterface)
	{
		return false;
	}

	const bool hasValidInput = MovementInputAmount > 0.9f;
	if (RotationMode == EOLSRotationMode::EVelocityDirection)
	{
		return hasValidInput;
	}

	if (RotationMode == EOLSRotationMode::ELookingDirection)
	{
		const TObjectPtr<APawn> owningPawn = Cast<APawn>(GetOwner());
		if (owningPawn)
		{
			const FRotator velocityRot = owningPawn->GetVelocity().ToOrientationRotator();
			FRotator delta = velocityRot - owningPawn->GetControlRotation();
			delta.Normalize();
			return (hasValidInput && FMath::Abs(delta.Yaw) < 50.f);
		}
	}
	
	return false;
}

bool UOLSLocomotionComponent::CanJog() const
{
	if (!MoveableInterface)
	{
		return false;
	}
	
	return (MovementInputAmount > .8f);
}

FOLSStanceNativeDelegate& UOLSLocomotionComponent::GetOnStanceChangedNativeDelegate()
{
	return OnStanceChangedNativeDelegate;
}

FOLSGaitNativeDelegate& UOLSLocomotionComponent::GetOnDesiredGaitChangedNativeDelegate()
{
	return OnDesiredGaitChangedNativeDelegate;
}

FOLSGaitNativeDelegate& UOLSLocomotionComponent::GetOnGaitChangedNativeDelegate()
{
	return OnGaitChangedNativeDelegate;
}

FOLSRotationModeStateNativeDelegate& UOLSLocomotionComponent::GetOnRotationModeChangedNativeDelegate()
{
	return OnRotationModeChangedNativeDelegate;
}
