// Fill out your copyright notice in the Description page of Project Settings.


#include "UtilityLibrary.h"
#include "LockedWrapper.h"
#include "Engine/GameViewportClient.h"
#include "GameFramework/PlayerController.h"
#include "EnhancedInputSubsystems.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Kismet/GameplayStatics.h"
#include "Blueprint/UserWidget.h"
#include "Components/GridPanel.h"
#include "Components/CanvasPanelSlot.h"
#include "Components/Widget.h"
#include "InputMappingContext.h"
#include "EnhancedInputComponent.h"
#include "Animation/AnimSequenceBase.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/InputSettings.h"


#define LOCTEXT_NAMESPACE "Luprex Utility"


void UlxUtilityLibrary::CallFunctionByName(UObject *object, const FString &namepart1, const FString &namepart2, const FString &fallback, bool bFailIfNotFound) {
	FString fullname = namepart1 + namepart2;
    if (!IsValid(object)) {
		UE_LOG(LogBlueprint, Error, TEXT("In CallFunctionByName, object passed in is not valid."));
		return;
	}
	UFunction* function = object->FindFunction(FName(*fullname));
	if (function == nullptr) {
		function = object->FindFunction(FName(*fallback));
		if (function == nullptr) {
            if (!bFailIfNotFound) {
                return;
            }
			UE_LOG(LogBlueprint, Error, TEXT("In CallFunctionByName, cannot find the named function or the fallback function"));
			return;
		}
	}
	if (function->ParmsSize != 0) {
		UE_LOG(LogBlueprint, Error, TEXT("CallFunctionByName can only call functions that have no parameters and no return values"));
		return;
	}
	object->ProcessEvent(function, nullptr);
}

FBox UlxUtilityLibrary::GetActorBounds(const AActor *target, bool bOnlyCollidingComponents, bool bIncludeFromChildActors)
{
        FVector ActorOrigin;
        FVector BoxExtent;

        // First argument is bOnlyCollidingComponents - if you want to get the bounds for components that don't have collision enabled then set to false
        // Last argument is bIncludeFromChildActors.  Usually this won't do anything but if we've child-ed an actor - like a gun child-ed to a character
        // then we wouldn't want the gun to be part of the bounds so set to false
        target->GetActorBounds(bOnlyCollidingComponents, ActorOrigin, BoxExtent, bIncludeFromChildActors);

        return FBox::BuildAABB(ActorOrigin, BoxExtent);
}

void UlxUtilityLibrary::AddMovementInputRightward(APawn *target, double ScaleValue, bool Force) {
	FRotator rotator = target->GetControlRotation();
	rotator.Pitch = 0.0;
	rotator.Roll = 0.0;
	rotator.Yaw += 90.0;
	FVector vector = rotator.Vector();
	target->AddMovementInput(vector, ScaleValue, Force);
}

void UlxUtilityLibrary::AddMovementInputForward(APawn *target, double ScaleValue, bool Force) {
	FRotator rotator = target->GetControlRotation();
	rotator.Roll = 0.0;
	rotator.Pitch = 0.0;
	FVector vector = rotator.Vector();
	target->AddMovementInput(vector, ScaleValue, Force);
}

UEnhancedInputLocalPlayerSubsystem *UlxUtilityLibrary::GetEnhancedInputLocalPlayerSubsystem(AController *Controller) {
	APlayerController *pc = Cast<APlayerController>(Controller);
	if (pc != nullptr) {
		UEnhancedInputLocalPlayerSubsystem* subsys = 
			ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(pc->GetLocalPlayer());
		if (subsys != nullptr) {
			return subsys;
		}
	}
	return nullptr;
}

FVector2D UlxUtilityLibrary::PixelToViewportPosition(FVector2D Pixel)
{
	FVector2D ViewportSize;
	GEngine->GameViewport->GetViewportSize(ViewportSize);
	return Pixel / ViewportSize;
}

FVector2D UlxUtilityLibrary::ViewportPositionToPixel(FVector2D Fraction, bool Snap)
{
	FVector2D ViewportSize;
	GEngine->GameViewport->GetViewportSize(ViewportSize);
	FVector2D Pixel = Fraction * ViewportSize;
	if (Snap)
	{
		Pixel.X = FMath::FloorToDouble(Pixel.X) + 0.5;
		Pixel.Y = FMath::FloorToDouble(Pixel.Y) + 0.5;
		Pixel.X = FMath::Min(ViewportSize.X - 0.5, FMath::Max(0.5, Pixel.X));
		Pixel.Y = FMath::Min(ViewportSize.Y - 0.5, FMath::Max(0.5, Pixel.Y));
	}
	else
	{
		Pixel.X = FMath::Min(ViewportSize.X, FMath::Max(0.0, Pixel.X));
		Pixel.Y = FMath::Min(ViewportSize.Y, FMath::Max(0.0, Pixel.Y));
	}
	return Pixel;
}


