// Copyright Pacificao. All Rights Reserved.

#include "AgrarianWildlifeBase.h"
#include "AgrarianGameCharacter.h"
#include "AgrarianInventoryComponent.h"
#include "AIController.h"
#include "Components/AudioComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/StaticMesh.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Materials/MaterialInterface.h"
#include "Navigation/PathFollowingComponent.h"
#include "NavigationSystem.h"
#include "Net/UnrealNetwork.h"
#include "UObject/ConstructorHelpers.h"

namespace
{
	void ConfigureWildlifeProxyComponent(UStaticMeshComponent* Component, UStaticMesh* MeshAsset, UMaterialInterface* MaterialAsset)
	{
		if (!Component)
		{
			return;
		}

		Component->SetCollisionEnabled(ECollisionEnabled::NoCollision);
		Component->SetGenerateOverlapEvents(false);
		if (MeshAsset)
		{
			Component->SetStaticMesh(MeshAsset);
		}
		if (MaterialAsset)
		{
			Component->SetMaterial(0, MaterialAsset);
		}
	}
}

AAgrarianWildlifeBase::AAgrarianWildlifeBase()
{
	PrimaryActorTick.bCanEverTick = true;
	bReplicates = true;
	SetNetCullDistanceSquared(FMath::Square(6000.0f));
	AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
	AIControllerClass = AAIController::StaticClass();

	GetCharacterMovement()->MaxWalkSpeed = WanderSpeed;
	GetCharacterMovement()->bOrientRotationToMovement = true;
	bUseControllerRotationYaw = false;

	static ConstructorHelpers::FObjectFinder<UStaticMesh> ChamferCubeMesh(TEXT("/Game/Agrarian/Environment/PlaceholderMeshes/SM_AGR_Placeholder_ChamferCube.SM_AGR_Placeholder_ChamferCube"));
	static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeMesh(TEXT("/Game/Agrarian/Environment/PlaceholderMeshes/SM_AGR_Placeholder_Cube.SM_AGR_Placeholder_Cube"));
	static ConstructorHelpers::FObjectFinder<UStaticMesh> CylinderMesh(TEXT("/Game/Agrarian/Environment/PlaceholderMeshes/SM_AGR_Placeholder_Cylinder.SM_AGR_Placeholder_Cylinder"));
	static ConstructorHelpers::FObjectFinder<UMaterialInterface> FurMaterial(TEXT("/Game/Agrarian/Materials/M_AGR_GZ_Fiber_Resource.M_AGR_GZ_Fiber_Resource"));

	WildlifeBodyProxy = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WildlifeBodyProxy"));
	WildlifeBodyProxy->SetupAttachment(RootComponent);
	ConfigureWildlifeProxyComponent(WildlifeBodyProxy, ChamferCubeMesh.Succeeded() ? ChamferCubeMesh.Object : nullptr, FurMaterial.Succeeded() ? FurMaterial.Object : nullptr);
	WildlifeBodyProxy->SetRelativeLocation(FVector(10.0f, 0.0f, -42.0f));
	WildlifeBodyProxy->SetRelativeScale3D(FVector(0.62f, 0.32f, 0.24f));

	WildlifeHeadProxy = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WildlifeHeadProxy"));
	WildlifeHeadProxy->SetupAttachment(WildlifeBodyProxy);
	ConfigureWildlifeProxyComponent(WildlifeHeadProxy, ChamferCubeMesh.Succeeded() ? ChamferCubeMesh.Object : nullptr, FurMaterial.Succeeded() ? FurMaterial.Object : nullptr);
	WildlifeHeadProxy->SetRelativeLocation(FVector(42.0f, 0.0f, 20.0f));
	WildlifeHeadProxy->SetRelativeScale3D(FVector(0.46f, 0.34f, 0.36f));

	WildlifeEarProxyA = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WildlifeEarProxyA"));
	WildlifeEarProxyA->SetupAttachment(WildlifeHeadProxy);
	ConfigureWildlifeProxyComponent(WildlifeEarProxyA, CubeMesh.Succeeded() ? CubeMesh.Object : nullptr, FurMaterial.Succeeded() ? FurMaterial.Object : nullptr);
	WildlifeEarProxyA->SetRelativeLocation(FVector(4.0f, -12.0f, 28.0f));
	WildlifeEarProxyA->SetRelativeRotation(FRotator(0.0f, 0.0f, -10.0f));
	WildlifeEarProxyA->SetRelativeScale3D(FVector(0.08f, 0.08f, 0.38f));

	WildlifeEarProxyB = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WildlifeEarProxyB"));
	WildlifeEarProxyB->SetupAttachment(WildlifeHeadProxy);
	ConfigureWildlifeProxyComponent(WildlifeEarProxyB, CubeMesh.Succeeded() ? CubeMesh.Object : nullptr, FurMaterial.Succeeded() ? FurMaterial.Object : nullptr);
	WildlifeEarProxyB->SetRelativeLocation(FVector(4.0f, 12.0f, 28.0f));
	WildlifeEarProxyB->SetRelativeRotation(FRotator(0.0f, 0.0f, 10.0f));
	WildlifeEarProxyB->SetRelativeScale3D(FVector(0.08f, 0.08f, 0.38f));

	WildlifeTailProxy = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WildlifeTailProxy"));
	WildlifeTailProxy->SetupAttachment(WildlifeBodyProxy);
	ConfigureWildlifeProxyComponent(WildlifeTailProxy, CylinderMesh.Succeeded() ? CylinderMesh.Object : nullptr, FurMaterial.Succeeded() ? FurMaterial.Object : nullptr);
	WildlifeTailProxy->SetRelativeLocation(FVector(-42.0f, 0.0f, 10.0f));
	WildlifeTailProxy->SetRelativeRotation(FRotator(0.0f, 90.0f, 90.0f));
	WildlifeTailProxy->SetRelativeScale3D(FVector(0.12f, 0.12f, 0.24f));

	WildlifeAudioComponent = CreateDefaultSubobject<UAudioComponent>(TEXT("WildlifeAudioComponent"));
	WildlifeAudioComponent->SetupAttachment(RootComponent);
	WildlifeAudioComponent->bAutoActivate = false;
	WildlifeAudioComponent->bAllowSpatialization = true;

	DisplayName = FText::FromString(TEXT("Wildlife"));
}

