// © 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 "AnimInstances/OLSBaseLayerAnimInstance.h"

#include "KismetAnimationLibrary.h"
#include "Components/OLSLocomotionComponent.h"
#include "Libraries/OLSLocomotionBPLibrary.h"
#include "GameFramework/PawnMovementComponent.h"
#include "Interfaces/OLSMoveableInterface.h"
#include "Kismet/KismetSystemLibrary.h"
#if WITH_EDITOR
#include "Misc/DataValidation.h"
#endif

#if ENABLE_LOCOMOTION_DEBUG
static TAutoConsoleVariable<bool> CVarAnimInstanceLocomotionDebug(TEXT("a.AnimInstance.Locomotion.Debug"), false, TEXT("Turn on visualization debugging for Locomotion"));
#endif

void UOLSBaseLayerAnimInstance::NativeInitializeAnimation()
{
	// Super::NativeInitializeAnimation();
	// Don't call Super since it is empty.

	if (const TObjectPtr<APawn> owningPawn = TryGetPawnOwner())
	{
		OwningPawn = owningPawn;
		MovementComponent = owningPawn->GetMovementComponent();
		MoveableInterface.SetObject(owningPawn);
		MoveableInterface.SetInterface(Cast<IOLSMoveableInterface>(owningPawn));
		if (MoveableInterface)
		{
			LocomotionComponent = MoveableInterface->GetLocomotionComponent();
		}
	}
}

void UOLSBaseLayerAnimInstance::NativeUninitializeAnimation()
{
	// Super::NativeUninitializeAnimation();
	// Don't call Super since it is empty.

	if (LocomotionComponent)
	{
		LocomotionComponent->GetOnStanceChangedNativeDelegate().RemoveAll(this);
		
		LocomotionComponent->GetOnGaitChangedNativeDelegate().RemoveAll(this);

		LocomotionComponent->GetOnRotationModeChangedNativeDelegate().RemoveAll(this);
	}
}

void UOLSBaseLayerAnimInstance::NativeBeginPlay()
{
	Super::NativeBeginPlay();

	if (LocomotionComponent)
	{
		LocomotionComponent->GetOnStanceChangedNativeDelegate().AddWeakLambda(
			this, [this](const EOLSStance& prevStance)
			{
				if (LocomotionComponent)
				{
					Stance = LocomotionComponent->GetStance();
				}
			});
		
		LocomotionComponent->GetOnDesiredGaitChangedNativeDelegate().AddWeakLambda(
			this, [this](const EOLSGait& prevDesiredGait)
			{
				if (LocomotionComponent)
				{
					DesiredGait = LocomotionComponent->GetDesiredGait();
				}
			});
		
		LocomotionComponent->GetOnGaitChangedNativeDelegate().AddWeakLambda(
			this, [this](const EOLSGait& prevGait)
			{
				if (LocomotionComponent)
				{
					PrevGait = prevGait;
					Gait = LocomotionComponent->GetGait();
					bHasGaitChanged = true;
				}
			});

		LocomotionComponent->GetOnRotationModeChangedNativeDelegate().AddWeakLambda(
			this, [this](const EOLSRotationMode& prevRotationMode)
			{
				if (LocomotionComponent)
				{
					RotationMode = LocomotionComponent->GetRotationMode();
					bHasRotationModeChanged = true;
				}
			});
	}
}

void UOLSBaseLayerAnimInstance::NativeUpdateAnimation(float deltaSeconds)
{
	// Super::NativeUpdateAnimation(deltaSeconds);
	// Don't call Super since it is empty.
	
	if (OwningPawn && MoveableInterface && MovementComponent)
	{
		NativeUpdateLocationData(OwningPawn, deltaSeconds);
		NativeUpdateRotationData(OwningPawn, deltaSeconds);
		NativeUpdateVelocityData(MoveableInterface, OwningPawn, deltaSeconds);
		NativeUpdateAccelerationData(OwningPawn, deltaSeconds);
		NativeUpdateCharacterStateData(MoveableInterface, MovementComponent, deltaSeconds);
		NativeUpdateAimingData(OwningPawn, deltaSeconds);
	}
}

