#include "server/ServerConnexion.h"
#include "td/protocol/PacketDispatcher.h"
#include "td/protocol/PacketFactory.h"
#include "server/Server.h"

#include "td/protocol/packets/ConnectionInfoPacket.h"
#include "td/protocol/packets/DisconnectPacket.h"
#include "td/protocol/packets/KeepAlivePacket.h"
#include "td/protocol/packets/PlaceTowerPacket.h"
#include "td/protocol/packets/PlayerLoginPacket.h"
#include "td/protocol/packets/PlayerJoinPacket.h"
#include "td/protocol/packets/PlayerListPacket.h"
#include "td/protocol/packets/RemoveTowerPacket.h"
#include "td/protocol/packets/SelectTeamPacket.h"
#include "td/protocol/packets/SendMobsPacket.h"
#include "td/protocol/packets/SpawnMobPacket.h"
#include "td/protocol/packets/UpdatePlayerTeamPacket.h"
#include "td/protocol/packets/UpdateGameStatePacket.h"
#include "td/protocol/packets/UpgradeTowerPacket.h"
#include "td/protocol/packets/WorldBeginDataPacket.h"
#include "td/protocol/packets/WorldDataPacket.h"
#include "td/protocol/packets/WorldAddTowerPacket.h"

#include "td/misc/Time.h"
#include "td/misc/Random.h"
#include "td/misc/Format.h"

#include <chrono>

#define KEEP_ALIVE_TIMEOUT 10 * 1000 // 10s

