#pragma once
#include "typemap.h"
#include <tuple>
#include <utility>
#include <panda/cast.h>
#include <panda/function.h>
#ifdef USE_ITHREADS
    #include <mutex>
#endif

namespace xs { namespace func {

using namespace panda;

extern Sv::payload_marker_t out_marker;
extern Sv::payload_marker_t in_marker;

template <class T, class F>
struct ConverterIn {
    using type = T;
    F f;
    ConverterIn (const F& f) : f(f) {}
    ConverterIn (F&& f)      : f(std::move(f)) {}
    decltype(std::declval<F>()(Sv())) in (const Sv& sv) { return f(sv); }
};

template <class T>
struct ConverterIn<T, std::nullptr_t> {
    using type = T;
    ConverterIn (std::nullptr_t) {}
    decltype(xs::in<T>(Sv())) in (const Sv& sv) { return xs::in<T>(sv); }
};

template <>
struct ConverterIn<void, std::nullptr_t> {
    using type = void;
    ConverterIn (std::nullptr_t) {}
    void in (const Sv&) {}
};

template <class T, class F>
struct ConverterOut {
    using type = T;
    F f;
    ConverterOut (const F& f) : f(f) {}
    ConverterOut (F&& f)      : f(std::move(f)) {}
    Sv out (const T& val) { return f(val); }
};

template <class T>
struct ConverterOut<T, std::nullptr_t> {
    using type = T;
    ConverterOut (std::nullptr_t) {}
    Sv out (const T& val) { return xs::out<T>(val); }
};

template <>
struct ConverterOut<void, std::nullptr_t> {
    using type = void;
    ConverterOut (std::nullptr_t) {}
    Sv out () { return {}; }
};

#ifdef USE_ITHREADS
    struct SubPad : AtomicRefcnt {
        SubPad (const Sub& sub);

        const Sub& get_sub    () const;
        void       add_sub    (const Sub& sub);
        void       remove_sub ();

        static SubPad* get (Sub);

    private:
        mutable std::mutex mutex;
        std::vector<std::pair<PerlInterpreter*, Sub>> pad;
    };

    template <typename Ret, typename...Args>
    struct SubHolder : panda::Ifunction<Ret, Args...> {
        SubHolder (const Sub& sub) : pad(SubPad::get(sub)) {}

        const Sub& sub () const { return pad->get_sub(); }

        bool equals (const panda::function_details::AnyFunction* oth) const override {
            auto oth_caller = dyn_cast<const SubHolder*>(oth);
            return oth_caller && this->sub() == oth_caller->sub();
        }

    private:
        iptr<SubPad> pad;
    };
#else
    template <typename Ret, typename...Args>
    struct SubHolder : panda::Ifunction<Ret, Args...> {
        SubHolder (const Sub& sub) : _sub(sub) {}

        const Sub& sub () const {
            if (SvREFCNT(_sub) == 0) throw "calling typemap'ed perl function after perl global destroy";
            return _sub;
        }

        bool equals (const panda::function_details::AnyFunction* oth) const override {
            auto oth_caller = dyn_cast<const SubHolder*>(oth);
            return oth_caller && this->_sub == oth_caller->_sub;
        }

    private:
        Sub _sub;
    };
#endif

template <typename Ret, typename...Args>
struct SubCallerComparator : SubHolder<Ret, Args...> {
    using SubHolder<Ret, Args...>::SubHolder;
    Ret operator() (Args...) override { throw "should not be called"; }
};

template <typename RetConv, typename...Convs>
struct SubCaller : SubHolder<typename RetConv::type, typename Convs::type...> {
    using Ret   = typename RetConv::type;
    using Super = SubHolder<Ret, typename Convs::type...>;
    using Tuple = std::tuple<Convs...>;

    template <class RetConvArg, class...ConvArgs>
    SubCaller (const Sub& sub, RetConvArg&& rcarg, ConvArgs&&...cargs)
        : Super(sub), ret_conv(RetConv{std::forward<RetConvArg>(rcarg)}), arg_convs(Convs{std::forward<ConvArgs>(cargs)}...) {}

    SubCaller (const Sub& sub, const RetConv& rc, const Convs&...acs) : Super(sub), ret_conv(rc), arg_convs(acs...) {}

    Ret operator() (typename Convs::type... args) override {
        constexpr size_t ARGS_COUNT = sizeof...(Convs);
        SV* sv_args[ARGS_COUNT];
        push(sv_args, args...);
        return call_impl(sv_args, ARGS_COUNT, typename std::is_void<Ret>::type());
    }

private:
    RetConv ret_conv;
    Tuple   arg_convs;

    template <size_t pos = 0>
    void push (SV**) {}

    template <size_t pos = 0, typename First, typename...Others>
    void push (SV** dest, First&& f, Others&&...oths) {
        dest[pos] = std::get<pos>(arg_convs).out(std::forward<First>(f)).detach();
        push<pos+1>(dest, std::forward<Others>(oths)...);
    }

