//
//  SuperTuxKart - a fun racing game with go-kart
//  Copyright (C) 2004-2015  Ingo Ruhnke <grumbel@gmx.de>
//  Copyright (C) 2006-2015  Joerg Henrichs
//
//  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 "race/grand_prix_data.hpp"

#include "config/player_profile.hpp"
#include "config/user_config.hpp"
#include "challenges/unlock_manager.hpp"
#include "config/player_manager.hpp"
#include "io/file_manager.hpp"
#include "io/utf_writer.hpp"
#include "tracks/track_manager.hpp"
#include "tracks/track.hpp"
#include "utils/string_utils.hpp"
#include "utils/translation.hpp"

#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <memory>
#include <stdexcept>


// ----------------------------------------------------------------------------
/** Loads a grand prix definition from a file.
 *  \param filename Name of the file to load.
 */
GrandPrixData::GrandPrixData(const std::string& filename, enum GPGroupType group)
{
    setFilename(filename);
    m_id       = StringUtils::getBasename(StringUtils::removeExtension(filename));
    m_editable = (filename.find(file_manager->getGPDir(), 0) == 0);
    m_group    = group;
    m_reverse_type = GP_NO_REVERSE;

    reload();
}   // GrandPrixData

// ----------------------------------------------------------------------------
/** Creates a random grand prix from the specified parameters.
 *  \param number_of_tracks How many tracks to select.
 *  \param track_group From which track group to select the tracks.
 *  \param use_reverse How the reverse setting is to be determined.
 *  \param new_tracks If true, new tracks are selected, otherwise existing
 *         tracks will not be changed (used to e.g. increase the number of
 *         tracks in an already existing random grand prix).
 *
 */
void GrandPrixData::createRandomGP(const unsigned int number_of_tracks,
                                   const std::string &track_group,
                                   const GPReverseType use_reverse,
                                   bool new_tracks)
{
    m_filename = "Random GP - Not loaded from a file!";
    m_id       = getRandomGPID();
    m_name     = getRandomGPName();
    m_editable = false;
    m_group    = GP_NONE;
    m_reverse_type = use_reverse;

    if(new_tracks)
    {
        m_tracks.clear();
        m_laps.clear();
        m_reversed.clear();
    }
    m_tracks.reserve(number_of_tracks);
    m_laps.reserve(number_of_tracks);
    m_reversed.reserve(number_of_tracks);

    changeTrackNumber(number_of_tracks, track_group);
    changeReverse(use_reverse);
}   // createRandomGP

// ----------------------------------------------------------------------------
/** Either adds or removes tracks to get the requested numder of tracks in
 *  a random GP.
 *  \param number_of_tracks How many tracks should be in the random list.
 *  \param track_group From which group to select the tracks.
 */
void GrandPrixData::changeTrackNumber(const unsigned int number_of_tracks,
                                      const std::string& track_group)
{
    // The problem with the track groups is that "all" isn't a track group
    // TODO: Add "all" to the track groups and rewrite this more elegant
    std::vector<int> track_indices;
    if (track_group == "all")
    {
        for(unsigned int i=0; i<track_manager->getNumberOfTracks(); i++)
        {
            const Track *track = track_manager->getTrack(i);
            // Ignore no-racing tracks:
            if(!track->isRaceTrack())
                continue;

            if (PlayerManager::getCurrentPlayer()->isLocked(track->getIdent()))
                continue;

            // Only add tracks that are not already picked.
            if(std::find(m_tracks.begin(), m_tracks.end(), track->getIdent())==
                m_tracks.end())
                track_indices.push_back(i);
        }
    }
    else
    {
        track_indices = track_manager->getTracksInGroup(track_group);
    }
    assert(number_of_tracks <= track_indices.size() + m_tracks.size());

    // add or remove the right number of tracks
    if (m_tracks.size() < number_of_tracks)
    {
        while (m_tracks.size() < number_of_tracks)
        {
            int index       = rand() % track_indices.size();
            int track_index = track_indices[index];

            const Track *track = track_manager->getTrack(track_index);
            std::string id = track->getIdent();

            if (PlayerManager::getCurrentPlayer()->isLocked(track->getIdent()))
                continue;

            bool is_already_added = false;
            for (unsigned int i = 0; i < m_tracks.size(); i++)
            {
                if (m_tracks[i] == id)
                {
                    is_already_added = true;
                    break;
                }
            }

            if (!is_already_added)
            {
                m_tracks.push_back(id);
                m_laps.push_back(track->getDefaultNumberOfLaps());
                m_reversed.push_back(false); // This will be changed later in the code
            }

            track_indices.erase(track_indices.begin()+index);
        }
    }
    else if (m_tracks.size() > number_of_tracks)
    {
        while (m_tracks.size() > number_of_tracks)
        {
            m_tracks.pop_back();
            m_laps.pop_back();
            m_reversed.pop_back();
        }
    }

    assert(m_tracks.size() == m_laps.size()    );
    assert(m_laps.size()   == m_reversed.size());
}   // changeTrackNumber

