/*! \brief Called when a player asks to select a kart.
 *  \param event : Event providing the information.
 *
 *  Format of the data :
 *  Byte 0          1                      2            
 *       ----------------------------------------------
 *  Size |    1     |           1         |     N     |
 *  Data |player id |  N (kart name size) | kart name |
 *       ----------------------------------------------
 */
void ServerLobbyRoomProtocol::kartSelectionRequested(Event* event)
{
    if(m_state!=SELECTING)
    {
        Log::warn("Server", "Received kart selection while in state %d.",
                  m_state);
        return;
    }

    if (!checkDataSize(event, 1)) return;

    const NetworkString &data = event->data();
    STKPeer* peer = event->getPeer();

    uint8_t player_id = data.getUInt8();
    std::string kart_name;
    data.decodeString(&kart_name);
    // check if selection is possible
    if (!m_selection_enabled)
    {
        NetworkString *answer = getNetworkString(2);
        // selection still not started
        answer->addUInt8(LE_KART_SELECTION_REFUSED).addUInt8(2);
        peer->sendPacket(answer);
        delete answer;
        return;
    }
    // check if somebody picked that kart
    if (!m_setup->isKartAvailable(kart_name))
    {
        NetworkString *answer = getNetworkString(2);
        // kart is already taken
        answer->addUInt8(LE_KART_SELECTION_REFUSED).addUInt8(0);
        peer->sendPacket(answer);
        delete answer;
        return;
    }
    // check if this kart is authorized
    if (!m_setup->isKartAllowed(kart_name))
    {
        NetworkString *answer = getNetworkString(2);
        // kart is not authorized
        answer->addUInt8(LE_KART_SELECTION_REFUSED).addUInt8(1);
        peer->sendPacket(answer);
        delete answer;
        return;
    }

    // send a kart update to everyone
    NetworkString *answer = getNetworkString(3+kart_name.size());
    // This message must be handled synchronously on the client.
    answer->setSynchronous(true);
    // kart update (3), 1, race id
    answer->addUInt8(LE_KART_SELECTION_UPDATE).addUInt8(player_id)
          .encodeString(kart_name);
    sendMessageToPeersChangingToken(answer);
    delete answer;
    m_setup->setPlayerKart(player_id, kart_name);
}   // kartSelectionRequested
/** This function informs each client to start the race, and then starts the
 *  StartGameProtocol.
 */
void ServerLobbyRoomProtocol::startGame()
{
    const std::vector<STKPeer*> &peers = STKHost::get()->getPeers();
    NetworkString *ns = getNetworkString(1);
    ns->addUInt8(LE_START_RACE);
    sendMessageToPeersChangingToken(ns, /*reliable*/true);
    delete ns;
    Protocol *p = new StartGameProtocol(m_setup);
    p->requestStart();
    m_state = RACING;
}   // startGame
/*! \brief Called when a player votes for a minor race mode.
 *  \param event : Event providing the information.
 *
 *  Format of the data :
 *  Byte 0           1
 *       -------------------------------
 *  Size |      1    |         4       |
 *  Data | player-id | minor mode vote |
 *       -------------------------------
 */
void ServerLobbyRoomProtocol::playerMinorVote(Event* event)
{
    if (!checkDataSize(event, 1)) return;
    NetworkString &data = event->data();
    uint8_t player_id   = data.getUInt8();
    uint32_t minor      = data.getUInt32();
    m_setup->getRaceConfig()->setPlayerMinorVote(player_id, minor);

    // Send the vote to everybody (including the sender)
    NetworkString *other = getNetworkString(3);
    other->addUInt8(LE_VOTE_MINOR).addUInt8(player_id).addUInt8(minor); 
    sendMessageToPeersChangingToken(other);
    delete other;
}   // playerMinorVote
/*! \brief Called when a player votes for a major race mode.
 *  \param event : Event providing the information.
 *
 *  Format of the data :
 *  Byte 0           1      2 
 *       ----------------------------------------
 *  Size |     1     |   1  |       1           |
 *  Data | player id | laps | track number (gp) |
 *       ----------------------------------------
 */