void UOLSBaseLayerAnimInstance::NativeThreadSafeUpdateAnimation(float deltaSeconds)
{
	// Super::NativeThreadSafeUpdateAnimation(deltaSeconds);
	// Don't call Super since it is empty.
	
	NativeThreadSafeUpdateLocationData(deltaSeconds);
	NativeThreadSafeUpdateRotationData(deltaSeconds);
	NativeThreadSafeUpdateVelocityData(deltaSeconds);
	NativeThreadSafeUpdateAccelerationData(deltaSeconds);
	NativeThreadSafeUpdateWallDetectionHeuristic(deltaSeconds);
	NativeThreadSafeUpdateCharacterStateData(deltaSeconds);
	NativeThreadSafeUpdateAimingData(deltaSeconds);
	NativeThreadSafeUpdateRootYawOffset(deltaSeconds);

	bIsFirstUpdate = false;
}

void UOLSBaseLayerAnimInstance::NativePostEvaluateAnimation()
{
	// Don't call Super since it's empty.
	
#if WITH_EDITOR
	const TObjectPtr<AActor> owningActor = GetOwningActor();
	if (owningActor)
	{
		const FVector& actorLocation = owningActor->GetActorLocation();
		const FVector& actorForwardDir = owningActor->GetActorForwardVector() * 100.f;

		UKismetSystemLibrary::DrawDebugArrow(this, actorLocation, actorLocation + actorForwardDir, 0.f, FLinearColor::Black, 0.f, 1.f);

		const TObjectPtr<USkeletalMeshComponent> owningComponent = GetOwningComponent();
		if (owningComponent)
		{
			const FVector& rightDir = UKismetMathLibrary::GetRightVector(owningComponent->GetSocketRotation(SKEL_RootBoneName)) * 100.f;
			UKismetSystemLibrary::DrawDebugArrow(this, actorLocation, actorLocation + rightDir, 0.f, FLinearColor::Red, 0.f, 1.f);
		}
	}
#endif
}

void UOLSBaseLayerAnimInstance::NativeUpdateRotationData(APawn* owningPawn, const float deltaSeconds)
{
	OwningPawnRotation = owningPawn->GetActorRotation();
}

void UOLSBaseLayerAnimInstance::NativeUpdateLocationData(APawn* owningPawn, const float deltaSeconds)
{
	OwningPawnLocation = owningPawn->GetActorLocation();
}

void UOLSBaseLayerAnimInstance::NativeUpdateVelocityData(const TScriptInterface<IOLSMoveableInterface>& moveableInterface,
													     APawn* owningPawn, const float deltaSeconds)
{
	OwningPawnVelocity = owningPawn->GetVelocity();
}

void UOLSBaseLayerAnimInstance::NativeUpdateAccelerationData(APawn* owningPawn, const float deltaSeconds)
{
	OwningPawnAcceleration = MoveableInterface->GetCurrentAcceleration();
}

void UOLSBaseLayerAnimInstance::NativeUpdateCharacterStateData(
	const TScriptInterface<IOLSMoveableInterface>& moveableInterface, UPawnMovementComponent* movementComponent,
	const float deltaSeconds)
{
	OwningPawnMovementMode = moveableInterface->GetMovementMode();
	
	bIsOwningPawnOnGround = movementComponent->IsMovingOnGround();
	bIsOwningPawnCrouching = movementComponent->IsCrouching();
	bIsOwningOrientRotationToMovement = moveableInterface->IsOrientRotationToMovement();

	OwningPawnGravityZ = movementComponent->GetGravityZ();
	OwningPawnMaxSpeed = movementComponent->GetMaxSpeed();
}

void UOLSBaseLayerAnimInstance::NativeUpdateAimingData(APawn* owningPawn, const float deltaSeconds)
{
	OwningPawnAimRotation = owningPawn->GetBaseAimRotation();
}