void AAgrarianWildlifeBase::BeginPlay()
{
	Super::BeginPlay();

	SpawnLocation = GetActorLocation();
	Health = FMath::Clamp(Health, 0.0f, MaxHealth);
	if (HasAuthority() && !GetController())
	{
		SpawnDefaultController();
	}
	ChooseWanderTarget();
	BroadcastHealthChanged();
	BroadcastStateChanged();
}

void AAgrarianWildlifeBase::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);

	if (HasAuthority())
	{
		if (ShouldRunServerThink(DeltaSeconds))
		{
			const float EffectiveDeltaSeconds = bEnablePerformanceLimits ? ServerThinkAccumulator : DeltaSeconds;
			ServerThink(EffectiveDeltaSeconds);
			ServerThinkAccumulator = 0.0f;
		}
	}
}

void AAgrarianWildlifeBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME(AAgrarianWildlifeBase, WildlifeState);
	DOREPLIFETIME(AAgrarianWildlifeBase, Health);
	DOREPLIFETIME(AAgrarianWildlifeBase, bHarvested);
}

bool AAgrarianWildlifeBase::IsAlive() const
{
	return WildlifeState != EAgrarianWildlifeState::Dead && Health > 0.0f;
}

void AAgrarianWildlifeBase::ApplyWildlifeDamage(float Amount, AActor* InstigatorActor)
{
	if (!HasAuthority() || !IsAlive())
	{
		return;
	}

	Health = FMath::Clamp(Health - FMath::Max(0.0f, Amount), 0.0f, MaxHealth);
	FocusActor = InstigatorActor;
	BroadcastHealthChanged();

	if (Health <= 0.0f)
	{
		EnterDeadState();
		return;
	}

	if (Disposition == EAgrarianWildlifeDisposition::Passive || Disposition == EAgrarianWildlifeDisposition::Fleeing)
	{
		SetWildlifeState(EAgrarianWildlifeState::Fleeing);
	}
	else if (Disposition == EAgrarianWildlifeDisposition::Aggressive)
	{
		SetWildlifeState(EAgrarianWildlifeState::Chasing);
	}
}

void AAgrarianWildlifeBase::SetWildlifeState(EAgrarianWildlifeState NewState)
{
	if (WildlifeState == NewState)
	{
		return;
	}

	WildlifeState = NewState;
	if (HasAuthority())
	{
		MulticastPlayWildlifeStateSound(WildlifeState);
	}
	BroadcastStateChanged();
}

