#include "DifficultySettingsManager.h"

#include "ieclass.h"
#include "itextstream.h"
#include "entitylib.h"
#include "gamelib.h"
#include "maplib.h"
#include "string/string.h"
#include "registry/registry.h"
#include "DifficultyEntity.h"
#include "DifficultyEntityFinder.h"

namespace difficulty {

DifficultySettingsPtr DifficultySettingsManager::getSettings(int level) {
    for (std::size_t i = 0; i < _settings.size(); i++) {
        if (_settings[i]->getLevel() == level) {
            return _settings[i];
        }
    }
    return DifficultySettingsPtr();
}

void DifficultySettingsManager::loadSettings() {
    loadDefaultSettings();
    loadMapSettings();
    loadDifficultyNames();
}

void DifficultySettingsManager::loadDefaultSettings() {
    // Try to lookup the given entityDef
    IEntityClassPtr eclass = GlobalEntityClassManager().findClass(
        game::current::getValue<std::string>(GKEY_DIFFICULTY_ENTITYDEF_DEFAULT)
    );

    if (eclass == NULL) {
        rError() << "Could not find default difficulty settings entityDef.\n";
        return;
    }

    // greebo: Setup the default difficulty levels using the found entityDef
    int numLevels = game::current::getValue<int>(GKEY_DIFFICULTY_LEVELS);
    for (int i = 0; i < numLevels; i++) {
        // Allocate a new settings object
        DifficultySettingsPtr settings(new DifficultySettings(i));

        // Parse the settings from the given default settings entityDef
        settings->parseFromEntityDef(eclass);

        // Store the settings object in the local list
        _settings.push_back(settings);
    }
}

void DifficultySettingsManager::loadMapSettings()
{
    // Construct a helper walker
    DifficultyEntityFinder finder;
    GlobalSceneGraph().root()->traverse(finder);

    const DifficultyEntityFinder::EntityList& found = finder.getEntities();

    // Pop all entities into each difficulty setting
    for (DifficultyEntityFinder::EntityList::const_iterator ent = found.begin();
         ent != found.end(); ent++)
    {
        for (std::size_t i = 0; i < _settings.size(); i++) {
            _settings[i]->parseFromMapEntity(*ent);
        }
    }
}

namespace
{

// Class to manage mod-specific difficulty names (which are probably string
// table entries like "#str_000300") and their English translations (we
// maintain these translations separately in the .game file since we don't have
// any support for parsing the mod-specific translation data).
class ModDifficultyNames
{
    // Lookup table of strings and their translations
    std::map<std::string, std::string> _transStrings;

    // Mod-specific entity giving default difficulty names
    IEntityClassPtr _menuEclass;

public:

    // Construct and initialise
    ModDifficultyNames()
    : _menuEclass(
        GlobalEntityClassManager().findClass(
            game::current::getValue<std::string>(GKEY_DIFFICULTY_ENTITYDEF_MENU)
        )
    )
    {
        // Load translations from xml nodes in the .game file
        xml::NodeList transNodes = game::current::getNodes(
            GKEY_DIFFICULTY_ENTITYDEF_MENU + "/string"
        );
        for (auto xmlNode: transNodes)
        {
            _transStrings[xmlNode.getAttributeValue("id")] = xmlNode.getContent();
        }
    }