namespace td {
namespace server {

/*
	NEVER TRUST USER INPUT
*/


ServerConnexion::ServerConnexion() : m_Player(0) {

}

ServerConnexion::ServerConnexion(network::TCPSocket& socket, std::uint8_t id) : Connexion::Connexion(&m_Dispatcher, socket), m_ID(id), m_Player(0) {
	Connexion::UpdateSocket();
}

ServerConnexion::ServerConnexion(ServerConnexion&& move) : Connexion::Connexion(std::move(move)), m_Server(move.m_Server),
m_ID(move.m_ID), m_KeepAlive(move.m_KeepAlive), m_Player(move.m_Player) {

	move.m_Server = nullptr;
	RegisterHandlers();
}

void ServerConnexion::RegisterHandlers() {
	GetDispatcher()->RegisterHandler(protocol::PacketType::PlayerLogin, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::KeepAlive, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::SelectTeam, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::Disconnect, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::PlaceTower, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::SendMobs, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::UpgradeTower, this);
	GetDispatcher()->RegisterHandler(protocol::PacketType::RemoveTower, this);
}

bool ServerConnexion::UpdateSocket() {
	CheckKeepAlive();
	return Connexion::UpdateSocket();
}

void ServerConnexion::CheckKeepAlive() {
	std::uint64_t time = utils::GetTime();
	if (time - m_KeepAlive.sendTime > KEEP_ALIVE_TIMEOUT) {
		if (m_KeepAlive.recievedResponse) {
			SendKeepAlive();
		} else {
			protocol::DisconnectPacket packet("Time out");
			SendPacket(&packet);
			CloseConnection();
		}
	}
}

void ServerConnexion::SendKeepAlive() {
	m_KeepAlive.keepAliveID = utils::GetRandomInt<std::uint64_t>(0, RAND_MAX);
	m_KeepAlive.recievedResponse = false;

	protocol::KeepAlivePacket keepAlivePacket(m_KeepAlive.keepAliveID);
	SendPacket(&keepAlivePacket);

	std::uint64_t time = utils::GetTime();
	m_KeepAlive.sendTime = time;
}

void ServerConnexion::HandlePacket(const protocol::PlayerLoginPacket* packet) {

	if (m_Server->GetGame().GetGameState() != game::GameState::Lobby) {
		protocol::DisconnectPacket packet("Cannot join during game");
		SendPacket(&packet);

		CloseConnection();
		return;
	}
	
	SAFE_CHECK(!packet->GetPlayerName().empty());

	if (!m_Player) {  // player does not exist yet
		auto playerPos = m_Server->GetPlayers().insert({ m_ID, game::Player{m_ID} });
		m_Player = &playerPos.first->second;
	}

	SAFE_CHECK(m_Player->GetName().empty());

	m_Player->SetName(packet->GetPlayerName());

	utils::LOG(utils::format("\t[%s] joined !", m_Player->GetName().c_str()));

	protocol::PlayerJoinPacket joinPacket(m_ID, m_Player->GetName());
	m_Server->BroadcastPacket(&joinPacket);

	std::map<std::uint8_t, protocol::PlayerInfo> playerNames;
	for (const auto& pair : m_Server->GetPlayers()) {
		const game::Player& player = pair.second;
		if (!player.GetName().empty()) {
			protocol::PlayerInfo playerInfo;
			playerInfo.name = player.GetName();
			playerInfo.team = player.GetTeamColor();
			playerNames.insert({ player.GetID(), playerInfo });
		}
	}

	protocol::PlayerListPacket listPacket(playerNames);
	SendPacket(&listPacket);

	protocol::UpdateGameStatePacket statePacket(m_Server->GetGame().GetGameState());
	SendPacket(&statePacket);

	m_Server->GetGame().NotifyListeners(&game::GameListener::OnPlayerJoin, m_ID);

	protocol::WorldBeginDataPacket headerDataPacket(m_Server->GetGame().GetWorld());
	protocol::WorldDataPacket dataPacket(m_Server->GetGame().GetWorld());
	SendPacket(&headerDataPacket);
	SendPacket(&dataPacket);

	// place towers
	for (auto tower : m_Server->GetGame().GetWorld()->GetTowers()) {
		protocol::WorldAddTowerPacket packet(tower->GetID(), static_cast<std::int32_t>(tower->GetCenterX() - 0.5f), 
			static_cast<std::int32_t>(tower->GetCenterY() - 0.5f), tower->GetType(), tower->GetBuilder());
		SendPacket(&packet);
	}

	// spawn mobs
	for (auto mob : m_Server->GetGame().GetWorld()->GetMobList()) {
		protocol::SpawnMobPacket packet(mob->GetMobID(), mob->GetType(), mob->GetLevel(), mob->GetSender(),
			mob->GetCenterX(), mob->GetCenterY(), mob->GetDirection());
		SendPacket(&packet);
		// TODO : update health
	}
}

void ServerConnexion::HandlePacket(const protocol::SelectTeamPacket* packet) {
	SAFE_CHECK(m_Server->GetGame().GetGameState() == game::GameState::Lobby);
	SAFE_CHECK(static_cast<std::int8_t>(packet->GetSelectedTeam()) >= -1 ||
		static_cast<std::int8_t>(packet->GetSelectedTeam()) <= 1);

	if (m_Player->GetTeamColor() == game::TeamColor::None) { // join a team
		m_Server->GetGame().GetTeam(packet->GetSelectedTeam()).AddPlayer(m_Player);
	} else if (packet->GetSelectedTeam() == game::TeamColor::None) { // leave a team
		m_Server->GetGame().GetTeam(m_Player->GetTeamColor()).RemovePlayer(m_Player);
		m_Player->SetTeamColor(game::TeamColor::None);
	} else { // change team
		m_Server->GetGame().GetTeam(m_Player->GetTeamColor()).RemovePlayer(m_Player);
		m_Server->GetGame().GetTeam(packet->GetSelectedTeam()).AddPlayer(m_Player);
	}
	m_Player->SetTeamColor(packet->GetSelectedTeam());
	protocol::UpdatePlayerTeamPacket updateTeamPacket(m_ID, packet->GetSelectedTeam());
	m_Server->BroadcastPacket(&updateTeamPacket);
}

void ServerConnexion::HandlePacket(const protocol::KeepAlivePacket* packet) {
	SAFE_CHECK(packet->GetAliveID() == m_KeepAlive.keepAliveID);

	m_KeepAlive.recievedResponse = true;
}

void ServerConnexion::HandlePacket(const protocol::DisconnectPacket* packet) {
	CloseConnection();
}

void ServerConnexion::SetServer(Server* server) {
	m_Server = server;
	InitConnection();
	SendKeepAlive();
}

void ServerConnexion::InitConnection() {
	protocol::ConnexionInfoPacket conPacket(m_ID);
	SendPacket(&conPacket);
}

void ServerConnexion::HandlePacket(const protocol::PlaceTowerPacket* packet) {
	game::TowerType towerType = packet->GetTowerType();
	const game::TowerInfo& towerInfo = game::GetTowerInfo(towerType);
	server::ServerWorld* world = m_Server->GetGame().GetServerWorld();

	Vec2f towerPos = { static_cast<float>(packet->GetTowerX()), static_cast<float>(packet->GetTowerY()) };

	SAFE_CHECK(world->CanPlaceLittleTower(towerPos, m_ID));

	if (towerInfo.IsBigTower())
		SAFE_CHECK(world->CanPlaceBigTower(towerPos, m_ID));

	game::TowerPtr tower = world->PlaceTowerAt(towerType, packet->GetTowerX(), packet->GetTowerY(), m_ID);

	world->GetWorldNotifier().NotifyListeners(&game::WorldListener::OnTowerAdd, tower);

	protocol::WorldAddTowerPacket addTowerPacket(tower->GetID(), packet->GetTowerX(), packet->GetTowerY(), packet->GetTowerType(), m_ID);
	m_Server->BroadcastPacket(&addTowerPacket);
}

void ServerConnexion::HandlePacket(const protocol::SendMobsPacket* packet) {
	const std::vector<protocol::MobSend>& mobSent = packet->GetMobSends();

	//TODO: verify the packet

	for (protocol::MobSend mobSend : mobSent) {
		m_Server->GetGame().GetServerWorld()->SpawnMobs(mobSend.mobType, mobSend.mobLevel, m_ID, mobSend.mobCount);
	}
}

void ServerConnexion::HandlePacket(const protocol::UpgradeTowerPacket* packet) {
	//TODO: verify the packet

	m_Server->BroadcastPacket(packet);
}

void ServerConnexion::HandlePacket(const protocol::RemoveTowerPacket* packet) {
	//TODO: verify the packet

	m_Server->BroadcastPacket(packet);
}

ServerConnexion::~ServerConnexion() {
	SAFE_CHECK(GetDispatcher() != nullptr);
	
	GetDispatcher()->UnregisterHandler(this);
}

} // namespace server
} // namespace td