    Ret call_impl (SV** args, size_t items, std::false_type) {
        Scalar ret = this->sub().call(args, items);
        for (size_t i = 0; i < items; ++i) SvREFCNT_dec(args[i]);
        return ret_conv.in(ret ? ret : Scalar::undef);
    }

    Ret call_impl (SV** args, size_t items, std::true_type) {
        this->sub().template call<void>(args, items);
        for (size_t i = 0; i < items; ++i) SvREFCNT_dec(args[i]);
    }
};

struct IFunctionCaller {
    virtual Sv call (SV**, size_t) = 0;
    virtual IFunctionCaller* clone () const = 0;
    virtual ~IFunctionCaller () {}
};

template <typename Ret, typename...Args>
struct FunctionHolder : IFunctionCaller {
    using Func = panda::function<Ret(Args...)>;
    Func func;
    FunctionHolder (const Func& f) : func(f) {}
};

template <typename RetConv, typename...Convs>
struct FunctionCaller : FunctionHolder<typename RetConv::type, typename Convs::type...> {
    using Ret   = typename RetConv::type;
    using Super = FunctionHolder<Ret, typename Convs::type...>;
    using Func  = typename Super::Func;
    using Tuple = std::tuple<Convs...>;

    template <class RetConvArg, class...ConvArgs>
    FunctionCaller (const Func& f, RetConvArg&& rcarg, ConvArgs&&...cargs)
        : Super(f), ret_conv(RetConv{std::forward<RetConvArg>(rcarg)}), arg_convs(Convs{std::forward<ConvArgs>(cargs)}...) {}

    FunctionCaller (const Func& f, const RetConv& rc, const Convs&...acs) : Super(f), ret_conv(rc), arg_convs(acs...) {}

    FunctionCaller (const FunctionCaller& oth) = default;

    Sv call (SV** svs, size_t items) override {
        constexpr size_t ARGS_COUNT = sizeof...(Convs);
        if (items != ARGS_COUNT) throw Simple::format("wrong number of arguments for subroutine call: expected %d, passed %d", ARGS_COUNT, items);
        return call_impl(svs, std::make_index_sequence<ARGS_COUNT>(), typename std::is_void<Ret>::type());
    }

    IFunctionCaller* clone () const override {
        return new FunctionCaller(*this);
    }

private:
    RetConv ret_conv;
    Tuple   arg_convs;

    template <size_t...Inds>
    Sv call_impl (SV** svs, std::index_sequence<Inds...>, std::false_type) {
        auto args = std::make_tuple(std::get<Inds>(arg_convs).in(svs[Inds])...); // proxy on stack to be able to pass non-const references
        (void)args; // supress warning when no input args
        return ret_conv.out(this->func(std::get<Inds>(args)...));
    }