// ----------------------------------------------------------------------------
/** Updates the GP data with newly decided reverse requirements.
 *  \param use_reverse How reverse setting for each track is to be determined.
 */
void GrandPrixData::changeReverse(const GrandPrixData::GPReverseType use_reverse)
{
    m_reverse_type = use_reverse;
    for (unsigned int i = 0; i < m_tracks.size(); i++)
    {
        if (use_reverse == GP_NO_REVERSE)
        {
            m_reversed[i] = false;
        }
        else if (use_reverse == GP_ALL_REVERSE) // all reversed
        {
            m_reversed[i] = track_manager->getTrack(m_tracks[i])->reverseAvailable();
        }
        else if (use_reverse == GP_RANDOM_REVERSE)
        {
            if (track_manager->getTrack(m_tracks[i])->reverseAvailable())
                m_reversed[i] = (rand() % 2 != 0);
            else
                m_reversed[i] = false;
        }
    }   // for i < m_tracks.size()
}   // changeReverse

// ----------------------------------------------------------------------------
/** Sets the id of this grand prix.
 *  \param id The new id.
 */
void GrandPrixData::setId(const std::string& id)
{
    m_id = id;
}   // setId

// ----------------------------------------------------------------------------
/** Sets the name of the grand prix.
 *  \param name New name.
 */
void GrandPrixData::setName(const irr::core::stringw& name)
{
    m_name = name;
}   // setName

// ----------------------------------------------------------------------------
/** Sets the filename of this grand prix.
 *  \param filename New filename.
 */
void GrandPrixData::setFilename(const std::string& filename)
{
    m_filename = filename;
}   // setFilename

// ----------------------------------------------------------------------------
/** Sets if this grand prix can be edited.
 *  \param editable New value.
 */
void GrandPrixData::setEditable(const bool editable)
{
    m_editable = editable;
}   // setEditable

// ----------------------------------------------------------------------------
/** Sets the group of this grand prix.
 *  \param editable New value.
 */
void GrandPrixData::setGroup(const enum GPGroupType group)
{
    m_group = group;
}   // setGroup

// ----------------------------------------------------------------------------
/** Reloads grand prix from file.
 */
