#include "VarText.h"

#include "../universe/Universe.h"
#include "../universe/ShipDesign.h"
#include "../universe/System.h"
#include "../Empire/EmpireManager.h"
#include "../Empire/Empire.h"
#include "i18n.h"
#include "Logger.h"

#include <boost/static_assert.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/spirit/include/classic.hpp>

#include <map>

// Forward declarations
class Tech;
class BuildingType;
class Special;
class Species;
class FieldType;
const Tech*         GetTech(const std::string& name);
const BuildingType* GetBuildingType(const std::string& name);
const Special*      GetSpecial(const std::string& name);
const Species*      GetSpecies(const std::string& name);
const FieldType*    GetFieldType(const std::string& name);

namespace {
    const std::string START_VAR("%");
    const std::string END_VAR("%");
    const std::string LABEL_SEPARATOR(":");

    //////////////////////////////////////////
    ///// Tag substitution generators////////
    ////////////////////////////////////////

    /// Surround content with approprite tags based on tag_of
    std::string WithTags(const std::string& content, const std::string& tag, const XMLElement& data) {
        std::string open_tag = "<" + tag + " " + data.Attribute("value") + ">";
        std::string close_tag = "</" + tag + ">";
        return open_tag + content + close_tag;
    }

    /// The signature of functions that generate substitution strings for
    /// tags.
    typedef std::string (*TagString)(const XMLElement& data, const std::string& tag, bool& valid);

    /// Get string substitute for a translated text tag
    std::string TextString(const XMLElement& data, const std::string& tag, bool& valid) {
        const std::string& text = data.Attribute("value");
        return UserString(text);
    }

    /// Get string substitute for a raw text tag
    std::string RawTextString(const XMLElement& data, const std::string& tag, bool& valid) {
        const std::string& text = data.Attribute("value");
        return text;
    }

    ///Get string substitute for a tag that is a universe object
    std::string UniverseObjectString(const XMLElement& data, const std::string& tag, bool& valid) {
        int object_id = INVALID_OBJECT_ID;
        try {
            object_id = boost::lexical_cast<int>(data.Attribute("value"));
        } catch (const std::exception&) {
            Logger().errorStream() << "UniverseObjectString couldn't cast \"" << data.Attribute("value") << "\" to int for object ID.";
            valid = false;
            return UserString("ERROR");
        }
        TemporaryPtr<const UniverseObject> obj = GetUniverseObject(object_id);
        if (!obj) {
            //Logger().errorStream() << "UniverseObjectString couldn't get object with ID " << object_id;
            valid = false;
            return UserString("ERROR");
        }

        return WithTags(GetVisibleObjectName(obj), tag, data);
    }

    /// combat links always just labelled "Combat"; don't need to look up details
    std::string CombatLogString(const XMLElement& data, const std::string& tag, bool& valid)
    { return WithTags(UserString("COMBAT"), tag, data); }

    /// Returns substitution string for a ship design tag
    std::string ShipDesignString(const XMLElement& data, const std::string& tag, bool& valid) {
        int design_id = ShipDesign::INVALID_DESIGN_ID;
        try {
            design_id = boost::lexical_cast<int>(data.Attribute("value"));
        } catch (const std::exception&) {
            Logger().errorStream() << "SubstituteAndAppend couldn't cast \"" << data.Attribute("value") << "\" to int for ship design ID.";
            valid = false;
            return UserString("ERROR");
        }
        const ShipDesign* design = GetShipDesign(design_id);
        if (!design) {
            Logger().errorStream() << "SubstituteAndAppend couldn't get ship design with ID " << design_id;
            valid = false;
            return UserString("ERROR");
        }
        return WithTags(design->Name(), tag, data);
    }

    /// Returns substitution string for a predefined ship design tag
    std::string PredefinedShipDesignString(const XMLElement& data, const std::string& tag, bool& valid) {
        const std::string& design_name = data.Attribute("value");
        const ShipDesign* design = GetPredefinedShipDesign(design_name);
        if (!design) {
            Logger().errorStream() << "SubstituteAndAppend couldn't get predefined ship design with name " << design_name;
            valid = false;
            return UserString("ERROR");
        }
        return WithTags(design->Name(), tag, data);
    }
    
