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

namespace tile {

LayeredConfiguration::LayeredConfiguration() {}

LayeredConfiguration::~LayeredConfiguration() {}

void
LayeredConfiguration::Add(Configuration::Ptr cfg)
{
    Add(cfg, highest(), false);
}

void
LayeredConfiguration::Add(Configuration::Ptr cfg, int priority)
{
    Add(cfg, priority, false);
}

void
LayeredConfiguration::Add(Configuration::Ptr cfg, const std::string &label)
{
    Add(cfg, label, highest(), false);
}

void
LayeredConfiguration::Add(Configuration::Ptr cfg, const std::string &label, int priority)
{
    Add(cfg, label, priority, false);
}

void
LayeredConfiguration::Add(Configuration::Ptr cfg, const std::string &label, bool writeable)
{
    Add(cfg, label, highest(), writeable);
}

void
LayeredConfiguration::Add(Configuration::Ptr cfg, const std::string &label, int priority, bool writeable)
{
    Configuration::ScopedLock lock(*this);
    ConfigItem item;
    item.cfg       = cfg;
    item.priority  = priority;
    item.writeable = writeable;
    item.label     = label;
    auto it        = configs_.begin();
    while (it != configs_.end() && it->priority < priority) { ++it; }
    configs_.insert(it, item);
}

void
LayeredConfiguration::Add(Configuration::Ptr cfg, int priority, bool writeable)
{
    Add(cfg, std::string(), priority, writeable);
}

void
LayeredConfiguration::AddWriteable(Configuration::Ptr cfg, int priority)
{
    Add(cfg, priority, true);
}

Configuration::Ptr
LayeredConfiguration::Find(const Slice &label) const
{
    Configuration::ScopedLock lock(*this);

    for (const auto &conf : configs_) {
        if (conf.label == label) { return conf.cfg; }
    }
    return 0;
}

void
LayeredConfiguration::RemoveConfig(Configuration::Ptr cfg)
{
    Configuration::ScopedLock lock(*this);
    for (auto it = configs_.begin(); it != configs_.end();) {
        if (it->cfg == cfg) {
            it = configs_.erase(it);
        } else {
            ++it;
        }
    }
}

bool
LayeredConfiguration::GetRaw(const Slice &key, std::string *value) const
{
    for (const auto &conf : configs_) {
        if (conf.cfg->GetRaw(key, value)) { return true; }
    }
    return false;
}

bool
LayeredConfiguration::SetRaw(const Slice &key, const Slice &value)
{
    for (const auto &conf : configs_) {
        if (conf.writeable && conf.cfg->SetRaw(key, value)) { return true; }
    }
    return false;
}

Configuration::Keys
LayeredConfiguration::Enumerate(const Slice &key) const
{
    Configuration::Keys keys;
    std::set<Slice> key_set;
    for (const auto &conf : configs_) {
        auto conf_keys = conf.cfg->Enumerate(key);
        for (const auto &k : conf_keys) {
            if (key_set.insert(k).second) { keys.push_back(k); }
        }
    }
    return keys;
}

void
LayeredConfiguration::RemoveRaw(const std::string &key)
{
    for (auto &conf : configs_) {
        if (conf.writeable) { conf.cfg->RemoveRaw(key); }
    }
}

int
LayeredConfiguration::lowest() const
{
    if (configs_.empty()) {
        return 0;
    } else {
        return configs_.front().priority - 1;
    }
}

int
LayeredConfiguration::highest() const
{
    if (configs_.empty()) {
        return 0;
    } else {
        return configs_.back().priority + 1;
    }
}

}// namespace tile
