#include "PortaLevelSubsystem.h"
#include "Internationalization/Regex.h"
#include "Kismet/GameplayStatics.h"
#include "PortaUtils.h"
#include "Engine/LevelStreaming.h"
#include "PortaConnectionSubsystem.h"
#include "Engine/World.h"

static FString GetLevelPackage(ULevelStreaming* Level)
{
	FString LevelName = Level->GetWorldAssetPackageName();

#if WITH_EDITOR
	if (GIsEditor || GIsPlayInEditorWorld)
	{
		FRegexMatcher Matcher(FRegexPattern(TEXT("UEDPIE_[0-9]*_")), LevelName);
		if (Matcher.FindNext())
			LevelName.RemoveAt(Matcher.GetMatchBeginning(), Matcher.GetMatchEnding() - Matcher.GetMatchBeginning());
	}
#endif

	return LevelName;

}

static FString GetLevelName(ULevelStreaming* Level)
{
	FString LevelName = GetLevelPackage(Level);
	int LastIndex = 0;
	if (LevelName.FindLastChar('/', LastIndex))
	{
		LevelName = LevelName.Mid(LastIndex + 1);
	}

	return LevelName;
}

UPortaLevelSubsystem::UPortaLevelSubsystem() : IsBusy(false)
{}


void UPortaLevelSubsystem::GetStreamingLevelsStatic(TArray<FString>& Levels)
{
	auto World = PortaUtils::GetCurrentWorld();
	if (!World)
		return;
	auto& StreamingLevels = World->GetStreamingLevels();
	for (auto Level : StreamingLevels)
	{
		Levels.Push(GetLevelName(Level));
	}
}

void UPortaLevelSubsystem::LoadStreamingLevelStatic(const FString& Name, bool UnloadCurrent, FLevelLoaded OnLoaded)
{
	PortaUtils::CallWorldSubsystemMember<UPortaLevelSubsystem, const FString&, bool, FLevelLoaded>(&UPortaLevelSubsystem::LoadStreamingLevel, Name, UnloadCurrent, OnLoaded);
}

void UPortaLevelSubsystem::LoadStreamingLevel(const FString& Name, bool UnloadCurrent, FLevelLoaded OnLoaded)
{
	auto World = GetWorld();

	bool alreadyLoaded = false;
	auto& StreamingLevels = World->GetStreamingLevels();
	for (auto Level : StreamingLevels)
	{
		if (Level->IsLevelLoaded())
		{
			FString LevelName = GetLevelName(Level);
			if (LevelName == Name)
			{
				alreadyLoaded = true;
			}
			else if (UnloadCurrent)
			{
				QueueUnload(LevelName);
			}
		}
	}

	if (alreadyLoaded)
	{
		OnLoaded.ExecuteIfBound();
	}
	else
	{
		LoadCallbacks.Add(Name, OnLoaded);
		QueueLoad(Name);
	}
}

void UPortaLevelSubsystem::GetLoadedLevelsStatic(TArray<FString>& LoadedLevels)
{
	PortaUtils::CallWorldSubsystemMember<UPortaLevelSubsystem, TArray<FString>& >(&UPortaLevelSubsystem::GetLoadedLevels, LoadedLevels);
}


void UPortaLevelSubsystem::GetLoadedLevels(TArray<FString>& LoadedLevels)
{
	auto World = GetWorld();

	bool alreadyLoaded = false;
	auto& StreamingLevels = World->GetStreamingLevels();
	for (auto Level : StreamingLevels)
	{
		if (Level->IsLevelLoaded())
		{
			LoadedLevels.Add(GetLevelPackage(Level));
		}
	}
}

void UPortaLevelSubsystem::SetStreamingLevelLocation(FVector Location)
{
	auto& StreamingLevels = GetWorld()->GetStreamingLevels();
	for (auto Level : StreamingLevels)
	{
		Level->LevelTransform.SetLocation(Location);
	}
}

void UPortaLevelSubsystem::SetStreamingLevelRotation(FRotator Rotation)
{
	auto& StreamingLevels = GetWorld()->GetStreamingLevels();
	for (auto Level : StreamingLevels)
	{
		Level->LevelTransform.SetRotation(Rotation.Quaternion());
	}

}

void UPortaLevelSubsystem::SetStreamingLevelScale(FVector Scale)
{
	auto& StreamingLevels = GetWorld()->GetStreamingLevels();
	for (auto Level : StreamingLevels)
	{
		Level->LevelTransform.SetScale3D(Scale);
	}

}


void UPortaLevelSubsystem::NextLoadAction()
{
	if (!LastLoad.IsEmpty())
	{
		OnLevelLoaded.Broadcast(LastLoad);
		FLevelLoaded Callback;
		if (LoadCallbacks.RemoveAndCopyValue(LastLoad, Callback))
		{
			Callback.ExecuteIfBound();
		}
		LastLoad = FString();
	}
	if (UnloadingLevels.Num() > 0)
	{
		DoUnload(UnloadingLevels[0]);
		UnloadingLevels.RemoveAt(0);
	}
	else if (LoadingLevels.Num() > 0)
	{
		DoLoad(LoadingLevels[0]);
		LoadingLevels.RemoveAt(0);
	}
	else
	{
		IsBusy = false;
	}
}

void UPortaLevelSubsystem::QueueUnload(const FString& Name)
{
	if (IsBusy)
		if (LoadingLevels.Contains(Name))
		{
			//we're trying to unload a level we're queued to load; simply just leave it unloaded
			LoadingLevels.Remove(Name);
		}
		else
			UnloadingLevels.Push(Name);
	else
	{
		DoUnload(Name);
		IsBusy = true;
	}

}

void UPortaLevelSubsystem::QueueLoad(const FString& Name)
{
	if (IsBusy)
	{
		if (UnloadingLevels.Contains(Name))
		{
			//we're trying to load a level we're queued to unload; simply just leave it loaded
			UnloadingLevels.Remove(Name);
		}
		else
			LoadingLevels.Push(Name);
	}
	else
	{
		DoLoad(Name);
		IsBusy = true;
	}
}

void UPortaLevelSubsystem::DoLoad(const FString& Name)
{
	LastLoad = Name;
	FLatentActionInfo LatentInfo;
	LatentInfo.CallbackTarget = this;
	LatentInfo.ExecutionFunction = FName(TEXT("NextLoadAction"));
	LatentInfo.Linkage = 0;
	UGameplayStatics::LoadStreamLevel(GetWorld(), FName(Name), true, false, LatentInfo);
}

void UPortaLevelSubsystem::DoUnload(const FString& Name)
{
	LastLoad = FString();
	FLatentActionInfo LatentInfo;
	LatentInfo.CallbackTarget = this;
	LatentInfo.ExecutionFunction = FName(TEXT("NextLoadAction"));
	LatentInfo.Linkage = 0;
	UGameplayStatics::UnloadStreamLevel(GetWorld(), FName(Name), LatentInfo, false);
}
