// Copyright Vladimir Prus 2004.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_PROGRAM_OPTIONS_SOURCE
# define BOOST_PROGRAM_OPTIONS_SOURCE
#endif
#include <boost/program_options/config.hpp>
#include <boost/program_options/value_semantic.hpp>
#include <boost/program_options/detail/convert.hpp>
#include <boost/program_options/detail/cmdline.hpp>
#include <set>

#include <cctype>

namespace boost { namespace program_options {

    using namespace std;


#ifndef BOOST_NO_STD_WSTRING
    namespace
    {
        std::string convert_value(const std::wstring& s)
        {
            try {
                return to_local_8_bit(s);
            }
            catch(const std::exception&) {
                return "<unrepresentable unicode string>";
            }
        }
    }
#endif

    void 
    value_semantic_codecvt_helper<char>::
    parse(boost::any& value_store, 
          const std::vector<std::string>& new_tokens,
          bool utf8) const
    {
        if (utf8) {
#ifndef BOOST_NO_STD_WSTRING
            // Need to convert to local encoding.
            std::vector<string> local_tokens;
            for (unsigned i = 0; i < new_tokens.size(); ++i) {
                std::wstring w = from_utf8(new_tokens[i]);
                local_tokens.push_back(to_local_8_bit(w));
            }
            xparse(value_store, local_tokens);
#else
            boost::throw_exception(
                std::runtime_error("UTF-8 conversion not supported."));
#endif
        } else {
            // Already in local encoding, pass unmodified
            xparse(value_store, new_tokens);
        }        
    }

#ifndef BOOST_NO_STD_WSTRING
    void 
    value_semantic_codecvt_helper<wchar_t>::
    parse(boost::any& value_store, 
          const std::vector<std::string>& new_tokens,
          bool utf8) const
    {
        std::vector<wstring> tokens;
        if (utf8) {
            // Convert from utf8
            for (unsigned i = 0; i < new_tokens.size(); ++i) {
                tokens.push_back(from_utf8(new_tokens[i]));
            }
               
        } else {
            // Convert from local encoding
            for (unsigned i = 0; i < new_tokens.size(); ++i) {
                tokens.push_back(from_local_8_bit(new_tokens[i]));
            }
        }      

        xparse(value_store, tokens);  
    }
#endif

    BOOST_PROGRAM_OPTIONS_DECL std::string arg("arg");

    std::string
    untyped_value::name() const
    {
        return arg;
    }
    
    unsigned 
    untyped_value::min_tokens() const
    {
        if (m_zero_tokens)
            return 0;
        else
            return 1;
    }

    unsigned 
    untyped_value::max_tokens() const
    {
        if (m_zero_tokens)
            return 0;
        else
            return 1;
    }


    void 
    untyped_value::xparse(boost::any& value_store,
                          const std::vector<std::string>& new_tokens) const
    {
        if (!value_store.empty()) 
            boost::throw_exception(
                multiple_occurrences());
        if (new_tokens.size() > 1)
            boost::throw_exception(multiple_values());
        value_store = new_tokens.empty() ? std::string("") : new_tokens.front();
    }

    BOOST_PROGRAM_OPTIONS_DECL typed_value<bool>*
    bool_switch()
    {
        return bool_switch(0);
    }

    BOOST_PROGRAM_OPTIONS_DECL typed_value<bool>*
    bool_switch(bool* v)
    {
        typed_value<bool>* r = new typed_value<bool>(v);
        r->default_value(0);
        r->zero_tokens();

        return r;
    }

    /* Validates bool value.
        Any of "1", "true", "yes", "on" will be converted to "1".<br>
        Any of "0", "false", "no", "off" will be converted to "0".<br>
        Case is ignored. The 'xs' vector can either be empty, in which
        case the value is 'true', or can contain explicit value.
    */
    BOOST_PROGRAM_OPTIONS_DECL void validate(any& v, const vector<string>& xs,
                       bool*, int)
    {
        check_first_occurrence(v);
        string s(get_single_string(xs, true));

        for (size_t i = 0; i < s.size(); ++i)
            s[i] = char(tolower(s[i]));

        if (s.empty() || s == "on" || s == "yes" || s == "1" || s == "true")
            v = any(true);
        else if (s == "off" || s == "no" || s == "0" || s == "false")
            v = any(false);
        else
            boost::throw_exception(invalid_bool_value(s));
    }