FText AAgrarianWildlifeBase::GetInteractionText_Implementation(const AAgrarianGameCharacter* Interactor) const
{
	if (WildlifeState == EAgrarianWildlifeState::Dead)
	{
		return bHarvested ? FText::FromString(TEXT("Harvested")) : FText::FromString(TEXT("Harvest"));
	}

	return DisplayName;
}

bool AAgrarianWildlifeBase::CanInteract_Implementation(const AAgrarianGameCharacter* Interactor) const
{
	return Interactor && WildlifeState == EAgrarianWildlifeState::Dead && !bHarvested;
}

void AAgrarianWildlifeBase::Interact_Implementation(AAgrarianGameCharacter* Interactor)
{
	if (HasAuthority())
	{
		Harvest(Interactor);
	}
}

void AAgrarianWildlifeBase::OnRep_Health()
{
	BroadcastHealthChanged();
}

void AAgrarianWildlifeBase::OnRep_WildlifeState()
{
	BroadcastStateChanged();
}

void AAgrarianWildlifeBase::MulticastPlayWildlifeStateSound_Implementation(EAgrarianWildlifeState NewState)
{
	if (GetNetMode() == NM_DedicatedServer || !WildlifeAudioComponent)
	{
		return;
	}

	USoundBase* StateSound = GetSoundForWildlifeState(NewState);
	if (!StateSound)
	{
		return;
	}

	WildlifeAudioComponent->SetSound(StateSound);
	WildlifeAudioComponent->Play();
}

void AAgrarianWildlifeBase::MulticastPlayWildlifeHarvestSound_Implementation()
{
	if (GetNetMode() == NM_DedicatedServer || !WildlifeAudioComponent || !HarvestWildlifeSound)
	{
		return;
	}

	WildlifeAudioComponent->SetSound(HarvestWildlifeSound);
	WildlifeAudioComponent->Play();
}

bool AAgrarianWildlifeBase::ShouldRunServerThink(float DeltaSeconds)
{
	if (!bEnablePerformanceLimits || WildlifeState == EAgrarianWildlifeState::Dead)
	{
		return true;
	}

	ServerThinkAccumulator += DeltaSeconds;
	if (GetNearestPlayerDistanceSquared() <= FMath::Square(FullUpdateRadius))
	{
		return true;
	}

	return ServerThinkAccumulator >= FarUpdateIntervalSeconds;
}

void AAgrarianWildlifeBase::ServerThink(float DeltaSeconds)
{
	if (!IsAlive())
	{
		return;
	}

	DecisionTimer -= DeltaSeconds;
	if (DecisionTimer <= 0.0f)
	{
		DecisionTimer = DecisionIntervalSeconds;

		if (Disposition == EAgrarianWildlifeDisposition::Aggressive)
		{
			FocusActor = FindNearestPlayer(AggroRadius);
			SetWildlifeState(FocusActor ? EAgrarianWildlifeState::Chasing : EAgrarianWildlifeState::Wandering);
		}
		else
		{
			FocusActor = FindNearestPlayer(FleeRadius);
			SetWildlifeState(FocusActor ? EAgrarianWildlifeState::Fleeing : EAgrarianWildlifeState::Wandering);
		}

		if (WildlifeState == EAgrarianWildlifeState::Wandering && FVector::DistSquared(GetActorLocation(), CurrentMoveTarget) < FMath::Square(125.0f))
		{
			ChooseWanderTarget();
		}
	}

	MoveTowardTarget();
}

void AAgrarianWildlifeBase::ChooseWanderTarget()
{
	FVector ReachableTarget = FVector::ZeroVector;
	if (ChooseReachableWanderTarget(ReachableTarget))
	{
		CurrentMoveTarget = ReachableTarget;
		ClearNavigationMove();
		return;
	}

	const FVector RandomOffset = FVector(
		FMath::FRandRange(-WanderRadius, WanderRadius),
		FMath::FRandRange(-WanderRadius, WanderRadius),
		0.0f);
	CurrentMoveTarget = SpawnLocation + RandomOffset;
	ClearNavigationMove();
}

