// Copyright Pacificao. All Rights Reserved.

#include "AgrarianMapBoundaryVolume.h"
#include "AgrarianGameCharacter.h"
#include "Components/BoxComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Kismet/GameplayStatics.h"

AAgrarianMapBoundaryVolume::AAgrarianMapBoundaryVolume()
{
	PrimaryActorTick.bCanEverTick = true;
	bReplicates = false;

	BoundaryVolume = CreateDefaultSubobject<UBoxComponent>(TEXT("BoundaryVolume"));
	RootComponent = BoundaryVolume;
	BoundaryVolume->SetBoxExtent(FVector(50000.0f, 50000.0f, 25000.0f));
	BoundaryVolume->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	BoundaryVolume->SetHiddenInGame(false);
	BoundaryVolume->ShapeColor = FColor::Yellow;
}

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

	if (!HasAuthority() || !bClampPlayersAtBoundary)
	{
		return;
	}

	TArray<AActor*> Characters;
	UGameplayStatics::GetAllActorsOfClass(this, AAgrarianGameCharacter::StaticClass(), Characters);
	for (AActor* Actor : Characters)
	{
		EnforceBoundaryForCharacter(Cast<AAgrarianGameCharacter>(Actor));
	}
}

bool AAgrarianMapBoundaryVolume::IsLocationOutsideBoundary(const FVector& WorldLocation) const
{
	if (!BoundaryVolume)
	{
		return false;
	}

	const FVector LocalLocation = GetActorTransform().InverseTransformPosition(WorldLocation);
	const FVector Extent = BoundaryVolume->GetUnscaledBoxExtent();
	return FMath::Abs(LocalLocation.X) > Extent.X
		|| FMath::Abs(LocalLocation.Y) > Extent.Y
		|| FMath::Abs(LocalLocation.Z) > Extent.Z;
}

bool AAgrarianMapBoundaryVolume::IsLocationInsideWarningZone(const FVector& WorldLocation) const
{
	if (!BoundaryVolume)
	{
		return false;
	}

	const FVector LocalLocation = GetActorTransform().InverseTransformPosition(WorldLocation);
	const FVector Extent = BoundaryVolume->GetUnscaledBoxExtent();
	if (FMath::Abs(LocalLocation.X) > Extent.X || FMath::Abs(LocalLocation.Y) > Extent.Y)
	{
		return true;
	}

	return FMath::Abs(LocalLocation.X) >= Extent.X - WarningDistanceCm
		|| FMath::Abs(LocalLocation.Y) >= Extent.Y - WarningDistanceCm;
}

FVector AAgrarianMapBoundaryVolume::ClampLocationToBoundary(const FVector& WorldLocation) const
{
	if (!BoundaryVolume)
	{
		return WorldLocation;
	}

	const FVector Extent = BoundaryVolume->GetUnscaledBoxExtent();
	const float SafePadding = FMath::Clamp(BoundaryPaddingCm, 0.0f, FMath::Min(Extent.X, Extent.Y) * 0.5f);
	const FVector LocalLocation = GetActorTransform().InverseTransformPosition(WorldLocation);
	const FVector ClampedLocalLocation(
		FMath::Clamp(LocalLocation.X, -Extent.X + SafePadding, Extent.X - SafePadding),
		FMath::Clamp(LocalLocation.Y, -Extent.Y + SafePadding, Extent.Y - SafePadding),
		FMath::Clamp(LocalLocation.Z, -Extent.Z + SafePadding, Extent.Z - SafePadding));

	return GetActorTransform().TransformPosition(ClampedLocalLocation);
}

void AAgrarianMapBoundaryVolume::EnforceBoundaryForCharacter(AAgrarianGameCharacter* Character) const
{
	if (!Character || !IsLocationOutsideBoundary(Character->GetActorLocation()))
	{
		return;
	}

	const FVector ClampedLocation = ClampLocationToBoundary(Character->GetActorLocation());
	Character->TeleportTo(ClampedLocation, Character->GetActorRotation(), false, true);

	if (UCharacterMovementComponent* Movement = Character->GetCharacterMovement())
	{
		Movement->StopMovementImmediately();
	}
}