    // This is blatant copy-paste. However, templating this will cause a problem,
    // since wstring can't be constructed/compared with char*. We'd need to
    // create auxiliary 'widen' routine to convert from char* into 
    // needed string type, and that's more work.
#if !defined(BOOST_NO_STD_WSTRING)
    BOOST_PROGRAM_OPTIONS_DECL 
    void validate(any& v, const vector<wstring>& xs, bool*, int)
    {
        check_first_occurrence(v);
        wstring s(get_single_string(xs, true));

        for (size_t i = 0; i < s.size(); ++i)
            s[i] = wchar_t(tolower(s[i]));

        if (s.empty() || s == L"on" || s == L"yes" || s == L"1" || s == L"true")
            v = any(true);
        else if (s == L"off" || s == L"no" || s == L"0" || s == L"false")
            v = any(false);
        else
            boost::throw_exception(invalid_bool_value(convert_value(s)));
    }
#endif
    BOOST_PROGRAM_OPTIONS_DECL 
    void validate(any& v, const vector<string>& xs, std::string*, int)
    {
        check_first_occurrence(v);
        v = any(get_single_string(xs));
    }

#if !defined(BOOST_NO_STD_WSTRING)
    BOOST_PROGRAM_OPTIONS_DECL 
    void validate(any& v, const vector<wstring>& xs, std::string*, int)
    {
        check_first_occurrence(v);
        v = any(get_single_string(xs));
    }
#endif

    namespace validators {

        BOOST_PROGRAM_OPTIONS_DECL 
        void check_first_occurrence(const boost::any& value)
        {
            if (!value.empty())
                boost::throw_exception(
                    multiple_occurrences());
        }
    }


    invalid_option_value::
    invalid_option_value(const std::string& bad_value)
    : validation_error(validation_error::invalid_option_value)
    {
        set_substitute("value", bad_value);
    }

#ifndef BOOST_NO_STD_WSTRING
    invalid_option_value::
    invalid_option_value(const std::wstring& bad_value)
    : validation_error(validation_error::invalid_option_value)
    {
        set_substitute("value", convert_value(bad_value));
    }
#endif



    invalid_bool_value::
    invalid_bool_value(const std::string& bad_value)
    : validation_error(validation_error::invalid_bool_value)
    {
        set_substitute("value", bad_value);
    }






    error_with_option_name::error_with_option_name( const std::string& template_,
                                                  const std::string& option_name,
                                                  const std::string& original_token,
                                                  int option_style) : 
                                        error(template_),
                                        m_option_style(option_style),
                                        m_error_template(template_)
    {
        //                     parameter            |     placeholder               |   value
        //                     ---------            |     -----------               |   -----
        set_substitute_default("canonical_option",  "option '%canonical_option%'",  "option");
        set_substitute_default("value",             "argument ('%value%')",         "argument");
        set_substitute_default("prefix",            "%prefix%",                     "");
        m_substitutions["option"] = option_name;
        m_substitutions["original_token"] = original_token;
    }


    const char* error_with_option_name::what() const BOOST_NOEXCEPT_OR_NOTHROW
    {
        // will substitute tokens each time what is run()
        substitute_placeholders(m_error_template);

        return m_message.c_str();
    }

    void error_with_option_name::replace_token(const string& from, const string& to) const
    {
        for (;;)
        {
            std::size_t pos = m_message.find(from.c_str(), 0, from.length());
            // not found: all replaced
            if (pos == std::string::npos)
                return;
            m_message.replace(pos, from.length(), to);
        }
    }

    string error_with_option_name::get_canonical_option_prefix() const
    {
        switch (m_option_style)
        {
        case command_line_style::allow_dash_for_short:
            return "-";
        case command_line_style::allow_slash_for_short:
            return "/";
        case command_line_style::allow_long_disguise:
            return "-";
        case command_line_style::allow_long:
            return "--";
        case 0:
            return "";
        }
        throw std::logic_error("error_with_option_name::m_option_style can only be "
                               "one of [0, allow_dash_for_short, allow_slash_for_short, "
                               "allow_long_disguise or allow_long]");
    }