void ServerLobbyRoomProtocol::playerLapsVote(Event* event)
{
    if (!checkDataSize(event, 2)) return;
    NetworkString &data = event->data();
    uint8_t player_id   = data.getUInt8();
    uint8_t lap_count   = data.getUInt8();
    uint8_t track_nb    = data.getUInt8();
    m_setup->getRaceConfig()->setPlayerLapsVote(player_id, lap_count,
                                                track_nb);
    NetworkString *other = getNetworkString(4);
    other->addUInt8(LE_VOTE_LAPS).addUInt8(player_id).addUInt8(lap_count)
          .addUInt8(track_nb);
    sendMessageToPeersChangingToken(other);
    delete other;
}   // playerLapsVote
/*! \brief Called when a player votes for the reverse mode of a race
 *  \param event : Event providing the information.
 *
 *  Format of the data :
 *  Byte 0           1          2
 *       --------------------------------------------
 *  Size |     1     |     1    |       1           |
 *  Data | player id | reversed | track number (gp) |
 *       --------------------------------------------
 */
void ServerLobbyRoomProtocol::playerReversedVote(Event* event)
{
    if (!checkDataSize(event, 3)) return;

    NetworkString &data = event->data();
    uint8_t player_id   = data.getUInt8();
    uint8_t reverse     = data.getUInt8();
    uint8_t nb_track    = data.getUInt8();
    m_setup->getRaceConfig()->setPlayerReversedVote(player_id,
                                                    reverse!=0, nb_track);
    // Send the vote to everybody (including the sender)
    NetworkString *other = getNetworkString(4);
    other->addUInt8(LE_VOTE_REVERSE).addUInt8(player_id).addUInt8(reverse)
          .addUInt8(nb_track);
    sendMessageToPeersChangingToken(other);
    delete other;
}   // playerReversedVote
/** Checks if the race is finished, and if so informs the clients and switches
 *  to state RESULT_DISPLAY, during which the race result gui is shown and all
 *  clients can click on 'continue'.
 */
 void ServerLobbyRoomProtocol::checkRaceFinished()
{
    assert(RaceEventManager::getInstance()->isRunning());
    assert(World::getWorld());
    if(!RaceEventManager::getInstance()->isRaceOver()) return;

    m_player_ready_counter = 0;
    // Set the delay before the server forces all clients to exit the race
    // result screen and go back to the lobby
    m_timeout = (float)(StkTime::getRealTime()+15.0f);
    m_state = RESULT_DISPLAY;

    // calculate karts ranks :
    int num_karts = race_manager->getNumberOfKarts();
    std::vector<int> karts_results;
    std::vector<float> karts_times;
    for (int j = 0; j < num_karts; j++)
    {
        float kart_time = race_manager->getKartRaceTime(j);
        for (unsigned int i = 0; i < karts_times.size(); i++)
        {
            if (kart_time < karts_times[i])
            {
                karts_times.insert(karts_times.begin() + i, kart_time);
                karts_results.insert(karts_results.begin() + i, j);
                break;
            }
        }
    }

    const std::vector<STKPeer*> &peers = STKHost::get()->getPeers();

    NetworkString *total = getNetworkString(1 + karts_results.size());
    total->setSynchronous(true);
    total->addUInt8(LE_RACE_FINISHED);
    for (unsigned int i = 0; i < karts_results.size(); i++)
    {
        total->addUInt8(karts_results[i]); // kart pos = i+1
        Log::info("ServerLobbyRoomProtocol", "Kart %d finished #%d",
            karts_results[i], i + 1);
    }
    sendMessageToPeersChangingToken(total, /*reliable*/ true);
    delete total;
    Log::info("ServerLobbyRoomProtocol", "End of game message sent");
        
}   // checkRaceFinished
/*! \brief Called when a player votes for the number of races in a GP.
 *  \param event : Event providing the information.
 *
 *  Format of the data :
 *  Byte 0   1            5   6             7
 *       ------------------------------------
 *  Size | 1 |      4     | 1 |      1      |
 *  Data | 4 | priv token | 1 | races count |
 *       ------------------------------------
 */
