#include "PlayerControllerBase.h"
#include "Common.h"
#include "GameFramework/InputDeviceSubsystem.h"
#include "GameFramework/InputSettings.h"
#include "RootCanvas.h"
#include "Tangible.h"
#include "TangibleManager.h"
#include "Blueprint/UserWidget.h"
#include "Blueprint/WidgetTree.h"
#include "Components/InputComponent.h"
#include "Engine/GameInstance.h"
#include "Engine/GameViewportClient.h"
#include "Engine/LevelScriptActor.h"
#include "Engine/LocalPlayer.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/Application/SlateUser.h"
#include "Kismet/GameplayStatics.h"
#include "Widgets/SViewport.h"

AlxPlayerControllerBase *AlxPlayerControllerBase::FromContext(const UObject *Context)
{
	APlayerController *PC = Context->GetWorld()->GetFirstPlayerController();
	AlxPlayerControllerBase *Result = Cast<AlxPlayerControllerBase>(PC);
	if (Result == nullptr)
	{
		UE_LOG(LogLuprexIntegration, Fatal, TEXT("Not currently using a Luprex Player Controller."));
	}
	return Result;
}

const FHitResult &AlxPlayerControllerBase::GetLookAt(const UObject *Context)
{
	return FromContext(Context)->CurrentLookAt;
}

const AActor *AlxPlayerControllerBase::GetLookAtActor(const UObject *Context)
{
	return FromContext(Context)->CurrentLookAt.GetActor();
}

void AlxPlayerControllerBase::SetLookAt(const UObject *Context, const FHitResult &HitResult)
{
	AlxPlayerControllerBase *PC = FromContext(Context);
	if (PC->CurrentLookAt.HitObjectHandle != HitResult.HitObjectHandle)
	{
		PC->MustCallLookAtChanged = true;
	}
	PC->CurrentLookAt = HitResult;
}

void AlxPlayerControllerBase::SetLookAtChanged(const UObject *Context)
{
	AlxPlayerControllerBase *PC = FromContext(Context);
	PC->MustCallLookAtChanged = true;
}

FVector2D AlxPlayerControllerBase::GetLookAtPixel(const UObject *Context)
{
	AlxPlayerControllerBase *PC = FromContext(Context);
	FVector2D ScreenPosition;
	if (!UGameplayStatics::ProjectWorldToScreen(PC, PC->CurrentLookAt.Location, ScreenPosition, false))
	{
		return FVector2D();
	}
	return ScreenPosition;
}

void AlxPlayerControllerBase::BeginPlay()
{
	// Build the root UMG stack BEFORE Super::BeginPlay. Super calls
	// ReceiveBeginPlay, which fires the Blueprint Event BeginPlay;
	// BP code there may immediately try to add widgets to RootCanvas,
	// so the canvas must already exist.
	RootWidget = CreateWidget<UlxRootWidget>(this);
	RootCanvas = RootWidget->WidgetTree->ConstructWidget<UlxRootCanvasPanel>();
	RootWidget->WidgetTree->RootWidget = RootCanvas;
	RootWidget->AddToViewport(0);

	Super::BeginPlay();
}

void AlxPlayerControllerBase::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	if (IsValid(RootWidget))
	{
		RootWidget->RemoveFromParent();
	}
	RootWidget = nullptr;
	RootCanvas = nullptr;

	Super::EndPlay(EndPlayReason);
}

UInputComponent* AlxPlayerControllerBase::GetWidgetInputComponent(UUserWidget *Widget)
{
	if (!IsValid(Widget)) return nullptr;

	// Cache the FProperty on first call. FProperties are owned by
	// the native UUserWidget UClass, which lives for the process
	// lifetime, so the pointer stays valid without us needing to
	// root anything against GC. Static local init is thread-safe.
	static FObjectProperty *InputComponentProp = FindFProperty<FObjectProperty>(
		UUserWidget::StaticClass(), TEXT("InputComponent"));
	check(InputComponentProp);
	
	UObject *Value = InputComponentProp->GetObjectPropertyValue_InContainer(Widget);
	return Cast<UInputComponent>(Value);
}

