//  SuperTuxKart - a fun racing game with go-kart
//  Copyright (C) 2014-2015 SuperTuxKart-Team
//
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU General Public License
//  as published by the Free Software Foundation; either version 3
//  of the License, or (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

#include "network/race_config.hpp"

#include "race/race_manager.hpp"
#include "utils/log.hpp"

#include <stdexcept>

/** \brief Gets the element with the highest count in a std::map<S,int>.
 *  \param histogram : A pointer to the histogram.
 *  \return The key of type S that has the highest second value.
 */
template<typename S>
S getHighestInHistogram(std::map<S,int>* histogram)
{
    S best_item;
    uint8_t highest_count = histogram->begin()->second;
    for (typename std::map<S, int>::iterator it = histogram->begin();
        it != histogram->end(); it++)
    {
        if (it->second > highest_count)
        {
            highest_count = it->second;
            best_item = it->first;
        }
    }
    return best_item;
}

//-----------------------------------------------------------------------------
//---------------------------------  TrackVote --------------------------------
//-----------------------------------------------------------------------------

TrackVote::TrackVote()
{
    has_voted_laps = false;
    has_voted_track = false;
    has_voted_reversed = false;
}
//-----------------------------------------------------------------------------
void TrackVote::voteTrack(std::string track)
{
    track_info.track = track;
    has_voted_track = true;
}
//-----------------------------------------------------------------------------
void TrackVote::voteReversed(bool reversed)
{
    track_info.reversed = reversed;
    has_voted_reversed = true;
}
//-----------------------------------------------------------------------------
void TrackVote::voteLaps(uint8_t laps)
{
    track_info.laps = laps;
    has_voted_laps = true;
}

//-----------------------------------------------------------------------------
//---------------------------------  RaceVote ---------------------------------
//-----------------------------------------------------------------------------

RaceVote::RaceVote()
{
    m_has_voted_major = false;
    m_has_voted_minor = false;
    m_has_voted_races_count = false;
    m_major_mode = 0;
    m_minor_mode = 0;
    m_races_count = 0;
}

//-----------------------------------------------------------------------------

void RaceVote::voteMajor(uint8_t major)
{
    m_has_voted_major = true;
    m_major_mode = major;
}

//-----------------------------------------------------------------------------

void RaceVote::voteRaceCount(uint8_t count)
{
    m_has_voted_races_count = true;
    m_races_count = count;
}

//-----------------------------------------------------------------------------

void RaceVote::voteMinor(uint8_t minor)
{
    m_has_voted_minor = true;
    m_minor_mode = minor;
}

//-----------------------------------------------------------------------------

void RaceVote::voteTrack(std::string track, uint8_t track_number)
{
    m_tracks_vote[track_number].voteTrack(track);
}

//-----------------------------------------------------------------------------

void RaceVote::voteReversed(bool reversed, uint8_t track_number)
{
    m_tracks_vote[track_number].voteReversed(reversed);
}

//-----------------------------------------------------------------------------

void RaceVote::voteLaps(uint8_t laps, uint8_t track_number)
{
    m_tracks_vote[track_number].voteLaps(laps);
}

//-----------------------------------------------------------------------------
bool RaceVote::hasVotedMajor() const
{
    return m_has_voted_major;
}
//-----------------------------------------------------------------------------
bool RaceVote::hasVotedRacesCount() const
{
    return m_has_voted_races_count;
}
//-----------------------------------------------------------------------------
bool RaceVote::hasVotedMinor() const
{
    return m_has_voted_minor;
}
//-----------------------------------------------------------------------------
bool RaceVote::hasVotedTrack(uint8_t track_number) const
{
    return m_tracks_vote[track_number].has_voted_track;
}
//-----------------------------------------------------------------------------
bool RaceVote::hasVotedReversed(uint8_t track_number) const
{
    return m_tracks_vote[track_number].has_voted_reversed;
}
//-----------------------------------------------------------------------------
bool RaceVote::hasVotedLaps(uint8_t track_number) const
{
    return m_tracks_vote[track_number].has_voted_laps;
}