    /// Returns substitution string for an empire tag
    std::string EmpireString(const XMLElement& data, const std::string& tag, bool& valid) {
        int empire_id = ALL_EMPIRES;
        try {
            empire_id = boost::lexical_cast<int>(data.Attribute("value"));
        } catch (const std::exception&) {
            Logger().errorStream() << "SubstituteAndAppend couldn't cast \"" << data.Attribute("value") << "\" to int for empire ID.";
            valid = false;
            return UserString("ERROR");
        }
        const Empire* empire = Empires().Lookup(empire_id);
        if (!empire) {
            Logger().errorStream() << "SubstituteAndAppend couldn't get empire with ID " << empire_id;
            valid = false;
            return UserString("ERROR");
        }
        return WithTags(empire->Name(), tag, data);
    }

    /// Interprets value of data as a name.
    /// Returns translation of name, if Get says
    /// that a thing by that name exists, otherwise ERROR.
    template <typename T,const T* (*GetByName)(const std::string&)>
    std::string NameString(const XMLElement& data, const std::string& tag, bool& valid) {
        std::string name = data.Attribute("value");
        if (!GetByName(name)) {
            valid = false;
            return UserString("ERROR");
        }
        return WithTags(UserString(name), tag, data);
    }

    /// Returns a map that tells shich function should be used to
    /// generate a substitution for which tag.
    std::map<std::string, TagString> CreateSubstituterMap() {
        std::map<std::string, TagString> subs;
        subs[VarText::TEXT_TAG] = TextString;
        subs[VarText::RAW_TEXT_TAG] = RawTextString;

        subs[VarText::PLANET_ID_TAG] =
            subs[VarText::SYSTEM_ID_TAG] =
            subs[VarText::SHIP_ID_TAG] =
            subs[VarText::FLEET_ID_TAG] =
            subs[VarText::BUILDING_ID_TAG] =
            subs[VarText::FIELD_ID_TAG] = UniverseObjectString;
        subs[VarText::COMBAT_ID_TAG] = CombatLogString;
        subs[VarText::TECH_TAG] = NameString<Tech, GetTech>;
        subs[VarText::BUILDING_TYPE_TAG] = NameString<BuildingType, GetBuildingType>;
        subs[VarText::SHIP_HULL_TAG] = NameString<HullType, GetHullType>;
        subs[VarText::SHIP_PART_TAG] = NameString<PartType, GetPartType>;
        subs[VarText::SPECIAL_TAG] = NameString<Special, GetSpecial>;
        subs[VarText::SPECIES_TAG] = NameString<Species, GetSpecies>;
        subs[VarText::FIELD_TYPE_TAG] = NameString<FieldType, GetFieldType>;
        subs[VarText::DESIGN_ID_TAG] = ShipDesignString;
        subs[VarText::PREDEFINED_DESIGN_TAG] = PredefinedShipDesignString;
        subs[VarText::EMPIRE_ID_TAG] = EmpireString;
        return subs;
    }

    /// Global substitution map, wrapped in a function to avoid initialization order issues
    const std::map<std::string, TagString>& SubstitutionMap() {
        static std::map<std::string, TagString> subs = CreateSubstituterMap();
        return subs;
    }


    /** Converts (first, last) to a string, looks up its value in the Universe,
      * then appends this to the end of a std::string. */
    struct SubstituteAndAppend {
        SubstituteAndAppend(const XMLElement& variables, std::string& str, bool& valid) :
            m_variables(variables),
            m_str(str),
            m_valid(valid)
        {}

        void operator()(const char* first, const char* last) const {
            std::string token(first, last);

            // special case: "%%" is interpreted to be a '%' character
            if (token.empty()) {
                m_str += "%";
                return;
            }

            // Labelled tokens have the form %tag:label%,  unlabelled are just %tag%
            std::vector<std::string> pieces;
            boost::split(pieces, token, boost::is_any_of(LABEL_SEPARATOR));

            std::string tag; //< The tag of the token (the type)
            std::string label; //< The label of the token (the kay to fetch data by)
            if (pieces.size() == 1) {
                // No separator. There is only a tag. The tag is the default label
                tag = token;
                label = token;
            } else if(pieces.size() == 2) {
                // Had a separator
                tag = pieces[0];
                label = pieces[1];
            }

            // look up child
            if (!m_variables.ContainsChild(label)) {
                m_str += UserString("ERROR");
                m_valid = false;
                return;
            }

            const XMLElement& token_elem = m_variables.Child(label);

            std::map<std::string, TagString>::const_iterator substituter = SubstitutionMap().find(tag);
            if (substituter != SubstitutionMap().end()) {
                m_str += substituter->second(token_elem, tag, m_valid);
            } else {
                Logger().errorStream() << "SubstituteAndAppend::operator(): No substitution scheme defined for tag: " << tag << " from token: " << token;
                m_str += UserString("ERROR");
                m_valid = false;
            }
        }

