// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "SoriaCharacter.generated.h"

UENUM()
enum EAnimMovementMode
{
	None = 0 UMETA(DisplayName = "None"),
	Walk = 1 UMETA(DisplayName = "Walk"),
	Run = 2 UMETA(DisplayName = "Run"),
	Sprint = 3 UMETA(DisplayName = "Sprint"),
	Skid = 4 UMETA(DisplayName = "Skid"),
	Wading = 5 UMETA(DisplayName = "Wading"),
	Balancing = 6 UMETA(DisplayName = "Balancing"),
	BusyIdle = 7 UMETA(DisplayName = "BusyIdle")
};

UCLASS(config=Game)
class ASoriaCharacter : public ACharacter
{
	GENERATED_BODY()

	/** Camera boom positioning the camera behind the character */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	class USpringArmComponent* CameraBoom;

	/** Follow camera */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	class UCameraComponent* FollowCamera;

public:
	ASoriaCharacter();

	// Begin AActor overrides
	virtual void Tick(float DeltaSeconds) override;
	virtual void NotifyHit(class UPrimitiveComponent* MyComp, class AActor* Other, class UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit) override;
	virtual void BeginPlay() override;
	// End AActor overrides

	/** 
	* Base turn rate, in deg/sec. Other scaling may affect final turn rate. 
	*/
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
	float BaseTurnRate;

	/** Base look up/down rate, in deg/sec. Other scaling may affect final rate. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
	float BaseLookUpRate;

	/** Bool for whether the gryphon has mounted the player. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Variables)
	bool GryphonMounted;

	/** Bool for whether the player is in the air. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MovementAir)
		bool InAir;

	/** Bool for whether the player is Moving. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Movement)
		bool IsMoving;

	/////////////////////////////////////////READWRITE PARAMETERS//////////////////////////////////////////////////////////
protected:
	/** Float for storing the Camera Correction speed that controls how quicly the camera will follow the player. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera)
		float CameraCorrectionSpeed;

	/** Float for storing the Spint Speed. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Movement)
		float SprintSpeed;

	/** Float for storing the Run Speed. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Movement)
		float RunSpeed;

	/** Float for storing the walk Speed. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Movement)
		float WalkSpeed;

	/** Float for storing the wade Speed. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Movement)
		float WadeSpeed;

	/** Float for storing the delay before going into a sprint. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Movement)
		float SprintDelay;

		/** Float for storing the right axis of the analog stick. */
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Movement)
		float MoveRightAxis;

	/** Float for storing the forard axis of the analog stick. */
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Movement)
		float MoveForwardAxis;

	/** Bool for whether the player is sitting at the checkpoint. */
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Movement)
		bool SittingAtCheckpoint;

	/** Bool for whether the player is Wading or Balancing. */
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Movement)
		bool Wade = false;

	/** Bool for whether the player is Sprinting. */
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Movement)
		bool Sprinting;

	/** Bool for whether the player can Sprint. */
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Movement)
		bool CanSprint;

	/** Bool for whether the game is paused */
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = UI)
		bool Paused;

	/** Bool for whether the player can trigger an event. */
	UPROPERTY(VisibleAnywhere, Category = MovementAir)
		bool CanTriggerEvent;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Animation)
		TEnumAsByte<EAnimMovementMode> MovementMode;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Movement)
		bool CanInput;

	/////////////////////////////////////////PUBLIC FUNCTIONS//////////////////////////////////////////////////////////
public:
	/** Return if the character is alive or not. */
	UFUNCTION(BlueprintCallable, Category = Movement)
		bool IsCharacterAlive();

	/** Change Characters Alive State. */
	UFUNCTION(BlueprintCallable, Category = Movement)
		void SetCharacterAliveState(bool IsAlive);


	/**A Blueprint implementable event so that the animation can be changes via an interface in BP*/
	UFUNCTION(BlueprintImplementableEvent, Category = "Movement")
		void SetAnimationMode();

	/**A Blueprint implementable event so that the sprint can be triggered adter a delay*/
	UFUNCTION(BlueprintImplementableEvent, Category = "Movement")
		void SprintCheck();

	/**A Blueprint implementable event so that the sprint DoOnce can be reset*/
	UFUNCTION(BlueprintImplementableEvent, Category = "Movement")
		void ResetSprintBP();

	/**Called to reset sprinting variables*/
	void ResetSprint();

	/** Called for forwards/backward input */
	void MoveForward(float Value);

	/** Called for side to side input */
	void MoveRight(float Value);

	/**Check if a Provided Axis is in range of the gives values*/
	bool AxisInRange(bool Forward, float Min, float Max, bool ABS);

	/**When there is no input reset variables and animations*/
	void NoInput();

	/**Set the movement speed and animation of the character*/
	void SetMovement();