void GrandPrixData::reload()
{
    m_tracks.clear();
    m_laps.clear();
    m_reversed.clear();

    std::unique_ptr<XMLNode> root(file_manager->createXMLTree(m_filename));
    if (root.get() == NULL)
    {
        Log::error("GrandPrixData",
                   "Error while trying to read xml Grand Prix from file '%s'. "
                   "Is the file readable for supertuxkart?",
                   m_filename.c_str());
        throw std::runtime_error("File couldn't be read");
    }

    if (root->getName() != "supertuxkart_grand_prix")
    {
        Log::error("GrandPrixData",
                   "Error while trying to read Grand Prix file '%s': "
                   "Root node has the wrong name %s", m_filename.c_str(),
                   root->getName().c_str());
        throw std::runtime_error("Wrong root node name");
    }

    if (!root->getAndDecode("name", &m_name))
    {
         Log::error("GrandPrixData",
                    "Error while trying to read grandprix file '%s': "
                    "Missing 'name' attribute", m_filename.c_str());
        throw std::runtime_error("Missing name attribute");
    }

    const int amount = root->getNumNodes();
    if (amount == 0)
    {
         Log::warn("GrandPrixData",
                   "Grandprix file '%s': There is no track defined",
                   m_filename.c_str());
    }

    // Every iteration means parsing one track entry
    for (int i = 0; i < amount; i++)
    {
        const XMLNode* node = root->getNode(i);

        if (node->getName() != "track")
        {
            Log::error("GrandPrixData"
                       "Unknown node in Grand Prix XML file '%s': %s",
                       m_filename.c_str(), node->getName().c_str());
            throw std::runtime_error("Unknown node in the XML file");
        }

        // 1. Parsing the id atttribute
        std::string track_id;
        if (!node->get("id", &track_id))
        {
            Log::error("GrandPrixData",
                       "The id attribute is missing in the %d. track entry of "
                       "the Grand Prix file '%s'.", i, m_filename.c_str());
            throw std::runtime_error("Missing track id");
        }

        // 1.1 Checking if the track exists
        Track* t = track_manager->getTrack(track_id);
        if (t == NULL)
        {
            Log::error("GrandPrixData",
                       "The Grand Prix file '%s' contains a track '%s' that "
                       "does not exist", m_filename.c_str(), track_id.c_str());
            throw std::runtime_error("Unknown track");
        }

        // 2. Parsing the number of laps
        int number_of_laps;
        if (!node->get("laps", &number_of_laps))
        {
            Log::error("GrandPrixData",
                       "The laps attribute is missing in the %d. track entry "
                       "of the Grand Prix file '%s'.", i, m_filename.c_str());
            throw std::runtime_error("Missing track id");
        }

        if (number_of_laps < 1 && !UserConfigParams::m_artist_debug_mode)
        {
            Log::error("GrandPrixData",
                       "Track '%s' in the Grand Prix file '%s' should be raced "
                       "with %d laps, which isn't possible.", track_id.c_str(),
                       m_filename.c_str());
            throw std::runtime_error("Lap count lower than 1");
        }

        // 3. Parsing the reversed attribute
        bool reversed = false; // Stays false if not found
        node->get("reverse", &reversed );
        if (!t->reverseAvailable())
            reversed = false;

        // Adding parsed data
        m_tracks.push_back(track_id);
        m_laps.push_back(number_of_laps);
        m_reversed.push_back(reversed);

        assert(m_tracks.size() == m_laps.size()    );
        assert(m_laps.size()   == m_reversed.size());
    }   // end for all root nodes
}   // reload()

// ----------------------------------------------------------------------------
/** Saves the grand prix data to a file.
 */
bool GrandPrixData::writeToFile()
{
    try
    {
        UTFWriter file(m_filename.c_str(), false);
        if (file.is_open())
        {
            file << "\n<supertuxkart_grand_prix name=\"" << StringUtils::xmlEncode(m_name)
                 << "\">\n\n";
            for (unsigned int i = 0; i < m_tracks.size(); i++)
            {
                file <<
                    "\t<track id=\"" << m_tracks[i] <<
                    "\" laps=\""     << m_laps[i] <<
                    "\" reverse=\""  << (m_reversed[i] ? L"true" : L"false")
                                      <<  "\" />\n";
            }
            file << "\n</supertuxkart_grand_prix>\n";

            file.close();

            return true;
        }

        return false;
    }
    catch (std::runtime_error& e)
    {
        Log::error("GrandPrixData",
                   "Failed to write grand prix to '%s'; cause: %s",
                   m_filename.c_str(), e.what());
        return false;
    }
}   // writeToFile

// ----------------------------------------------------------------------------
/** Checks if the grand prix data are consistent.
 *  \param log_error: If errors should be sent to the logger.
 */
