#ifndef TILE_BASE_EXPOSED_VAR_H
#define TILE_BASE_EXPOSED_VAR_H

#pragma once

#include "tile/base/chrono.h"
#include "tile/base/deferred.h"
#include "tile/base/internal/meta.h"
#include "tile/base/optional.h"
#include "tile/base/slice.h"
#include "json/value.h"

#include <unordered_map>

namespace tile {
namespace exposed_var {

template<typename T>
auto
ToJsonValue(const T &t) -> enable_if_t<std::is_same<T, Json::Value>::value, Json::Value>
{
    return t;
}

template<typename T>
auto
ToJsonValue(const T &t) -> enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, Json::Value>
{
    return Json::Value(static_cast<Json::UInt64>(t));
}

template<typename T>
auto
ToJsonValue(const T &t) -> enable_if_t<std::is_integral<T>::value && !std::is_unsigned<T>::value, Json::Value>
{
    return Json::Value(static_cast<Json::Int64>(t));
}

template<typename T>
auto
ToJsonValue(const T &t) -> enable_if_t<std::is_floating_point<T>::value || std::is_same<T, std::string>::value
                                           || std::is_same<T, const char *>::value || std::is_same<T, bool>::value,
                                       Json::Value>
{
    return Json::Value(t);
}

template<typename T>
auto
ToJsonValue(const T &t) -> enable_if_t<!std::is_integral<T>::value && !std::is_floating_point<T>::value
                                           && !std::is_same<T, std::string>::value
                                           && !std::is_same<T, const char *>::value && !std::is_same<T, bool>::value
                                           && std::is_same<std::string, decltype(format_as(std::declval<T>()))>::value,
                                       Json::Value>
{
    return Json::Value(format_as(t));
}

template<class T>
Json::Value
ToJsonValue(const std::atomic<T> &v)
{
    return ToJsonValue(v.load(std::memory_order_relaxed));
}
}// namespace exposed_var

class ExposedVarDynamicTree;

class ExposedVarGroup {
public:
    using Handle = Deferred;

    Handle Add(Slice rel_path, std::function<Json::Value()> value);
    Handle Add(Slice rel_path, ExposedVarDynamicTree *dyanmic_tree);
    static ExposedVarGroup *FindOrCreate(Slice abs_path);
    static std::optional<Json::Value> TryGet(Slice abs_path);

private:
    using Getter = std::function<std::optional<Json::Value>(Slice)>;

    explicit ExposedVarGroup(std::string abs_path);
    const std::string &AbsolutePath() const;

    ExposedVarGroup *FindLowest(Slice rel_path, Slice *left);
    ExposedVarGroup *CreateUpto(Slice rel_path);
    Handle AddDirect(Slice name, Getter getter);
    Json::Value Dump() const;
    static ExposedVarGroup *Root();

private:
    std::string abs_path_;
    mutable std::mutex lock_;

    std::unordered_map<std::string, std::unique_ptr<ExposedVarGroup>> nodes_;
    std::unordered_map<std::string, Getter> leaves_;
};

template<typename T>
class ExposedVar {
public:
    ExposedVar(Slice rel_path) { LinkToParent(rel_path, ExposedVarGroup::FindOrCreate("/")); }

    template<typename U>
    ExposedVar(Slice rel_path, U &&initial_value, ExposedVarGroup *parent = ExposedVarGroup::FindOrCreate("/"))
        : obj_(std::forward<U>(initial_value))
    {
        LinkToParent(rel_path, parent);
    }

    T *operator->() noexcept { return &obj_; }

    T &operator*() noexcept { return obj_; }

private:
    void LinkToParent(Slice rel_path, ExposedVarGroup *parent)
    {
        handle_ = parent->Add(rel_path, [this] { return exposed_var::ToJsonValue(obj_); });
    }

private:
    T obj_{};
    ExposedVarGroup::Handle handle_;
};

template<typename T>
class ExposedVarDynamic {
public:
    ExposedVarDynamic(Slice rel_path,
                      std::function<T()> getter,
                      ExposedVarGroup *parent = ExposedVarGroup::FindOrCreate("/"))
        : getter_(std::move(getter))
    {
        handle_ = parent->Add(rel_path, [this] { return exposed_var::ToJsonValue(getter_()); });
    }

private:
    std::function<T()> getter_;
    ExposedVarGroup::Handle handle_;
};

class ExposedVarDynamicTree {
public:
    ExposedVarDynamicTree(Slice rel_path,
                          std::function<Json::Value()> getter,
                          ExposedVarGroup *parent = ExposedVarGroup::FindOrCreate("/"));

    std::optional<Json::Value> TryGet(Slice rel_path) const;

private:
    std::function<Json::Value()> getter_;
    ExposedVarGroup::Handle handle_;
};

namespace detail {
template<typename T>
struct IdentityTime {
    std::uint64_t operator()(const T &val) const
    {
        return (ReadSteadyClock() - std::chrono::steady_clock::time_point()) / std::chrono::nanoseconds(1);
    }
};
}// namespace detail

template<typename T, typename F = detail::IdentityTime<T>>
class ExposedMetrics {
public:
    explicit ExposedMetrics(Slice rel_path) { LinkToParent(rel_path, ExposedVarGroup::FindOrCreate("/")); }

private:
    Json::Value ToJsonValue(const T &v)
    {
        Json::Value result;
        std::unordered_map<std::string, T> m = {
            {"1s", 1}
        };
        for (auto &&item : m) {}
        return result;
    }

    void LinkToParent(Slice rel_path, ExposedVarGroup *parent)
    {
        handle_ = parent->Add(rel_path, [this] { return ToJsonValue(obj_); });
    }

private:
    T obj_{};
    ExposedVarGroup::Handle handle_;
};

template<typename T>
using ExposedCounter = ExposedVar<T>;
template<typename T>
using ExposedGauge = ExposedVar<T>;
template<typename T>
using ExposedMiner = ExposedVar<T>;
template<typename T>
using ExposedMaxer = ExposedVar<T>;
template<typename T>
using ExposedAverager = ExposedVar<T>;

}// namespace tile

#endif// TILE_BASE_EXPOSED_VAR_H
