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

#include "tile/base/logging.h"
#include "tile/base/string.h"
#include "toml.hpp"

namespace tile {
class TomlConfiguration::Impl {
public:
    Impl() {}

    ~Impl() = default;

    bool load(std::istream &istr)
    {
        try {
            value_ = toml::parse(istr);
            return true;
        } catch (...) {
            return false;
        }
    }

    bool load(const std::string &path)
    {
        try {
            value_ = toml::parse(path);
            return true;
        } catch (...) {
            return false;
        }
    }

    toml::value *operator->() { return &value_; }

    toml::value &operator*() { return value_; }

private:
    toml::value value_;
};

TomlConfiguration::TomlConfiguration() : impl_(new Impl) {}

TomlConfiguration::~TomlConfiguration() {}

bool
TomlConfiguration::load(std::istream &istr)
{
    return impl_->load(istr);
}

bool
TomlConfiguration::load(const std::string &path)
{
    return impl_->load(path);
}

void
TomlConfiguration::SetInt(const Slice &key, int value)
{
    Configuration::SetInt(key, value);
}

void
TomlConfiguration::SetBool(const Slice &key, bool value)
{
    Configuration::SetBool(key, value);
}

void
TomlConfiguration::SetDouble(const Slice &key, double value)
{
    Configuration::SetDouble(key, value);
}

void
TomlConfiguration::SetString(const Slice &key, const std::string &value)
{
    Configuration::SetString(key, value);
}

std::string
TomlConfiguration::Dump() const
{
    try {
        return toml::format(**impl_);
    } catch (const std::exception &e) {
        TILE_LOG_WARNING_EVERY_SECOND("{}", e.what());
        return "";
    }
}

bool
TomlConfiguration::GetRaw(const Slice &key, std::string *value) const
{
    if (key.empty()) { return false; }
    auto keys = Split(key, '.');

    try {
        auto cur = **impl_;
        for (const auto &k : keys) {
            auto next = toml::find(cur, k);
            cur       = next;
        }
        std::stringstream ss;
        if (cur.is_string()) {
            ss << cur.as_string();
        } else if (cur.is_integer()) {
            ss << cur.as_integer();
        } else if (cur.is_boolean()) {
            ss << cur.as_boolean();
        } else if (cur.is_floating()) {
            ss << cur.as_floating();
        } else if (cur.is_empty()) {
            ss << "";
        } else {
            ss << toml::format(cur);
        }
        *value = ss.str();
        return true;
    } catch (const std::exception &e) {
        TILE_LOG_WARNING_EVERY_SECOND("{}", e.what());
        return false;
    }
}

bool
TomlConfiguration::SetRaw(const Slice &key, const Slice &value)
{
    if (key.empty()) { return false; }
    auto keys = Split(key, '.');
    try {
        auto &root = **impl_;
        for (size_t i = 0; i < keys.size() - 1; ++i) {
            auto &next = toml::find(root, keys[i]);
            root       = next;
        }
        root[keys.back()] = value.ToString();
        return true;
    } catch (const std::exception &e) {
        TILE_LOG_WARNING_EVERY_SECOND("{}", e.what());
        return false;
    }
}

void
TomlConfiguration::RemoveRaw(const Slice &key)
{
    if (key.empty()) { return; }
    auto keys = Split(key, '.');
    while (!keys.empty() && keys.back() == "") { keys.pop_back(); }
    try {
        if (keys.empty()) {
            auto &root = **impl_;
            root       = toml::table();
            return;
        }

        auto root = **impl_;
        for (size_t i = 0; i + 1 < keys.size(); ++i) {
            if (!root.is_table() || !root.contains(keys[i])) { return; }
            root = root.at(keys[i]);
        }

        if (!root.contains(keys.back())) { return; }
        auto last_root = root.at(key.back());

        // remove the last key
        toml::value new_node = toml::table();
        for (const auto &p : last_root.as_table()) {
            if (p.first != keys.back()) { new_node[p.first] = p.second; }
        }
        last_root = new_node;
    } catch (...) {}
}

Configuration::Keys
TomlConfiguration::Enumerate(const Slice &range) const
{
    Configuration::Keys keys;
    auto split_keys    = Split(range, '.');
    auto last_key      = split_keys.empty() ? "" : split_keys.back();
    std::string prefix = range;
    if (!prefix.empty() && prefix.back() != '.') { prefix += "."; }

    auto root = **impl_;
    try {
        // if (!root.is_empty()) {
        for (int i = 0; i < split_keys.size(); ++i) {
            auto next = toml::find(root, split_keys[i]);
            root      = next;
        }
        // }

        // if (root.is_empty()) { return keys; }

        // for (const auto &k : keys_) {
        //     auto next = toml::find(root, k);
        //     root      = next;
        // }

        auto visitor = [&](const toml::value &node) {
            if (node.is_table()) {
                for (const auto &p : node.as_table()) {
                    /*if (EndsWith(p.first, prefix)) */ {
                        keys.push_back(prefix + p.first);
                    }
                }
            }
        };
        toml::visit(visitor, root);
        // TILE_LOG_INFO("{}", toml::format(root));
    } catch (const std::exception &e) {
        TILE_LOG_WARNING_EVERY_SECOND("{}\n{}\n", e.what(), Dump());
    }
    return keys;
}

}// namespace tile
