#include "tile/base/config/ini_file_configuration.h"

#include "tile/base/string.h"
#include "tile/base/thread/scoped_lock.h"

namespace tile {

IniFileConfiguration::IniFileConfiguration() {}

// IniFileConfig::IniFileConfig(std::istream &istr) { load(istr); }
// IniFileConfig::IniFileConfig(const std::string &path) { load(path); }
IniFileConfiguration::~IniFileConfiguration() {}

bool
IniFileConfiguration::load(std::istream &istr)
{
    if (!istr.good()) { return false; }

    Configuration::ScopedLock lock(*this);
    map_.clear();
    section_key_.clear();
    while (!istr.eof()) { ParseLine(istr); }
    return true;
}

bool
IniFileConfiguration::load(const std::string &path)
{
    std::ifstream istr(path);
    return load(istr);
}

bool
IniFileConfiguration::GetRaw(const Slice &key, std::string *value) const
{
    auto iter = map_.find(key.ToString());
    if (iter != map_.end()) {
        *value = iter->second;
        return true;
    } else {
        return false;
    }
}

bool
IniFileConfiguration::SetRaw(const Slice &key, const Slice &value)
{
    map_[key] = value;
    return true;
}

void
IniFileConfiguration::RemoveRaw(const Slice &key)
{
    std::string prefix = key;
    if (!prefix.empty()) { prefix.push_back('.'); }
    IStringMap::iterator it = map_.begin();
    IStringMap::iterator cur;
    while (it != map_.end()) {
        if (EqualsIgnoreCase(cur->first, key) || EqualsIgnoreCase(cur->first, prefix)) {
            it = map_.erase(cur);
        } else {
            ++it;
        }
    }
}

Configuration::Keys
IniFileConfiguration::Enumerate(const Slice &key) const
{
    Configuration::Keys range;
    std::set<Slice> keys;
    std::string prefix = key.ToString();
    if (prefix.empty()) { prefix.push_back('.'); }

    std::string::size_type psize = prefix.size();
    for (const auto &p : map_) {
        auto &key = p.first;
        if (EqualsIgnoreCase(key, prefix, psize)) {
            std::string::size_type end = key.find('.', psize);
            std::string subkey;
            if (end == std::string::npos) {
                subkey = key.substr(psize);
            } else {
                subkey = key.substr(psize, end - psize);
            }

            if (keys.find(subkey) == keys.end()) {
                keys.insert(subkey);
                range.push_back(subkey);
            }
        }
    }
    return range;
}

void
IniFileConfiguration::ParseLine(std::istream &istr)
{
    static const int eof = std::char_traits<char>::eof();
    auto ReadLine        = [&](std::string *line) {
        line->clear();
        while (true) {
            int c = istr.get();
            if (c == eof || c == '\n') {
                return c != eof;
            } else {
                *line += (char) c;
            }
        }
    };

    std::string raw_line;
    while (ReadLine(&raw_line)) {
        if (raw_line.empty()) { continue; }
        Slice line = TrimLeft(raw_line);
        if (line.empty() || line[0] == ';' || line[0] == '#') {
            // skip empty line
            // skip comment line ; or #
            continue;
        }

        // parse section
        if (line[0] == '[') {
            auto pos = line.find(']');
            if (pos == Slice::npos) {
                section_key_ = Trim(line.substr(1));
            } else {
                section_key_ = Trim(line.substr(1, pos - 1));
            }
        } else {
            auto strs            = Split(line, "=", true, 2);
            std::string full_key = section_key_;
            if (!full_key.empty()) { full_key.push_back('.'); }
            full_key.append(Trim(strs[0]));
            if (strs.size() > 1) {
                map_[full_key] = Trim(strs[1]);
            } else {
                map_[full_key] = "";
            }
        }
    }
}

bool
IniFileConfiguration::ICompare::operator()(const std::string &s1, const std::string &s2) const
{
    auto len = std::min(s1.size(), s2.size());
    return strncmp(s1.c_str(), s2.c_str(), len) < 0;
}

}// namespace tile
