// Project Lab - NHTV Igad

#include "UnrealProject.h"
#include "DefaultGameInstance.h"
#include "MenuController.h"
#include "SplashScreen.h"
#include "LobbyMenu.h"
#include "CharacterSettings.h"
#include "StartPromptScreen.h"
#include "PlayerStateBase.h"
#include "GameStateBase.h"
#include "SessionManager.h"
#include "TransitionScreen.h"
#include "ScreenOverlay.h"
#include "SkillTreeWidget.h"
#include "DefaultPlayerController.h"
#include "MenuScreen.h"
#include "MenuGameMode.h"

static UClass* menuWidgetClass = nullptr;
static UClass* lobbyWidgetClass = nullptr;
static UClass* splashScreenClass = nullptr;
static UClass* startScreenClass = nullptr;
static UClass* skillTreeClass = nullptr;
static TSubclassOf<UMenuPanel> mainSubMenuClass;

AMenuController::AMenuController(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	menuWidgetClass = ConstructorHelpers::FClassFinder<UMenuScreen>(TEXT("/Game/Assets/GUI/WEEGEE_Menu")).Class;
	lobbyWidgetClass = ConstructorHelpers::FClassFinder<ULobbyMenu>(TEXT("/Game/Assets/GUI/WEEGEE_Lobby")).Class;
	splashScreenClass = ConstructorHelpers::FClassFinder<USplashScreen>(TEXT("/Game/Assets/GUI/WEEGEE_Splash")).Class;
	startScreenClass = ConstructorHelpers::FClassFinder<UStartPromptScreen>(TEXT("/Game/Assets/GUI/WEEGEE_StartPrompt")).Class;
	skillTreeClass = ConstructorHelpers::FClassFinder<USkillTreeWidget>(TEXT("/Game/Assets/GUI/WEEGEE_SkillTree")).Class;
	mainSubMenuClass = ConstructorHelpers::FClassFinder<UMenuPanel>(TEXT("/Game/Assets/GUI/Menus/WEEGEE_MainSubMenu")).Class;

	this->bShowMouseCursor = true;
	m_inLobby = false;
	PrimaryActorTick.bCanEverTick = true;
}

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

	if(IsLocalController())
	{
		// Create skill tree widget
		skillTree = CreateWidget<USkillTreeWidget>(GetWorld(), skillTreeClass);
		skillTree->AddToViewport(5);

		UDefaultGameInstance* inst = Cast<UDefaultGameInstance>(GetGameInstance());
		UWorld* world = GetWorld();
		bool showSplash = !inst->splashScreenShown;
		if(world->WorldType == EWorldType::PIE)
			showSplash = false;
		if(inst->sessionManager->HasNetConnection())
			showSplash = false;

		if(showSplash)
		{
			m_splash = CreateWidget<USplashScreen>(this, splashScreenClass);
			m_splash->onDone.AddDynamic(this, &AMenuController::m_OnSplashScreenEnded);
			check(m_splash);
			m_splash->AddToViewport();
		}
		else
		{
			m_OnStartPromptClosed();
		}

		inst->splashScreenShown = true;

		inst->sessionManager->onAcceptInvite.AddUObject(this, &AMenuController::m_OnInviteAccepted);
	}
}
void AMenuController::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);

	UDefaultGameInstance* inst = Cast<UDefaultGameInstance>(GetGameInstance());
	inst->sessionManager->onAcceptInvite.RemoveAll(this);
}

void AMenuController::NotifyLoadedWorld(FName WorldPackageName, bool bFinalDest)
{
	Super::NotifyLoadedWorld(WorldPackageName, bFinalDest);
	if(bFinalDest)
	{
		GPRINT("Final level loaded for player controller " + GetName());
		if(transitionScreen)
		{
			// Hide transition screen
			transitionScreen->OnHide();
		}
	}
}

void AMenuController::Destroyed()
{
	GPRINT("Menu controller destroyed " + GetName());
	Super::Destroyed();
}

void AMenuController::PreClientTravel(const FString& PendingURL, ETravelType TravelType, bool bIsSeamlessTravel)
{
	if(bIsSeamlessTravel)
	{
		// Hide lobby UI
		if(lobbyMenu)
			lobbyMenu->OnHide();

		// Close all the menu
		if(m_splash)
			m_splash->RemoveFromViewport();
		if(m_startScreen)
			m_startScreen->RemoveFromViewport();
		if(lobbyMenu)
			lobbyMenu->RemoveFromViewport();
		if(mainMenu)
			mainMenu->RemoveFromViewport();
		if(skillTree)
			skillTree->RemoveFromViewport();
		mainMenu = nullptr;
		lobbyMenu = nullptr;
		skillTree = nullptr;
		m_startScreen = nullptr;
		m_splash = nullptr;

		// Show transition screen
		transitionScreen->OnShow();

		GPRINT("Seamless travel on player " + GetName() + " to \"" + PendingURL + "\"");
	}
	else
	{
		GPRINT("Regular travel on player to " + GetName() + " to \"" + PendingURL + "\"");
	}
	Super::PreClientTravel(PendingURL, TravelType, bIsSeamlessTravel);
}

void AMenuController::SeamlessTravelFrom(class APlayerController* OldPC)
{
	Super::SeamlessTravelFrom(OldPC);
	GPRINT("Seamless travel to menu [" + GetName() + "]<-[" + OldPC->GetName() + "]");
}
void AMenuController::SeamlessTravelTo(class APlayerController* NewPC)
{
	Super::SeamlessTravelTo(NewPC);
	GPRINT("Seamless travel to game level [" + GetName() + "]->[" + NewPC->GetName() + "]");
	APlayerStateBase* pcbn = Cast<APlayerStateBase>(NewPC->PlayerState);
	APlayerStateBase* pcbo = GetPlayerState<APlayerStateBase>();

	check(pcbn && pcbo);
	pcbn->Transfer(pcbo);

	ADefaultPlayerController* newDPC = Cast<ADefaultPlayerController>(NewPC);
	if(newDPC)
	{
		newDPC->setupState = setupState;
	}
}

