// Project Lab - NHTV Igad

#include "UnrealProject.h"
#include "Runtime/Engine/Classes/Engine/LocalPlayer.h"

#if PLATFORM_SPECIFIC_WIN == 0 
// Include input manager for windows to allow DS4 controllers
#include "InputManager.hpp"
using namespace Input;
#endif

#include "DefaultGameInstance.h"
#include "CharacterSettings.h"
#include "Prefs.h"
#include "PlayerControllerBase.h"
#include "ScreenOverlay.h"
#include "SessionManager.h"
#include "ScreenOverlay.h"

#include "MapData.h"
#include "MapList.h"
#include <regex>

// Replicates the exact same data structure as FWindowsCursor except with a public cursor handle member
#if PLATFORM_SPECIFIC_WIN == 0
class FWindowsCursor1 : public ICursor
{
public:
	EMouseCursor::Type CurrentType;
	HCURSOR CursorHandles[EMouseCursor::TotalCursorCount];
};
HCURSOR originalCursor;
void InitWindowsCursors()
{
	// Store original application cursor for editor
	FWindowsCursor1* windowsCursor = (FWindowsCursor1*)FSlateApplication::Get().GetPlatformCursor().Get();
	originalCursor = windowsCursor->CursorHandles[EMouseCursor::Default];

}
void ShutdownWindowsCursors()
{
	// Reset original cursor, mainly for editor
	FWindowsCursor1* windowsCursor = (FWindowsCursor1*)FSlateApplication::Get().GetPlatformCursor().Get();
	windowsCursor->CursorHandles[EMouseCursor::Default] = originalCursor;
}
HCURSOR LoadCustomCursor(const FString& path)
{
	FString parentPath = FPaths::GameDir();
	FString cursorPath = parentPath + "\\" + path;

	HCURSOR cursorHandle = LoadCursorFromFileW(*cursorPath);
	return cursorHandle;
}
void SetApplicationCursor(HCURSOR cursor)
{
	FWindowsCursor1* windowsCursor = (FWindowsCursor1*)FSlateApplication::Get().GetPlatformCursor().Get();
	if(windowsCursor)
	{
		HCURSOR handleDst;
		if(cursor != INVALID_HANDLE_VALUE)
		{
			handleDst = cursor;
		}
		else
		{
			handleDst = LoadCursor(GetModuleHandle(0), IDC_ARROW);
		}

		// Overwrite default cursor that unreal sets hack
		windowsCursor->CursorHandles[EMouseCursor::Default] = handleDst;
	}
}
#endif

static UMapList* mapList;
UDefaultGameInstance::UDefaultGameInstance()
{
	mapList = ConstructorHelpers::FObjectFinder<UMapList>(TEXT("/Game/Assets/Levels/MapList")).Object;
	menuLevelPath = "/Game/Assets/Levels/Menu";
	splashScreenShown = false;

}

void UDefaultGameInstance::Init()
{
	m_characterSettings = nullptr;
	LoadSettings();

#if PLATFORM_SPECIFIC_WIN == 0
	InitWindowsCursors();
	SetApplicationCursor(LoadCustomCursor("cursor.cur"));
#endif
	Super::Init(); 

	m_prefs = nullptr;
	LoadPrefs();

	m_LoadMaps();

#if PLATFORM_SPECIFIC_WIN == 0
	// Initialize TEA Input
	Input::InputManager::Initialize();
	Input::InputManager::GetInstance()->SearchForJoystick();
	m_tickDelegateHandle = FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateUObject(this, &UDefaultGameInstance::m_Tick), 0.016666667f);
#endif

	m_localPlayer = nullptr;

	// Create session manager
	sessionManager = NewObject<USessionManager>(this);
	sessionManager->m_instance = this;
}

bool UDefaultGameInstance::HandleOpenCommand(const TCHAR* Cmd, FOutputDevice& Ar, UWorld* InWorld)
{
	GPRINT("Open command -> " + FString(Cmd) + " [" + InWorld->GetFullName() + "]");
	APlayerControllerBase* pcb = Cast<APlayerControllerBase>(GetFirstLocalPlayerController());
	if(pcb && Super::HandleOpenCommand(Cmd, Ar, InWorld))
	{
		FString msg = FString("Opening level [") + Cmd + "]";
		pcb->overlay->ShowOverlay(msg);
		return true;
	}
	return false;
}

