#pragma once
#include "refcnt.h"
#include "traits.h"
#include <assert.h>

namespace panda {

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

namespace function_details {
    struct AnyFunction {
        virtual ~AnyFunction() {}

        virtual const AnyFunction* get_base() const {
            return this;
        }
    };
}


template <typename Ret, typename... Args>
struct Ifunction : function_details::AnyFunction, virtual AtomicRefcnt {
    virtual ~Ifunction() {}
    virtual Ret operator()(Args...) = 0;
    virtual bool equals(const function_details::AnyFunction* oth) const = 0;
};

namespace function_details {

template <typename Func, typename Ret, bool Comparable, typename... Args>
class abstract_function {};

template <typename Func>
class storage {
public:
    template <typename F>
    explicit storage(F&& f) : func(std::forward<F>(f)) {}

    std::remove_reference_t<Func> func;
};

template <typename Func, typename Ret, bool SELF, typename Self,  typename... Args>
class callable {};

template <typename Func, typename Ret, typename Self, typename... Args>
class callable<Func, Ret, false, Self, Args...> : public storage<Func>
{
public:
    template <typename F>
    explicit callable(F&& f) : storage<Func>(std::forward<F>(f)) {}

    Ret operator()(Self&, Args... args) {
        return this->func(args...);
    }
};


template <typename Func, typename Ret, typename Self, typename... Args>
class callable<Func, Ret, true, Self, Args...> : public storage<Func>
{
public:
    template <typename F>
    explicit callable(F&& f) : storage<Func>(std::forward<F>(f)) {}

    Ret operator()(Self& self, Args... args) {
        return this->func(self, args...);
    }
};

template <typename Func, typename Ret, typename... Args>
class abstract_function<Func, Ret, true, Args...> : public Ifunction<Ret, Args...>, public storage<Func> {
public:
    template <typename F>
    explicit abstract_function(F&& f) : storage<Func>(std::forward<F>(f)) {}

    Ret operator()(Args... args) override {
        static_assert(std::is_convertible<decltype(this->func(args...)), Ret>::value, "return type mismatch");
        return this->func(args...);
    }

    bool equals(const AnyFunction* oth) const override {
        auto foth = dynamic_cast<const storage<Func>*>(oth->get_base());
        if (foth == nullptr) {
            return false;
        }

        return this->func == foth->func;
    }
};

template <typename Func, typename Ret, typename... Args>
class abstract_function<Func, Ret, false, Args...> : public Ifunction<Ret, Args...>
        , public callable<Func, Ret, !has_call_operator<Func, Args...>::value, Ifunction<Ret, Args...>, Args...>
{
public:
    using Derfed = std::remove_reference_t<Func>;
    using Caller = callable<Func, Ret, !has_call_operator<Func, Args...>::value, Ifunction<Ret, Args...>, Args...>;

    template <typename F>
    explicit abstract_function(F&& f) : Caller(std::forward<F>(f)) {}

    Ret operator()(Args... args) override {
        return Caller::operator()(*this, args...);
    }

    bool equals(const AnyFunction* oth) const override {
        return this->get_base() == oth->get_base();
    }

private:
    template <Ret(Derfed::*meth)(Args...)>
    Ret call(Args... args) {
       return (this->template func.*meth)(args...);
    }
};


template <typename FRet, typename... FArgs, typename Ret, typename... Args>
class abstract_function<FRet (*)(FArgs...), Ret, true, Args...> : public Ifunction<Ret, Args...>, public storage<FRet (*)(FArgs...)> {
public:
    using Func = FRet (*)(FArgs...);
    explicit abstract_function(const Func& f) : storage<Func>(f) {}

    Ret operator()(Args... args) override {
        return this->func(args...);
    }

    bool equals(const AnyFunction* oth) const override {
        auto foth = dynamic_cast<const storage<Func>*>(oth->get_base());
        if (foth == nullptr) return false;

        return this->func == foth->func;
    }
};

template <typename From, typename Ret, typename... Args>
struct function_caster : public Ifunction<Ret, Args...> {
    From src;
    using Check = std::is_base_of<AnyFunction, decltype(*src)>;

    function_caster(From src) : src(src) {}

    Ret operator()(Args... args) override {
        return src->operator()(args...);
    }

    bool equals(const function_details::AnyFunction* oth) const override {
        return src->equals(oth);
    }