void ServerLobbyRoomProtocol::playerRaceCountVote(Event* event)
{
    NetworkString &data = event->data();
    STKPeer* peer = event->getPeer();
    if (!checkDataSizeAndToken(event, 7))
        return;
    if (!isByteCorrect(event, 5, 1))
        return;
    uint8_t player_id = peer->getPlayerProfile()->getGlobalPlayerId();
    m_setup->getRaceConfig()->setPlayerRaceCountVote(player_id, data[6]);
    // Send the vote to everybody (including the sender)
    data.removeFront(5); // remove the token
    NetworkString other(2+data.size());
    other.ai8(1).ai8(player_id); // add the player id
    other += data; // add the data
    sendMessageToPeersChangingToken(LE_VOTE_RACE_COUNT, other);
}   // playerRaceCountVote
/*! \brief Called when a player votes for a minor race mode.
 *  \param event : Event providing the information.
 *
 *  Format of the data :
 *  Byte 0   1            5   6                 7
 *       ----------------------------------------
 *  Size | 1 |      4     | 1 |        1        |
 *  Data | 4 | priv token | 1 | minor mode vote |
 *       ----------------------------------------
 */
void ServerLobbyRoomProtocol::playerMinorVote(Event* event)
{
    NetworkString data = event->data();
    STKPeer* peer = *(event->peer);
    if (!checkDataSizeAndToken(event, 7))
        return;
    if (!isByteCorrect(event, 5, 1))
        return;
    uint8_t player_id = peer->getPlayerProfile()->race_id;
    m_setup->getRaceConfig()->setPlayerMinorVote(player_id, data[6]);
    // Send the vote to everybody (including the sender)
    NetworkString other;
    other.ai8(1).ai8(player_id); // add the player id
    data.removeFront(5); // remove the token
    other += data; // add the data
    NetworkString prefix;
    prefix.ai8(0xc2); // prefix the token with the ype
    sendMessageToPeersChangingToken(prefix, other);
}
/*! \brief Called when a player votes for a track.
 *  \param event : Event providing the information.
 *
 *  Format of the data :
 *  Byte 0           1                    2  3
 *       --------------------------------------------------
 *  Size |     1     |        1          | 1 |      N     |
 *  Data | player id | track number (gp) | N | track name |
 *       --------------------------------------------------
 */
void ServerLobbyRoomProtocol::playerTrackVote(Event* event)
{
    if (!checkDataSize(event, 3)) return;
    NetworkString &data  = event->data();
    uint8_t player_id    = data.getUInt8();
    // As which track this track should be used, e.g. 1st track: Sandtrack
    // 2nd track Mathclass, ...
    uint8_t track_number = data.getUInt8();
    std::string track_name;
    int N = data.decodeString(&track_name);
    m_setup->getRaceConfig()->setPlayerTrackVote(player_id, track_name,
                                                 track_number);
    // Send the vote to everybody (including the sender)
    NetworkString *other = getNetworkString(3+1+data.size());
    other->addUInt8(LE_VOTE_TRACK).addUInt8(player_id).addUInt8(track_number)
          .encodeString(track_name);
    sendMessageToPeersChangingToken(other);
    delete other;
    if(m_setup->getRaceConfig()->getNumTrackVotes()==m_setup->getPlayerCount())
        startGame();
}   // playerTrackVote
/** Instructs all clients to start the kart selection. If event is not NULL,
 *  the command comes from a client (which needs to be authorised).
 */
void ServerLobbyRoomProtocol::startSelection(const Event *event)
{
    if(event && !event->getPeer()->isAuthorised())
    {
        Log::warn("ServerLobby", 
                  "Client %lx is not authorised to start selection.",
                  event->getPeer());
        return;
    }
    const std::vector<STKPeer*> &peers = STKHost::get()->getPeers();
    NetworkString *ns = getNetworkString(1);
    // start selection
    ns->addUInt8(LE_START_SELECTION);
    sendMessageToPeersChangingToken(ns, /*reliable*/true);
    delete ns;

    m_selection_enabled = true;

    m_state = SELECTING;
    WaitingForOthersScreen::getInstance()->push();
}   // startSelection
/*! \brief Called when a player votes for a track.
 *  \param event : Event providing the information.
 *
 *  Format of the data :
 *  Byte 0   1            5   6            N+6 N+7                 N+8
 *       -----------------------------------------------------------
 *  Size | 1 |      4     | 1 |      N     | 1 |       1           |
 *  Data | 4 | priv token | N | track name | 1 | track number (gp) |
 *       -----------------------------------------------------------
 */