bool UDefaultGameInstance::m_Tick(float DeltaSeconds)
{
#if PLATFORM_SPECIFIC_WIN == 0
	InputManager* inputManager = Input::InputManager::GetInstance();

	inputManager->Update();
	if(!inputManager->joystick)
	{
		std::vector<std::wstring> names = inputManager->GetJoystickNames();
		for(size_t i = 0; i < names.size(); i++)
		{
			inputManager->ConnectJoystickByIndex(i);
			if(inputManager->joystick)
				break;
		}
	}
#endif
	return true;
}


void UDefaultGameInstance::Shutdown()
{
	FTicker::GetCoreTicker().RemoveTicker(m_tickDelegateHandle);
	Super::Shutdown();

#if PLATFORM_SPECIFIC_WIN == 0
	ShutdownWindowsCursors();
	Input::InputManager::Cleanup();
#endif

	sessionManager->Shutdown();
}

const FString levelPath = TEXT("/Game/Assets/Levels");
void UDefaultGameInstance::m_LoadMaps()
{
	if (mapList)
	{
		for (int32 i = 0; i < mapList->mapList.Num(); i++)
		{
			UMapData* mapData = mapList->mapList[i];

			FString name = mapData->GetName();
			wchar_t* nameStrPtr = name.GetCharArray().GetData();
			std::wstring nameStr = std::wstring(nameStrPtr, nameStrPtr + name.Len());

			//GWPRINT(mapData->GetName() + L"\n" + mapData->GetPathName() + L"\n" + mapData->GetFullGroupName(false));

			static std::wregex nameRx = std::wregex(L"I_([_a-zA-Z0-9]+)");
			std::wsmatch match;

			if (std::regex_match(nameStr, match, nameRx))
			{
				std::wstring levelNameStr = match[1];
				FString matchingLevelName = FString(levelNameStr.c_str());
				mapData->pathToAsset = levelPath + FString(L"/") + matchingLevelName + FString(L".") + matchingLevelName;
				mapData->pathToAsset = levelPath + FString(L"/") + matchingLevelName;
				//GWPRINT(L"Level asset path: " + mapData->pathToAsset);
				maps.Add(mapData);
				mapsByLevelName.Add(matchingLevelName, mapData);
				mapsByLevelPath.Add(mapData->pathToAsset, mapData);
			}
			else
			{
				GWERROR(L"Can't extract map name from level asset " + mapData->GetPathName());
			}
		}
	}
}

ULocalPlayer* UDefaultGameInstance::CreateInitialPlayer(FString& OutError)
{
	m_localPlayer = Super::CreateInitialPlayer(OutError);
	check(m_localPlayer);
	//m_localNetID = m_localPlayer->GetPreferredUniqueNetId();
	//GWPRINT(L"Created local player " + m_localPlayer->GetName() + 
	//	", NetID = " + m_localNetID->ToDebugString());
	//check(m_localNetID.IsValid());

	// Retrieve player name
	//IOnlineIdentityPtr identity = m_onlineSystem->GetIdentityInterface();
	//playerID = identity->GetPlayerNickname(*m_localNetID);
	//GWPRINT(L"Assigned player ID: " + playerID);

	//IOnlineFriendsPtr friends = m_onlineSystem->GetFriendsInterface();
	//if (friends.IsValid())
	//{
	//	onReadFriendsListCompleteDelegate = FOnReadFriendsListComplete::CreateUObject(this, &UDefaultGameInstance::m_OnReadFriendsListCompleteDelegate);
	//	friends->ReadFriendsList(0, TEXT("Friends"), onReadFriendsListCompleteDelegate);
	//}
	
	//ISteamFriends* steamFriends = steamIdentity->GetSteamFriendsPtr();
	//steamIdentity
	//friends->GetLargeFriendAvatar()
	//ISteamUser* user;

	//FOnlineIdentitySteam* steam = (FOnlineIdentitySteam*)*identity;
	//class ISteamUser* steamUser = identity->Get

	return m_localPlayer;
}