void UOLSBaseLayerAnimInstance::NativeThreadSafeUpdateRotationData(const float deltaSeconds)
{
	if (bIsFirstUpdate)
	{
		YawDeltaSinceLastUpdate = 0.f;
		YawDeltaSpeed = 0.f;
		return;
	}
	
	YawDeltaSinceLastUpdate = OwningPawnRotation.Yaw - WorldRotation.Yaw;
	
	YawDeltaSpeed = UKismetMathLibrary::SafeDivide(YawDeltaSinceLastUpdate, deltaSeconds);
	AdditiveLeanAngle = YawDeltaSpeed * ((bIsOwningPawnCrouching) ? .025f : .0375f);

	WorldRotation = OwningPawnRotation;
	
	// Call custom logic on blueprint.
	BlueprintThreadSafeUpdateRotationData(deltaSeconds);
}

void UOLSBaseLayerAnimInstance::NativeThreadSafeUpdateLocationData(const float deltaSeconds)
{
	if (bIsFirstUpdate)
	{
		DisplacementSinceLastUpdate = 0.f;
		DisplacementSpeed = 0.f;
		return;
	}
	
	DisplacementSinceLastUpdate = (OwningPawnLocation - WorldLocation).Size2D();

	WorldLocation = OwningPawnLocation;

	DisplacementSpeed = UKismetMathLibrary::SafeDivide(DisplacementSinceLastUpdate, deltaSeconds);

	// Call custom logic on blueprint.
	BlueprintThreadSafeUpdateLocationData(deltaSeconds);
}

void UOLSBaseLayerAnimInstance::NativeThreadSafeUpdateVelocityData(const float deltaSeconds)
{
	const bool wasMovingLastUpdate = !LocalVelocity2D.IsNearlyZero(1.e-4f);
	if (wasMovingLastUpdate)
	{
		StopLocalVelocityCardinalDirection = LocalVelocityDirection;
	}
	
	WorldVelocity = OwningPawnVelocity;

	const FVector worldVelocity2D = WorldVelocity * FVector(1.f, 1.f, 0.f);
	LocalVelocity2D = UKismetMathLibrary::LessLess_VectorRotator(worldVelocity2D, WorldRotation);

	LocalVelocityDirectionAngle = UKismetAnimationLibrary::CalculateDirection(worldVelocity2D, WorldRotation);
	LocalVelocityDirectionAngleWithOffset = LocalVelocityDirectionAngle - RootYawOffset;
 
	LocalVelocityDirection = UOLSLocomotionBPLibrary::SelectCardinalDirectionFromAngle(LocalVelocityDirectionAngleWithOffset,
		CardinalDirectionDeadZone,
		LocalVelocityDirection,
		wasMovingLastUpdate);

	LocalVelocityDirectionNoOffset = UOLSLocomotionBPLibrary::SelectCardinalDirectionFromAngle(
		LocalVelocityDirectionAngle,
		CardinalDirectionDeadZone,
		LocalVelocityDirection,
		wasMovingLastUpdate);
	
	bHasVelocity = !FMath::IsNearlyZero(LocalVelocity2D.SizeSquared2D());

	// Call custom logic on blueprint.
	BlueprintThreadSafeUpdateVelocityData(deltaSeconds);
}

void UOLSBaseLayerAnimInstance::NativeThreadSafeUpdateAccelerationData(const float deltaSeconds)
{
	const FVector worldAcceleration2D = OwningPawnAcceleration * FVector(1.f, 1.f, 0.f);

	LocalAcceleration2D = UKismetMathLibrary::LessLess_VectorRotator(worldAcceleration2D, WorldRotation);

	bHasAcceleration = (!FMath::IsNearlyZero(LocalAcceleration2D.SizeSquared2D(), .000001f));

	PivotDirection2D =
		UKismetMathLibrary::Normal(UKismetMathLibrary::VLerp(PivotDirection2D, UKismetMathLibrary::Normal(worldAcceleration2D), .5f));

	CardinalDirectionFromAcceleration = (IsLookingOrAimingDirection()
		                                     ? UOLSLocomotionBPLibrary::GetOppositeCardinalDirectional(
			                                     UOLSLocomotionBPLibrary::SelectCardinalDirectionFromAngle(
				                                     UKismetAnimationLibrary::CalculateDirection(PivotDirection2D, WorldRotation),
				                                     CardinalDirectionDeadZone, EOLSCardinalDirection::EForward))
		                                     : EOLSCardinalDirection::EBackward);

	// Call custom logic on blueprint.
	BlueprintThreadSafeUpdateAccelerationData(deltaSeconds);
}