void AMenuController::OnLearnSkill(class UBaseSkillObject* object)
{
}
void AMenuController::OnUnlearnSkill(class UBaseSkillObject* object)
{
}

void AMenuController::OnEnterLobby()
{
	if(m_inLobby)
		return;

	if(mainMenu && lobbyMenu)
	{
		mainMenu->OnHide();
		lobbyMenu->OnShow();
		lobbyMenu->OnLobbyEnter();
		m_inLobby = true;
		OnRep_PlayerState();
	}
	else
	{
		GERROR("wat");
	}

	// Set Default character slot
	/// TODO: Add selector widget
	//UCharacterSettings* settings = Cast<UDefaultGameInstance>(GetGameInstance())->GetCharacterSettings();
	//if(settings->characterSaves.Num() > 0)
	//{
	//	const FCharacterSave& save = settings->characterSaves[0];
	//	FPlayerSetupState pss;
	//	pss.customizations = save.characterCustomization;
	//	pss.skills = save.skillTreeState;
	//	pss.characterClass = save.characterClass;
	//	SetSetupState(pss);
	//}
}

/// TODO, validate skill tree / customization here
bool AMenuController::SetSetupState_Validate(const FPlayerSetupState& state)
{
	return true;
}
void AMenuController::SetSetupState_Implementation(const FPlayerSetupState& state)
{
	setupState = state;

	// Resync the lobby
	UWorld* const world = GetWorld();
	if (IsValid(world))
	{
		AGameStateBase* const gameState = Cast<AGameStateBase>(GetWorld()->GetGameState());
		if (IsValid(gameState))
			gameState->OnPlayerStateChange.Broadcast(this);
	}
}

void AMenuController::OnRep_PlayerState()
{
	// Request a team if we're in a lobby
	if(m_inLobby)
	{
		m_AutoRequestTeam();
	}

	// Send to the server that we got our player state
	m_OnClientInitialized();
}

void AMenuController::SwitchToMenu()
{
	mainMenu->OnShow();
	lobbyMenu->OnHide();
	m_inLobby = false;
}

bool AMenuController::m_OnClientInitialized_Validate()
{
	return true;
}
void AMenuController::m_OnClientInitialized_Implementation()
{
	AMenuGameMode* gm = Cast<AMenuGameMode>(GetWorld()->GetAuthGameMode());
	if(gm)
	{
		gm->onPlayerJoined.Broadcast(this);
	}
}

void AMenuController::m_OnInviteAccepted(bool success, const FOnlineSessionSearchResult& result)
{
	if(success)
	{
		GPRINT("Accepted an invite");
		UDefaultGameInstance* inst = Cast<UDefaultGameInstance>(GetGameInstance());

		inst->sessionManager->onJoinSessionComplete.AddUObject(this, &AMenuController::m_OnSessionJoined);
		inst->sessionManager->JoinSession(result);
	}
}
void AMenuController::m_OnSessionJoined(int32 result)
{
	UDefaultGameInstance* inst = Cast<UDefaultGameInstance>(GetGameInstance());
	inst->sessionManager->onJoinSessionComplete.RemoveAll(this);

	if(result == EOnJoinSessionCompleteResult::Success)
	{
		ClientTravel(inst->sessionManager->joinConnectionString, ETravelType::TRAVEL_Absolute);
	}
	else
	{
		TArray<FString> options;
		options.Add("OK");
		overlay->ShowMessageBox("Error", FString("Failed to accept invite (") + FString::FromInt(result) + ")", options);
		GERROR("Failed to join session invite");
		SwitchToMenu();
	}
}

void AMenuController::m_AutoRequestTeam()
{
	UWorld* world = GetWorld();
	APlayerStateBase* state = GetPlayerState<APlayerStateBase>();
	if(state)
	{
		state->AutoAssignTeam();
		if(world->IsPlayInEditor())
			state->SetReadyState(true);
		//GetWorld()->GetTimerManager().ClearTimer(m_teamRequestTimer);
	}
}
void AMenuController::m_OnSplashScreenEnded()
{
	m_splash = nullptr;

	// Create start prompt
	m_startScreen = CreateWidget<UStartPromptScreen>(this, startScreenClass);
	check(m_startScreen);
	m_startScreen->onClosed.AddUObject(this, &AMenuController::m_OnStartPromptClosed);
	m_startScreen->AddToViewport();
}
void AMenuController::m_OnStartPromptClosed()
{
	m_startScreen = nullptr;

	// Open main menu
	lobbyMenu = CreateWidget<ULobbyMenu>(this, lobbyWidgetClass);
	lobbyMenu->AddToViewport();
	mainMenu = CreateWidget<UMenuScreen>(this, menuWidgetClass);
	mainMenu->AddToViewport();
	mainMenu->OnShow();
	mainMenu->OpenScreenMenu(mainSubMenuClass);

	// Enter lobby when initially connected
	UWorld* world = GetWorld();
	UDefaultGameInstance* inst = Cast<UDefaultGameInstance>(world->GetGameInstance());
	if(inst->sessionManager->HasNetConnection())
	{
		OnEnterLobby();
	}
}

bool AMenuController::WorldIsPIE() const
{
	UWorld* const world = GetWorld();
	if (!IsValid(world)) return false;
	return world->WorldType == EWorldType::PIE;
}