#include <iostream>
#include <map>
#include <string>
#include <boost/xpressive/xpressive.hpp>

bool FindVariableString(
    const std::string &str,
    const std::string::size_type pos,
    std::string::size_type &beginVarStringPos,
    std::string::size_type &endVarStringPos,
    std::string::size_type &beginVarNamePos,
    std::string::size_type &endVarNamePos)
{
    const char *TestString = "%$#";
    const char PercentSign = '%';
    const char LeftParenthesis = '(';
    const char LeftSquareBracket = '[';
    const char LeftCurlyBracket = '{';
    const char RightParenthesis = ')';
    const char RightSquareBracket = ']';
    const char RightCurlyBracket = '}';
    beginVarStringPos = std::string::npos;
    endVarStringPos = std::string::npos;
    beginVarNamePos = std::string::npos;
    endVarNamePos = std::string::npos;
    if (str.empty())
    {
        return false;
    }
    beginVarStringPos = str.find_first_of(TestString, pos);
    if (std::string::npos == beginVarStringPos)
    {
        return false;
    }
    if (beginVarStringPos >= str.length() - 1)
    {
        return false;
    }
    char ch = str[beginVarStringPos];
    char ch1 = str[beginVarStringPos + 1];
    if (
       PercentSign == ch
       && LeftParenthesis != ch1 && LeftSquareBracket != ch1
       && LeftCurlyBracket != ch1
       )
    {
        beginVarNamePos = beginVarStringPos + 1;
        endVarStringPos = str.find(PercentSign, beginVarNamePos);
        if (std::string::npos == endVarStringPos)
        {
            return false;
        }
    }
    else if (
       LeftParenthesis != ch1 && LeftSquareBracket != ch1
       && LeftCurlyBracket != ch1
       )
    {
        return false;
    }
    else
    {
        beginVarNamePos = beginVarStringPos + 2;
        char closeChar = 0;
        if (LeftParenthesis == ch1)
        {
            closeChar = RightParenthesis;
        }
        else if (LeftSquareBracket == ch1)
        {
            closeChar = RightSquareBracket;
        }
        else if (LeftCurlyBracket == ch1)
        {
            closeChar = RightCurlyBracket;
        }
        endVarStringPos = str.find(closeChar, beginVarNamePos);
        if (std::string::npos == endVarStringPos)
        {
            return false;
        }
    }
    endVarNamePos = endVarStringPos - 1;
    return true;
}

bool StringContainsVariableStrings(const std::string &str)
{
    std::string::size_type beginVarStringPos = 0;
    std::string::size_type endVarStringPos = 0;
    std::string::size_type beginVarNamePos = 0;
    std::string::size_type endVarNamePos = 0;
    bool ret = FindVariableString(str, 0, beginVarStringPos, endVarStringPos, beginVarNamePos, endVarNamePos);
    return ret;
}

std::string GetVariableValue(
    const std::string &varName,
    const std::map<std::string, std::string> &env,
    bool &fromEnvMap, bool &valueContainsVariableStrings)
{
    typedef std::map<std::string, std::string> my_map;
    fromEnvMap = false;
    valueContainsVariableStrings = false;
    std::string ret;
    my_map::const_iterator itFind = env.find(varName);
    if (itFind != env.end())
    {
        ret = (*itFind).second;
        if (!ret.empty())
        {
            fromEnvMap = true;
            valueContainsVariableStrings = StringContainsVariableStrings(ret);
        }
    }
    if (ret.empty())
    {
        ret = ::getenv(varName.c_str());
    }
    return ret;
}