bool GrandPrixData::checkConsistency(bool log_error) const
{
    for (unsigned int i = 0; i < m_tracks.size(); i++)
    {
        if (track_manager->getTrack(m_tracks[i]) == NULL)
        {
            if (log_error)
            {
                Log::error("GrandPrixData",
                           "The grand prix '%ls' won't be available because "
                           "the track '%s' does not exist!", m_name.c_str(),
                           m_tracks[i].c_str());
            }
            return false;
        }
    }
    return true;
}   // checkConsistency

// ----------------------------------------------------------------------------
/** Returns true if the track is available. This is used to test if Fort Magma
 *  is available (this way FortMagma is not used in the last Grand Prix in
 *  story mode, but will be available once all challenges are done and nolok
 *  is unlocked). It also prevents people from using the grand prix editor as
 *  a way to play tracks that still haven't been unlocked
 *  \param id Name of the track to test.
 *  \param include_locked If set to true, all tracks (including locked tracks)
 *         are considered to be available.
 */
bool GrandPrixData::isTrackAvailable(const std::string &id,
                                     bool include_locked     ) const
{
    if (include_locked)
        return true;
    else if (id == "fortmagma")
        return !PlayerManager::getCurrentPlayer()->isLocked("fortmagma");
    else
        return (!m_editable ||
                !PlayerManager::get()->getCurrentPlayer()->isLocked(id));
}   // isTrackAvailable

// ----------------------------------------------------------------------------
/** Returns the list of tracks that is available (i.e. unlocked) of this
 *   grand prix.
 *  \param include_locked If data for locked tracks should be included or not.
 *  \return A copy of the list of available tracks in this grand prix.
 */
std::vector<std::string> GrandPrixData::getTrackNames(bool include_locked) const
{
    std::vector<std::string> names;
    for (unsigned int i = 0; i < m_tracks.size(); i++)
    {
        if(isTrackAvailable(m_tracks[i], include_locked))
            names.push_back(m_tracks[i]);
    }
    return names;
}   // getTrackNames

// ----------------------------------------------------------------------------
/** Returns the laps for each available track of the grand prix.
 *  \param include_locked If data for locked tracks should be included or not.
 *  \return a std::vector containing the laps for each grand prix.
 */
std::vector<int> GrandPrixData::getLaps(bool include_locked) const
{
    std::vector<int> laps;
    for (unsigned int i = 0; i< m_tracks.size(); i++)
        if(isTrackAvailable(m_tracks[i], include_locked))
            laps.push_back(m_laps[i]);

    return laps;
}   // getLaps

// ----------------------------------------------------------------------------
/** Returns the reverse setting for each available grand prix.
 *  \param include_locked If data for locked tracks should be included or not.
 *  \return A copy of alist with the reverse status for each track.
 */
std::vector<uint8_t> GrandPrixData::getReverse(bool include_locked) const
{
    std::vector<uint8_t> reverse;
    for (unsigned int i = 0; i< m_tracks.size(); i++)
        if(isTrackAvailable(m_tracks[i], include_locked))
            reverse.push_back(m_reversed[i]);

    return reverse;
}   // getReverse

// ----------------------------------------------------------------------------
/** Returns true if this grand prix can be edited.
 */
bool GrandPrixData::isEditable() const
{
    return m_editable;
}   // isEditable

// ----------------------------------------------------------------------------
/** Returns the number of tracks in this grand prix.
 *  \param include_locked If data for locked tracks should be included or not.
 */
unsigned int GrandPrixData::getNumberOfTracks(bool includeLocked) const
{
    if (includeLocked)
        return (unsigned int)m_tracks.size();
    else
        return (unsigned int)getTrackNames(false).size();
}   // getNumberOfTracks

// ----------------------------------------------------------------------------
/** Returns the (translated) name of the track with the specified index.
 */
irr::core::stringw GrandPrixData::getTrackName(const unsigned int track) const
{
    assert(track < getNumberOfTracks(true));
    Track* t = track_manager->getTrack(m_tracks[track]);
    assert(t != NULL);
    return t->getName();
}   // getTrackName
// ----------------------------------------------------------------------------

bool GrandPrixData::containsUnavailableTracks() const
{
    return getNumberOfTracks() < getNumberOfTracks(true);
}   // containsUnavailableTracks

