#include <xrpl/basics/BasicConfig.h>
#include <xrpl/basics/StringUtilities.h>

#include <boost/regex/v5/regbase.hpp>
#include <boost/regex/v5/regex.hpp>
#include <boost/regex/v5/regex_fwd.hpp>
#include <boost/regex/v5/regex_match.hpp>

#include <ostream>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

namespace xrpl {

Section::Section(std::string const& name) : name_(name)
{
}

void
Section::set(std::string const& key, std::string const& value)
{
    lookup_.insert_or_assign(key, value);
}

void
Section::append(std::vector<std::string> const& lines)
{
    // <key> '=' <value>
    static boost::regex const re1(
        "^"                        // start of line
        "(?:\\s*)"                 // whitespace (optional)
        "([a-zA-Z][_a-zA-Z0-9]*)"  // <key>
        "(?:\\s*)"                 // whitespace (optional)
        "(?:=)"                    // '='
        "(?:\\s*)"                 // whitespace (optional)
        "(.*\\S+)"                 // <value>
        "(?:\\s*)"                 // whitespace (optional)
        ,
        boost::regex_constants::optimize);

    lines_.reserve(lines_.size() + lines.size());
    for (auto line : lines)
    {
        auto remove_comment = [](std::string& val) -> bool {
            bool removed_trailing = false;
            auto comment = val.find('#');
            while (comment != std::string::npos)
            {
                if (comment == 0)
                {
                    // entire value is a comment. In most cases, this
                    // would have already been handled by the file reader
                    val = "";
                    break;
                }
                if (val.at(comment - 1) == '\\')
                {
                    // we have an escaped comment char. Erase the escape char
                    // and keep looking
                    val.erase(comment - 1, 1);
                }
                else
                {
                    // this must be a real comment. Extract the value
                    // as a substring and stop looking.
                    val = trim_whitespace(val.substr(0, comment));
                    removed_trailing = true;
                    break;
                }

                comment = val.find('#', comment);
            }
            return removed_trailing;
        };

        if (remove_comment(line) && !line.empty())
            had_trailing_comments_ = true;

        if (line.empty())
            continue;

        boost::smatch match;
        if (boost::regex_match(line, match, re1))
        {
            set(match[1], match[2]);
        }
        else
        {
            values_.push_back(line);
        }

        lines_.push_back(std::move(line));
    }
}

bool
Section::exists(std::string const& name) const
{
    return lookup_.contains(name);
}

std::ostream&
operator<<(std::ostream& os, Section const& section)
{
    for (auto const& [k, v] : section.lookup_)
        os << k << "=" << v << "\n";
    return os;
}

//------------------------------------------------------------------------------

bool
BasicConfig::exists(std::string const& name) const
{
    return map_.contains(name);
}

Section&
BasicConfig::section(std::string const& name)
{
    return map_.emplace(name, name).first->second;
}

Section const&
BasicConfig::section(std::string const& name) const
{
    static Section none("");
    auto const iter = map_.find(name);
    if (iter == map_.end())
        return none;
    return iter->second;
}

void
BasicConfig::overwrite(std::string const& section, std::string const& key, std::string const& value)
{
    auto const result =
        map_.emplace(std::piecewise_construct, std::make_tuple(section), std::make_tuple(section));
    result.first->second.set(key, value);
}

void
BasicConfig::deprecatedClearSection(std::string const& section)
{
    auto i = map_.find(section);
    if (i != map_.end())
        i->second = Section(section);
}

void
BasicConfig::legacy(std::string const& section, std::string value)
{
    map_.emplace(section, section).first->second.legacy(std::move(value));
}

std::string
BasicConfig::legacy(std::string const& sectionName) const
{
    return section(sectionName).legacy();
}

void
BasicConfig::build(IniFileSections const& ifs)
{
    for (auto const& entry : ifs)
    {
        auto const result = map_.emplace(
            std::piecewise_construct, std::make_tuple(entry.first), std::make_tuple(entry.first));
        result.first->second.append(entry.second);
    }
}

std::ostream&
operator<<(std::ostream& ss, BasicConfig const& c)
{
    for (auto const& [k, v] : c.map_)
        ss << "[" << k << "]\n" << v;
    return ss;
}

}  // namespace xrpl