void ServerLobbyRoomProtocol::playerTrackVote(Event* event)
{
    NetworkString &data = event->data();
    STKPeer* peer = event->getPeer();
    if (!checkDataSizeAndToken(event, 8))
        return;
    std::string track_name;
    int N = data.decodeString(5, &track_name);
    if (!isByteCorrect(event, N+5, 1))
        return;
    uint8_t player_id = peer->getPlayerProfile()->getGlobalPlayerId();
    m_setup->getRaceConfig()->setPlayerTrackVote(player_id, track_name, data[N+6]);
    // Send the vote to everybody (including the sender)
    data.removeFront(5); // remove the token
    NetworkString other(2+data.size());
    other.ai8(1).ai8(player_id); // add the player id
    other += data; // add the data
    sendMessageToPeersChangingToken(LE_VOTE_TRACK, other);
    if(m_setup->getRaceConfig()->getNumTrackVotes()==m_setup->getPlayerCount())
        startGame();
}   // playerTrackVote
/** Called when a client disconnects.
 *  \param event The disconnect event.
 */
void ServerLobbyRoomProtocol::clientDisconnected(Event* event)
{
    std::vector<NetworkPlayerProfile*> players_on_host = 
                                      event->getPeer()->getAllPlayerProfiles();

    NetworkString *msg = getNetworkString(2);
    msg->addUInt8(LE_PLAYER_DISCONNECTED);

    for(unsigned int i=0; i<players_on_host.size(); i++)
    {
        msg->addUInt8(players_on_host[i]->getGlobalPlayerId());
        Log::info("ServerLobbyRoomProtocol", "Player disconnected : id %d",
                  players_on_host[i]->getGlobalPlayerId());
        m_setup->removePlayer(players_on_host[i]);
    }

    sendMessageToPeersChangingToken(msg, /*reliable*/true);
    // Remove the profile from the peer (to avoid double free)
    STKHost::get()->removePeer(event->getPeer());
    delete msg;
    
}   // clientDisconnected
/** Simple finite state machine. First get the public ip address. Once this
 *  is known, register the server and its address with the stk server so that
 *  client can find it.
 */
void ServerLobbyRoomProtocol::update(float dt)
{
    switch (m_state)
    {
    case NONE:
        // Start the protocol to find the public ip address.
        m_current_protocol = new GetPublicAddress(this);
        m_current_protocol->requestStart();
        m_state = GETTING_PUBLIC_ADDRESS;
        // The callback from GetPublicAddress will wake this protocol up
        requestPause();
        break;
    case GETTING_PUBLIC_ADDRESS:
        {
            Log::debug("ServerLobbyRoomProtocol", "Public address known.");
            // Free GetPublicAddress protocol
            delete m_current_protocol;

            // Register this server with the STK server. This will block
            // this thread, but there is no need for the protocol manager
            // to react to any requests before the server is registered.
            registerServer();
            Log::info("ServerLobbyRoomProtocol", "Server registered.");
            m_state = ACCEPTING_CLIENTS;
        }
        break;
    case ACCEPTING_CLIENTS:
    {
        // Only poll the STK server if this is a WAN server.
        if(NetworkConfig::get()->isWAN())
            checkIncomingConnectionRequests();
        break;
    }
    case SELECTING:
        break;   // Nothing to do, this is event based
    case RACING:
        if (World::getWorld() &&
            RaceEventManager::getInstance<RaceEventManager>()->isRunning())
        {
            checkRaceFinished();
        }
        break;
    case RESULT_DISPLAY:
        if(StkTime::getRealTime() > m_timeout)
        {
            // Send a notification to all clients to exit 
            // the race result screen
            NetworkString *exit_result_screen = getNetworkString(1);
            exit_result_screen->setSynchronous(true);
            exit_result_screen->addUInt8(LE_EXIT_RESULT);
            sendMessageToPeersChangingToken(exit_result_screen,
                                            /*reliable*/true);
            delete exit_result_screen;
            m_state = ACCEPTING_CLIENTS;
            RaceResultGUI::getInstance()->backToLobby();
            // notify the network world that it is stopped
            RaceEventManager::getInstance()->stop();
            // stop race protocols
            findAndTerminateProtocol(PROTOCOL_CONTROLLER_EVENTS);
            findAndTerminateProtocol(PROTOCOL_KART_UPDATE);
            findAndTerminateProtocol(PROTOCOL_GAME_EVENTS);
        }
        break;
    case DONE:
        m_state = EXITING;
        requestTerminate();
        break;
    case EXITING:
        break;
    }
}   // update