#ifndef TILE_BASE_OPTION_KEY_H
#define TILE_BASE_OPTION_KEY_H

#pragma once

#include "tile/base/internal/meta.h"
#include "tile/base/make_unique.h"

#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>

namespace tile {
namespace option {
class Key;

namespace detail {

template<typename To, typename From, typename... Args>
struct is_convertible_multi_impl {
    static constexpr bool value = std::is_convertible<From, To>::value && is_convertible_multi_impl<To, Args...>::value;
};

template<typename To, typename From>
struct is_convertible_multi_impl<To, From> {
    static constexpr bool value = std::is_convertible<From, To>::value;
};

template<typename To, typename... Args>
struct is_convertible_multi {
    static constexpr bool value = is_convertible_multi_impl<To, Args...>::value;
};

}// namespace detail

class Key {
public:
    template<typename T, typename = decltype(std::declval<T>().Get())>
    Key(T &&impl) : impl_(CreateImpl<typename std::decay<T>::type>(std::forward<T>(impl)))
    {}

    Key(std::string name);

    Key(const char *name) : Key(std::string(name)) {}

    std::string Get() const;
    bool Ready() const noexcept;

private:
    class AbstractKey;
    template<typename T>
    class KeyImpl;

    template<typename T, typename U>
    static std::unique_ptr<AbstractKey> CreateImpl(U &&impl);

    std::unique_ptr<AbstractKey> impl_;
};

// Multi-part key
class MultiKey {
public:
    template<typename... Args, typename = enable_if_t<detail::is_convertible_multi_impl<Key, Args...>::value>>
    MultiKey(Args &&...keys);

    MultiKey(MultiKey &&first, Key &&second);
    MultiKey(MultiKey &&first, MultiKey &&second);

    MultiKey(std::string name);
    MultiKey(const char *name);

    const std::vector<Key> &GetKeys() const noexcept;
    bool Ready() const noexcept;
    std::string ToString() const;

private:
    std::vector<Key> keys_;
};

class FixedKey {
public:
    explicit FixedKey(std::string s) : str_(std::move(s)) {}

    std::string Get() const { return str_; }

    bool Ready() const noexcept { return true; }

private:
    std::string str_;
};

class DynamicKey {
public:
    explicit DynamicKey(std::string name) : name_(std::move(name)) {}

    std::string Get() const;
    bool Ready() const noexcept;

private:
    std::string name_;
};

class ReferencingKey {
public:
    explicit ReferencingKey(const std::string &ref) : ref_(&ref) {}

    explicit ReferencingKey(std::string &&ref) = delete;

    std::string Get() const { return *ref_; }

    bool Ready() const noexcept { return true; }

private:
    const std::string *ref_;
};

void SetDynamicKey(const std::string &name, std::string value);

class Key::AbstractKey {
public:
    virtual ~AbstractKey()              = default;
    virtual std::string Get() const     = 0;
    virtual bool Ready() const noexcept = 0;
};

template<typename T>
class Key::KeyImpl : public Key::AbstractKey {
public:
    template<typename U>
    explicit KeyImpl(U &&impl) : impl_(std::forward<U>(impl))
    {}

    std::string Get() const override { return impl_.Get(); }

    bool Ready() const noexcept override { return impl_.Ready(); }

private:
    T impl_;
};

template<typename T, typename U>
std::unique_ptr<Key::AbstractKey>
Key::CreateImpl(U &&impl)
{
    return make_unique<KeyImpl<T>>(std::forward<U>(impl));
}

namespace detail {
template<typename T>
void
AppendKeys(std::vector<Key> &keys, T &&key)
{
    keys.emplace_back(std::forward<T>(key));
}

// append keys
template<typename T, typename... Args>
void
AppendKeys(std::vector<Key> &keys, T &&key, Args &&...args)
{
    keys.emplace_back(std::forward<T>(key));
    AppendKeys(keys, std::forward<Args>(args)...);
}

}// namespace detail

template<typename... Args, typename>
MultiKey::MultiKey(Args &&...keys)
{
    detail::AppendKeys(keys_, std::forward<Args>(keys)...);
}

}// namespace option
}// namespace tile

#endif// TILE_BASE_OPTION_KEY_H
