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

#include "misc/Time.h"
#include "misc/Random.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);
}

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_Player->getName().empty() && !packet->getPlayerName().empty()) {
        m_Player->setName(packet->getPlayerName());

        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);
    }
}

void ServerConnexion::HandlePacket(const protocol::SelectTeamPacket* packet) {
    if (m_Server->getGame().getGameState() != game::GameState::Lobby)
        return;
    if ((std::int8_t)packet->getSelectedTeam() >= -1 || (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) {
    if (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;
    m_Player = &m_Server->getPlayers().at(m_ID);
    initConnection();
    sendKeepAlive();
}

void ServerConnexion::initConnection() {
    protocol::UpdateGameStatePacket statePacket(m_Server->getGame().getGameState());
    sendPacket(&statePacket);

    protocol::ConnexionInfoPacket conPacket(m_ID);
    sendPacket(&conPacket);

    if (m_Server->getGame().getGameState() == game::GameState::Game) {
        protocol::WorldBeginDataPacket headerDataPacket(m_Server->getGame().getWorld());
        protocol::WorldBeginDataPacket dataPacket(m_Server->getGame().getWorld());
        sendPacket(&headerDataPacket);
        sendPacket(&dataPacket);
    }
}

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();

    if (!world->CanPlaceLittleTower({ packet->getTowerX(), packet->getTowerY() }, m_ID))
        return;

    if (towerInfo.isBigTower())
        if (!world->CanPlaceBigTower({ packet->getTowerX(), packet->getTowerY() }, m_ID))
            return;

    game::TowerPtr tower = world->placeTowerAt(towerType, packet->getTowerX(), packet->getTowerY(), m_ID);

    world->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);
}

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

} // namespace server
} // namespace td