void AAgrarianWildlifeBase::MoveTowardTarget()
{
	FVector DesiredTarget = GetActorLocation();
	float DesiredSpeed = WanderSpeed;

	if (WildlifeState == EAgrarianWildlifeState::Fleeing && FocusActor)
	{
		FVector DesiredDirection = GetActorLocation() - FocusActor->GetActorLocation();
		DesiredDirection.Z = 0.0f;
		if (DesiredDirection.IsNearlyZero())
		{
			DesiredDirection = GetActorForwardVector();
			DesiredDirection.Z = 0.0f;
		}

		DesiredTarget = GetActorLocation() + DesiredDirection.GetSafeNormal() * FleeRadius;
		DesiredSpeed = FleeSpeed;
	}
	else if (WildlifeState == EAgrarianWildlifeState::Chasing && FocusActor)
	{
		DesiredTarget = FocusActor->GetActorLocation();
		DesiredSpeed = FleeSpeed;
	}
	else if (WildlifeState == EAgrarianWildlifeState::Wandering)
	{
		DesiredTarget = CurrentMoveTarget;
		DesiredSpeed = WanderSpeed;
	}

	if (RequestNavigationMove(DesiredTarget))
	{
		GetCharacterMovement()->MaxWalkSpeed = DesiredSpeed;
		return;
	}

	DirectMoveTowardLocation(DesiredTarget, DesiredSpeed);
}

bool AAgrarianWildlifeBase::ChooseReachableWanderTarget(FVector& OutTarget) const
{
	if (!bUseNavigationMovement)
	{
		return false;
	}

	const UWorld* World = GetWorld();
	const UNavigationSystemV1* NavigationSystem = World ? FNavigationSystem::GetCurrent<UNavigationSystemV1>(World) : nullptr;
	if (!NavigationSystem)
	{
		return false;
	}

	FNavLocation ReachableLocation;
	if (!NavigationSystem->GetRandomReachablePointInRadius(SpawnLocation, WanderRadius, ReachableLocation))
	{
		return false;
	}

	OutTarget = ReachableLocation.Location;
	return true;
}

bool AAgrarianWildlifeBase::ProjectPointToNavigation(const FVector& CandidateLocation, FVector& OutProjectedLocation) const
{
	if (!bUseNavigationMovement)
	{
		return false;
	}

	const UWorld* World = GetWorld();
	const UNavigationSystemV1* NavigationSystem = World ? FNavigationSystem::GetCurrent<UNavigationSystemV1>(World) : nullptr;
	if (!NavigationSystem)
	{
		return false;
	}

	FNavLocation ProjectedLocation;
	if (!NavigationSystem->ProjectPointToNavigation(CandidateLocation, ProjectedLocation, NavigationProjectionExtent))
	{
		return false;
	}

	OutProjectedLocation = ProjectedLocation.Location;
	return true;
}

bool AAgrarianWildlifeBase::RequestNavigationMove(const FVector& TargetLocation)
{
	if (!bUseNavigationMovement)
	{
		return false;
	}

	AAIController* AIController = Cast<AAIController>(GetController());
	if (!AIController)
	{
		return false;
	}

	FVector ProjectedTarget = TargetLocation;
	if (!ProjectPointToNavigation(TargetLocation, ProjectedTarget))
	{
		return false;
	}

	if (FVector::DistSquared(GetActorLocation(), ProjectedTarget) <= FMath::Square(NavigationAcceptanceRadius))
	{
		AIController->StopMovement();
		ClearNavigationMove();
		return true;
	}

	if (bHasNavigationMoveTarget
		&& AIController->GetMoveStatus() != EPathFollowingStatus::Idle
		&& FVector::DistSquared(LastNavigationMoveTarget, ProjectedTarget) <= FMath::Square(NavigationRepathDistance))
	{
		return true;
	}

	const EPathFollowingRequestResult::Type MoveResult = AIController->MoveToLocation(
		ProjectedTarget,
		NavigationAcceptanceRadius,
		true,
		true,
		true,
		true,
		nullptr,
		true);

	if (MoveResult == EPathFollowingRequestResult::Failed)
	{
		ClearNavigationMove();
		return false;
	}

	LastNavigationMoveTarget = ProjectedTarget;
	bHasNavigationMoveTarget = true;
	return true;
}

void AAgrarianWildlifeBase::ClearNavigationMove()
{
	bHasNavigationMoveTarget = false;
	LastNavigationMoveTarget = FVector::ZeroVector;
}

