#include "UnrealProject.h"
#include "GameStateBase.h"

#include "PlayerStateBase.h"
#include "DefaultPlayerController.h"
#include "DefaultGameInstance.h"
#include "MapData.h"

AGameStateBase::AGameStateBase()
{
	m_mapTeamCount = 4;
	m_hasWarnedFrank = false;
}
void AGameStateBase::BeginPlay()
{
	Super::BeginPlay();
	mapPath = GetWorld()->GetPathName();
	m_LoadActiveMapInfo();
}
void AGameStateBase::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);
	OnPlayerStateChange.Clear();
}
void AGameStateBase::RegisterPlayer(APlayerControllerBase* player)
{
	check(Role == ROLE_Authority);
	check(player);
	APlayerStateBase* state = Cast<APlayerStateBase>(player->PlayerState);
	check(state);
	m_playersByController.Add(player, state);
	m_players.Add(state);

	// More than 1 player in editor means multiplayer in PIE mode
	if(m_players.Num() > 1)
	{
		EnterLobby();
	}

	OnPlayerStateChange.Broadcast(nullptr);
}
void AGameStateBase::DeregisterPlayer(APlayerControllerBase* player)
{
	check(Role == ROLE_Authority);
	m_playersByController.Remove(player);

	m_players.SetNum(0);
	for(auto it = m_playersByController.CreateIterator(); it; ++it)
	{
		m_players.Add(it->Value);
	}

	OnPlayerStateChange.Broadcast(nullptr);
}
void AGameStateBase::EnterLobby()
{
	check(Role == ROLE_Authority);
	for(auto it = m_playersByController.CreateIterator(); it; ++it)
	{
		it->Key->EnterLobby();
	}
}

void AGameStateBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME(AGameStateBase, m_players);
	DOREPLIFETIME(AGameStateBase, m_mapTeamCount);
	DOREPLIFETIME(AGameStateBase, mapPath);
}

TArray<class APlayerStateBase*>& AGameStateBase::GetPlayers()
{
	return m_players;
}
TArray<TArray<class APlayerStateBase*>> AGameStateBase::GetPlayersByTeam()
{
	TArray<TArray<class APlayerStateBase*>> ret;
	for(int32 i = 0; i < m_players.Num(); i++)
	{
		APlayerStateBase* player = m_players[i];
		if(!player)
			continue;
		int32 team = player->GetTeam();
		if(team >= ret.Num())
		{
			ret.SetNum(team + 1);
		}
		TArray<class APlayerStateBase*>& dstArray = ret[team];

		dstArray.Add(player);
	}
	return ret;
}

TArray<int32> AGameStateBase::GetTeamSizes()
{
	TArray<int32> teamSizes;
	teamSizes.SetNumZeroed(m_mapTeamCount);

	for(int32 i = 0; i < m_players.Num(); i++)
	{
		if(!m_players[i])
			continue;
		int32 team = m_players[i]->GetTeam() - 1;
		if(team < 0 || team >= m_mapTeamCount)
			continue;
		teamSizes[team]++;
	}

	return TArray<int32>(teamSizes);
}
int32 AGameStateBase::GetOptimalAutoJoinTeam()
{
	if(GetWorld()->IsPlayInEditor())
	{
		// PIE uses different assignment, default to 2 players per team and then move to the next team
		TArray<int32> teamSize = GetTeamSizes();
		int32 optimalTeam = -1;

		for(int32 i = 0; i < m_mapTeamCount; i++)
		{
			if(teamSize[i] < 2)
			{
				optimalTeam = i;
				break;
			}
		}

		return optimalTeam + 1;
	}
	else
	{
		TArray<int32> teamSize = GetTeamSizes();
		int32 smallestSize = 2;
		int32 optimalTeam = -1;

		for(int32 i = 0; i < m_mapTeamCount; i++)
		{
			if(teamSize[i] < smallestSize)
			{
				optimalTeam = i;
				smallestSize = teamSize[i];
			}
		}

		return optimalTeam + 1;
	}
}
int32 AGameStateBase::GetMapTeamCount() const
{
	return m_mapTeamCount;
}

UMapData* AGameStateBase::GetMapData() const
{
	return m_mapData;
}
void AGameStateBase::m_OnRep_MapPath()
{
	GPRINT("Map changed to " + mapPath);
	m_LoadActiveMapInfo();
	if(m_mapData)
		GPRINT("Found attached map data " + m_mapData->GetName());
	else
		GERROR("No attached map data found.");
}

void AGameStateBase::OnPlayerStateChange_Multi_Implementation()
{
	if (m_playersByController.Num() > 0)
		OnPlayerStateChange.Broadcast(nullptr);
}

TMap<APlayerControllerBase*, APlayerStateBase*> AGameStateBase::GetPlayersByController()
{
	return m_playersByController;
}

bool AGameStateBase::m_LoadActiveMapInfo()
{
	UDefaultGameInstance* gameInstance = Cast<UDefaultGameInstance>(GetGameInstance());
	if(!gameInstance)
		return false;
	m_mapData = gameInstance->GetMapData(mapPath);
	if(!m_mapData)
	{
		if(!m_hasWarnedFrank)
		{
			GWERROR(L"Failed to load map info, Create a map info asset with the name formated as I_ + map name in the same folder and add the I_<map_name> file to the map list frank!!!");
			m_hasWarnedFrank = true;
		}
		m_mapTeamCount = 4;
	}
	else
	{
		m_mapTeamCount = m_mapData->maxTeamCount;
	}
	return true;
}