#include "tile/base/option/option_service.h"

#include "tile/base/data.h"
#include "tile/base/internal/logging.h"
#include "tile/base/internal/time_keeper.h"
#include "tile/base/internal/utility.h"
#include "tile/base/option/option_provider.h"

#include "gflags/gflags.h"

DEFINE_int32(tile_option_update_interval,
             10,
             "Interval between update values of `Option<T>`, in seconds, "
             "Do NOT set it too low, or you'll overload "
             "the server or your program (or both)");

namespace tile {
namespace option {

OptionService::OptionService() {}

OptionService::~OptionService() {}

OptionService *
OptionService::Instance()
{
    static NeverDestroyed<OptionService> os;
    return os.Get();
}

bool
OptionService::ResolveAll()
{
    bool all_success = true;
    std::lock_guard<std::mutex> _(lock_);
    for (auto &&item : options_) {
        const auto &provider_name = item.first;
        auto &options             = item.second;

        auto p = options.provider = option_passive_provider_registry.Get(provider_name);

        std::vector<const option::MultiKey *> keys;
        for (auto &&e : options.watchers) {
            if (!e->is_fixed && e->name_ref->Ready()) { keys.push_back(e->name_ref); }
        }

        if (!options.provider->GetAll(keys)) {
            TILE_LOG_ERROR("Initial try of reading options from [{}] failed. Failing "
                           "back to defaults.",
                           provider_name);
            all_success = false;
            continue;
        }

        for (auto &&e : options.watchers) {
            if (!internal::Exchange(e->initial_resolution_done, true) && e->name_ref->Ready()) {
                auto status = e->read_cb(p);
                if (!status.ok()) {
                    TILE_LOG_ERROR("Initial try of reading [{}] from [{}] failed, "
                                   "failing back to the defaults: {}",
                                   e->name_ref->ToString(), provider_name, status.ToString());
                    all_success = false;
                }
                e->initial_resolution_done = true;
            }
        }
    }

    resolve_all_done_ = true;
    // update interval
    TILE_CHECK_EQ(timer_id_, 0, "OptionService has been started.");
    timer_id_ = internal::TimeKeeper::Instance()->AddTimer(
        {}, FLAGS_tile_option_update_interval * std::chrono::seconds(1), [this](internal::TimerID) { UpdateOptions(); },
        true);
    return all_success;
}

void
OptionService::DeregisterOptionWatcher(std::uint64_t id)
{
    std::lock_guard<std::mutex> _(lock_);
    for (auto &&item : options_) {
        auto &&k = item.first;
        auto &&v = item.second;
        for (auto iter = v.watchers.begin(); iter != v.watchers.end(); ++iter) {
            if ((*iter)->id == id) {
                v.watchers.erase(iter);
                return;
            }
        }
    }
    TILE_CHECK(0, "Option #{} is not recognized.", id);
}

void
OptionService::UpdateOptions()
{
    std::lock_guard<std::mutex> _(lock_);
    for (auto &&item : options_) {
        const auto &provider_name = item.first;
        auto &options             = item.second;

        if (!options.provider) {
            // run before `ResolveAll()`
            continue;
        }

        std::vector<const MultiKey *> keys;
        for (auto &&e : options.watchers) {
            if (!e->is_fixed && e->name_ref->Ready()) { keys.push_back(e->name_ref); }
        }

        if (!options.provider->GetAll(keys)) {
            TILE_LOG_WARNING_EVERY_SECOND(
                "Failed to read options from [{}]. Keep using current values.", provider_name);
            continue;
        }

        for (auto &&e : options.watchers) {
            if (e->is_fixed || !e->name_ref->Ready()) { continue; }

            auto status = e->read_cb(options.provider);
            if (!status.ok()) {
                TILE_LOG_WARNING_EVERY_SECOND("Failed to read option [{}] from [{}], keep using current "
                                              "value: {}",
                                              e->name_ref->ToString(), provider_name, status.ToString());
            }
        }
    }
}

Json::Value
OptionService::Dump() const
{
    Json::Value data;
    std::lock_guard<std::mutex> _(lock_);
    for (auto &&item : options_) {
        auto &&provider_name = item.first;
        auto &&options       = item.second;

        auto &&opts          = data[provider_name];

        for (auto &&e : options.watchers) {
            if (!e->name_ref->Ready()) { continue; }

            auto current_path = &opts;
            for (auto &&key : e->name_ref->GetKeys()) { current_path = &(*current_path)[key.Get()]; }
            *current_path = e->current_value_as_json;
        }
    }

    return data;
}

void
OptionService::Shutdown()
{
    TILE_CHECK_NE(timer_id_, 0, "OptionService has not been started.");
    auto timer_id = internal::Exchange(timer_id_, internal::TimerID(0));
    internal::TimeKeeper::Instance()->KillTimer(timer_id);
}

#define TILE_DETAIL_OPTION_DEFINE_CREATE_WATCHER_FOR(Type, TypeName)                                                   \
    OptionService::ReadCallback OptionService::CreateReader(                                                           \
        const std::string &provider, const MultiKey *name, std::function<bool(std::optional<Type>)> cb,                \
        Json::Value *current_value)                                                                                    \
    {                                                                                                                  \
        auto last = std::optional<std::optional<Type>>();                                                              \
        return [cb, provider, name, current_value, last](OptionPassiveProvider *p) mutable {                           \
            std::optional<Type> value;                                                                                 \
            auto status = p->Get##TypeName(*name, &value);                                                             \
            if (status.ok()) {                                                                                         \
                if (last && *last == value) { return Status(0); /* No change. */ }                                     \
                last = value;                                                                                          \
                if (cb(value)) {                                                                                       \
                    if (value) {                                                                                       \
                        *current_value = ToJson(*value);                                                               \
                    } else {                                                                                           \
                        *current_value = Json::Value();                                                                \
                    }                                                                                                  \
                    return Status(0);                                                                                  \
                }                                                                                                      \
                return Status(-1);                                                                                     \
            }                                                                                                          \
            return status;                                                                                             \
        };                                                                                                             \
    }

TILE_DETAIL_OPTION_DEFINE_CREATE_WATCHER_FOR(bool, Bool)
TILE_DETAIL_OPTION_DEFINE_CREATE_WATCHER_FOR(std::int8_t, Int8)
TILE_DETAIL_OPTION_DEFINE_CREATE_WATCHER_FOR(std::uint8_t, UInt8)
TILE_DETAIL_OPTION_DEFINE_CREATE_WATCHER_FOR(std::int16_t, Int16)
TILE_DETAIL_OPTION_DEFINE_CREATE_WATCHER_FOR(std::uint16_t, UInt16)
TILE_DETAIL_OPTION_DEFINE_CREATE_WATCHER_FOR(std::int32_t, Int32)
TILE_DETAIL_OPTION_DEFINE_CREATE_WATCHER_FOR(std::uint32_t, UInt32)
TILE_DETAIL_OPTION_DEFINE_CREATE_WATCHER_FOR(std::int64_t, Int64)
TILE_DETAIL_OPTION_DEFINE_CREATE_WATCHER_FOR(std::uint64_t, UInt64)
TILE_DETAIL_OPTION_DEFINE_CREATE_WATCHER_FOR(float, Float)
TILE_DETAIL_OPTION_DEFINE_CREATE_WATCHER_FOR(double, Double)
TILE_DETAIL_OPTION_DEFINE_CREATE_WATCHER_FOR(std::string, String)

#undef TILE_DETAIL_OPTION_DEFINE_CREATE_WATCHER_FOR

}// namespace option
}// namespace tile
