// Gravity Stomp Copyright Kevin Poretti

#include "Camera/GSCameraActor.h"

// UE includes
#include "Kismet/KismetMathLibrary.h"
#include "World/GSWorldSettings.h"

AGSCameraActor::AGSCameraActor()
{
    PanSpeed = 100.0f;
    ZoomSpeed = 100.0f;
    MinZoom = 2666;
    MaxZoom = 5000;
}


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

    FBox2D ScreenBoundingBox = GetBoundingBoxFromViewportPercentage(0);

    // bounding box of what is considered the "edge" of the screen
    // when characters are outside of this box the camera should pan or zoom out
    FBox2D EdgeBoundingBox = GetBoundingBoxFromViewportPercentage(0.15);

    // bounding box of what is considered the "center" of the screen
    // when all characters are inside of this box the camera should zoom in
    FBox2D CenterBoundingBox = GetBoundingBoxFromViewportPercentage(0.15);

    // debug
    FVector2D ScreenBoxCenter = GetBoxCenter(ScreenBoundingBox);
    float ScreenHeight, ScreenWidth;
    GetBoxDimensions(ScreenBoundingBox, ScreenHeight, ScreenWidth);
    DrawDebugBox(GetWorld(), FVector(0.0f, ScreenBoxCenter.X, ScreenBoxCenter.Y), FVector(0.0f, ScreenWidth/2, ScreenHeight/2), 
    FColor::Magenta, false, -1, 0, 2.0f);

    FVector CurrLocation = GetActorLocation();
    float ZoomMove = ZoomDirection * ZoomSpeed * DeltaSeconds;
    float NewZoom = CurrLocation.X + ZoomMove;
    NewZoom = ClampZoom(NewZoom);
    FVector2D PanMove = PanDirection * PanSpeed * DeltaSeconds;
    FVector2D NewPan = FVector2D(CurrLocation.Y + PanMove.X, CurrLocation.Z + PanMove.Y);
    NewPan = ClampCameraLocationToStageBounds(NewPan, ScreenBoundingBox);

    FVector NewLocation = FVector(NewZoom, NewPan.X, NewPan.Y);
    SetActorLocation(NewLocation);
}


FBox2D AGSCameraActor::GetBoundingBoxFromViewportPercentage(float ScreenPercentage) const
{
    APlayerController* PC = GetWorld()->GetFirstPlayerController();
    int32 SizeX, SizeY;
    PC->GetViewportSize(SizeX, SizeY);
    float MinX = ((float)SizeX) * ScreenPercentage;
    float MinY = ((float)SizeY) * ScreenPercentage;
    float MaxX = ((float)SizeX) * (1 - ScreenPercentage);
    float MaxY = ((float)SizeY) * (1 - ScreenPercentage);
    FVector TopRight = GetWorldLocationFromScreenCoords(MaxX, MinY);
    FVector BottomLeft = GetWorldLocationFromScreenCoords(MinX, MaxY);

    FBox2D Result;
    Result.Min = FVector2D(BottomLeft.Y, BottomLeft.Z);
    Result.Max = FVector2D(TopRight.Y, TopRight.Z);
    return Result;
}


FVector2D AGSCameraActor::GetBoxCenter(FBox2D Box) const
{
    return FVector2D((Box.Min.X + Box.Max.X) / 2.0f, (Box.Min.Y + Box.Max.Y) / 2.0f);
}


void AGSCameraActor::GetBoxDimensions(FBox2D Box, float& BoxHeight, float& BoxWidth) const
{
    BoxWidth = FMath::Abs(Box.Max.X - Box.Min.X);
    BoxHeight = FMath::Abs(Box.Max.Y - Box.Min.Y);
}


FVector AGSCameraActor::GetWorldLocationFromScreenCoords(float InX, float InY) const
{
    APlayerController* PC = GetWorld()->GetFirstPlayerController();
    FVector WorldLocation;
    FVector WorldDirection;
    PC->DeprojectScreenPositionToWorld(InX, InY, WorldLocation, WorldDirection);

    FPlane XPlane = UKismetMathLibrary::MakePlaneFromPointAndNormal(FVector::ZeroVector, FVector::ForwardVector);
    // NOTE: 10000 is a big enough number to make sure the intersection goes through the X = 0 plane
    // there is probably a more elegant way to do this
    float T;
    FVector Result = FVector::ZeroVector;
    UKismetMathLibrary::LinePlaneIntersection(WorldLocation, WorldLocation + (WorldDirection * 10000), XPlane, T, Result);
    return Result;    
}


FVector2D AGSCameraActor::ClampCameraLocationToStageBounds(FVector2D CameraLocation, FBox2D ScreenBox) const
{
    AGSWorldSettings* WS = Cast<AGSWorldSettings>(GetWorld()->GetWorldSettings());
    FBox2D StageBounds = WS->StageBounds;

    float ScreenHeight, ScreenWidth;
    GetBoxDimensions(ScreenBox, ScreenHeight, ScreenWidth);
    float HalfHeight = ScreenHeight / 2;
    float HalfWidth = ScreenWidth / 2;

    float MinStageX = StageBounds.Min.X + HalfWidth;
    float MinStageY = StageBounds.Min.Y + HalfHeight;
    float MaxStageX = StageBounds.Max.X - HalfWidth;
    float MaxStageY = StageBounds.Max.Y - HalfHeight;

    float FinalX = FMath::Clamp(CameraLocation.X, MinStageX, MaxStageX);
    float FinalY = FMath::Clamp(CameraLocation.Y, MinStageY, MaxStageY);
    return FVector2D(FinalX, FinalY);
}

float AGSCameraActor::ClampZoom(float Zoom) const
{
    return FMath::Clamp(Zoom, -MaxZoom, -MinZoom);
}