void UDefaultGameInstance::OnSessionUserInviteAccepted(const bool bWasSuccess, const int32 ControllerId, TSharedPtr<const FUniqueNetId> Us, const FOnlineSessionSearchResult& res)
{
	sessionManager->AcceptUserInvite(bWasSuccess, ControllerId, Us, res);
}

void UDefaultGameInstance::SaveSettings()
{
	if (m_characterSettings)
	{
		UCharacterSettings::Save(m_characterSettings);
	}
}

void UDefaultGameInstance::LoadSettings()
{
	m_characterSettings = UCharacterSettings::Load();
}

UCharacterSettings* UDefaultGameInstance::GetCharacterSettings()
{
	return m_characterSettings;
}

void UDefaultGameInstance::SavePrefs()
{
	if (m_prefs)
	{
		UPrefs::Save(m_prefs);
	}
}

void UDefaultGameInstance::LoadPrefs()
{
	m_prefs = UPrefs::Load();
}

UPrefs* UDefaultGameInstance::GetPrefs()
{
	return m_prefs;
}

UMapData* UDefaultGameInstance::GetCurrentLevelMapData()
{
	UWorld* world = GetWorld();
	check(world);
	FString mapName = world->GetMapName();
	FString name1 = world->GetName();
	UMapData** res = mapsByLevelName.Find(name1);
	if (res)
		return *res;
	return nullptr;
}

class UMapData* UDefaultGameInstance::GetMapData(FString levelPath)
{
	FString first;
	FString last;
	if(!levelPath.Split("/", &first, &last, ESearchCase::IgnoreCase, ESearchDir::FromEnd))
		return nullptr;

	FString la, lb;
	if(last.Split(".", &la, &lb))
	{
		last = lb;
	}

	levelPath = first + "/" + last;

	UMapData** res = mapsByLevelPath.Find(levelPath);
	if(res)
		return *res;
	return nullptr;
}

void UDefaultGameInstance::ShowNetworkError(const FString& msg)
{
	APlayerControllerBase* controller = Cast<APlayerControllerBase>(GetFirstLocalPlayerController());
	check(controller);

	TArray<FString> options;
	options.Add("OK");
	controller->overlay->ShowMessageBox("Network Error", msg, options);
}

int32 UDefaultGameInstance::GetScalabilityQuality()
{
	const Scalability::FQualityLevels current = GEngine->GetGameUserSettings()->ScalabilityQuality;

	// Check if any we know
	Scalability::FQualityLevels setting;
	for (int32 i = 0; i < 4; i++)
	{
		setting.SetFromSingleQualityLevel(i);
		if (current == setting)
			return i;
	}

	// Custom
	return -1;
}
void UDefaultGameInstance::SetScalabilityQuality(int32 scalability)
{
	GEngine->GetGameUserSettings()->ScalabilityQuality.SetFromSingleQualityLevel(scalability);
	GEngine->GetGameUserSettings()->ApplySettings(false);
	GEngine->GetGameUserSettings()->SaveSettings();

	m_prefs->usingCustomGraphicsSettings = false;
	SavePrefs();

	//if (GConfig)
	//{
	//	GConfig->SetInt(
	//		TEXT("ScalabilityGroups"),
	//		TEXT("sg.Preset"),
	//		scalability,
	//		GGameUserSettingsIni);
	//	GConfig->Flush(false, GGameUserSettingsIni);
	//}
}

