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

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

namespace tile {
JSONConfiguration::JSONConfiguration() : object_() {}

JSONConfiguration::~JSONConfiguration() {}

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

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

    Configuration::ScopedLock _(*this);
    object_.clear();
    Json::Reader reader;
    return reader.parse(istr, object_, true);
}

void
JSONConfiguration::SetInt(const Slice &key, int value)
{
    Configuration::ScopedLock _(*this);
    SetValue(key, value);
}

void
JSONConfiguration::SetBool(const Slice &key, bool value)
{
    Configuration::ScopedLock _(*this);
    SetValue(key, value);
}

void
JSONConfiguration::SetDouble(const Slice &key, double value)
{
    Configuration::ScopedLock _(*this);
    SetValue(key, value);
}

void
JSONConfiguration::SetString(const Slice &key, const std::string &value)
{
    Configuration::ScopedLock _(*this);
    SetValue(key, value);
}

void
JSONConfiguration::RemoveRaw(const Slice &key)
{
    Json::Value *root;
    Slice last_part;
    if (!FindStart(key, &last_part, &root)) { return; }
    root->removeMember(last_part);
}

std::string
JSONConfiguration::Dump() const
{
    Configuration::ScopedLock _(*this);
    return object_.toStyledString();
}

bool
JSONConfiguration::GetRaw(const Slice &key, std::string *value) const
{
    auto keys = Split(key, '.');
    auto root = object_;
    for (std::size_t i = 0; i < keys.size(); ++i) {
        const auto &cur_key = keys[i];
        if (cur_key.empty()) {
            TILE_LOG_ERROR("Invalid key: {}", key);
            return false;
        }
        if (!root.isMember(cur_key)) { return false; }
        root = root[cur_key];
    }

    if (root.isConvertibleTo(Json::stringValue)) {
        *value = root.asString();
    } else {
        *value = root.toStyledString();
    }
    return true;
}

bool
JSONConfiguration::SetRaw(const Slice &key, const Slice &value)
{
    return SetValue(key, value.ToString());
}

Configuration::Keys
JSONConfiguration::Enumerate(const Slice &range) const
{
    Configuration::Keys key_set;
    std::string prefix = range;
    if (!prefix.empty()) { prefix += "."; }

    auto keys = Split(range, '.');
    auto root = object_;
    for (std::size_t i = 0; i < keys.size(); ++i) {
        const auto &cur_key = keys[i];
        if (cur_key.empty()) {
            TILE_LOG_ERROR("Invalid range: {}", range);
            return key_set;
        }
        if (!root.isMember(cur_key)) { return key_set; }
        root = root[cur_key];
    }

    for (const auto &key : root.getMemberNames()) { key_set.push_back(prefix + key); }
    return key_set;
}

bool
JSONConfiguration::FindStart(const Slice &key, Slice *last_part, Json::Value **parent_obj)
{
    auto keys = Split(key, '.');
    if (keys.empty()) { return false; }

    Json::Value *root = &object_;
    for (std::size_t i = 0; i < keys.size() - 1; ++i) {
        const auto &cur_key = keys[i];
        if (cur_key.empty()) {
            TILE_LOG_ERROR("Invalid key: {}", key);
            return false;
        }

        if (!root->isMember(cur_key)) {
            (*root)[cur_key] = Json::Value(Json::objectValue);
        } else if (!(*root)[cur_key].isObject()) {
            TILE_LOG_ERROR("only leaf nodes can be set: key: {}, cur_key(idx={}): {}", key, i, cur_key);
            return false;
        }
        root = &(*root)[cur_key];
    }

    *last_part  = keys.back();
    *parent_obj = root;
    return true;
}

}// namespace tile
