#include "server/game/ServerGame.h"
#include "server/Server.h"

#include "td/protocol/packets/DisconnectPacket.h"
#include "td/protocol/packets/PlayerLeavePacket.h"
#include "td/protocol/packets/RemoveMobPacket.h"
#include "td/protocol/packets/RemoveTowerPacket.h"
#include "td/protocol/packets/UpdatePlayerTeamPacket.h"
#include "td/protocol/packets/UpdateGameStatePacket.h"
#include "td/protocol/packets/UpdateMoneyPacket.h"
#include "td/protocol/packets/UpdateExpPacket.h"
#include "td/protocol/packets/UpdateMobStatesPacket.h"
#include "td/protocol/packets/WorldDataPacket.h"
#include "td/protocol/packets/WorldBeginDataPacket.h"

namespace td {
namespace server {

ServerGame::ServerGame(server::Server* server) : game::Game(&m_ServerWorld), m_Server(server), m_ServerWorld(server, this),
m_GoldMineTimer{ 1000, std::bind(&ServerGame::UpdateGoldMines, this) },
m_MobStatesTimer{ 5000, std::bind(&ServerGame::UpdateMobStates, this) },
m_EndGameCooldown{ 1000 * 10 } {
	BindListener(this);
}

void ServerGame::Tick(std::uint64_t delta) {
	if (m_GameState == game::GameState::Game) {
		Game::Tick(delta);
		m_MobStatesTimer.Update(delta);
		UpdatePlayerStats();
	} else if (m_GameState == game::GameState::EndGame) {
		if (m_EndGameCooldown.Update(delta)) {
			NotifyListeners(&game::GameListener::OnGameClose);
		}
	}
}

void ServerGame::StartGame() {
	BalanceTeams();

	protocol::WorldBeginDataPacket headerMapData(m_World);
	m_Server->BroadcastPacket(&headerMapData);

	protocol::WorldDataPacket mapData(m_World);
	m_Server->BroadcastPacket(&mapData);

	InitPlayerStats();

	m_GameState = game::GameState::Game;
}

void ServerGame::InitPlayerStats() {
	static const unsigned int START_GOLD = 100;
	static const unsigned int START_EXP = 0;
	for (auto& [id, player] : m_Server->GetPlayers()) {
		player.SetGold(START_GOLD);
		player.SetExp(START_EXP);
		player.getUpgrades().SetGoldPerSecond(5);
	}
}

void ServerGame::UpdatePlayerStats() {
	m_GoldMineTimer.Update();
}

void ServerGame::UpdateGoldMines() {
	for (auto& [id, player] : m_Server->GetPlayers()) {
		player.AddGold(player.getUpgrades().GetGoldPerSecond());

		// Update player money and exp every second
		protocol::UpdateMoneyPacket moneyPacket(player.GetGold());
		m_Server->GetConnexions()[player.GetID()].SendPacket(&moneyPacket);

		protocol::UpdateExpPacket expPacket(player.GetExp());
		m_Server->GetConnexions()[player.GetID()].SendPacket(&expPacket);
	}
}

void ServerGame::UpdateMobStates() {
	protocol::UpdateMobStatesPacket packet;
	for (auto mob : m_World->GetMobList()) {
		packet.addMobState({ mob->GetMobID(), mob->GetCenter(), mob->GetHealth(), mob->GetDirection() });
	}
	m_Server->BroadcastPacket(&packet);
}

void ServerGame::BalanceTeams() {
	for (auto& playerInfo : Game::m_Players) {
		game::Player& player = playerInfo.second;
		if (player.GetTeamColor() == game::TeamColor::None) {
			game::Team& redTeam = GetRedTeam();
			game::Team& blueTeam = GetBlueTeam();
			if (blueTeam.GetPlayerCount() > redTeam.GetPlayerCount()) {
				player.SetTeamColor(game::TeamColor::Red);
				redTeam.AddPlayer(&player);
			} else {
				player.SetTeamColor(game::TeamColor::Blue);
				blueTeam.AddPlayer(&player);
			}
			protocol::UpdatePlayerTeamPacket packet(player.GetID(), player.GetTeamColor());
			m_Server->BroadcastPacket(&packet);
		}
	}
}

void ServerGame::Reset() {
	Game::Reset();

	// update players teams to none
	for (auto& playerInfo : m_Players) {
		game::Player& player = playerInfo.second;
		protocol::UpdatePlayerTeamPacket packet(player.GetID(), player.GetTeamColor());
		m_Server->BroadcastPacket(&packet);
	}
}

void ServerGame::OnGameStateUpdate(game::GameState newState) {
	SetGameState(newState);
	protocol::UpdateGameStatePacket packet(newState);
	m_Server->BroadcastPacket(&packet);
}

void ServerGame::OnGameBegin() {
	NotifyListeners(&game::GameListener::OnGameStateUpdate, game::GameState::Game);
	StartGame();
}

void ServerGame::OnGameEnd() {
	NotifyListeners(&game::GameListener::OnGameStateUpdate, game::GameState::EndGame);
	m_EndGameCooldown.ApplyCooldown();
}

void ServerGame::OnGameClose() {
	NotifyListeners(&game::GameListener::OnGameStateUpdate, game::GameState::Closed);

	// Put back to lobby state
	m_Server->Restart();
}

void ServerGame::OnPlayerJoin(game::PlayerID id) {
	if (m_GameState == game::GameState::Game)
		BalanceTeams();
}

void ServerGame::OnPlayerLeave(game::PlayerID playerId) {
	// temporary fix
	
	auto& mobList = GetWorld()->GetMobList();
	for(std::size_t i = 0; i < mobList.size(); i++) {
		auto mob = mobList.at(i);

		if(mob->GetSender() == playerId) {
			protocol::RemoveMobPacket packet(mob->GetMobID());
			m_Server->BroadcastPacket(&packet);

			mobList.erase(mobList.begin() + i);
		}
	}

	auto& towerList = GetWorld()->GetTowers();
	for(std::size_t i = 0; i < towerList.size(); i++) {
		auto tower = towerList.at(i);

		if(tower->GetBuilder() == playerId) {
			protocol::RemoveTowerPacket packet(tower->GetID());
			m_Server->BroadcastPacket(&packet);

			towerList.erase(towerList.begin() + i);
		}
	}

	protocol::PlayerLeavePacket packet(playerId);
	m_Server->BroadcastPacket(&packet);
}

} // namespace game
} // namespace td
