// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation


#ifndef OPENCV_GAPI_GTYPED_HPP
#define OPENCV_GAPI_GTYPED_HPP
#if !defined(GAPI_STANDALONE)

#include <vector>

#include <opencv2/gapi/gcomputation.hpp>
#include <opencv2/gapi/gcompiled.hpp>
#include <opencv2/gapi/gproto.hpp>
#include <opencv2/gapi/gcommon.hpp>

namespace cv {

namespace detail
{
    // FIXME: How to prevent coolhackers from extending it by their own types?
    // FIXME: ...Should we care?
    template<typename T> struct ProtoToParam;
    template<> struct ProtoToParam<cv::GMat>    { using type = cv::Mat; };
    template<> struct ProtoToParam<cv::GScalar> { using type = cv::Scalar; };
    template<typename U> struct ProtoToParam<cv::GArray<U> > { using type = std::vector<U>; };
    template<typename T> using ProtoToParamT = typename ProtoToParam<T>::type;

    template<typename T> struct ProtoToMeta;
    template<> struct ProtoToMeta<cv::GMat>     { using type = cv::GMatDesc; };
    template<> struct ProtoToMeta<cv::GScalar>  { using type = cv::GScalarDesc; };
    template<typename U> struct ProtoToMeta<cv::GArray<U> > { using type = cv::GArrayDesc; };
    template<typename T> using ProtoToMetaT = typename ProtoToMeta<T>::type;

    //workaround for MSVC 19.0 bug
    template <typename T>
    auto make_default()->decltype(T{}) {return {};}
}; // detail

template<typename> class GComputationT;

// Single return value implementation
template<typename R, typename... Args> class GComputationT<R(Args...)>
{
public:
    typedef std::function<R(Args...)> Gen;

    class GCompiledT
    {
    private:
        friend class GComputationT<R(Args...)>;

        cv::GCompiled m_comp;

        explicit GCompiledT(const cv::GCompiled &comp) : m_comp(comp) {}

    public:
        GCompiledT() {}

        void operator()(detail::ProtoToParamT<Args>... inArgs,
                        detail::ProtoToParamT<R> &outArg)
        {
            m_comp(cv::gin(inArgs...), cv::gout(outArg));
        }

        explicit operator bool() const
        {
            return static_cast<bool>(m_comp);
        }
    };

private:
    typedef std::pair<R, GProtoInputArgs > Captured;

    Captured capture(const Gen& g, Args... args)
    {
        return Captured(g(args...), cv::GIn(args...));
    }

    Captured m_capture;
    cv::GComputation m_comp;

public:
    GComputationT(const Gen &generator)
        : m_capture(capture(generator, detail::make_default<Args>()...))
        , m_comp(cv::GProtoInputArgs(std::move(m_capture.second)),
                 cv::GOut(m_capture.first))
    {
    }

    void apply(detail::ProtoToParamT<Args>... inArgs,
               detail::ProtoToParamT<R> &outArg)
    {
        m_comp.apply(cv::gin(inArgs...), cv::gout(outArg));
    }

    GCompiledT compile(detail::ProtoToMetaT<Args>... inDescs)
    {
        GMetaArgs inMetas = { GMetaArg(inDescs)... };
        return GCompiledT(m_comp.compile(std::move(inMetas), GCompileArgs()));
    }

    GCompiledT compile(detail::ProtoToMetaT<Args>... inDescs, GCompileArgs &&args)
    {
        GMetaArgs inMetas = { GMetaArg(inDescs)... };
        return GCompiledT(m_comp.compile(std::move(inMetas), std::move(args)));
    }
};

// Multiple (fixed) return value implementation. FIXME: How to avoid copy-paste?
template<typename... R, typename... Args> class GComputationT<std::tuple<R...>(Args...)>
{
public:
    typedef std::function<std::tuple<R...>(Args...)> Gen;

    class GCompiledT
    {
    private:
        friend class GComputationT<std::tuple<R...>(Args...)>;

        cv::GCompiled m_comp;
        explicit GCompiledT(const cv::GCompiled &comp) : m_comp(comp) {}

    public:
        GCompiledT() {}

        void operator()(detail::ProtoToParamT<Args>... inArgs,
                        detail::ProtoToParamT<R>&... outArgs)
        {
            m_comp(cv::gin(inArgs...), cv::gout(outArgs...));
        }

        explicit operator bool() const
        {
            return static_cast<bool>(m_comp);
        }
    };

private:
    typedef std::pair<GProtoArgs, GProtoArgs> Captured;

    template<int... IIs>
    Captured capture(GProtoArgs &&args, const std::tuple<R...> &rr, detail::Seq<IIs...>)
    {
        return Captured(cv::GOut(std::get<IIs>(rr)...).m_args, args);
    }

    Captured capture(const Gen& g, Args... args)
    {
        return capture(cv::GIn(args...).m_args, g(args...), typename detail::MkSeq<sizeof...(R)>::type());
    }

    Captured m_capture;
    cv::GComputation m_comp;

public:
    GComputationT(const Gen &generator)
        : m_capture(capture(generator, detail::make_default<Args>()...))
        , m_comp(cv::GProtoInputArgs(std::move(m_capture.second)),
                 cv::GProtoOutputArgs(std::move(m_capture.first)))
    {
    }

    void apply(detail::ProtoToParamT<Args>... inArgs,
               detail::ProtoToParamT<R>&... outArgs)
    {
        m_comp.apply(cv::gin(inArgs...), cv::gout(outArgs...));
    }

    GCompiledT compile(detail::ProtoToMetaT<Args>... inDescs)
    {
        GMetaArgs inMetas = { GMetaArg(inDescs)... };
        return GCompiledT(m_comp.compile(std::move(inMetas), GCompileArgs()));
    }

    GCompiledT compile(detail::ProtoToMetaT<Args>... inDescs, GCompileArgs &&args)
    {
        GMetaArgs inMetas = { GMetaArg(inDescs)... };
        return GCompiledT(m_comp.compile(std::move(inMetas), std::move(args)));
    }
};

} // namespace cv
#endif // !defined(GAPI_STANDALONE)
#endif // OPENCV_GAPI_GTYPED_HPP