void UOLSBaseLayerAnimInstance::NativeThreadSafeUpdateWallDetectionHeuristic(const float deltaSeconds)
{
	const bool hasAccelerationPassedThreshold = (LocalAcceleration2D.Size2D() > .1f);
	const bool hasVelocityBelowThreshold = (LocalVelocity2D.Size2D() < 200.f);

	const FVector normalLocalAcceleration2D = UKismetMathLibrary::Normal(LocalAcceleration2D);
	const FVector normalLocalVelocity2D = UKismetMathLibrary::Normal(LocalVelocity2D);
	const bool isDotProductInRange = UKismetMathLibrary::InRange_FloatFloat(
		UKismetMathLibrary::Dot_VectorVector(
			normalLocalAcceleration2D, normalLocalVelocity2D),
			-.6f, .6f);

	bIsRunningIntoWall = hasAccelerationPassedThreshold && hasVelocityBelowThreshold && isDotProductInRange;
	
	// Call custom logic on blueprint.
	BlueprintThreadSafeUpdateWallDetectionHeuristic(deltaSeconds);
}

void UOLSBaseLayerAnimInstance::NativeThreadSafeUpdateCharacterStateData(const float deltaSeconds)
{
	// Locomotion Component Data.
	{
		bHasGaitChanged = (Gait != LastGait);
		LastGait = Gait;

		bHasRotationModeChanged = (RotationMode != LastRotationMode);
		LastRotationMode = RotationMode;
	}
	
	// Crouch state.
	{
		const bool wasCrouchingLastUpdate = bIsCrouching;
		bIsCrouching = bIsOwningPawnCrouching;
		bHasCrouchStateChanged = (bIsCrouching != wasCrouchingLastUpdate);
	}

	// ADS state.
	{
		bHasADSStateChanged = (bGameplayTag_IsADS != bWasADSLastUpdate);
		bWasADSLastUpdate = bGameplayTag_IsADS;
	}

	// Weapon fired state.
	{
		TimeSinceFiredWeapon = (bGameplayTag_IsFiring) ? 0.f : TimeSinceFiredWeapon + deltaSeconds;
	}

	// In air state.
	{
		bIsJumping = false;
		bIsFalling = false;

		if (OwningPawnMovementMode == MOVE_Falling)
		{
			if (WorldVelocity.Z > 0.f)
			{   
				bIsJumping = true;
			}
			else
			{
				bIsFalling = true;
			}

			// Update Jump Fall Data.
			{
				if (bIsJumping)
				{
					TimeToJumpApex = UKismetMathLibrary::SafeDivide(0.f - WorldVelocity.Z, OwningPawnGravityZ);
				}
				else
				{
					TimeToJumpApex = 0.f;
				}
			}
		}
	}
	
	// Call custom logic on blueprint.
	BlueprintThreadSafeUpdateCharacterData(deltaSeconds);
}

void UOLSBaseLayerAnimInstance::NativeThreadSafeUpdateAimingData(const float deltaSeconds)
{
	AimPitch = UKismetMathLibrary::NormalizeAxis(OwningPawnRotation.Pitch);
	
	// Call custom logic on blueprint.
	BlueprintThreadSafeUpdateAimingData(deltaSeconds);
}

void UOLSBaseLayerAnimInstance::NativeThreadSafeUpdateRootYawOffset(const float deltaSeconds)
{
	if (RootYawOffsetMode == EOLSRootYawOffsetMode::EAccumulate)
	{
		SetRootYawOffset(RootYawOffset - YawDeltaSinceLastUpdate);
	}

	if (RootYawOffsetMode == EOLSRootYawOffsetMode::EBlendOut)
	{
		SetRootYawOffset(
			UKismetMathLibrary::FloatSpringInterp(RootYawOffset,
			0.f,
			RootYawOffsetSpringState,
			80.f,
			1.f,
			deltaSeconds,
			1.f,
			.5f));
	}

	RootYawOffsetMode = EOLSRootYawOffsetMode::EBlendOut;
}