void AlxPlayerControllerBase::AddWidgetToRoot(UUserWidget *Widget)
{
	if (!IsValid(Widget))
	{
		UE_LOG(LogLuprexIntegration, Error, TEXT("AddWidgetToRoot called with an invalid widget."));
		return;
	}
	APlayerController *OwningPC = Widget->GetOwningPlayer();
	AlxPlayerControllerBase *PC = Cast<AlxPlayerControllerBase>(OwningPC);
	if (PC == nullptr)
	{
		UE_LOG(LogLuprexIntegration, Error,
			TEXT("AddWidgetToRoot: widget '%s' owning player is not an AlxPlayerControllerBase (got %s)."),
			*Widget->GetName(), *GetNameSafe(OwningPC));
		return;
	}
	if (PC->RootCanvas == nullptr)
	{
		UE_LOG(LogLuprexIntegration, Error,
			TEXT("AddWidgetToRoot: root canvas is not initialized, this is probably an initialization order issue"));
		return;
	}

	if (Widget->GetParent() == PC->RootCanvas) return;

	PC->RootCanvas->AddChildToRootCanvas(Widget);
}

void AlxPlayerControllerBase::BuildInputStack(TArray<UInputComponent*>& InputStack)
{
	// Controlled pawn gets last dibs on the input stack
	APawn* ControlledPawn = GetPawnOrSpectator();
	if (ControlledPawn)
	{
		if (ControlledPawn->InputEnabled())
		{
			// Get the explicit input component that is created upon Pawn possession. This one gets last dibs.
			if (ControlledPawn->InputComponent)
			{
				InputStack.Push(ControlledPawn->InputComponent);
			}

			// See if there is another InputComponent that was added to the Pawn's components array (possibly by script).
			for (UActorComponent* ActorComponent : ControlledPawn->GetComponents())
			{
				UInputComponent* PawnInputComponent = Cast<UInputComponent>(ActorComponent);
				if (PawnInputComponent && PawnInputComponent != ControlledPawn->InputComponent)
				{
					InputStack.Push(PawnInputComponent);
				}
			}
		}
	}

	// LevelScriptActors are put on the stack next
	for (ULevel* Level : GetWorld()->GetLevels())
	{
		ALevelScriptActor* ScriptActor = Level->GetLevelScriptActor();
		if (ScriptActor)
		{
			if (ScriptActor->InputEnabled() && ScriptActor->InputComponent)
			{
				InputStack.Push(ScriptActor->InputComponent);
			}
		}
	}

	if (InputEnabled())
	{
		InputStack.Push(InputComponent);
	}

	// Get the widget slots.
	TArray<UlxRootCanvasSlot*> WidgetSlots = RootCanvas->GetSortedUserWidgets();

	// Generate a set of input components that are being managed by the WidgetSlots.
	TSet<UInputComponent*> WidgetManagedComponents;
	for (UlxRootCanvasSlot *Slot : WidgetSlots)
	{
		UUserWidget *Widget = Cast<UUserWidget>(Slot->GetContent());
		UInputComponent *IC = GetWidgetInputComponent(Widget);
		if (IC) WidgetManagedComponents.Add(IC);
	}

	// Add components in the CurrentInputStack, *unless* they are being managed
	// by widgets.  If they're being managed by widgets, they get added later.
	for (int32 Idx=0; Idx<CurrentInputStack.Num(); ++Idx)
	{
		UInputComponent* IC = CurrentInputStack[Idx].Get();
		if (IsValid(IC))
		{
			if (!WidgetManagedComponents.Contains(IC)) InputStack.Push(IC);
		}
		else
		{
			CurrentInputStack.RemoveAt(Idx--);
		}
	}

	// Now add the widget-managed input components.
	for (UlxRootCanvasSlot *Slot : WidgetSlots)
	{
		if (Slot->EnableEnhancedInput)
		{
			UUserWidget *Widget = Cast<UUserWidget>(Slot->GetContent());
			UInputComponent *IC = GetWidgetInputComponent(Widget);
			if (IC)
			{
				IC->bBlockInput = Slot->BlockInput;
				InputStack.Push(IC);
			}
		}
	}
}