protected:
	/** 
	 * Called via input to turn at a given rate. 
	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
	 */
	void TurnAtRate(float Rate);

	/**
	 * Called via input to turn look up/down at a given rate. 
	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
	 */
	void LookUpAtRate(float Rate);

	// APawn interface
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
	// End of APawn interface

private:
	/** Is the character alive or not */
	UPROPERTY(Category = Trait, EditAnywhere)
		bool CharacterAlive = true;

	/** How quickly forward speed changes */
	UPROPERTY(Category = Plane, EditAnywhere)
		float Acceleration;


public:
	/** Returns CameraBoom subobject **/
	FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
	/** Returns FollowCamera subobject **/
	FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }

};
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
// Copyright 2019-2020 Polargryph Ltd. All Rights Reserved.

#include "MainCharacter/SoriaCharacter.h"
#include "HeadMountedDisplayFunctionLibrary.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "Components/BoxComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "GameFramework/SpringArmComponent.h"
#include "Engine/World.h"
#include "Engine/StaticMesh.h"
#include "AI/Enemies/BaseEnemyController.h"
#include "Kismet/KismetMathLibrary.h"
#include "Engine.h"

//////////////////////////////////////////////////////////////////////////
// ASoriaCharacter

ASoriaCharacter::ASoriaCharacter()
{
	// Set size for collision capsule
	GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);

	// set our turn rates for input
	BaseTurnRate = 45.f;
	BaseLookUpRate = 45.f;

	// Don't rotate when the controller rotates. Let that just affect the camera.
	bUseControllerRotationPitch = false;
	bUseControllerRotationYaw = false;
	bUseControllerRotationRoll = false;

	// Configure character movement
	GetCharacterMovement()->bOrientRotationToMovement = true; // Character moves in the direction of input...	
	//GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f); // ...at this rotation rate
	GetCharacterMovement()->JumpZVelocity = 600.f;
	GetCharacterMovement()->AirControl = 0.2f;

	// Create a camera boom (pulls in towards the player if there is a collision)
	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
	CameraBoom->SetupAttachment(RootComponent);
	CameraBoom->TargetArmLength = 300.0f; // The camera follows at this distance behind the character	
	CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller
	
	// Create a follow camera
	FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
	FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
	FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm

	// Set handling parameters
	// Acceleration is high as to ensure movement is snappy
	Acceleration = 50000.f;
	WalkSpeed = 60.0f;
	RunSpeed = 350.0f;
	SprintSpeed = 500.0f;
	SprintDelay = 2.0f;
	WadeSpeed = 100.0f;
	// Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character) 
	// are set in the derived blueprint asset named MyCharacter (to avoid direct content references in C++)
}


void ASoriaCharacter::BeginPlay()
{
	Super::BeginPlay();
}


//////////////////////////////////////////////////////////////////////////
// Input

void ASoriaCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
	// Set up gameplay key bindings
	check(PlayerInputComponent);
	//PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
	//PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);

	PlayerInputComponent->BindAxis("MoveForward", this, &ASoriaCharacter::MoveForward);
	PlayerInputComponent->BindAxis("MoveRight", this, &ASoriaCharacter::MoveRight);

	// We have 2 versions of the rotation bindings to handle different kinds of devices differently
	// "turn" handles devices that provide an absolute delta, such as a mouse.
	// "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick
	PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
	PlayerInputComponent->BindAxis("TurnRate", this, &ASoriaCharacter::TurnAtRate);
	PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
	PlayerInputComponent->BindAxis("LookUpRate", this, &ASoriaCharacter::LookUpAtRate);
}

void ASoriaCharacter::MoveForward(float Value)
{
	if (CanInput)
	{
		MoveForwardAxis = Value;
		if (AxisInRange(true, -0.05f, 0.05f, false) == true)
		{
			NoInput();
		}
		else
		{
			// find out which way is forward
			const FRotator Rotation = Controller->GetControlRotation();
			const FRotator YawRotation(0, Rotation.Yaw, 0);

			// get forward vector
			const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
			AddMovementInput(Direction, Value);
			SetMovement();
			IsMoving = true;
		}
	}
}


void ASoriaCharacter::MoveRight(float Value)
{
	if (CanInput)
	{
		MoveRightAxis = Value;
		if (AxisInRange(false, -0.05f, 0.05f, false) == true)
		{
			NoInput();
		}
		else {
			// find out which way is right
			const FRotator Rotation = Controller->GetControlRotation();
			const FRotator YawRotation(0, Rotation.Yaw, 0);

			// get right vector 
			const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
			// add movement in that direction
			AddMovementInput(Direction, Value);
			SetMovement();
			IsMoving = true;
		}
	}
}