::boost::xpressive::sregex GetRegex()
{
    namespace xpr = ::boost::xpressive;
    xpr::sregex ret =
        "%" >> (xpr::s1 = +(xpr::_w | xpr::_s | "(" | ")")) >> '%'
        | "%(" >> (xpr::s1 = +(xpr::_w | xpr::_s)) >> ')'
        | "%[" >> (xpr::s1 = +(xpr::_w | xpr::_s | "(" | ")")) >> ']'
        | "%{" >> (xpr::s1 = +(xpr::_w | xpr::_s | "(" | ")")) >> '}'
        | "$(" >> (xpr::s1 = +(xpr::_w | xpr::_s)) >> ')'
        | "$[" >> (xpr::s1 = +(xpr::_w | xpr::_s | "(" | ")")) >> ']'
        | "${" >> (xpr::s1 = +(xpr::_w | xpr::_s | "(" | ")")) >> '}'
        | "#(" >> (xpr::s1 = +(xpr::_w | xpr::_s)) >> ')'
        | "#[" >> (xpr::s1 = +(xpr::_w | xpr::_s | "(" | ")")) >> ']'
        | "#{" >> (xpr::s1 = +(xpr::_w | xpr::_s | "(" | ")")) >> '}';
    return ret;
}

struct string_formatter
{
    typedef std::map<std::string, std::string> env_map;
    env_map env;
    mutable bool valueContainsVariables;
    string_formatter()
    {
        valueContainsVariables = false;
    }
    template<typename Out>
    Out operator()(::boost::xpressive::smatch const& what, Out out) const
    {
        bool fromEnvMap;
        bool valueContainsVariableStrings;
        std::string value = GetVariableValue(
            what.str(1), env, fromEnvMap, valueContainsVariableStrings);
        if (fromEnvMap && !value.empty() && valueContainsVariableStrings)
        {
            valueContainsVariables = true;
        }
        if (value.empty())
        {
            value = what[0];
        }
        if (!value.empty())
        {
            out = std::copy(value.begin(), value.end(), out);
        }
        return out;
    }
};

std::string ExpandVarsR(
    const std::string &original,
    const std::map<std::string, std::string> &env)
{
    std::string ret = original;
    if (original.empty())
    {
        return ret;
    }
    string_formatter fmt;
    fmt.env = env;
    fmt.valueContainsVariables = false;
    ::boost::xpressive::sregex envar = GetRegex();
    ret = ::boost::xpressive::regex_replace(original, envar, fmt);
    if (fmt.valueContainsVariables)
    {
        std::string newValue;
        std::string prevValue = ret;
        do
        {
            fmt.valueContainsVariables = false;
            newValue = ::boost::xpressive::regex_replace(prevValue, envar, fmt);
            if (0 == prevValue.compare(newValue))
            {
                break;
            }
            prevValue.erase();
            prevValue = newValue;
        }
        while (fmt.valueContainsVariables);
        if (0 != ret.compare(newValue))
        {
            ret = newValue;
        }
    }
    return ret;
}

std::string GetDateFormatStringExpandVars(const std::string& langCode)
{
    if (
       0 == langCode.compare(0, 2, "en")
       || 0 == langCode.compare(0, 2, "EN")
       )
    {
        return std::string("${month}/${day}/${year}");
    }
    else if (
       0 == langCode.compare(0, 2, "fr")
       || 0 == langCode.compare(0, 2, "FR")
       )
    {
        return std::string("${day}/${month}/${year}");
    }
    else if (
       0 == langCode.compare(0, 2, "ja")
       || 0 == langCode.compare(0, 2, "JA")
       || 0 == langCode.compare(0, 2, "jp")
       || 0 == langCode.compare(0, 2, "JP")
       )
    {
        return std::string("${year}/${day}/${month}");
    }
    return std::string("${month}/${day}/${year}");
}

std::string GetDateStringExpandVarsR(
    const std::string &langCode, int month, int day, int year)
{
    std::string fmt = GetDateFormatStringExpandVars(langCode);
    std::map<std::string,std::string> env{
        {"month", std::to_string(month)},
        {"day", std::to_string(day)},
        {"year", std::to_string(year)}
    };
    std::string ret = ExpandVarsR(fmt, env);
    return ret;
}

int main()
{
    std::cout << GetDateStringExpandVarsR("en", 11, 7, 2018) << "\n";
    return 0;
}
