#pragma once
#include "refcnt.h"
#include "function_utils.h"
#include <utility>

namespace panda {

template <typename Ret, typename... Args>
class function;

template <typename Ret, typename... Args>
class function {
public:
    using Func = iptr<Ifunction<Ret, Args...>>;
    Func func;

public:
    function(){}
    function(std::nullptr_t){}

    template <typename Derr>
    function(const iptr<Derr>& f) : func(f) {}

    template<typename... F,
             typename = decltype(function_details::make_abstract_function<Ret, Args...>(std::declval<F>()...)),
             typename = typename std::enable_if<!std::is_constructible<function, F...>::value>::type>
    function(F&&... f)
        : func(function_details::make_abstract_function<Ret, Args...>(std::forward<F>(f)...))
    {}

    function(Func func) : func(func) {};

    function(const function& oth) = default;
    function(function&& oth) = default;

    function& operator=(const function& oth) = default;
    function& operator=(function&& oth) = default;

    Ret operator ()(Args... args) const {return func->operator ()(std::forward<Args>(args)...);}

    template <typename ORet, typename... OArgs,
              typename = typename std::enable_if<std::is_convertible<function<ORet, OArgs...>, function>::value>::type>
    bool operator ==(const function<ORet, OArgs...>& oth) const {
        return (!func && !oth.func) || (func && oth && func->equals(oth.func.get()));
    }

    template <typename ORet, typename... OArgs,
              typename = typename std::enable_if<std::is_convertible<function<ORet, OArgs...>, function>::value>::type>
    bool operator !=(const function<ORet, OArgs...>& oth) const {return !operator ==(oth);}

    bool operator ==(const Ifunction<Ret, Args...>& oth) const {
        return func && func->equals(&oth);
    }
    bool operator !=(const Ifunction<Ret, Args...>& oth) const {return !operator ==(oth);}

    explicit operator bool() const {
        return func;
    }
};

template <typename Ret, typename... Args>
class function<Ret (Args...)> : public function<Ret, Args...>{
public:
    using function<Ret, Args...>::function;
    using ArgsTuple = std::tuple<Args...>;
    using RetType = Ret;
};

template <class Class, typename Ret, typename... Args>
inline function<Ret( Args...)> make_function(Ret (Class::*meth)(Args...), iptr<Class> thiz = nullptr) {
    return function<Ret(Args...)>(meth, thiz);
}

template <typename Ret, typename... Args>
inline function<Ret (Args...)> make_function(Ret (*f)(Args...)) {
    return function<Ret(Args...)>(f);
}

}