        const XMLElement&   m_variables;
        std::string&        m_str;
        bool&               m_valid;
    };

    // sticks a sequence of characters onto the end of a std::string
    struct StringAppend {
        StringAppend(std::string& str) :
            m_str(str)
        {}

        void operator()(const char* first, const char* last) const
        { m_str += std::string(first, last); }
        std::string& m_str;
    };
}

// static(s)
const std::string VarText::TEXT_TAG = "text";
const std::string VarText::RAW_TEXT_TAG = "rawtext";

const std::string VarText::PLANET_ID_TAG = "planet";
const std::string VarText::SYSTEM_ID_TAG = "system";
const std::string VarText::SHIP_ID_TAG = "ship";
const std::string VarText::FLEET_ID_TAG = "fleet";
const std::string VarText::BUILDING_ID_TAG = "building";
const std::string VarText::FIELD_ID_TAG = "field";

const std::string VarText::COMBAT_ID_TAG = "combat";

const std::string VarText::EMPIRE_ID_TAG = "empire";
const std::string VarText::DESIGN_ID_TAG = "shipdesign";
const std::string VarText::PREDEFINED_DESIGN_TAG = "predefinedshipdesign";

const std::string VarText::TECH_TAG = "tech";
const std::string VarText::BUILDING_TYPE_TAG = "buildingtype";
const std::string VarText::SPECIAL_TAG = "special";
const std::string VarText::SHIP_HULL_TAG = "shiphull";
const std::string VarText::SHIP_PART_TAG = "shippart";
const std::string VarText::SPECIES_TAG = "species";
const std::string VarText::FIELD_TYPE_TAG = "fieldtype";


VarText::VarText() :
    m_template_string(""),
    m_stringtable_lookup_flag(false),
    m_variables(),
    m_text(),
    m_validated(false)
{}

VarText::VarText(const std::string& template_string, bool stringtable_lookup_template/* = true*/) :
    m_template_string(template_string),
    m_stringtable_lookup_flag(stringtable_lookup_template),
    m_variables(),
    m_text(),
    m_validated(false)
{}

const std::string& VarText::GetText() const {
    if (m_text.empty())
        GenerateVarText();
    return m_text;
}

bool VarText::Validate() const {
    if (m_text.empty())
        GenerateVarText();
    return m_validated;
}

void VarText::SetTemplateString(const std::string& text, bool stringtable_lookup_template/* = true*/) {
    m_text = text;
    m_stringtable_lookup_flag = stringtable_lookup_template;
}

std::vector<std::string> VarText::GetVariableTags() const {
    std::vector<std::string> retval;
    for (XMLElement::const_child_iterator it = m_variables.child_begin(); it != m_variables.child_end(); ++it)
        retval.push_back(it->Tag());
    return retval;
}

void VarText::AddVariable(const std::string& tag, const std::string& data) {
    XMLElement elem(tag);
    elem.SetAttribute("value", data);
    m_variables.AppendChild(elem);
}

void VarText::GenerateVarText() const {
    // generate a string complete with substituted variables and hyperlinks
    // the procedure here is to replace any tokens within %% with variables of
    // the same name in the SitRep XML data
    m_text.clear();
    m_validated = true;
    if (m_template_string.empty())
        return;

    // get string into which to substitute variables
    std::string template_str = m_stringtable_lookup_flag ? UserString(m_template_string) : m_template_string;

    // set up parser
    using namespace boost::spirit::classic;
    rule<> token = *(anychar_p - space_p - END_VAR.c_str());
    rule<> var = START_VAR.c_str() >> token[SubstituteAndAppend(m_variables, m_text, m_validated)] >> END_VAR.c_str();
    rule<> non_var = anychar_p - START_VAR.c_str();

    // parse and substitute variables
    try {
        parse(template_str.c_str(), *(non_var[StringAppend(m_text)] | var));
    } catch (const std::exception&) {
        Logger().errorStream() << "VarText::GenerateVartText caught exception when parsing template string: " << m_template_string;
    }
}