    // Return the translated name for the difficulty level identified by the
    // given key (e.g. the translated name for "diff0Default" might be "Easy").
    std::string getNameForKey(const std::string& nameKey) const
    {
        if (_menuEclass)
        {
            std::string rawName = _menuEclass->getAttributeValue(nameKey);
            if (!rawName.empty())
            {
                // Look for a translation, otherwise use the raw name
                auto found = _transStrings.find(rawName);
                if (found != _transStrings.end())
                    return found->second;
                else
                    return rawName;
            }
        }

        return "";
    }
};

// Return key for a difficulty level name
std::string diffNameKeyForLevel(int level)
{
    return "diff" + std::to_string(level) + "default";
}

}

void DifficultySettingsManager::loadDifficultyNames()
{
    // Load mod-specific difficulty names first if possible
    ModDifficultyNames diffNames;

    // Look up a name for each difficulty level, starting with a map-specific
    // name set on the worldspawn, then falling back to mod default names and
    // then autogenerated names.
    int numLevels = game::current::getValue<int>(GKEY_DIFFICULTY_LEVELS);

    for (int i = 0; i < numLevels; i++)
    {
        std::string nameKey = diffNameKeyForLevel(i);

        // Load the mod default, get the translated value by the helper class
        std::string defaultName = diffNames.getNameForKey(nameKey);

        if (!defaultName.empty())
        {
            // Use the (hopefully translated) mod-specific default name
            _difficultyNames.push_back(defaultName);
        }
        else
        {
            // Fall back to a non-empty default
            _difficultyNames.push_back(std::to_string(i));
        }
    }

    // Store the current set as default for later change detection
    _defaultDifficultyNames = _difficultyNames;

    // Locate the worldspawn entity to find any map-specific names
    Entity* worldspawn = map::current::getWorldspawn();

    if (worldspawn != nullptr)
    {
        assert(static_cast<int>(_difficultyNames.size()) == numLevels);

        for (int i = 0; i < numLevels; i++)
        {
            std::string nameKey = diffNameKeyForLevel(i);

            // First, try to find a map-specific name
            std::string name = worldspawn->getKeyValue(nameKey);

            if (!name.empty())
            {
                // Found a setting on worldspawn, take it
                _difficultyNames[i] = name;
            }
        }
    }
}

void DifficultySettingsManager::saveSettings()
{
    // Locates all difficulty entities
    DifficultyEntityFinder finder;
    GlobalSceneGraph().root()->traverse(finder);

    // Copy the list from the finder to a local list
    DifficultyEntityFinder::EntityList entities = finder.getEntities();

    if (entities.empty())
    {
        // Create a new difficulty entity
        std::string eclassName = game::current::getValue<std::string>(GKEY_DIFFICULTY_ENTITYDEF_MAP);
        IEntityClassPtr diffEclass = GlobalEntityClassManager().findClass(eclassName);

        if (diffEclass == NULL) {
            rError() << "[Diff]: Cannot create difficulty entity!\n";
            return;
        }

        // Create and insert a new entity node into the scenegraph root
        IEntityNodePtr entNode = GlobalEntityModule().createEntity(diffEclass);
        GlobalSceneGraph().root()->addChildNode(entNode);

        // Add the entity to the list
        entities.push_back(&entNode->getEntity());
    }

    // Clear all difficulty-spawnargs from existing entities
    for (DifficultyEntityFinder::EntityList::const_iterator i = entities.begin();
         i != entities.end(); i++)
    {
        // Construct a difficulty entity using the raw Entity* pointer
        DifficultyEntity diffEnt(*i);
        // Clear the difficulty-related spawnargs from the entity
        diffEnt.clear();
    }

    // Take the first entity
    DifficultyEntity diffEnt(*entities.begin());

    // Cycle through all settings objects and issue save call
    for (std::size_t i = 0; i < _settings.size(); i++) {
        _settings[i]->saveToEntity(diffEnt);
    }

    // Write the changed difficulty (and only those) names into the worldspawn
    Entity* worldspawn = map::current::getWorldspawn(true);

    if (!worldspawn)
    {
        throw std::logic_error("DifficultySettingsManager::saveSettings():"
            " could not find or create worldspawn"
        );
    }

    for (std::size_t i = 0; i < _difficultyNames.size(); ++i)
    {
        // Compare each value to the default set
        if (_defaultDifficultyNames.size() > i && _defaultDifficultyNames[i] != _difficultyNames[i])
        {
            worldspawn->setKeyValue(diffNameKeyForLevel(i), _difficultyNames[i]);
        }
        else
        {
            // Remove the corresponding spawnarg from the worldspawn if there's no override
            worldspawn->setKeyValue(diffNameKeyForLevel(i), "");
        }
    }
}

std::string DifficultySettingsManager::getDifficultyName(int level)
{
    if (level < 0 || level >= static_cast<int>(_difficultyNames.size()))
    {
        return "";
    }

    return _difficultyNames[level];
}

void DifficultySettingsManager::setDifficultyName(int level, const std::string& name)
{
    if (level < 0 || level >= static_cast<int>(_difficultyNames.size()))
    {
        throw std::logic_error(
            "Invalid difficulty level (" + std::to_string(level) + ")"
        );
    }

    _difficultyNames[level] = name;
}

} // namespace difficulty
