// Project Lab - NHTV Igad

#include "UnrealProject.h"
#include "GameList.h"
#include "GameListItem.h"
#include "DefaultGameInstance.h"
#include "SessionManager.h"
#include "ScreenOverlay.h"
#include "PlayerControllerBase.h"

static UClass* itemWidgetClass;

UGameList::UGameList(const FObjectInitializer& init)
	: Super(init)
{
	itemWidgetClass = ConstructorHelpers::FClassFinder<UGameListItem>(TEXT("/Game/Assets/GUI/Components/WEEGEE_GameListItem")).Class;
	m_quickJoining = false;
}

void UGameList::NativeConstruct()
{
	Super::NativeConstruct();
	m_searchingSessions = false;
	m_joining = false;

	UWorld* world = this->GetWorld();
	if (!world)
		return;
	m_gameInstance = Cast<UDefaultGameInstance>(world->GetGameInstance());
	if (!m_gameInstance)
		return;

	onItemSelectionConfirmed.AddDynamic(this, &UGameList::OnListItemPressed);
}
void UGameList::NativeDestruct()
{
	m_StopPingingGames();
	Super::NativeDestruct();
}
void UGameList::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
	Super::NativeTick(MyGeometry, InDeltaTime);
}

void UGameList::OnStartSearching_Implementation()
{
}
void UGameList::OnEndSearching_Implementation(int32)
{
}
void UGameList::SetInitialSearchMode_Implementation(bool useLan)
{
}
void UGameList::OnQuickJoin()
{
	OnSearchButton();
	m_quickJoining = true;
}
void UGameList::JoinSession(const class FOnlineSessionSearchResult& result)
{
	m_StopPingingGames();

	if(!m_joining)
	{
		APlayerControllerBase* pc = Cast<APlayerControllerBase>(GetOwningPlayer());
		check(!m_joiningOverlay);
		m_joiningOverlay = pc->overlay->ShowOverlay("Joining game");
		m_joining = true;

		UDefaultGameInstance* inst = Cast<UDefaultGameInstance>(GetGameInstance());
		inst->sessionManager->onJoinSessionComplete.AddUObject(this, &UGameList::m_OnJoinSessionComplete);
		inst->sessionManager->JoinSession(result);
	}
}
void UGameList::SetSearchMode(bool useLan)
{
}
void UGameList::OnSearchButton()
{
	UDefaultGameInstance* inst = Cast<UDefaultGameInstance>(GetGameInstance());
	if (!m_searchingSessions)
	{
		check(!m_searchingOverlay);
		inst->sessionManager->onFindSessionsComplete.AddUObject(this, &UGameList::m_OnFindSessionsComplete);
		inst->sessionManager->FindSessions();
		m_searchingSessions = true;
		container->ClearChildren();

		m_StopPingingGames();
		m_gameItems.SetNum(0);

		RescanItems();
		OnStartSearching();

		APlayerControllerBase* pc = Cast<APlayerControllerBase>(GetOwningPlayer());
		m_searchingOverlay = pc->overlay->ShowOverlay("Searching for games");
	}
}

void UGameList::OnListItemPressed(UMenuItemBase* item)
{
	GWPRINT(L"Pressed item " + item);
	UGameListItem* listItem = Cast<UGameListItem>(item);
	if(listItem)
	{
		JoinSession(*listItem->result);
	}
}

void UGameList::m_OnFindSessionsComplete(bool success)
{
	UDefaultGameInstance* inst = Cast<UDefaultGameInstance>(GetGameInstance());

	// Hide searching overlay
	m_searchingOverlay->Close();
	m_searchingOverlay = nullptr;

	m_searchingSessions = false;

	if(!success)
	{
		APlayerControllerBase* pc = Cast<APlayerControllerBase>(GetOwningPlayer());
		TArray<FString> options;
		options.Add("OK");
		pc->overlay->ShowMessageBox("Error", "Error occured while fining games", options);
		return;
	}

	if(m_quickJoining)
	{
		// Find session with most players in, that is not full
		auto& results = inst->sessionManager->sessionSearch->SearchResults;
		if(results.Num() > 0)
		{
			FOnlineSessionSearchResult* optimal = &results[0];
			int32 largest = 0;
			for(int32 i = 1; i < results.Num(); i++)
			{
				const int32 available = results[i].Session.NumOpenPublicConnections;
				if(available == 0) continue;
				const int32 max = results[i].Session.SessionSettings.NumPublicConnections;
				const int32 current = max - available;
				if(current > largest)
				{
					optimal = &results[i];
					largest = current;
				}
			}
			JoinSession(*optimal);
		}
		m_quickJoining = false;
	}
	else
	{
		// Get session results
		auto& results = inst->sessionManager->sessionSearch->SearchResults;
		for(int32 i = 0; i < results.Num(); i++)
		{
			// Create a selectable slot in the game list
			UGameListItem* childItem = CreateWidget<UGameListItem>(GetWorld(), itemWidgetClass);
			check(container);
			container->AddChild(childItem);
			childItem->SetItem(this, results[i]);
			m_gameItems.Add(childItem);
		}

		OnEndSearching(results.Num());
		RescanItems();

		// Give ping data for games
		m_StartPingingGames();
	}

	m_searchingSessions = false;

	// Remove callback
	inst->sessionManager->onFindSessionsComplete.RemoveAll(this);
}
void UGameList::m_OnJoinSessionComplete(int32 result)
{
	UDefaultGameInstance* inst = Cast<UDefaultGameInstance>(GetGameInstance());
	APlayerControllerBase* pc = Cast<APlayerControllerBase>(GetOwningPlayer());

	m_joining = false;

	// Remove callback
	inst->sessionManager->onJoinSessionComplete.RemoveAll(this);

	if(result != EOnJoinSessionCompleteResult::Success)
	{
		FString error = "Unknown";
		switch(result)
		{
		case EOnJoinSessionCompleteResult::AlreadyInSession:
			error = "Already in session";
			break;
		case EOnJoinSessionCompleteResult::CouldNotRetrieveAddress:
			error = "Could not retrieve address";
			break;
		case EOnJoinSessionCompleteResult::SessionDoesNotExist:
			error = "Session does not exist";
			break;
		case EOnJoinSessionCompleteResult::SessionIsFull:
			error = "Session is full";
			break;
		}

		// Close joining overlay
		m_joiningOverlay->Close();
		m_joiningOverlay = nullptr;

		// Show error message
		TArray<FString> options;
		options.Add("OK");
		pc->overlay->ShowMessageBox("Error", FString("Failed to join session:\n") + error, options);
	}
	else
	{
		// Travel to destination player lobby
		pc->ClientTravel(inst->sessionManager->joinConnectionString, ETravelType::TRAVEL_Absolute, false);
		//UWorld* world = GetWorld();
		//world->SeamlessTravel(inst->sessionManager->joinConnectionString, true);
		//world->ServerTravel()
		//AMenuGameMode* mgm = Cast<AMenuGameMode>(world->GetAuthGameMode());
	}
}

void UGameList::m_PingGames()
{
	for(auto& g : m_gameItems)
	{
		g->Ping();
	}
}
void UGameList::m_StartPingingGames()
{
	GetGameInstance()->GetTimerManager().SetTimer(m_pinger, this, &UGameList::m_PingGames, 1.0f, true);
}
void UGameList::m_StopPingingGames()
{
	GetGameInstance()->GetTimerManager().ClearTimer(m_pinger);
	for(UGameListItem* i : m_gameItems)
	{
		i->Ping();
	}
}
