#ifndef TILE_BASE_OPTION_OPTION_SERVICE_H
#define TILE_BASE_OPTION_OPTION_SERVICE_H

#pragma once
#include "tile/base/option/key.h"
#include "tile/base/optional.h"
#include "tile/base/status.h"

#include "json/json.h"

#include <functional>
#include <mutex>
#include <unordered_map>

namespace tile {
class OptionPassiveProvider;
}

namespace tile {
namespace option {
class OptionService {
public:
    OptionService();
    ~OptionService();

    static OptionService *Instance();

    bool ResolveAll();

    template<typename T>
    std::uint64_t RegisterOptionWatcher(const std::string &provider,
                                        const MultiKey *name_ref,
                                        bool is_fixed,
                                        std::function<bool(std::optional<T>)> cb)
    {
        auto option_provider_id = ++option_id_;
        {
            std::lock_guard<std::mutex> _(lock_);
            auto &&opt = options_[provider];

            for (auto &&watcher : opt.watchers) {
                TILE_LOG_FATAL_IF(watcher->name_ref->ToString() == name_ref->ToString(),
                                  "Option [{}] from [{}] has already been registered.", name_ref->ToString(), provider);
            }

            auto watcher      = make_unique<WatchedOption>();
            watcher->id       = option_provider_id;
            watcher->name_ref = name_ref;
            watcher->is_fixed = is_fixed;
            watcher->read_cb  = CreateReader(provider, name_ref, std::move(cb), &watcher->current_value_as_json);

            options_[provider].watchers.push_back(std::move(watcher));
        }

        if (resolve_all_done_) { ResolveAll(); }

        return option_provider_id;
    }

    void DeregisterOptionWatcher(std::uint64_t id);

    void UpdateOptions();

    Json::Value Dump() const;

    void Shutdown();

private:
    using ReadCallback = std::function<Status(OptionPassiveProvider *)>;

    struct WatchedOption {
        bool initial_resolution_done{false};
        std::uint64_t id;
        const MultiKey *name_ref;
        bool is_fixed;
        ReadCallback read_cb;
        Json::Value current_value_as_json;
    };

    struct Options {
        OptionPassiveProvider *provider = nullptr;
        std::vector<std::unique_ptr<WatchedOption>> watchers;
    };

#define TILE_DETAIL_OPTION_DECLARE_CREATE_WATCHER_FOR(Type)                                                            \
    ReadCallback CreateReader(const std::string &provider, const MultiKey *name,                                       \
                              std::function<bool(std::optional<Type>)> cb, Json::Value *current_value);

    TILE_DETAIL_OPTION_DECLARE_CREATE_WATCHER_FOR(bool)
    TILE_DETAIL_OPTION_DECLARE_CREATE_WATCHER_FOR(std::int8_t)
    TILE_DETAIL_OPTION_DECLARE_CREATE_WATCHER_FOR(std::uint8_t)
    TILE_DETAIL_OPTION_DECLARE_CREATE_WATCHER_FOR(std::int16_t)
    TILE_DETAIL_OPTION_DECLARE_CREATE_WATCHER_FOR(std::uint16_t)
    TILE_DETAIL_OPTION_DECLARE_CREATE_WATCHER_FOR(std::int32_t)
    TILE_DETAIL_OPTION_DECLARE_CREATE_WATCHER_FOR(std::uint32_t)
    TILE_DETAIL_OPTION_DECLARE_CREATE_WATCHER_FOR(std::int64_t)
    TILE_DETAIL_OPTION_DECLARE_CREATE_WATCHER_FOR(std::uint64_t)
    TILE_DETAIL_OPTION_DECLARE_CREATE_WATCHER_FOR(float)
    TILE_DETAIL_OPTION_DECLARE_CREATE_WATCHER_FOR(double)
    TILE_DETAIL_OPTION_DECLARE_CREATE_WATCHER_FOR(std::string)

#undef TILE_DETAIL_OPTION_DECLARE_CREATE_WATCHER_FOR

private:
    std::uint64_t timer_id_{};
    std::atomic<bool> resolve_all_done_{false};
    std::atomic<std::uint64_t> option_id_{};
    mutable std::mutex lock_;
    std::unordered_map<std::string, Options> options_;
};
}// namespace option
}// namespace tile

#endif// TILE_BASE_OPTION_OPTION_SERVICE_H
