// Copyright (C) 2011  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_AnY_FUNCTION_Hh_
#define DLIB_AnY_FUNCTION_Hh_

#include "../assert.h"
#include "../functional.h"
#include "any.h"
#include "any_function_abstract.h"

namespace dlib
{

// ----------------------------------------------------------------------------------------

    template <
        class Storage, 
        class F
    > 
    class any_function_basic;

    template <
        class Storage,
        class R, 
        class... Args
    > 
    class any_function_basic<Storage, R(Args...)> 
    {
    private:
        template<class F>
        using is_valid = std::enable_if_t<!std::is_same<std::decay_t<F>, any_function_basic>::value &&
                                          dlib::is_invocable_r<R, F, Args...>::value,
                                          bool>;

        template<typename Func>
        static auto make_invoker()
        {
            return [](void* self, Args... args) -> R {
                return dlib::invoke(*reinterpret_cast<std::add_pointer_t<Func>>(self),
                                    std::forward<Args>(args)...);
            };
        }

        Storage str;
        R (*func)(void*, Args...) = nullptr;

    public:

        using result_type = R;
        
        constexpr any_function_basic(std::nullptr_t) noexcept {}
        constexpr any_function_basic()                                              = default;
        constexpr any_function_basic(const any_function_basic& other)               = default;
        constexpr any_function_basic& operator=(const any_function_basic& other)    = default;

        constexpr any_function_basic(any_function_basic&& other) 
        :   str{std::move(other.str)}, 
            func{std::exchange(other.func, nullptr)} 
        {
        }

        constexpr any_function_basic& operator=(any_function_basic&& other)
        {
            if (this != &other)
            {
                str     = std::move(other.str);
                func    = std::exchange(other.func, nullptr);
            }

            return *this;
        }

        template<class F, is_valid<F> = true>
        any_function_basic(
            F&& f
        ) : str{std::forward<F>(f)},
            func{make_invoker<F&&>()}
        {
        }

        template<class F, is_valid<F> = true>
        any_function_basic(
            F* f
        ) : str{f},
            func{make_invoker<F*>()}
        {
        }

        R operator()(Args... args) const {
            return func(const_cast<void*>(str.get_ptr()), std::forward<Args>(args)...);
        }

        void clear()                            { str.clear(); }
        void swap (any_function_basic& item)    { std::swap(*this, item); }
        bool is_empty()          const noexcept { return str.is_empty() || func == nullptr; }
        bool is_set()            const noexcept { return !is_empty(); }
        explicit operator bool() const noexcept { return is_set(); }

        template <typename T>     bool contains() const { return str.template contains<T>();}
        template <typename T>       T& cast_to()        { return str.template cast_to<T>(); }
        template <typename T> const T& cast_to() const  { return str.template cast_to<T>(); }
        template <typename T>       T& get()            { return str.template get<T>(); }
    };

// ----------------------------------------------------------------------------------------

    template <class T, class Storage, class F> 
    T& any_cast(any_function_basic<Storage, F>& a) { return a.template cast_to<T>(); }

    template <class T, class Storage, class F> 
    const T& any_cast(const any_function_basic<Storage, F>& a) { return a.template cast_to<T>(); }

// ----------------------------------------------------------------------------------------

    template <class F> 
    using any_function = any_function_basic<te::storage_sbo<16>, F>;

    template <class F> 
    using any_function_view = any_function_basic<te::storage_view, F>;

// ----------------------------------------------------------------------------------------

}

#endif // DLIB_AnY_FUNCTION_Hh_