/**Check to see if the provided axis in in the provided range with the choice of checking it againt
the absoulute value of the axis or the true one.
There is also the option to check if it should be looking at the forward or right axis*/
bool ASoriaCharacter::AxisInRange(bool Forward, float Min, float Max, bool ABS)
{
	float Value;
	if (Forward)
	{
		if (ABS)
		{
			Value = UKismetMathLibrary::Abs(MoveForwardAxis);
		}
		else
		{
			Value = MoveForwardAxis;
		}
	}
	else
	{
		if (ABS)
		{
			Value = UKismetMathLibrary::Abs(MoveRightAxis);
		}
		else
		{
			Value = MoveRightAxis;
		}
	}
	if (!ABS)
	{
		if (Value < Max && Value > Min)
		{
			return true;
		}
		return false;
	}
	else
	{
		if (Value <= Max && Value >= Min)
		{
			return true;
		}
		return false;
	}
	return false;
}


/**If there is minimal/no input on the axis then set movement to Zero and reset the relevant variables*/
void ASoriaCharacter::NoInput()
{
	//Though both the Forward and Right Input can Trigger the NoInput() function individually 
	//both axis' need to be near 0 to count as No Input.
	if (AxisInRange(false, -0.05f, 0.05f, false) && AxisInRange(true, -0.05, 0.05, false))
	{
		MovementMode = EAnimMovementMode::None;
		SetAnimationMode();
		ResetSprint();
		IsMoving = false;
	}
}

//As long as there is input from either the forward or right axis trigger this function.
void ASoriaCharacter::SetMovement()
{
	//If the character is in a wading volume trigger this code.
	if (Wade)
	{
		//Set the max walk speed and min analog input, the speeds can be overriden in the editor.
		GetCharacterMovement()->MaxWalkSpeed = WadeSpeed;
		GetCharacterMovement()->MinAnalogWalkSpeed = WadeSpeed;
		ResetSprint();
		//This is for the camera function as to change its speed following the player dependant on the speed of the character.
		CameraCorrectionSpeed = 0.05f;
		//Set the correct animation.
		MovementMode = EAnimMovementMode::Wading;
		//The implementation of the animation change is done through calling this function that is triggered in the Blueprint.
		SetAnimationMode();
	}
	else
	{
		//If the character is not in a wading volume and is detecting full input on either axis
		if (AxisInRange(true, 0.36f, 1.25f, true) == true || AxisInRange(false, 0.36f, 1.25f, true) == true)
		{
			if (!Sprinting)
			{
				GetCharacterMovement()->MaxWalkSpeed = RunSpeed;
				GetCharacterMovement()->MinAnalogWalkSpeed = RunSpeed;
				CanSprint = true;
				CameraCorrectionSpeed = 0.1f;
				MovementMode = EAnimMovementMode::Run;
				SetAnimationMode();
				//Used a Blueprint implementation for a DoOnce and Delay as it's simpler to do.
				//The function just triggeres once and after a delay that sets the Sprinting variable to true.
				//The delay duration can be set/changed in the Blueprint.
				SprintCheck();
			}
			else
			{
				//Once sprinting is true
				GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
				GetCharacterMovement()->MinAnalogWalkSpeed = SprintSpeed;
				CameraCorrectionSpeed = 0.25f;
				MovementMode = EAnimMovementMode::Sprint;
				SetAnimationMode();
			}
		}
		//If there is minimal input then set the character to walk.
		else if (AxisInRange(true, 0.05f, 0.35f, true) == true || AxisInRange(false, 0.05f, 0.35f, true) == true)
		{
			ResetSprint();
			GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;
			GetCharacterMovement()->MinAnalogWalkSpeed = WalkSpeed;
			CameraCorrectionSpeed = 0.05f;
			MovementMode = EAnimMovementMode::Walk;
			SetAnimationMode();			
		}
	}
}

void ASoriaCharacter::ResetSprint()
{
	CanSprint = false;
	Sprinting = false;
	ResetSprintBP();
}

void ASoriaCharacter::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);
}

void ASoriaCharacter::NotifyHit(class UPrimitiveComponent* MyComp, class AActor* Other, class UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit)
{
	if (GetCharacterMovement()->IsFlying() == true)
	{
		Super::NotifyHit(MyComp, Other, OtherComp, bSelfMoved, HitLocation, HitNormal, NormalImpulse, Hit);

		// Deflect along the surface when we collide.
		FRotator CurrentRotation = GetActorRotation();
		SetActorRotation(FQuat::Slerp(CurrentRotation.Quaternion(), HitNormal.ToOrientationQuat(), 0.025f));
	}
}

void ASoriaCharacter::TurnAtRate(float Rate)
{
	// calculate delta for this frame from the rate information
	AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds());
}

void ASoriaCharacter::LookUpAtRate(float Rate)
{
	// calculate delta for this frame from the rate information
	AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds());
}

bool ASoriaCharacter::IsCharacterAlive()
{
	return CharacterAlive;
}

void ASoriaCharacter::SetCharacterAliveState(bool IsAlive)
{
	CharacterAlive = IsAlive;
}