UPawnMovementComponent* UOLSBaseLayerAnimInstance::GetMovementComponent() const
{
	return MovementComponent;
}

const FVector& UOLSBaseLayerAnimInstance::GetWorldVelocity() const
{
	return WorldVelocity;
}

const float& UOLSBaseLayerAnimInstance::GetLocalVelocityDirectionAngle() const
{
	return LocalVelocityDirectionAngle;
}

const float& UOLSBaseLayerAnimInstance::GetDisplacementSpeed() const
{
	return DisplacementSpeed;
}

const FVector& UOLSBaseLayerAnimInstance::GetLocalAcceleration2D() const
{
	return LocalAcceleration2D;
}

bool UOLSBaseLayerAnimInstance::IsLookingOrAimingDirection() const
{
	return (IsLookingDirection() || IsAimingDirection());
}

bool UOLSBaseLayerAnimInstance::IsVelocityOrLookingDirection() const
{
	return (IsVelocityDirection() || IsLookingDirection());
}

bool UOLSBaseLayerAnimInstance::IsVelocityOrAimingDirection() const
{
	return (IsVelocityDirection() || IsAimingDirection());
}

bool UOLSBaseLayerAnimInstance::IsAimingDirection() const
{
	return (RotationMode == EOLSRotationMode::EAiming);
}

bool UOLSBaseLayerAnimInstance::IsLookingDirection() const
{
	return (RotationMode == EOLSRotationMode::ELookingDirection);
}

bool UOLSBaseLayerAnimInstance::IsVelocityDirection() const
{
	return (RotationMode == EOLSRotationMode::EVelocityDirection);
}

bool UOLSBaseLayerAnimInstance::IsCrouching() const
{
	return bIsCrouching;
}

bool UOLSBaseLayerAnimInstance::HasVelocity() const
{
	return bHasVelocity;
}

bool UOLSBaseLayerAnimInstance::HasAcceleration() const
{
	return bHasAcceleration;
}

bool UOLSBaseLayerAnimInstance::ShouldDistanceMatchStop() const
{
	return HasVelocity() && !HasAcceleration();
}

bool UOLSBaseLayerAnimInstance::IsMovingPerpendicularToInitialPivot() const
{
	const bool isPivotFwdOrBwd = (PivotInitialDirection == EOLSCardinalDirection::EForward || PivotInitialDirection == EOLSCardinalDirection::EBackward);
	const bool isVelocityFwdOrBwd = (LocalVelocityDirection == EOLSCardinalDirection::EForward || LocalVelocityDirection == EOLSCardinalDirection::EBackward);
	const bool isPivotLeftOrRight = (PivotInitialDirection == EOLSCardinalDirection::ELeft || PivotInitialDirection == EOLSCardinalDirection::ERight);
	const bool isVelocityLeftOrRight = (LocalVelocityDirection == EOLSCardinalDirection::ELeft || LocalVelocityDirection == EOLSCardinalDirection::ERight);
	return (isPivotFwdOrBwd && !isVelocityFwdOrBwd) || (isPivotLeftOrRight && !isVelocityLeftOrRight);
}

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

const float& UOLSBaseLayerAnimInstance::GetOwningPawnMaxSpeed() const
{
	return OwningPawnMaxSpeed;
}

void UOLSBaseLayerAnimInstance::SetRootYawOffset(const float rootYawOffset)
{
	if (!bEnableRootYawOffset)
	{
		RootYawOffset = 0.f;
		AimYaw = 0.f;
		return;
	}

	const float normalRootYawOffset = UKismetMathLibrary::NormalizeAxis(rootYawOffset);
	RootYawOffset = (RootYawOffsetAngleClamp.X == RootYawOffsetAngleClamp.Y) ? normalRootYawOffset : FMath::Clamp(normalRootYawOffset, RootYawOffsetAngleClamp.X, RootYawOffsetAngleClamp.Y);

	AimYaw = RootYawOffset * -1.f;
}