static int32 GenerateResolutionQuality(int32 value)
{
	Scalability::FQualityLevels setting;
	setting.SetFromSingleQualityLevel(value);
	return setting.ResolutionQuality;
}
static int32 RevertResolutionQuality(int32 value)
{
	Scalability::FQualityLevels setting;
	for (int32 i = 0; i < 4; i++)
	{
		setting.SetFromSingleQualityLevel(i);
		if (setting.ResolutionQuality == value)
			return i;
	}

	return 0;
}
void UDefaultGameInstance::SetScalabilityQualityValues(int32 ResolutionQuality, int32 AntiAliasingQuality, int32 ShadowQuality, int32 PostProcessQuality, int32 TextureQuality, int32 EffectsQuality)
{
	Scalability::FQualityLevels setting;
	setting.SetFromSingleQualityLevel(ResolutionQuality);

	setting.AntiAliasingQuality = AntiAliasingQuality;
	setting.ShadowQuality = ShadowQuality;
	setting.PostProcessQuality = PostProcessQuality;
	setting.TextureQuality = TextureQuality;
	setting.EffectsQuality = EffectsQuality;

	setting.ViewDistanceQuality = TextureQuality;

	GEngine->GetGameUserSettings()->ScalabilityQuality = setting;
	GEngine->GetGameUserSettings()->ApplySettings(false);
	GEngine->GetGameUserSettings()->SaveSettings();

	m_prefs->usingCustomGraphicsSettings = true;
	m_prefs->customGraphicsSettings[0] = ResolutionQuality;
	m_prefs->customGraphicsSettings[1] = AntiAliasingQuality;
	m_prefs->customGraphicsSettings[2] = ShadowQuality;
	m_prefs->customGraphicsSettings[3] = PostProcessQuality;
	m_prefs->customGraphicsSettings[4] = TextureQuality;
	m_prefs->customGraphicsSettings[5] = EffectsQuality;
	SavePrefs();
}

int32 UDefaultGameInstance::GetScalabilityQualityValue(EGraphicsSetting setting)
{
	const Scalability::FQualityLevels current = GEngine->GetGameUserSettings()->ScalabilityQuality;

	switch (int32(setting))
	{
	case 0: return RevertResolutionQuality(current.ResolutionQuality);
	case 1: return current.AntiAliasingQuality;
	case 2: return current.ShadowQuality;
	case 3: return current.PostProcessQuality;
	case 4: return current.TextureQuality;
	case 5: return current.EffectsQuality;
	}

	return 0;
}
void UDefaultGameInstance::SetScalabilityQualityValue(EGraphicsSetting setting, int32 value)
{
	if (int32(setting) >= 6)
		return;

	Scalability::FQualityLevels current = GEngine->GetGameUserSettings()->ScalabilityQuality;

	switch (int32(setting))
	{
	case 0: current.ResolutionQuality = GenerateResolutionQuality(value); break;
	case 1: current.AntiAliasingQuality = value; break;
	case 2: current.ShadowQuality = value; break;
	case 3: current.PostProcessQuality = value; break;
	case 4: current.TextureQuality = value; break;
	case 5: current.EffectsQuality = value; break;
	}

	GEngine->GetGameUserSettings()->ScalabilityQuality = current;
	GEngine->GetGameUserSettings()->ApplySettings(false);
	GEngine->GetGameUserSettings()->SaveSettings();

	m_prefs->usingCustomGraphicsSettings = true;
	m_prefs->customGraphicsSettings[int32(setting)] = value;
	SavePrefs();
}

void UDefaultGameInstance::SetupLoadingScreen()
{
	m_loadingScreen.bAutoCompleteWhenLoadingCompletes = true;
	m_loadingScreen.WidgetLoadingScreen = FLoadingScreenAttributes::NewTestLoadingScreenWidget(); // <-- test screen that comes with 
	m_loadingScreen.MinimumLoadingScreenDisplayTime = 1.0f;
	GetMoviePlayer()->SetupLoadingScreen(m_loadingScreen);
}