    string error_with_option_name::get_canonical_option_name() const
    {
        if (!m_substitutions.find("option")->second.length())
            return m_substitutions.find("original_token")->second;

        string original_token   = strip_prefixes(m_substitutions.find("original_token")->second);
        string option_name      = strip_prefixes(m_substitutions.find("option")->second);

        //  For long options, use option name
        if (m_option_style == command_line_style::allow_long        || 
             m_option_style == command_line_style::allow_long_disguise)
            return get_canonical_option_prefix() + option_name;

        //  For short options use first letter of original_token
        if (m_option_style && original_token.length())
            return get_canonical_option_prefix() + original_token[0];

        // no prefix
        return option_name;
    }


    void error_with_option_name::substitute_placeholders(const string& error_template) const
    {
        m_message = error_template;
        std::map<std::string, std::string> substitutions(m_substitutions);
        substitutions["canonical_option"]   = get_canonical_option_name();
        substitutions["prefix"]             = get_canonical_option_prefix();


        //
        //  replace placeholder with defaults if values are missing 
        // 
        for (map<string, string_pair>::const_iterator iter = m_substitution_defaults.begin();
              iter != m_substitution_defaults.end(); ++iter)
        {
            // missing parameter: use default
            if (substitutions.count(iter->first) == 0 ||
                substitutions[iter->first].length() == 0)
                replace_token(iter->second.first, iter->second.second);
        }


        //
        //  replace placeholder with values
        //  placeholder are denoted by surrounding '%'
        // 
        for (map<string, string>::iterator iter = substitutions.begin();
              iter != substitutions.end(); ++iter)
            replace_token('%' + iter->first + '%', iter->second);
    }


    void ambiguous_option::substitute_placeholders(const string& original_error_template) const
    {
        // For short forms, all alternatives must be identical, by
        //      definition, to the specified option, so we don't need to
        //      display alternatives
        if (m_option_style == command_line_style::allow_dash_for_short || 
            m_option_style == command_line_style::allow_slash_for_short)
        {
            error_with_option_name::substitute_placeholders(original_error_template);
            return;
        }


        string error_template  = original_error_template;
        // remove duplicates using std::set
        std::set<std::string>   alternatives_set (m_alternatives.begin(), m_alternatives.end());
        std::vector<std::string> alternatives_vec (alternatives_set.begin(), alternatives_set.end());

        error_template += " and matches ";
        // Being very cautious: should be > 1 alternative!
        if (alternatives_vec.size() > 1)
        {
            for (unsigned i = 0; i < alternatives_vec.size() - 1; ++i)
                error_template += "'%prefix%" + alternatives_vec[i] + "', ";
            error_template += "and ";
        }

        // there is a programming error if multiple options have the same name...
        if (m_alternatives.size() > 1 && alternatives_vec.size() == 1)
            error_template += "different versions of ";

        error_template += "'%prefix%" + alternatives_vec.back() + "'";


        // use inherited logic
        error_with_option_name::substitute_placeholders(error_template);
    }






    string 
    validation_error::get_template(kind_t kind)
    {
        // Initially, store the message in 'const char*' variable,
        // to avoid conversion to std::string in all cases.
        const char* msg;
        switch(kind)
        {
        case invalid_bool_value:
            msg = "the argument ('%value%') for option '%canonical_option%' is invalid. Valid choices are 'on|off', 'yes|no', '1|0' and 'true|false'";
            break;
        case invalid_option_value:
            msg = "the argument ('%value%') for option '%canonical_option%' is invalid";
            break;
        case multiple_values_not_allowed:
            msg = "option '%canonical_option%' only takes a single argument";
            break;
        case at_least_one_value_required:
            msg = "option '%canonical_option%' requires at least one argument";
            break;
        // currently unused
        case invalid_option:
            msg = "option '%canonical_option%' is not valid";
            break;
        default:
            msg = "unknown error";
        }
        return msg;
    }

}}
