#include "HyperTwistSimulation/HyperTwistMelindaProjectionOrbitPawn.h"

#include "Camera/CameraComponent.h"
#include "Components/SceneComponent.h"
#include "EngineUtils.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/SpringArmComponent.h"
#include "HyperTwistSimulation/HyperTwistMelindaProjectionActor.h"
#include "HyperTwistUX/HyperTwistPlayerControllerBase.h"
#include "InputCoreTypes.h"

AHyperTwistMelindaProjectionOrbitPawn::AHyperTwistMelindaProjectionOrbitPawn()
{
	PrimaryActorTick.bCanEverTick = true;

	SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
	RootComponent = SceneRoot;

	SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
	SpringArm->SetupAttachment(SceneRoot);
	SpringArm->bDoCollisionTest = false;
	SpringArm->bEnableCameraLag = false;
	SpringArm->bUsePawnControlRotation = false;
	SpringArm->TargetArmLength = InitialArmLength;

	CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
	CameraComponent->SetupAttachment(SpringArm, USpringArmComponent::SocketName);
	CameraComponent->bUsePawnControlRotation = false;
}

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

	CurrentYawDegrees = InitialYawDegrees;
	CurrentPitchDegrees = InitialPitchDegrees;
	RefreshOrbitFocusPointFromProjection();
	if (SpringArm != nullptr)
	{
		SpringArm->TargetArmLength = FMath::Clamp(
			InitialArmLength,
			MinimumArmLength,
			MaximumArmLength
		);
	}
	ApplyOrbitTransform();
}

void AHyperTwistMelindaProjectionOrbitPawn::Tick(const float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);
	static_cast<void>(DeltaSeconds);

	const FVector PreviousFocusPoint = OrbitFocusPoint;
	RefreshOrbitFocusPointFromProjection();
	if (!OrbitFocusPoint.Equals(PreviousFocusPoint))
	{
		ApplyOrbitTransform();
	}

	APlayerController* PlayerController = Cast<APlayerController>(GetController());
	if (PlayerController == nullptr)
	{
		return;
	}
	const AHyperTwistPlayerControllerBase* HyperTwistController =
		Cast<AHyperTwistPlayerControllerBase>(PlayerController);
	const FHyperTwistPlayerPreferences* Preferences = HyperTwistController != nullptr
		? &HyperTwistController->GetPlayerPreferences()
		: nullptr;
	const float ZoomMultiplier = Preferences != nullptr
		? Preferences->ZoomSensitivity
		: 1.0f;
	const float OrbitMultiplier = Preferences != nullptr
		? Preferences->OrbitSensitivity
		: 1.0f;
	const float VerticalDirection = Preferences != nullptr && Preferences->bInvertOrbitY
		? -1.0f
		: 1.0f;

	if (SpringArm != nullptr)
	{
		const float MouseWheelDelta = PlayerController->GetInputAnalogKeyState(EKeys::MouseWheelAxis);
		if (!FMath::IsNearlyZero(MouseWheelDelta))
		{
			SpringArm->TargetArmLength = FMath::Clamp(
				SpringArm->TargetArmLength - (MouseWheelDelta * ZoomStep * ZoomMultiplier),
				MinimumArmLength,
				MaximumArmLength
			);
		}
	}

	if (!ShouldOrbitFromMouseInput())
	{
		return;
	}

	float MouseDeltaX = 0.0f;
	float MouseDeltaY = 0.0f;
	PlayerController->GetInputMouseDelta(MouseDeltaX, MouseDeltaY);
	if (!FMath::IsNearlyZero(MouseDeltaX) || !FMath::IsNearlyZero(MouseDeltaY))
	{
		CurrentYawDegrees += MouseDeltaX * OrbitYawDegreesPerPixel * OrbitMultiplier;
		CurrentPitchDegrees = FMath::Clamp(
			CurrentPitchDegrees
				- (MouseDeltaY * OrbitPitchDegreesPerPixel * OrbitMultiplier * VerticalDirection),
			MinimumPitchDegrees,
			MaximumPitchDegrees
		);
		ApplyOrbitTransform();
	}
}

void AHyperTwistMelindaProjectionOrbitPawn::ApplyOrbitTransform()
{
	SetActorLocation(OrbitFocusPoint);
	if (SpringArm != nullptr)
	{
		SpringArm->SetRelativeRotation(FRotator(CurrentPitchDegrees, CurrentYawDegrees, 0.0f));
	}
}

void AHyperTwistMelindaProjectionOrbitPawn::RefreshOrbitFocusPointFromProjection()
{
	if (!bFollowActiveProjectionActor)
	{
		return;
	}

	if (const AHyperTwistMelindaProjectionActor* ProjectionActor = ResolveProjectionActor())
	{
		OrbitFocusPoint = ProjectionActor->GetActorLocation();
	}
}

AHyperTwistMelindaProjectionActor* AHyperTwistMelindaProjectionOrbitPawn::ResolveProjectionActor() const
{
	if (GetWorld() == nullptr)
	{
		return nullptr;
	}

	for (TActorIterator<AHyperTwistMelindaProjectionActor> ActorIt(GetWorld()); ActorIt; ++ActorIt)
	{
		return *ActorIt;
	}

	return nullptr;
}

bool AHyperTwistMelindaProjectionOrbitPawn::ShouldOrbitFromMouseInput() const
{
	const APlayerController* PlayerController = Cast<APlayerController>(GetController());
	if (PlayerController == nullptr)
	{
		return false;
	}

	const bool bMiddleMouseOrbit =
		bUseMiddleMouseOrbit && PlayerController->IsInputKeyDown(EKeys::MiddleMouseButton);
	const bool bShiftRightMouseOrbit =
		bUseRightMouseOrbitWithShift
		&& PlayerController->IsInputKeyDown(EKeys::RightMouseButton)
		&& (
			PlayerController->IsInputKeyDown(EKeys::LeftShift)
			|| PlayerController->IsInputKeyDown(EKeys::RightShift)
		);

	return bMiddleMouseOrbit || bShiftRightMouseOrbit;
}