/*
void UDefaultGameInstance::OnSessionUserInviteAccepted(const bool bWasSuccess, const int32 ControllerId, TSharedPtr< const FUniqueNetId > Us, const FOnlineSessionSearchResult& res)
{ 
	if (bWasSuccess)
	{
		m_JoinOnlineSession(res);
	}
	else
	{
		GWWARNING(L"OnSessionUserInviteAccepted was not successful");
	}
}
void UDefaultGameInstance::m_OnCreateSessionComplete(FName sessionName, bool successful)
{
	JWPRINT(L"OnCreateSessionComplete " + sessionName.ToString().GetCharArray().GetData() + L", " + successful);

	m_session->ClearOnCreateSessionCompleteDelegate_Handle(onCreateSessionCompleteDelegateHandle);
	if (successful)
	{
		//onStartSessionCompleteDelegateHandle = m_session->AddOnStartSessionCompleteDelegate_Handle(onStartSessionCompleteDelegate);
		//m_session->StartSession(sessionName);
		FString options = FString("listen");

		// Add additional game settings here
		options += FString("?teamCount=") + FString::FromInt(hostMaxTeamCount);

		SetupLoadingScreen();
		UGameplayStatics::OpenLevel(GetWorld(), FName(hostLoadMap.GetCharArray().GetData()), true, options);
	}
	else
	{
		ShowNetworkError("Failed to create session");
	}
}
void UDefaultGameInstance::m_OnStartOnlineGameComplete(FName sessionName, bool successful)
{
	JWPRINT(L"OnStartOnlineGameComplete " + sessionName.ToString().GetCharArray().GetData() + L", " + successful);

	m_session->ClearOnStartSessionCompleteDelegate_Handle(onStartSessionCompleteDelegateHandle);

	if (successful)
	{
		FString options = FString("listen");

		// Add additional game settings here
		options += FString("?teamCount=") + FString::FromInt(hostMaxTeamCount);

		SetupLoadingScreen();
		UGameplayStatics::OpenLevel(GetWorld(), FName(hostLoadMap.GetCharArray().GetData()), true, options);
	}
	else
	{
		ShowNetworkError("Failed to start session");
	}
}
void UDefaultGameInstance::m_OnFindSessionsComplete(bool successful) //__attribute__((optnone))
{

	JWPRINT(L"OnFindSessionsComplete " + successful);

	m_operationInProgress = false;
	if(m_findingOverlay)
	{
		m_findingOverlay->Close();
		m_findingOverlay = nullptr;
	}

	m_session->ClearOnFindSessionsCompleteDelegate_Handle(onFindSessionsCompleteDelegateHandle);

	JPRINT("OnFindSessionsComplete: " + sessionSearch->SearchResults.Num() + " sessions");

	if (sessionSearch->SearchResults.Num() > 0)
	{
		for (int32 SearchIdx = 0; SearchIdx < sessionSearch->SearchResults.Num(); SearchIdx++)
		{
			JWPRINT(SearchIdx + L", " + sessionSearch->SearchResults[SearchIdx].Session.OwningUserName);
		}
	}
}
void UDefaultGameInstance::m_OnJoinSessionComplete(FName sessionName, EOnJoinSessionCompleteResult::Type result)
{
	JWPRINT(L"OnStartOnlineGameComplete " + sessionName.ToString().GetCharArray().GetData() + L", " + int32(result));
	m_session->ClearOnJoinSessionCompleteDelegate_Handle(onJoinSessionCompleteDelegateHandle);
	m_operationInProgress = false;

	if(result != EOnJoinSessionCompleteResult::Success)
	{
		ShowNetworkError(FString("Failed to join session, Error Code ") + FString::FromInt(result));
		SetGameJoined();
		return;
	}

	APlayerController* const playerController = GetFirstLocalPlayerController();
	check(playerController);

	SetupLoadingScreen();
	playerController->ClientTravel(m_travelURL, ETravelType::TRAVEL_Absolute);
}
void UDefaultGameInstance::m_OnDestroySessionComplete(FName sessionName, bool successful)
{
	JWPRINT(L"OnDestroySessionComplete " + sessionName.ToString().GetCharArray().GetData() + L", " + successful);

	m_session->ClearOnDestroySessionCompleteDelegate_Handle(onDestroySessionCompleteDelegateHandle);
	if(m_travelOnSessionDestroy)
	{
		UGameplayStatics::OpenLevel(GetWorld(), "/Game/Assets/Levels/Menu", true);
	}
}
void UDefaultGameInstance::m_OnReadFriendsListCompleteDelegate(int32, bool, const FString&, const FString&)
{
	GPRINT("Got friends list");
}
*/