void UOLSBaseLayerAnimInstance::ProcessTurnYawCurve()
{
	float previousTurnYawCurveValue = TurnYawCurveValue;
	const float turnYawWeightCurveValue = GetCurveValue(TurnYawWeightCurveName);
	if (FMath::IsNearlyZero(turnYawWeightCurveValue, 0.0001f))
	{
		TurnYawCurveValue = 0.f;
		previousTurnYawCurveValue = 0.f;
		return;
	}

	const float remainingTurnYawCurveValue = GetCurveValue(RemainingTurnYawCurveName);
	TurnYawCurveValue = UKismetMathLibrary::SafeDivide(remainingTurnYawCurveValue, turnYawWeightCurveValue);

	// Avoid applying the curve delta when the curve first becomes relevant. E.g.
	// When a turn animation starts, the previous curve value will be 0 and the current value will be 90,
	// but no actual rotation has happened yet.
	if (previousTurnYawCurveValue != 0.f)
	{
		// Reduce the target yaw offset by the amount of rotation from the turn animation.
		SetRootYawOffset(RootYawOffset - (TurnYawCurveValue - previousTurnYawCurveValue));		
	}
}

void UOLSBaseLayerAnimInstance::ProcessTurnYawCurve_ForwardFacing()
{
	float previousTurnYawCurveValue = TurnYawCurveValue;
	const float turnYawWeightCurveValue = GetCurveValue(TurnYawWeightCurveName);
	if (FMath::IsNearlyZero(turnYawWeightCurveValue, 0.0001f))
	{
		TurnYawCurveValue = 0.f;
		previousTurnYawCurveValue = 0.f;

		// When the animation blends in, we could have near-zero weights.
		// When this value is goes up, and then down to 0, we know.
		if (MaxTurnYawValue != 0.f)
		{
			bHasReachedEndTurn = true;
		}
	}
	else
	{
		const float remainingTurnYawCurveValue = GetCurveValue(RemainingTurnYawCurveName);
		TurnYawCurveValue = UKismetMathLibrary::SafeDivide(remainingTurnYawCurveValue, turnYawWeightCurveValue);

		if (FMath::Abs(TurnYawCurveValue) > FMath::Abs(MaxTurnYawValue))
		{
			MaxTurnYawValue = TurnYawCurveValue;
		}

		// Avoid applying the curve delta when the curve first becomes relevant. E.g.
		// When a turn animation starts, the previous curve value will be 0 and the current value will be 90,
		// but no actual rotation has happened yet.
		if (previousTurnYawCurveValue != 0.f)
		{
			const float actualAngle = UKismetMathLibrary::SafeDivide(LocalVelocityDirectionAngleWithOffset,
			                                                         TurnYawCurveValue) * (TurnYawCurveValue - previousTurnYawCurveValue);
			const float minAngle = FMath::Abs(LocalVelocityDirectionAngleWithOffset) * -1.f;
			const float maxAngle = FMath::Abs(LocalVelocityDirectionAngleWithOffset);
			// Reduce the target yaw offset by the amount of rotation from the turn animation.
			SetRootYawOffset(RootYawOffset - FMath::Clamp(actualAngle, minAngle, maxAngle));
		}
	}	
}

float UOLSBaseLayerAnimInstance::CalculateMeshSpaceBlendWeight(const FName localSpaceLayerCurveName) const
{
	// If this condition fails so MeshSpace will be prioritized.
	if (localSpaceLayerCurveName.IsNone())
	{
		return 1.f;
	}

	const int32 localSpaceCurveValue = FMath::FloorToInt(GetCurveValue(localSpaceLayerCurveName));
	return 1 - localSpaceCurveValue;
}

UOLSLocomotionComponent* UOLSBaseLayerAnimInstance::ThreadSafeGetLocomotionComponent() const
{
	return LocomotionComponent;
}