bool UlxUtilityLibrary::LineTraceThroughPixel(const APlayerController* PlayerController, 
	FVector2D PixelXY, double MaxDistanceFromCamera,
	ETraceTypeQuery TraceChannel, bool bTraceComplex, EDrawDebugTrace::Type DrawDebugType, bool bIgnorePlayerPawn,
	const TArray<AActor*>& ActorsToIgnore, FHitResult& HitResult)
{
	const FLinearColor TraceColor = FLinearColor::Red;
	const FLinearColor TraceHitColor = FLinearColor::Green;
	const double DrawTime = 1.0;

	// Zero out the return values.
	HitResult.Init();

	// Sanity check the distance.
	if (MaxDistanceFromCamera <= 0.0)
	{
		return false;
	}

	// Make sure there's a player pawn.
	AActor *PlayerPawn = PlayerController->GetPawn();
	if (!PlayerPawn) return false;

	// Calculate the trace start and trace end positions in world space.
	FVector WorldStart, WorldDirection, WorldEnd;
	if (!UGameplayStatics::DeprojectScreenToWorld(PlayerController, PixelXY, WorldStart, WorldDirection))
	{
		return false;
	}
	WorldEnd = WorldStart + (WorldDirection * MaxDistanceFromCamera);

	// Find the hit.
	if (UKismetSystemLibrary::LineTraceSingle(PlayerPawn, WorldStart, WorldEnd, TraceChannel, bTraceComplex,
		ActorsToIgnore, DrawDebugType, HitResult, bIgnorePlayerPawn, TraceColor, TraceHitColor, DrawTime))
	{
		return true;
	}

	// Fail.
	HitResult.Init();
	return false;
}

void UlxUtilityLibrary::ConfigureCanvasPanelSlot(UObject *Target, FAnchors Anchors, FVector2D Position, FVector2D Size, FVector2D Alignment, bool SizeToContent)
{
	UCanvasPanelSlot *CanvasSlot = Cast<UCanvasPanelSlot>(Target);
	if (CanvasSlot == nullptr)
	{
		UWidget *Widget = Cast<UWidget>(Target);
		if (Widget != nullptr)
		{
			CanvasSlot = Cast<UCanvasPanelSlot>(Widget->Slot);
		}
	}
	if (CanvasSlot == nullptr)
	{
		UE_LOG(LogBlueprint, Error, TEXT("ConfigureCanvasPanelSlot: object is not a CanvasPanelSlot, and is not a Widget in a CanvasPanel."));
		return;
	}

	CanvasSlot->SetAnchors(Anchors);
	CanvasSlot->SetAlignment(Alignment);
	CanvasSlot->SetPosition(Position);
	CanvasSlot->SetSize(Size);
	CanvasSlot->SetAutoSize(SizeToContent);
}

ElxUsedOrNotUsed UlxUtilityLibrary::IsKeyUsedByMappingContext(const FKey &Key, const UInputMappingContext *MappingContext)
{
    if (!MappingContext)
    {
        return ElxUsedOrNotUsed::NotUsed;
    }

    for (const FEnhancedActionKeyMapping& Mapping : MappingContext->GetMappings())
    {
        if (Mapping.Key == Key) return ElxUsedOrNotUsed::Used;
    }

    return ElxUsedOrNotUsed::NotUsed;
}

FKey UlxUtilityLibrary::GetKeyByName(const FName &Name)
{
	FKey Key = FKey(Name);
	return Key.IsValid() ? Key : FKey();
}

FKey UlxUtilityLibrary::GetKeyByNameString(const FString &Name)
{
	FKey Key = FKey(FName(*Name));
	return Key.IsValid() ? Key : FKey();
}

UInputAction *UlxUtilityLibrary::FindInputActionByName(const UInputMappingContext *MappingContext, const FString &Name)
{
	if (!MappingContext) return nullptr;
	FName Target(*Name);
	for (const FEnhancedActionKeyMapping &Mapping : MappingContext->GetMappings())
	{
		if (Mapping.Action && Mapping.Action->GetFName() == Target)
		{
			return const_cast<UInputAction *>(Mapping.Action.Get());
		}
	}
	return nullptr;
}

FVector UlxUtilityLibrary::GetActorForwardVelocity(const AActor *Actor, double Speed, bool bSnapToXY)
{
	if (!Actor) return FVector::ZeroVector;
	FVector Forward = Actor->GetActorForwardVector();
	if (bSnapToXY)
	{
		Forward.Z = 0.0;
		if (!Forward.Normalize()) return FVector::ZeroVector;
	}
	return Forward * Speed;
}

void UlxUtilityLibrary::ValidateLuaExpr(
    ElxLuaSyntaxCheck &Status, FString &ErrorMessage, UObject *context, const FString &Code)
{
    FlxLockedWrapper w;
    Status = w.ValidateLuaExpr(Code, ErrorMessage);
}

void UlxUtilityLibrary::LogLuprexConsoleMessage(const FString &Message)
{
    UE_LOG(LogLuprexConsole, Warning, TEXT("%s"), *Message);
}