// ----------------------------------------------------------------------------
const std::string& GrandPrixData::getTrackId(const unsigned int track) const
{
    assert(track < getNumberOfTracks(true));
    return m_tracks[track];
}

// ----------------------------------------------------------------------------
unsigned int GrandPrixData::getLaps(const unsigned int track) const
{
    assert(track < getNumberOfTracks(true));
    return m_laps[track];
}

// ----------------------------------------------------------------------------
bool GrandPrixData::getReverse(const unsigned int track) const
{
    assert(track < getNumberOfTracks(true));
    return m_reversed[track];
}

// ----------------------------------------------------------------------------
void GrandPrixData::moveUp(const unsigned int track)
{
    assert (track > 0 && track < getNumberOfTracks(true));

    std::swap(m_tracks[track], m_tracks[track - 1]);
    std::swap(m_laps[track], m_laps[track - 1]);
    bool tmp              = m_reversed[track    ];
    m_reversed[track]     = m_reversed[track - 1];
    m_reversed[track - 1] = tmp;
}

// ----------------------------------------------------------------------------
void GrandPrixData::moveDown(const unsigned int track)
{
    assert (track < (getNumberOfTracks(true) - 1));

    std::swap(m_tracks[track], m_tracks[track + 1]);
    std::swap(m_laps[track], m_laps[track + 1]);
    bool tmp              = m_reversed[track    ];
    m_reversed[track    ] = m_reversed[track + 1];
    m_reversed[track + 1] = tmp;
}

// ----------------------------------------------------------------------------
void GrandPrixData::addTrack(Track* track, unsigned int laps, bool reverse,
                             int position)
{
    int n = getNumberOfTracks(true);
    assert (track != NULL);
    assert (laps > 0);
    assert (-1 <= position && position < n);

    if (position < 0 || position == (n - 1) || m_tracks.empty())
    {
        // Append new track to the end of the list
        m_tracks.push_back(track->getIdent());
        m_laps.push_back(laps);
        m_reversed.push_back(reverse);
    }
    else
    {
        // Insert new track right after the specified position. Caution:
        // std::vector inserts elements _before_ the specified position
        m_tracks.  insert(m_tracks.begin()   + position + 1, track->getIdent());
        m_laps.    insert(m_laps.begin()     + position + 1, laps             );
        m_reversed.insert(m_reversed.begin() + position + 1, reverse          );
    }
}

// ----------------------------------------------------------------------------
void GrandPrixData::editTrack(unsigned int index, Track* track,
                              unsigned int laps, bool reverse)
{
    assert (index < getNumberOfTracks(true));
    assert (track != NULL);
    assert (laps > 0);

    m_tracks[index]   = track->getIdent();
    m_laps[index]     = laps;
    m_reversed[index] = reverse;
}

// ----------------------------------------------------------------------------
void GrandPrixData::remove(const unsigned int track)
{
    assert (0 <= track && track < getNumberOfTracks(true));

    m_tracks.erase(m_tracks.begin() + track);
    m_laps.erase(m_laps.begin() + track);
    m_reversed.erase(m_reversed.begin() + track);
}

// ----------------------------------------------------------------------------
irr::core::stringw GrandPrixData::getRandomGPName()
{
    return _("Random Grand Prix");
}

// ----------------------------------------------------------------------------
/** @return the (potentially translated) user-visible name of the Grand
 *  Prix, m_editable is true if it's custom GP name. */
irr::core::stringw GrandPrixData::getName() const
{
    return m_editable ? m_name.c_str() : _(m_name.c_str());
}
// ----------------------------------------------------------------------------
irr::core::stringw GrandPrixData::reverseTypeToString(GPReverseType reverse_type)
{
    switch (reverse_type)
    {
    case GrandPrixData::GP_DEFAULT_REVERSE:
        return _("Default");
    case GrandPrixData::GP_NO_REVERSE:
        return _("None");
    case GrandPrixData::GP_ALL_REVERSE:
        return _("All");
    case GrandPrixData::GP_RANDOM_REVERSE:
        return _("Random");
    default:
        return "N/A";
    }
    return "N/A";
}

/* EOF */