    template <size_t... Inds>
    Sv call_impl (SV** svs, std::index_sequence<Inds...>, std::true_type) {
        auto args = std::make_tuple(std::get<Inds>(arg_convs).in(svs[Inds])...);
        (void)args;
        this->func(std::get<Inds>(args)...);
        return {};
    }
};

template <int N, class R, class F, class...Args>
std::enable_if_t<(sizeof...(Args) == N), R>
fill_default_args (const F& f, Args&&... args) {
    return f(std::forward<Args>(args)...);
}

template <int N, class R, class F, class...Args>
std::enable_if_t<(sizeof...(Args) < N), R>
fill_default_args (const F& f, Args&&... args) {
    return fill_default_args<N, R>(f, std::forward<Args>(args)..., nullptr);
}

template <typename Ret, typename...Args, typename...ConvArgs>
panda::iptr<panda::Ifunction<Ret, Args...>> sub2function_impl (const Sub& sub, ConvArgs&&...cargs) {
    return fill_default_args<sizeof...(Args) + 1, panda::Ifunction<Ret, Args...>*>([&](auto&& rcarg, auto&&...cargs) {
        return new SubCaller<ConverterIn<Ret, std::decay_t<decltype(rcarg)>>, ConverterOut<Args, std::decay_t<decltype(cargs)>>...>(
            sub, std::forward<decltype(rcarg)>(rcarg), std::forward<decltype(cargs)>(cargs)...
        );
    }, std::forward<ConvArgs>(cargs)...);
}

template <typename Ret, typename...Args, typename...ConvArgs>
IFunctionCaller* function2sub_impl (const panda::function<Ret(Args...)>& f, ConvArgs&&...cargs) {
    return fill_default_args<sizeof...(Args) + 1, IFunctionCaller*>([&](auto&& rcarg, auto&&...cargs) {
        return new FunctionCaller<ConverterOut<Ret, std::decay_t<decltype(rcarg)>>, ConverterIn<Args, std::decay_t<decltype(cargs)>>...>(
            f, std::forward<decltype(rcarg)>(rcarg), std::forward<decltype(cargs)>(cargs)...
        );
    }, std::forward<ConvArgs>(cargs)...);
}

template <typename Ret, typename...Args, typename...ConvArgs>
std::enable_if_t<std::is_void<Ret>::value, panda::function<Ret(Args...)>>
sub2function (panda::function<Ret(Args...)>*, const Sub& sub, ConvArgs&&...cargs) {
    auto fc = reinterpret_cast<IFunctionCaller*>(sub.payload(&out_marker).ptr);
    if (auto holder = dyn_cast<FunctionHolder<Ret, Args...>*>(fc)) return holder->func;
    return sub2function_impl<Ret, Args...>(sub, nullptr, std::forward<ConvArgs>(cargs)...);
}

template <typename Ret, typename...Args, typename...ConvArgs>
std::enable_if_t<!std::is_void<Ret>::value, panda::function<Ret(Args...)>>
sub2function (panda::function<Ret(Args...)>*, const Sub& sub, ConvArgs&&...cargs) {
    auto fc = reinterpret_cast<IFunctionCaller*>(sub.payload(&out_marker).ptr);
    if (auto holder = dyn_cast<FunctionHolder<Ret, Args...>*>(fc)) return holder->func;
    return sub2function_impl<Ret, Args...>(sub, std::forward<ConvArgs>(cargs)...);
}

template <typename Ret, typename...Args, typename...ConvArgs>
std::enable_if_t<std::is_void<Ret>::value, IFunctionCaller*>
function2sub (const panda::function<Ret(Args...)>& f, ConvArgs&&...cargs) {
    return function2sub_impl(f, nullptr, std::forward<ConvArgs>(cargs)...);
}

template <typename Ret, typename...Args, typename...ConvArgs>
std::enable_if_t<!std::is_void<Ret>::value, IFunctionCaller*>
function2sub (const panda::function<Ret(Args...)>& f, ConvArgs&&...cargs) {
    return function2sub_impl(f, std::forward<ConvArgs>(cargs)...);
}

Sub create_sub (IFunctionCaller*);


// sub2function low-level interface (used by CallbackDispatcher)
template <typename RetConv, typename...Convs>
panda::iptr<panda::Ifunction<typename std::decay_t<RetConv>::type, typename std::decay_t<Convs>::type...>>
sub2function_with_convs (const Sub& sub, RetConv&& rc, Convs&&...acs) {
    return new SubCaller<std::decay_t<RetConv>, std::decay_t<Convs>...>(sub, std::forward<RetConv>(rc), std::forward<Convs>(acs)...);
}

// function2sub low-level interface (used by CallbackDispatcher)
template <typename RetConv, typename...Convs>
Sub function2sub_with_convs (const panda::function<typename std::decay_t<RetConv>::type(typename std::decay_t<Convs>::type...)>& f, RetConv&& rc, Convs&&...acs) {
    auto fc = new FunctionCaller<std::decay_t<RetConv>, std::decay_t<Convs>...>(f, std::forward<RetConv>(rc), std::forward<Convs>(acs)...);
    return create_sub(fc);
}

}}

namespace xs {
    template <typename F, typename...ConvArgs>
    std::enable_if_t<std::is_function<F>::value, panda::function<F>>
    sub2function (const Sub& sub, ConvArgs&&...cargs) {
        if (!sub) return {};
        return xs::func::sub2function((panda::function<F>*)(nullptr), sub, std::forward<ConvArgs>(cargs)...);
    }

    template <typename F, typename...ConvArgs>
    std::enable_if_t<!std::is_function<F>::value, F>
    sub2function (const Sub& sub, ConvArgs&&...cargs) {
        if (!sub) return {};
        return xs::func::sub2function((F*)(nullptr), sub, std::forward<ConvArgs>(cargs)...);
    }

    template <class Ret, class...Args, class...ConvArgs>
    Sub function2sub (const panda::function<Ret(Args...)>& f, ConvArgs&&...cargs) {
        if (!f) return {};
        auto caller = panda::dyn_cast<xs::func::SubHolder<Ret, Args...>*>(f.func.get());
        if (caller) return caller->sub();
        return xs::func::create_sub(xs::func::function2sub(f, std::forward<ConvArgs>(cargs)...));
    }

    template <typename Ret, typename... Args>
    struct Typemap<panda::function<Ret(Args...)>> : TypemapBase<panda::function<Ret(Args...)>> {
        using Func = panda::function<Ret(Args...)>;

        static inline Func in (const Sub& sub) {
            return sub2function<Ret(Args...)>(sub);
        }

        static inline Sv out (const Func& f, const Sv& = {}) {
            if (!f) return Sv::undef;
            return Ref::create(function2sub(f));
        }
    };
}