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

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

namespace tile {
bool
Configuration::Has(const Slice &key) const
{
    UniqueLock<tile::Mutex> lock(mutex_);
    std::string value;
    return GetRaw(key, &value);
}

void
Configuration::Remove(const Slice &key)
{
    UniqueLock<tile::Mutex> lock(mutex_);
    if (events_enabled_) { OnRemoving(key); }
    RemoveRaw(key);
    if (events_enabled_) { OnRemoved(key); }
}

void
Configuration::EnableEvents(bool enable)
{
    events_enabled_ = enable;
}

bool
Configuration::EventsEnabled() const
{
    return events_enabled_;
}

Configuration::Keys
Configuration::keys(const Slice &root) const
{
    UniqueLock<tile::Mutex> lock(mutex_);
    return Enumerate(root);
}

#define TILE_DEFINE_CONFIG_GETTER(type, name)                                                                          \
    std::optional<type> Configuration::Get##name(const Slice &key) const                                               \
    {                                                                                                                  \
        std::string value;                                                                                             \
        if (GetRaw(key, &value)) {                                                                                     \
            auto opt = TryParseTraits<type>::TryParse(value);                                                          \
            if (opt.has_value()) {                                                                                     \
                return *opt;                                                                                           \
            } else {                                                                                                   \
                return std::nullopt;                                                                                   \
            }                                                                                                          \
        } else {                                                                                                       \
            return std::nullopt;                                                                                       \
        }                                                                                                              \
    }                                                                                                                  \
    type Configuration::Get##name(const Slice &key, type default_value) const                                          \
    {                                                                                                                  \
        auto opt = Get##name(key);                                                                                     \
        if (opt.has_value()) {                                                                                         \
            return *opt;                                                                                               \
        } else {                                                                                                       \
            return default_value;                                                                                      \
        }                                                                                                              \
    }

#define TILE_DEFINE_CONFIG_SETTER(type, name)                                                                          \
    void Configuration::Set##name(const Slice &key, type value) { SetRawWithEvent(key, Format("{}", value)); }

std::optional<std::string>
Configuration::GetString(const Slice &key) const
{
    std::string value;
    UniqueLock<tile::Mutex> lock(mutex_);
    if (GetRaw(key, &value)) {
        return value;
    } else {
        return std::nullopt;
    }
}

std::string
Configuration::GetString(const Slice &key, std::string default_value) const
{
    auto opt = GetString(key);
    return opt.has_value() ? *opt : default_value;
}

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

void
Configuration::SetBool(const Slice &key, bool value)
{
    SetRawWithEvent(key, value ? "true" : "false");
}

TILE_DEFINE_CONFIG_GETTER(int, Int)
TILE_DEFINE_CONFIG_GETTER(unsigned int, UInt)
TILE_DEFINE_CONFIG_GETTER(int16_t, Int16)
TILE_DEFINE_CONFIG_GETTER(int32_t, Int32)
TILE_DEFINE_CONFIG_GETTER(int64_t, Int64)
TILE_DEFINE_CONFIG_GETTER(uint16_t, UInt16)
TILE_DEFINE_CONFIG_GETTER(uint32_t, UInt32)
TILE_DEFINE_CONFIG_GETTER(uint64_t, UInt64)
TILE_DEFINE_CONFIG_GETTER(double, Double)
TILE_DEFINE_CONFIG_GETTER(bool, Bool)

TILE_DEFINE_CONFIG_SETTER(int, Int)
TILE_DEFINE_CONFIG_SETTER(unsigned int, UInt)
TILE_DEFINE_CONFIG_SETTER(int16_t, Int16)
TILE_DEFINE_CONFIG_SETTER(int32_t, Int32)
TILE_DEFINE_CONFIG_SETTER(int64_t, Int64)
TILE_DEFINE_CONFIG_SETTER(uint16_t, UInt16)
TILE_DEFINE_CONFIG_SETTER(uint32_t, UInt32)
TILE_DEFINE_CONFIG_SETTER(uint64_t, UInt64)
TILE_DEFINE_CONFIG_SETTER(double, Double)

void
Configuration::SetRawWithEvent(const Slice &key, const std::string &value)
{
    if (events_enabled_) { OnChanging(key, value); }

    {
        UniqueLock<tile::Mutex> lock(mutex_);
        SetRaw(key, value);
    }

    if (events_enabled_) { OnChanged(key, value); }
}

Configuration::~Configuration() {}

}// namespace tile