    const AnyFunction* get_base() const override {
        return src->get_base();
    }


};

template <typename... Tail>
struct is_panda_function_t {
    static constexpr bool value = false;
};

template <typename... Params>
struct is_panda_function_t<function<Params...>> {
    static constexpr bool value = true;
};

template <typename Ret, typename... Args>
auto make_abstract_function(Ret (*f)(Args...)) -> iptr<abstract_function<Ret (*)(Args...), Ret, true, Args...>> {
    if (!f) return nullptr;
    return new abstract_function<Ret (*)(Args...), Ret, true, Args...>(f);
}

template <typename Ret, typename... Args>
auto tmp_abstract_function(Ret (*f)(Args...)) -> abstract_function<Ret (*)(Args...), Ret, true, Args...> {
    assert(f);
    return abstract_function<Ret (*)(Args...), Ret, true, Args...>(f);
}

template <typename Ret, typename... Args, typename Functor,
          bool IsComp = is_comparable<std::remove_reference_t<Functor>>::value,
          typename DeFunctor = std::remove_reference_t<Functor>,
          typename = std::enable_if_t<has_call_operator<Functor, Ifunction<Ret, Args...>&, Args...>::value ||
                                      has_call_operator<Functor, Args...>::value>,
          typename = std::enable_if_t<!std::is_same<Functor, Ret(&)(Args...)>::value>,
          typename = std::enable_if_t<!is_panda_function_t<DeFunctor>::value>>
iptr<abstract_function<DeFunctor, Ret, IsComp, Args...>> make_abstract_function(Functor&& f) {
    if (!bool_or(f, true)) return nullptr;
    return new abstract_function<DeFunctor, Ret, IsComp, Args...>(std::forward<Functor>(f));
}

template <typename Ret, typename... Args, typename Functor,
          bool IsComp = is_comparable<std::remove_reference_t<Functor>>::value,
          typename DeFunctor = std::remove_reference_t<Functor>,
          typename = std::enable_if_t<has_call_operator<Functor, Ifunction<Ret, Args...>&, Args...>::value ||
                                      has_call_operator<Functor, Args...>::value>,
          typename = std::enable_if_t<!std::is_same<Functor, Ret(&)(Args...)>::value>,
          typename = std::enable_if_t<!is_panda_function_t<DeFunctor>::value>>
abstract_function<DeFunctor, Ret, IsComp, Args...> tmp_abstract_function (Functor&& f) {
    assert(bool_or(f, true));
    return abstract_function<DeFunctor, Ret, IsComp, Args...>(std::forward<Functor>(f));
}

template <typename Ret, typename... Args, typename ORet, typename... OArgs,
          typename = std::enable_if_t<has_call_operator<function<ORet, OArgs...>, Args...>::value>>
auto make_abstract_function(const function<ORet, OArgs...>& func) -> iptr<function_caster<decltype(func.func), Ret, Args...>> {
    if (!func) return nullptr;
    return new function_caster<decltype(func.func), Ret, Args...>(func.func);
}


template <class Class, typename Ret, typename... Args>
struct method : public Ifunction<Ret, Args...>{
    using Method = Ret (Class::*)(Args...);
    using ifunction = Ifunction<Ret, Args...>;

    method(Method method, iptr<Class> thiz = nullptr) : thiz(thiz), meth(method) {}
    iptr<method> bind(iptr<Class> thiz) {
        this->thiz = thiz;
        return iptr<method>(this);
    }

    Ret operator()(Args... args) override {
        return (thiz.get()->*meth)(std::forward<Args>(args)...);
    }

    bool equals(const AnyFunction* oth) const override {
        auto moth = dynamic_cast<const method<Class, Ret, Args...>*>(oth->get_base());
        if (moth == nullptr) return false;

        return operator ==(*moth);
    }

    bool operator==(const method& oth) const {
        return thiz == oth.thiz && meth == oth.meth;
    }

    bool operator !=(const method& oth) const {
        return !operator ==(oth);
    }

    explicit operator bool() const {
        return thiz && meth;
    }

private:
    iptr<Class> thiz;
    Method meth;
};

template <class Class, typename Ret, typename... Args>
inline iptr<method<Class, Ret, Args...>> make_method(Ret (Class::*meth)(Args...), iptr<Class> thiz = nullptr) {
    if (!meth) return nullptr;
    return new method<Class, Ret, Args...>(meth, thiz);
}

template <typename Ret, typename... Args, class Class>
inline iptr<method<Class, Ret, Args...>> make_abstract_function(Ret (Class::*meth)(Args...), iptr<Class> thiz = nullptr) {
    if (!meth) return nullptr;
    return new method<Class, Ret, Args...>(meth, thiz);
}

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

}