//-----------------------------------------------------------------------------

uint8_t RaceVote::getMajorVote() const
{
    return m_major_mode;
}
//-----------------------------------------------------------------------------
uint8_t RaceVote::getRacesCountVote() const
{
    return m_races_count;
}
//-----------------------------------------------------------------------------
uint8_t RaceVote::getMinorVote() const
{
    return m_minor_mode;
}
//-----------------------------------------------------------------------------
std::string RaceVote::getTrackVote(uint8_t track_number) const
{
    return m_tracks_vote[track_number].track_info.track;
}
//-----------------------------------------------------------------------------
bool RaceVote::getReversedVote(uint8_t track_number) const
{
    return m_tracks_vote[track_number].track_info.reversed;
}
//-----------------------------------------------------------------------------
uint8_t RaceVote::getLapsVote(uint8_t track_number) const
{
    return m_tracks_vote[track_number].track_info.laps;
}

//-----------------------------------------------------------------------------
//---------------------------------  RaceConfig -------------------------------
//-----------------------------------------------------------------------------

RaceConfig::RaceConfig()
{
    m_max_players = 0;
}

//-----------------------------------------------------------------------------

void RaceConfig::setPlayerCount(uint8_t count)
{
    m_max_players = count;
    m_votes.resize(m_max_players);
}

//-----------------------------------------------------------------------------

void RaceConfig::setPlayerMajorVote(uint8_t player_id, uint8_t major)
{
    Log::info("RaceConfig", "Player %d voted for major %d", player_id, major);
    m_votes[player_id].voteMajor(major);
}

//-----------------------------------------------------------------------------

void RaceConfig::setPlayerRaceCountVote(uint8_t player_id, uint8_t count)
{
    Log::info("RaceConfig", "Player %d voted for %d races in GP", player_id, count);
    m_votes[player_id].voteRaceCount(count);
}

//-----------------------------------------------------------------------------

void RaceConfig::setPlayerMinorVote(uint8_t player_id, uint8_t minor)
{
    Log::info("RaceConfig", "Player %d voted for minor %d", player_id, minor);
    m_votes[player_id].voteMinor(minor);
}

//-----------------------------------------------------------------------------

void RaceConfig::setPlayerTrackVote(uint8_t player_id, std::string track, uint8_t track_nb)
{
    Log::info("RaceConfig", "Player %d voted for track %s", player_id, track.c_str());
    m_votes[player_id].voteTrack(track, track_nb);
}

//-----------------------------------------------------------------------------

void RaceConfig::setPlayerReversedVote(uint8_t player_id, bool reversed, uint8_t track_nb)
{
    if (reversed)
        Log::info("RaceConfig", "Player %d voted map %d to be reversed", player_id, track_nb);
    else
        Log::info("RaceConfig", "Player %d voted map %d NOT to be reversed", player_id, track_nb);
    m_votes[player_id].voteReversed(reversed, track_nb);
}

//-----------------------------------------------------------------------------

void RaceConfig::setPlayerLapsVote(uint8_t player_id, uint8_t lap_count, uint8_t track_nb)
{
    Log::info("RaceConfig", "Player %d voted map %d to have %d laps", player_id, track_nb, lap_count);
    m_votes[player_id].voteLaps(lap_count, track_nb);
}

//-----------------------------------------------------------------------------

