#ifndef TILE_BASE_FUTURE_EXECUTOR_H
#define TILE_BASE_FUTURE_EXECUTOR_H

#pragma once

#include <atomic>
#include <functional>
#include <memory>
#include <utility>

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

namespace tile {
namespace future {
class Executor {
public:
    constexpr Executor() = default;

    // Copy & move
    Executor(const Executor &other) : impl_(other.impl_->Clone()) {}

    Executor &operator=(const Executor &e)
    {
        impl_ = e.impl_->Clone();
        return *this;
    }

    Executor(Executor &&other)       = default;
    Executor &operator=(Executor &&) = default;

    // Base Executor, Has Execute function
    template<typename T,
             typename = enable_if_t<!std::is_same<Executor, decay_t<T>>::value>,
             typename = decltype(std::declval<T &&>().Execute(std::declval<std::function<void()>>()))>
    Executor(T &&executor)
    {
        impl_ = EraseExecutorType(std::forward<T>(executor));
    }

    template<typename T>
    void Execute(T &&job)
    {
        job();
        // impl_->Execute(std::forward<T>(job));
    }

private:
    class ConcreteExecutor {
    public:
        virtual ~ConcreteExecutor()                       = default;
        virtual void Execute(std::function<void()> job)   = 0;
        virtual std::unique_ptr<ConcreteExecutor> Clone() = 0;
    };

    template<typename T>
    class ConcreteExecutorImpl : public ConcreteExecutor {
    public:
        template<typename U, typename = decltype(std::declval<U>().Execute(std::declval<std::function<void()>>()))>
        explicit ConcreteExecutorImpl(U &&e) : impl_(std::forward<U>(e))
        {}

        void Execute(std::function<void()> job) override { impl_.Execute(std::move(job)); }

        std::unique_ptr<ConcreteExecutor> Clone() override { return make_unique<ConcreteExecutorImpl>(impl_); }

    private:
        T impl_;
    };

    template<typename T>
    std::unique_ptr<ConcreteExecutor> EraseExecutorType(T &&executor)
    {
        return make_unique<ConcreteExecutorImpl<typename std::decay<T>::type>>(std::forward<T>(executor));
    }

    std::unique_ptr<ConcreteExecutor> impl_;
};

class InlineExecutor {
public:
    void Execute(std::function<void()> job) { job(); }
};

namespace detail {
inline std::shared_ptr<Executor> &
DefaultExecutorPtr()
{
    static std::shared_ptr<Executor> default_executor = std::make_shared<Executor>(InlineExecutor());
    return default_executor;
}
}// namespace detail

inline Executor
GetDefaultExecutor()
{
    return *std::atomic_load_explicit(&detail::DefaultExecutorPtr(), std::memory_order_acquire);
}

inline Executor
SetDefaultExecutor(Executor exec)
{
    auto ptr = std::make_shared<Executor>(std::move(exec));
    return *std::atomic_exchange_explicit(&detail::DefaultExecutorPtr(), ptr, std::memory_order_release);
}

}// namespace future
}// namespace tile

#endif// TILE_BASE_FUTURE_EXECUTOR_H
