#ifndef TILE_BASE_OPTION_OPTION_IMPL_H
#define TILE_BASE_OPTION_OPTION_IMPL_H

#pragma once

#include "tile/base/logging.h"
#include "tile/base/option/dynamically_changed.h"
#include "tile/base/option/key.h"
#include "tile/base/option/option_service.h"
#include "tile/base/optional.h"

namespace tile {
namespace option {
namespace detail {
template<typename T>
struct IdentityParser {
    static std::optional<T> TryParse(const T &value) { return value; }
};

template<typename T>
struct remove_optional {
    using type = T;
};

template<typename T>
struct remove_optional<std::optional<T>> {
    using type = T;
};

template<typename T>
using remove_optional_t = typename remove_optional<T>::type;
}// namespace detail

template<typename T, typename Parser>
class OptionImpl {
public:
    using parsed_t     = typename decltype(Parser::TryParse(std::declval<T>()))::value_type;
    using value_or_ref = decltype(std::declval<detail::DynamicallyChanged<parsed_t>>().Get());

    OptionImpl(const std::string &provider,
               option::MultiKey name,
               T default_value,
               std::function<bool(T)> validator,
               bool fixed)
        : provider_(provider),
          name_(std::move(name)),
          value_(ParseDefaultValue(std::move(default_value)))
    {
        option_id_ = option::OptionService::Instance()->RegisterOptionWatcher<T>(
            provider, &name_, fixed, [=](std::optional<T> value) { return OnChanged(std::move(value)); });
    }

    ~OptionImpl() { option::OptionService::Instance()->DeregisterOptionWatcher(option_id_); }

    OptionImpl(OptionImpl &&)            = default;
    OptionImpl &operator=(OptionImpl &&) = default;

    value_or_ref Get() const noexcept { return value_.Get(); }

    operator value_or_ref() const noexcept { return Get(); }

private:
    bool OnChanged(std::optional<T> value)
    {
        if (!value) {
            TILE_LOG_WARNING_EVERY_SECOND("Key [{}] is not recognized by provider [{}].", name_.ToString(), provider_);
            return false;
        }

        if (validator_ && !validator_(*value)) {
            TILE_LOG_WARNING_EVERY_SECOND("New value [{}] of option [{}] from provider [{}] didn't pass "
                                          "validatioin. Keep use the old value.",
                                          *value, name_.ToString(), provider_);
        }

        auto parsed = Parser{}.TryParse(std::move(*value));
        if (!parsed) {
            TILE_LOG_WARNING_EVERY_SECOND("Use-supplied parser failed. Keep using the old value.");
            return false;
        }

        value_.Emplace(std::move(*parsed));
        return true;
    }

    static parsed_t ParseDefaultValue(T &&default_value)
    {
        auto parsed = Parser{}.TryParse(std::move(default_value));
        TILE_CHECK(parsed, "Value parser failed to parse the default value.");
        return *parsed;
    }

private:
    std::uint64_t option_id_;
    std::string provider_;
    MultiKey name_;
    detail::DynamicallyChanged<parsed_t> value_;
    std::function<bool(T)> validator_;
};

}// namespace option
}// namespace tile

#endif// TILE_BASE_OPTION_OPTION_IMPL_H
