// Copyright Epic Games, Inc. All Rights Reserved.

#include "ForestryCharacter.h"

#include "DebugMessages.h"
#include "Animation/AnimInstance.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "EnhancedInputComponent.h"
#include "InputActionValue.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Forestry.h"

AForestryCharacter::AForestryCharacter()
{
	// Set size for collision capsule
	GetCapsuleComponent()->InitCapsuleSize(55.f, 96.0f);
	
	// Create the first person mesh that will be viewed only by this character's owner
	FirstPersonMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("First Person Mesh"));

	FirstPersonMesh->SetupAttachment(GetMesh());
	FirstPersonMesh->SetOnlyOwnerSee(true);
	FirstPersonMesh->FirstPersonPrimitiveType = EFirstPersonPrimitiveType::FirstPerson;
	FirstPersonMesh->SetCollisionProfileName(FName("NoCollision"));

	// Create the Camera Component	
	FirstPersonCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("First Person Camera"));
	FirstPersonCameraComponent->SetupAttachment(FirstPersonMesh, FName("head"));
	FirstPersonCameraComponent->SetRelativeLocationAndRotation(FVector(-2.8f, 5.89f, 0.0f), FRotator(0.0f, 90.0f, -90.0f));
	FirstPersonCameraComponent->bUsePawnControlRotation = true;
	FirstPersonCameraComponent->bEnableFirstPersonFieldOfView = true;
	FirstPersonCameraComponent->bEnableFirstPersonScale = true;
	FirstPersonCameraComponent->FirstPersonFieldOfView = 70.0f;
	FirstPersonCameraComponent->FirstPersonScale = 0.6f;

	// configure the character comps
	GetMesh()->SetOwnerNoSee(true);
	GetMesh()->FirstPersonPrimitiveType = EFirstPersonPrimitiveType::WorldSpaceRepresentation;

	GetCapsuleComponent()->SetCapsuleSize(34.0f, 96.0f);

	// Interactable Searcher
	InteractableSearcher = CreateDefaultSubobject<UInteractableSearcher>("Interactable Searcher");

	// Statistics Component
	StatisticsComponent = CreateDefaultSubobject<UStatisticsComponent>("Statistics");

	// Flashlight Component
	FlashlightComponent = CreateDefaultSubobject<UFlashlightComponent>("Flashlight");

	// Crouch Helper
	CrouchHelper = CreateDefaultSubobject<UCrouchHelper>("Crouch Helper");
	CrouchHelper->Setup(GetCapsuleComponent());

	// Configure character movement
	GetCharacterMovement()->BrakingDecelerationFalling = 1500.0f;
	GetCharacterMovement()->AirControl = 0.5f;
	GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;

	// Physics Handle
	PhysicsHandleComponent = CreateDefaultSubobject<UPhysicsHandleComponent>("PhysicsHandle");
	PhysicsHandleComponent->LinearStiffness = 2000.f;
	PhysicsHandleComponent->AngularStiffness = 2000.f;
	PhysicsHandleComponent->LinearDamping = 100.f;
	PhysicsHandleComponent->AngularDamping = 100.f;
}

bool AForestryCharacter::CanStand() const
{
	if (!GetCapsuleComponent()) return false;

	UCapsuleComponent* Capsule = GetCapsuleComponent();
	float CurrentHalfHeight = Capsule->GetUnscaledCapsuleHalfHeight();
	float DesiredHalfHeight = CrouchHelper->GetStandHeight();

	if (CurrentHalfHeight >= DesiredHalfHeight) 
		return true;

	float Radius = Capsule->GetUnscaledCapsuleRadius();

	FVector Start = Capsule->GetComponentLocation() + FVector(0,0,CurrentHalfHeight);
	FVector End = Capsule->GetComponentLocation() + FVector(0,0,DesiredHalfHeight);

	FHitResult Hit;
	FCollisionQueryParams Params;
	Params.AddIgnoredActor(this);

	bool bHit = GetWorld()->SweepSingleByChannel(
		Hit,
		Start,
		End,
		FQuat::Identity,
		ECC_Visibility,
		FCollisionShape::MakeCapsule(Radius, DesiredHalfHeight - CurrentHalfHeight),
		Params
	);

	return !bHit;
}

void AForestryCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{	
	// Set up action bindings
	if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
	{
		// Crouching
		EnhancedInputComponent->BindAction(CrouchAction, ETriggerEvent::Started, this, &AForestryCharacter::DoCrouchStart);
		EnhancedInputComponent->BindAction(CrouchAction, ETriggerEvent::Completed, this, &AForestryCharacter::DoCrouchEnd);
		
		// Sprinting
		EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Started, this, &AForestryCharacter::DoSprintStart);
		EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Completed, this, &AForestryCharacter::DoSprintEnd);
		
		// Jumping
		EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &AForestryCharacter::DoJumpStart);
		EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &AForestryCharacter::DoJumpEnd);

		// Moving
		EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AForestryCharacter::MoveInput);

		// Looking/Aiming
		EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AForestryCharacter::LookInput);
		EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this, &AForestryCharacter::LookInput);

		if (InteractableSearcher) InteractableSearcher->SetupPlayerInput(EnhancedInputComponent);
		if (FlashlightComponent) FlashlightComponent->SetupPlayerInput(EnhancedInputComponent);
	}
	else
	{
		UE_LOG(LogForestry, Error, TEXT("'%s' Failed to find an Enhanced Input Component! This template is built to use the Enhanced Input system. If you intend to use the legacy system, then you will need to update this C++ file."), *GetNameSafe(this));
	}
}

void AForestryCharacter::TickActor(float DeltaTime, ELevelTick TickType, FActorTickFunction& ThisTickFunction)
{
	Super::TickActor(DeltaTime, TickType, ThisTickFunction);
		
	if (bIsCrouching)
	{
		if (bWannaStand)
		{
			if (!CanStand()) return;

			bIsCrouching = false;
			bWannaStand = false;
			CrouchHelper->Stand();
			ChangeMoveSpeed(WalkSpeed);
		}
	}
}


void AForestryCharacter::MoveInput(const FInputActionValue& Value)
{
	// get the Vector2D move axis
	FVector2D MovementVector = Value.Get<FVector2D>();

	// pass the axis values to the move input
	DoMove(MovementVector.X, MovementVector.Y);
	
	// bool bIsMoving = MovementVector != FVector2D::ZeroVector;
}

void AForestryCharacter::LookInput(const FInputActionValue& Value)
{
	// get the Vector2D look axis
	FVector2D LookAxisVector = Value.Get<FVector2D>();

	// pass the axis values to the aim input
	DoAim(LookAxisVector.X, LookAxisVector.Y);
}

void AForestryCharacter::ChangeMoveSpeed(const float Speed) const
{
	GetCharacterMovement()->MaxWalkSpeed = Speed;
}

void AForestryCharacter::DoAim(float Yaw, float Pitch)
{
	if (GetController())
	{
		// pass the rotation inputs
		AddControllerYawInput(Yaw);
		AddControllerPitchInput(Pitch);
	}
}

void AForestryCharacter::DoMove(float Right, float Forward)
{
	if (GetController())
	{
		// pass the move inputs
		AddMovementInput(GetActorRightVector(), Right);
		AddMovementInput(GetActorForwardVector(), Forward);
	}
}

void AForestryCharacter::DoJumpStart()
{
	// pass Jump to the character
	if (!bIsCrouching) Jump();
}

void AForestryCharacter::DoJumpEnd()
{
	// pass StopJumping to the character
	StopJumping();
}

void AForestryCharacter::DoSprintStart()
{
	if (StatisticsComponent->GetStatisticCurrentValue(EStatisticType::STAMINA) < MinStaminaToSprint)
	{
		// TODO: Notify that stamina is too low!
		printk(110, "Character: Stamina is too low to sprint!")
		return;
	}

	if (bIsCrouching) return;
	if (!GetCharacterMovement()->IsMovingOnGround()) return;

	bIsSprinting = true;
	StatisticsComponent->SetDrainMultiplierForType(EStatisticType::STAMINA, StatisticsComponent->StaminaSprintMultiplier);
	StatisticsComponent->SetDrainMultiplierForType(EStatisticType::THIRST, StatisticsComponent->ThirstSprintMultiplier);
	ChangeMoveSpeed(SprintSpeed);
}

void AForestryCharacter::DoSprintEnd()
{
	if (!bIsSprinting) return;
	bIsSprinting = false;

	StatisticsComponent->SetDrainMultiplierForType(EStatisticType::STAMINA, StatisticsComponent->DefaultMultiplier);
	StatisticsComponent->SetDrainMultiplierForType(EStatisticType::THIRST, StatisticsComponent->DefaultMultiplier);
	ChangeMoveSpeed(WalkSpeed);
}

void AForestryCharacter::DoCrouchStart()
{
	if (bIsCrouching) return;
	if (bIsSprinting)
		DoSprintEnd();

	bWannaStand = false;
	bIsCrouching = true;
	CrouchHelper->Crouch();
	ChangeMoveSpeed(CrouchSpeed);
}

void AForestryCharacter::DoCrouchEnd()
{
	if (!bIsCrouching) return;
	if (!bWannaStand) bWannaStand = true;
}