void AAgrarianWildlifeBase::DirectMoveTowardLocation(const FVector& TargetLocation, float MovementSpeed)
{
	ClearNavigationMove();
	GetCharacterMovement()->MaxWalkSpeed = MovementSpeed;

	FVector DesiredDirection = TargetLocation - GetActorLocation();
	DesiredDirection.Z = 0.0f;
	if (!DesiredDirection.IsNearlyZero())
	{
		AddMovementInput(DesiredDirection.GetSafeNormal());
	}
}

void AAgrarianWildlifeBase::EnterDeadState()
{
	Health = 0.0f;
	SetWildlifeState(EAgrarianWildlifeState::Dead);
	if (AAIController* AIController = Cast<AAIController>(GetController()))
	{
		AIController->StopMovement();
	}
	ClearNavigationMove();
	GetCharacterMovement()->StopMovementImmediately();
	GetCharacterMovement()->DisableMovement();
	SetActorTickEnabled(false);
	SetLifeSpan(0.0f);
}

AAgrarianGameCharacter* AAgrarianWildlifeBase::FindNearestPlayer(float Radius) const
{
	UWorld* World = GetWorld();
	if (!World)
	{
		return nullptr;
	}

	AAgrarianGameCharacter* NearestCharacter = nullptr;
	float BestDistanceSq = FMath::Square(Radius);

	TArray<AActor*> PlayerPawns;
	UGameplayStatics::GetAllActorsOfClass(World, AAgrarianGameCharacter::StaticClass(), PlayerPawns);
	for (AActor* Actor : PlayerPawns)
	{
		AAgrarianGameCharacter* Candidate = Cast<AAgrarianGameCharacter>(Actor);
		if (!Candidate)
		{
			continue;
		}

		const float DistanceSq = FVector::DistSquared(GetActorLocation(), Candidate->GetActorLocation());
		if (DistanceSq < BestDistanceSq)
		{
			BestDistanceSq = DistanceSq;
			NearestCharacter = Candidate;
		}
	}

	return NearestCharacter;
}

float AAgrarianWildlifeBase::GetNearestPlayerDistanceSquared() const
{
	UWorld* World = GetWorld();
	if (!World)
	{
		return TNumericLimits<float>::Max();
	}

	float BestDistanceSq = TNumericLimits<float>::Max();

	TArray<AActor*> PlayerPawns;
	UGameplayStatics::GetAllActorsOfClass(World, AAgrarianGameCharacter::StaticClass(), PlayerPawns);
	for (AActor* Actor : PlayerPawns)
	{
		const AAgrarianGameCharacter* Candidate = Cast<AAgrarianGameCharacter>(Actor);
		if (!Candidate)
		{
			continue;
		}

		BestDistanceSq = FMath::Min(BestDistanceSq, FVector::DistSquared(GetActorLocation(), Candidate->GetActorLocation()));
	}

	return BestDistanceSq;
}

bool AAgrarianWildlifeBase::Harvest(AAgrarianGameCharacter* Interactor)
{
	if (!Interactor || WildlifeState != EAgrarianWildlifeState::Dead || bHarvested)
	{
		return false;
	}

	UAgrarianInventoryComponent* Inventory = Interactor->GetInventoryComponent();
	if (!Inventory)
	{
		return false;
	}

	for (const FAgrarianItemStack& Yield : HarvestYields)
	{
		if (Yield.IsValidStack())
		{
			Inventory->AddItem(Yield);
		}
	}

	bHarvested = true;
	MulticastPlayWildlifeHarvestSound();
	return true;
}

void AAgrarianWildlifeBase::BroadcastHealthChanged()
{
	OnWildlifeHealthChanged.Broadcast(Health, MaxHealth);
}

void AAgrarianWildlifeBase::BroadcastStateChanged()
{
	OnWildlifeStateChanged.Broadcast(WildlifeState);
}

USoundBase* AAgrarianWildlifeBase::GetSoundForWildlifeState(EAgrarianWildlifeState State) const
{
	switch (State)
	{
	case EAgrarianWildlifeState::Fleeing:
	case EAgrarianWildlifeState::Chasing:
		return FleeWildlifeSound;
	case EAgrarianWildlifeState::Dead:
		return DeathWildlifeSound;
	case EAgrarianWildlifeState::Idle:
	case EAgrarianWildlifeState::Wandering:
	default:
		return IdleWildlifeSound;
	}
}