void RaceConfig::computeRaceMode()
{
    // calculate the race type and number of tracks (in GP mode).
    std::map<int,int> major_histogram;
    std::map<int,int> races_count_histogram;
    std::map<int,int> minor_histogram;
    for (unsigned int i = 0; i < m_max_players; i++)
    {
        // increase the count of votes
        if (m_votes[i].hasVotedMajor())
        {
            try
            {
                major_histogram.at(m_votes[i].getMajorVote()) ++;
            }
            catch (const std::out_of_range&) // doesn't exist in the map
            {
                major_histogram[m_votes[i].getMajorVote()] = 1;
            }
        }
        else if (m_votes[i].hasVotedRacesCount())
        {
            try
            {
                races_count_histogram.at(m_votes[i].getRacesCountVote()) ++;
            }
            catch (const std::out_of_range&) // doesn't exist in the map
            {
                races_count_histogram[m_votes[i].getRacesCountVote()] = 1;
            }
        }
        else if (m_votes[i].hasVotedMinor())
        {
            try
            {
                minor_histogram.at(m_votes[i].getMinorVote()) ++;
            }
            catch (const std::out_of_range&) // doesn't exist in the map
            {
                minor_histogram[m_votes[i].getMinorVote()] = 1;
            }
        }
    }
    // now we know :
    m_major_mode = (!major_histogram.empty() ? getHighestInHistogram<int>(&major_histogram) : 1);
    m_races_count = (!minor_histogram.empty() ? getHighestInHistogram<int>(&races_count_histogram) : 1);
    m_minor_mode = (!minor_histogram.empty() ? getHighestInHistogram<int>(&minor_histogram) : 0);

    if (m_major_mode == RaceManager::MAJOR_MODE_GRAND_PRIX)
        m_tracks.resize(m_races_count);
    else
    {
        m_tracks.resize(1);
        m_races_count = 1;
    }

    Log::info("RaceConfig", "Major mode will be %d with %d races. Minor is %d", m_major_mode, m_races_count, m_minor_mode);
}
void RaceConfig::computeNextTrack()
{
    for (unsigned int j = 0; j < m_races_count; j++)
    {
        // first create histograms of the votes
        std::map<std::string,int> tracks_histogram;
        std::map<bool,int> reversed_histogram;
        std::map<int,int> laps_histogram;
        for (unsigned int i = 0; i < m_max_players; i++)
        {
            // increase the count of votes
            if (m_votes[i].hasVotedTrack())
            {
                try // maps
                {
                    tracks_histogram.at(m_votes[i].getTrackVote()) ++;
                }
                catch (const std::out_of_range&) // doesn't exist in the map
                {
                    tracks_histogram[m_votes[i].getTrackVote()] = 1;
                }
            }
            else if (m_votes[i].hasVotedReversed())
            {
                try // reversed
                {
                    reversed_histogram.at(m_votes[i].getReversedVote()) ++;
                }
                catch (const std::out_of_range&) // doesn't exist in the map
                {
                    reversed_histogram[m_votes[i].getReversedVote()] = 1;
                }
            }
            else if (m_votes[i].hasVotedLaps())
            {
                try // laps
                {
                    laps_histogram.at(m_votes[i].getLapsVote()) ++;
                }
                catch (const std::out_of_range&) // doesn't exist in the mapt
                {
                    laps_histogram[m_votes[i].getLapsVote()] = 1;
                }
            }
        }
        // now find the highest votes
        m_tracks[j].track = getHighestInHistogram<std::string>(&tracks_histogram);
        m_tracks[j].reversed = getHighestInHistogram<bool>(&reversed_histogram);
        m_tracks[j].laps = getHighestInHistogram<int>(&laps_histogram);
        if (m_tracks[j].reversed)
            Log::info("RaceConfig", "Race %d will be on %s with %d laps and reversed", j, m_tracks[j].track.c_str(), m_tracks[j].laps);
        else
            Log::info("RaceConfig", "Race %d will be on %s with %d laps", j, m_tracks[j].track.c_str(), m_tracks[j].laps);
    }
}

//-----------------------------------------------------------------------------

const TrackInfo* RaceConfig::getNextTrackInfo() const
{
    return &m_tracks[0];
}

//-----------------------------------------------------------------------------