void AlxPlayerControllerBase::UpdateInputMode()
{
	// Get all the various objects we need to be able to manipulate
	// the input mode.
	UGameViewportClient *GameViewportClient = GetWorld()->GetGameViewport();
	ULocalPlayer *LocalPlayer = Cast<ULocalPlayer>(Player);
	if (GameViewportClient == nullptr || LocalPlayer == nullptr) return;
	TSharedPtr<SViewport> ViewportWidget = GameViewportClient->GetGameViewportWidget();
	if (!ViewportWidget.IsValid()) return;
	TSharedRef<SViewport> ViewportWidgetRef = ViewportWidget.ToSharedRef();
	FReply &SlateOperations = LocalPlayer->GetSlateOperations();
	TSharedPtr<FSlateUser> SlateUser = LocalPlayer->GetSlateUser();
	if (!SlateUser.IsValid()) return;


	RootCanvas->UpdateZOrders();

	// Get the desired configuration from the top widget.
	UUserWidget *Widget = nullptr;
	UWidget *Focus = nullptr;
	bool ShowPointer = false;
	if (UlxRootCanvasSlot *Top = RootCanvas->GetTopWidget())
	{
		Widget = Cast<UUserWidget>(Top->GetContent());
		Focus = Widget->GetDesiredFocusWidget();
		ShowPointer = Top->ShowPointer;
	}

	SetShowMouseCursor(ShowPointer);

	if (ShowPointer)
	{
		// Only release capture if the viewport is currently holding it
		// (e.g. we just came from GameOnly). A blanket ReleaseMouseCapture
		// every tick would yank capture away from widgets mid-gesture
		// (scrollbar drags, slider drags, etc.).
		if (SlateUser->DoesWidgetHaveAnyCapture(ViewportWidget))
		{
			SlateOperations.ReleaseMouseCapture();
		}
		SlateOperations.ReleaseMouseLock();
		GameViewportClient->SetMouseLockMode(EMouseLockMode::DoNotLock);
		GameViewportClient->SetHideCursorDuringCapture(false);
		GameViewportClient->SetMouseCaptureMode(EMouseCaptureMode::CaptureDuringMouseDown);
	}
	else
	{
		// Also captures the mouse to the viewport.
		SlateOperations.UseHighPrecisionMouseMovement(ViewportWidgetRef);
		SlateOperations.LockMouseToWidget(ViewportWidgetRef);
		GameViewportClient->SetMouseLockMode(EMouseLockMode::LockOnCapture);
		GameViewportClient->SetMouseCaptureMode(EMouseCaptureMode::CapturePermanently);
	}

	GameViewportClient->SetIgnoreInput(false);

	// We always put keyboard focus on whatever user widget is in
	// front.  If the front widget doesn't want keyboard focus,
	// then we put keyboard focus on the viewport.
	if (Focus)
	{
		if (TSharedPtr<SWidget> SlateFocus = Focus->GetCachedWidget())
		{
			SlateOperations.SetUserFocus(SlateFocus.ToSharedRef());
		}
	}
	else
	{
		SlateOperations.SetUserFocus(ViewportWidgetRef);
	}
}

void AlxPlayerControllerBase::UpdateLookAt()
{
	UlxTangibleManager *TM = GetGameInstance()->GetSubsystem<UlxTangibleManager>();
	if (TM == nullptr) return;
	UlxTangible *Possessed = TM->GetPossessedTangible();
	if (Possessed == nullptr) return;
	APawn *Pawn = GetPawn();
	if (Pawn == nullptr) return;
	if (Possessed->GetActor() != Cast<AActor>(Pawn)) return;
	if (PlayerCameraManager == nullptr) return;

	CalculateLookAt();

	if (MustCallLookAtChanged)
	{
		MustCallLookAtChanged = false;
		LookAtChanged();
	}
}
