#ifdef HAVE_OPENCV_GAPI

#ifdef _MSC_VER
#pragma warning(disable: 4503)  // "decorated name length exceeded"
#endif

#include <opencv2/gapi/cpu/gcpukernel.hpp>
// #include <opencv2/gapi/python/python.hpp>

// NB: Python wrapper replaces :: with _ for classes
using gapi_GKernelPackage           = cv::GKernelPackage;
using gapi_GNetPackage              = cv::gapi::GNetPackage;
using gapi_ie_PyParams              = cv::gapi::ie::PyParams;
using gapi_wip_IStreamSource_Ptr    = cv::Ptr<cv::gapi::wip::IStreamSource>;
using detail_ExtractArgsCallback    = cv::detail::ExtractArgsCallback;
using detail_ExtractMetaCallback    = cv::detail::ExtractMetaCallback;
using vector_GNetParam              = std::vector<cv::gapi::GNetParam>;
using gapi_streaming_queue_capacity = cv::gapi::streaming::queue_capacity;
using GStreamerSource_OutputType    = cv::gapi::wip::GStreamerSource::OutputType;

// NB: Python wrapper generate T_U for T<U>
// This behavior is only observed for inputs
using GOpaque_bool    = cv::GOpaque<bool>;
using GOpaque_int     = cv::GOpaque<int>;
using GOpaque_double  = cv::GOpaque<double>;
using GOpaque_float   = cv::GOpaque<double>;
using GOpaque_string  = cv::GOpaque<std::string>;
using GOpaque_Point2i = cv::GOpaque<cv::Point>;
using GOpaque_Point2f = cv::GOpaque<cv::Point2f>;
using GOpaque_Size    = cv::GOpaque<cv::Size>;
using GOpaque_Rect    = cv::GOpaque<cv::Rect>;

using GArray_bool    = cv::GArray<bool>;
using GArray_int     = cv::GArray<int>;
using GArray_double  = cv::GArray<double>;
using GArray_float   = cv::GArray<double>;
using GArray_string  = cv::GArray<std::string>;
using GArray_Point2i = cv::GArray<cv::Point>;
using GArray_Point2f = cv::GArray<cv::Point2f>;
using GArray_Size    = cv::GArray<cv::Size>;
using GArray_Rect    = cv::GArray<cv::Rect>;
using GArray_Scalar  = cv::GArray<cv::Scalar>;
using GArray_Mat     = cv::GArray<cv::Mat>;
using GArray_GMat    = cv::GArray<cv::GMat>;
using GArray_Prim    = cv::GArray<cv::gapi::wip::draw::Prim>;

// FIXME: Python wrapper generate code without namespace std,
// so it cause error: "string wasn't declared"
// WA: Create using
using std::string;

namespace cv
{
namespace detail
{

// class PyObjectHolder
// {
// public:
//     PyObjectHolder(PyObject* o, bool owner = true);
//     PyObject* get() const;

// private:
//     class Impl;
//     std::shared_ptr<Impl> m_impl;
// };

} // namespace detail
} // namespace cv

// class cv::detail::PyObjectHolder::Impl
// {
// public:
//     Impl(PyObject* object, bool owner);
//     PyObject* get() const;
//     ~Impl();

// private:
//     PyObject* m_object;
// };

// cv::detail::PyObjectHolder::Impl::Impl(PyObject* object, bool owner)
//     : m_object(object)
// {
//     // NB: Become an owner of that PyObject.
//     // Need to store this and get access
//     // after the caller which provide the object is out of range.
//     if (owner)
//     {
//         // NB: Impossible take ownership if object is NULL.
//         GAPI_Assert(object);
//         Py_INCREF(m_object);
//     }
// }

// cv::detail::PyObjectHolder::Impl::~Impl()
// {
//     // NB: If NULL was set, don't decrease counter.
//     if (m_object)
//     {
//         Py_DECREF(m_object);
//     }
// }

// PyObject* cv::detail::PyObjectHolder::Impl::get() const
// {
//     return m_object;
// }

// cv::detail::PyObjectHolder::PyObjectHolder(PyObject* object, bool owner)
//         : m_impl(new cv::detail::PyObjectHolder::Impl{object, owner})
// {
// }

// PyObject* cv::detail::PyObjectHolder::get() const
// {
//     return m_impl->get();
// }

// template<>
// PyObject* pyopencv_from(const cv::detail::PyObjectHolder& v)
// {
//     PyObject* o = cv::util::any_cast<cv::detail::PyObjectHolder>(v).get();
//     Py_INCREF(o);
//     return o;
// }

// #FIXME: Is it possible to implement pyopencv_from/pyopencv_to for generic
// cv::variant<Types...> ?
template <>
ERL_NIF_TERM evision_from(ErlNifEnv *env, const cv::gapi::wip::draw::Prim& prim)
{
    switch (prim.index())
    {
        case cv::gapi::wip::draw::Prim::index_of<cv::gapi::wip::draw::Rect>():
            return evision_from(env, cv::util::get<cv::gapi::wip::draw::Rect>(prim));
        case cv::gapi::wip::draw::Prim::index_of<cv::gapi::wip::draw::Text>():
            return evision_from(env, cv::util::get<cv::gapi::wip::draw::Text>(prim));
        case cv::gapi::wip::draw::Prim::index_of<cv::gapi::wip::draw::Circle>():
            return evision_from(env, cv::util::get<cv::gapi::wip::draw::Circle>(prim));
        case cv::gapi::wip::draw::Prim::index_of<cv::gapi::wip::draw::Line>():
            return evision_from(env, cv::util::get<cv::gapi::wip::draw::Line>(prim));
        case cv::gapi::wip::draw::Prim::index_of<cv::gapi::wip::draw::Poly>():
            return evision_from(env, cv::util::get<cv::gapi::wip::draw::Poly>(prim));
        case cv::gapi::wip::draw::Prim::index_of<cv::gapi::wip::draw::Mosaic>():
            return evision_from(env, cv::util::get<cv::gapi::wip::draw::Mosaic>(prim));
        case cv::gapi::wip::draw::Prim::index_of<cv::gapi::wip::draw::Image>():
            return evision_from(env, cv::util::get<cv::gapi::wip::draw::Image>(prim));
    }

    util::throw_error(std::logic_error("Unsupported draw primitive type"));
}

template <>
ERL_NIF_TERM evision_from(ErlNifEnv *env, const cv::gapi::wip::draw::Prims& value)
{
    return evision_from_generic_vec(env, value);
}

template<>
bool evision_to(ErlNifEnv *env, ERL_NIF_TERM obj, cv::gapi::wip::draw::Prim& value, const ArgInfo&)
{
    return false;
}

template <>
bool evision_to(ErlNifEnv *env, ERL_NIF_TERM obj, cv::gapi::wip::draw::Prims& value, const ArgInfo& info)
{
    return evision_to_generic_vec(env, obj, value, info);
}

template <>
bool evision_to(ErlNifEnv *env, ERL_NIF_TERM obj, cv::GMetaArg& value, const ArgInfo&)
{
    return false;
}

template <>
bool evision_to(ErlNifEnv *env, ERL_NIF_TERM obj, cv::GMetaArgs& value, const ArgInfo& info)
{
    return evision_to_generic_vec(env, obj, value, info);
}


template<>
ERL_NIF_TERM evision_from(ErlNifEnv *env, const cv::GArg& value)
{
    GAPI_Assert(value.kind != cv::detail::ArgKind::GOBJREF);
#define HANDLE_CASE(T, O) case cv::detail::OpaqueKind::CV_##T:  \
    {                                                           \
        return evision_from(value.get<O>());                   \
    }

#define UNSUPPORTED(T) case cv::detail::OpaqueKind::CV_##T: break
    switch (value.opaque_kind)
    {
        HANDLE_CASE(BOOL,      bool);
        HANDLE_CASE(INT,       int);
        HANDLE_CASE(INT64,     int64_t);
        HANDLE_CASE(DOUBLE,    double);
        HANDLE_CASE(FLOAT,     float);
        HANDLE_CASE(STRING,    std::string);
        HANDLE_CASE(POINT,     cv::Point);
        HANDLE_CASE(POINT2F,   cv::Point2f);
        HANDLE_CASE(SIZE,      cv::Size);
        HANDLE_CASE(RECT,      cv::Rect);
        HANDLE_CASE(SCALAR,    cv::Scalar);
        HANDLE_CASE(MAT,       cv::Mat);
        HANDLE_CASE(UNKNOWN,   cv::detail::PyObjectHolder);
        HANDLE_CASE(DRAW_PRIM, cv::gapi::wip::draw::Prim);
        UNSUPPORTED(UINT64);
#undef HANDLE_CASE
#undef UNSUPPORTED
    }
    util::throw_error(std::logic_error("Unsupported kernel input type"));
}

// template<>
// bool pyopencv_to(PyObject* obj, cv::GArg& value, const ArgInfo& info)
// {
//     value = cv::GArg(cv::detail::PyObjectHolder(obj));
//     return true;
// }

// template <>
// bool pyopencv_to(PyObject* obj, std::vector<cv::gapi::GNetParam>& value, const ArgInfo& info)
// {
//     return pyopencv_to_generic_vec(obj, value, info);
// }

// template <>
// PyObject* pyopencv_from(const std::vector<cv::gapi::GNetParam>& value)
// {
//     return pyopencv_from_generic_vec(value);
// }

// template <>
// bool pyopencv_to(PyObject* obj, std::vector<GCompileArg>& value, const ArgInfo& info)
// {
//     return pyopencv_to_generic_vec(obj, value, info);
// }

// template <>
// PyObject* pyopencv_from(const std::vector<GCompileArg>& value)
// {
//     return pyopencv_from_generic_vec(value);
// }

// template<>
// PyObject* pyopencv_from(const cv::detail::OpaqueRef& o)
// {
//     switch (o.getKind())
//     {
//         case cv::detail::OpaqueKind::CV_BOOL      : return pyopencv_from(o.rref<bool>());
//         case cv::detail::OpaqueKind::CV_INT       : return pyopencv_from(o.rref<int>());
//         case cv::detail::OpaqueKind::CV_INT64     : return pyopencv_from(o.rref<int64_t>());
//         case cv::detail::OpaqueKind::CV_DOUBLE    : return pyopencv_from(o.rref<double>());
//         case cv::detail::OpaqueKind::CV_FLOAT     : return pyopencv_from(o.rref<float>());
//         case cv::detail::OpaqueKind::CV_STRING    : return pyopencv_from(o.rref<std::string>());
//         case cv::detail::OpaqueKind::CV_POINT     : return pyopencv_from(o.rref<cv::Point>());
//         case cv::detail::OpaqueKind::CV_POINT2F   : return pyopencv_from(o.rref<cv::Point2f>());
//         case cv::detail::OpaqueKind::CV_SIZE      : return pyopencv_from(o.rref<cv::Size>());
//         case cv::detail::OpaqueKind::CV_RECT      : return pyopencv_from(o.rref<cv::Rect>());
//         case cv::detail::OpaqueKind::CV_UNKNOWN   : return pyopencv_from(o.rref<cv::GArg>());
//         case cv::detail::OpaqueKind::CV_DRAW_PRIM : return pyopencv_from(o.rref<cv::gapi::wip::draw::Prim>());
//         case cv::detail::OpaqueKind::CV_UINT64    : break;
//         case cv::detail::OpaqueKind::CV_SCALAR    : break;
//         case cv::detail::OpaqueKind::CV_MAT       : break;
//     }

//     PyErr_SetString(PyExc_TypeError, "Unsupported GOpaque type");
//     return NULL;
// };

// template <>
// PyObject* pyopencv_from(const cv::detail::VectorRef& v)
// {
//     switch (v.getKind())
//     {
//         case cv::detail::OpaqueKind::CV_BOOL      : return pyopencv_from_generic_vec(v.rref<bool>());
//         case cv::detail::OpaqueKind::CV_INT       : return pyopencv_from_generic_vec(v.rref<int>());
//         case cv::detail::OpaqueKind::CV_INT64     : return pyopencv_from_generic_vec(v.rref<int64_t>());
//         case cv::detail::OpaqueKind::CV_DOUBLE    : return pyopencv_from_generic_vec(v.rref<double>());
//         case cv::detail::OpaqueKind::CV_FLOAT     : return pyopencv_from_generic_vec(v.rref<float>());
//         case cv::detail::OpaqueKind::CV_STRING    : return pyopencv_from_generic_vec(v.rref<std::string>());
//         case cv::detail::OpaqueKind::CV_POINT     : return pyopencv_from_generic_vec(v.rref<cv::Point>());
//         case cv::detail::OpaqueKind::CV_POINT2F   : return pyopencv_from_generic_vec(v.rref<cv::Point2f>());
//         case cv::detail::OpaqueKind::CV_SIZE      : return pyopencv_from_generic_vec(v.rref<cv::Size>());
//         case cv::detail::OpaqueKind::CV_RECT      : return pyopencv_from_generic_vec(v.rref<cv::Rect>());
//         case cv::detail::OpaqueKind::CV_SCALAR    : return pyopencv_from_generic_vec(v.rref<cv::Scalar>());
//         case cv::detail::OpaqueKind::CV_MAT       : return pyopencv_from_generic_vec(v.rref<cv::Mat>());
//         case cv::detail::OpaqueKind::CV_UNKNOWN   : return pyopencv_from_generic_vec(v.rref<cv::GArg>());
//         case cv::detail::OpaqueKind::CV_DRAW_PRIM : return pyopencv_from_generic_vec(v.rref<cv::gapi::wip::draw::Prim>());
//         case cv::detail::OpaqueKind::CV_UINT64    : break;
//     }

//     PyErr_SetString(PyExc_TypeError, "Unsupported GArray type");
//     return NULL;
// }

// template <>
// PyObject* pyopencv_from(const GRunArg& v)
// {
//     switch (v.index())
//     {
//         case GRunArg::index_of<cv::Mat>():
//             return pyopencv_from(util::get<cv::Mat>(v));

//         case GRunArg::index_of<cv::Scalar>():
//             return pyopencv_from(util::get<cv::Scalar>(v));

//         case GRunArg::index_of<cv::detail::VectorRef>():
//             return pyopencv_from(util::get<cv::detail::VectorRef>(v));

//         case GRunArg::index_of<cv::detail::OpaqueRef>():
//             return pyopencv_from(util::get<cv::detail::OpaqueRef>(v));
//     }

//     PyErr_SetString(PyExc_TypeError, "Failed to unpack GRunArgs. Index of variant is unknown");
//     return NULL;
// }

// template <typename T>
// PyObject* pyopencv_from(const cv::optional<T>& opt)
// {
//     if (!opt.has_value())
//     {
//         Py_RETURN_NONE;
//     }
//     return pyopencv_from(*opt);
// }

// template <>
// PyObject* pyopencv_from(const GOptRunArg& v)
// {
//     switch (v.index())
//     {
//         case GOptRunArg::index_of<cv::optional<cv::Mat>>():
//             return pyopencv_from(util::get<cv::optional<cv::Mat>>(v));

//         case GOptRunArg::index_of<cv::optional<cv::Scalar>>():
//             return pyopencv_from(util::get<cv::optional<cv::Scalar>>(v));

//         case GOptRunArg::index_of<optional<cv::detail::VectorRef>>():
//             return pyopencv_from(util::get<optional<cv::detail::VectorRef>>(v));

//         case GOptRunArg::index_of<optional<cv::detail::OpaqueRef>>():
//             return pyopencv_from(util::get<optional<cv::detail::OpaqueRef>>(v));
//     }

//     PyErr_SetString(PyExc_TypeError, "Failed to unpack GOptRunArg. Index of variant is unknown");
//     return NULL;
// }

// template<>
// PyObject* pyopencv_from(const GRunArgs& value)
// {
//      return value.size() == 1 ? pyopencv_from(value[0]) : pyopencv_from_generic_vec(value);
// }

// template<>
// PyObject* pyopencv_from(const GOptRunArgs& value)
// {
//     return value.size() == 1 ? pyopencv_from(value[0]) : pyopencv_from_generic_vec(value);
// }

// // FIXME: cv::variant should be wrapped once for all types.
// template <>
// PyObject* pyopencv_from(const cv::util::variant<cv::GRunArgs, cv::GOptRunArgs>& v)
// {
//     using RunArgs = cv::util::variant<cv::GRunArgs, cv::GOptRunArgs>;
//     switch (v.index())
//     {
//         case RunArgs::index_of<cv::GRunArgs>():
//             return pyopencv_from(util::get<cv::GRunArgs>(v));
//         case RunArgs::index_of<cv::GOptRunArgs>():
//             return pyopencv_from(util::get<cv::GOptRunArgs>(v));
//     }

//     PyErr_SetString(PyExc_TypeError, "Failed to recognize kind of RunArgs. Index of variant is unknown");
//     return NULL;
// }

// template <typename T>
// void pyopencv_to_with_check(PyObject* from, T& to, const std::string& msg = "")
// {
//     if (!pyopencv_to(from, to, ArgInfo("", false)))
//     {
//         cv::util::throw_error(std::logic_error(msg));
//     }
// }

// template <typename T>
// void pyopencv_to_generic_vec_with_check(PyObject* from,
//                                         std::vector<T>& to,
//                                         const std::string& msg = "")
// {
//     if (!pyopencv_to_generic_vec(from, to, ArgInfo("", false)))
//     {
//         cv::util::throw_error(std::logic_error(msg));
//     }
// }

// template <typename T>
// static T extract_proto_args(PyObject* py_args)
// {
//     using namespace cv;

//     GProtoArgs args;
//     Py_ssize_t size = PyList_Size(py_args);
//     args.reserve(size);
//     for (int i = 0; i < size; ++i)
//     {
//         PyObject* item = PyList_GetItem(py_args, i);
//         if (PyObject_TypeCheck(item, reinterpret_cast<PyTypeObject*>(pyopencv_GScalar_TypePtr)))
//         {
//             args.emplace_back(reinterpret_cast<pyopencv_GScalar_t*>(item)->v);
//         }
//         else if (PyObject_TypeCheck(item, reinterpret_cast<PyTypeObject*>(pyopencv_GMat_TypePtr)))
//         {
//             args.emplace_back(reinterpret_cast<pyopencv_GMat_t*>(item)->v);
//         }
//         else if (PyObject_TypeCheck(item, reinterpret_cast<PyTypeObject*>(pyopencv_GOpaqueT_TypePtr)))
//         {
//             args.emplace_back(reinterpret_cast<pyopencv_GOpaqueT_t*>(item)->v.strip());
//         }
//         else if (PyObject_TypeCheck(item, reinterpret_cast<PyTypeObject*>(pyopencv_GArrayT_TypePtr)))
//         {
//             args.emplace_back(reinterpret_cast<pyopencv_GArrayT_t*>(item)->v.strip());
//         }
//         else
//         {
//             util::throw_error(std::logic_error("Unsupported type for GProtoArgs"));
//         }
//     }

//     return T(std::move(args));
// }

// static cv::detail::OpaqueRef extract_opaque_ref(PyObject* from, cv::detail::OpaqueKind kind)
// {
// #define HANDLE_CASE(T, O) case cv::detail::OpaqueKind::CV_##T:  \
// {                                                               \
//     O obj{};                                                    \
//     pyopencv_to_with_check(from, obj, "Failed to obtain " # O); \
//     return cv::detail::OpaqueRef{std::move(obj)};               \
// }
// #define UNSUPPORTED(T) case cv::detail::OpaqueKind::CV_##T: break
//     switch (kind)
//     {
//         HANDLE_CASE(BOOL,    bool);
//         HANDLE_CASE(INT,     int);
//         HANDLE_CASE(DOUBLE,  double);
//         HANDLE_CASE(FLOAT,   float);
//         HANDLE_CASE(STRING,  std::string);
//         HANDLE_CASE(POINT,   cv::Point);
//         HANDLE_CASE(POINT2F, cv::Point2f);
//         HANDLE_CASE(SIZE,    cv::Size);
//         HANDLE_CASE(RECT,    cv::Rect);
//         HANDLE_CASE(UNKNOWN, cv::GArg);
//         UNSUPPORTED(UINT64);
//         UNSUPPORTED(INT64);
//         UNSUPPORTED(SCALAR);
//         UNSUPPORTED(MAT);
//         UNSUPPORTED(DRAW_PRIM);
// #undef HANDLE_CASE
// #undef UNSUPPORTED
//     }
//     util::throw_error(std::logic_error("Unsupported type for GOpaqueT"));
// }

// static cv::detail::VectorRef extract_vector_ref(PyObject* from, cv::detail::OpaqueKind kind)
// {
// #define HANDLE_CASE(T, O) case cv::detail::OpaqueKind::CV_##T:                        \
// {                                                                                     \
//     std::vector<O> obj;                                                               \
//     pyopencv_to_generic_vec_with_check(from, obj, "Failed to obtain vector of " # O); \
//     return cv::detail::VectorRef{std::move(obj)};                                     \
// }
// #define UNSUPPORTED(T) case cv::detail::OpaqueKind::CV_##T: break
//     switch (kind)
//     {
//         HANDLE_CASE(BOOL,      bool);
//         HANDLE_CASE(INT,       int);
//         HANDLE_CASE(DOUBLE,    double);
//         HANDLE_CASE(FLOAT,     float);
//         HANDLE_CASE(STRING,    std::string);
//         HANDLE_CASE(POINT,     cv::Point);
//         HANDLE_CASE(POINT2F,   cv::Point2f);
//         HANDLE_CASE(SIZE,      cv::Size);
//         HANDLE_CASE(RECT,      cv::Rect);
//         HANDLE_CASE(SCALAR,    cv::Scalar);
//         HANDLE_CASE(MAT,       cv::Mat);
//         HANDLE_CASE(UNKNOWN,   cv::GArg);
//         HANDLE_CASE(DRAW_PRIM, cv::gapi::wip::draw::Prim);
//         UNSUPPORTED(UINT64);
//         UNSUPPORTED(INT64);
// #undef HANDLE_CASE
// #undef UNSUPPORTED
//     }
//     util::throw_error(std::logic_error("Unsupported type for GArrayT"));
// }

// static cv::GRunArg extract_run_arg(const cv::GTypeInfo& info, PyObject* item)
// {
//     switch (info.shape)
//     {
//         case cv::GShape::GMAT:
//         {
//             // NB: In case streaming it can be IStreamSource or cv::Mat
//             if (PyObject_TypeCheck(item,
//                         reinterpret_cast<PyTypeObject*>(pyopencv_gapi_wip_IStreamSource_TypePtr)))
//             {
//                 cv::gapi::wip::IStreamSource::Ptr source =
//                     reinterpret_cast<pyopencv_gapi_wip_IStreamSource_t*>(item)->v;
//                 return source;
//             }
//             cv::Mat obj;
//             pyopencv_to_with_check(item, obj, "Failed to obtain cv::Mat");
//             return obj;
//         }
//         case cv::GShape::GSCALAR:
//         {
//             cv::Scalar obj;
//             pyopencv_to_with_check(item, obj, "Failed to obtain cv::Scalar");
//             return obj;
//         }
//         case cv::GShape::GOPAQUE:
//         {
//             return extract_opaque_ref(item, info.kind);
//         }
//         case cv::GShape::GARRAY:
//         {
//             return extract_vector_ref(item, info.kind);
//         }
//         case cv::GShape::GFRAME:
//         {
//             // NB: Isn't supported yet.
//             break;
//         }
//     }

//     util::throw_error(std::logic_error("Unsupported output shape"));
// }

// static cv::GRunArgs extract_run_args(const cv::GTypesInfo& info, PyObject* py_args)
// {
//     GAPI_Assert(PyList_Check(py_args));

//     cv::GRunArgs args;
//     Py_ssize_t list_size = PyList_Size(py_args);
//     args.reserve(list_size);

//     for (int i = 0; i < list_size; ++i)
//     {
//         args.push_back(extract_run_arg(info[i], PyList_GetItem(py_args, i)));
//     }

//     return args;
// }

// static cv::GMetaArg extract_meta_arg(const cv::GTypeInfo& info, PyObject* item)
// {
//     switch (info.shape)
//     {
//         case cv::GShape::GMAT:
//         {
//             cv::Mat obj;
//             pyopencv_to_with_check(item, obj, "Failed to obtain cv::Mat");
//             return cv::GMetaArg{cv::descr_of(obj)};
//         }
//         case cv::GShape::GSCALAR:
//         {
//             cv::Scalar obj;
//             pyopencv_to_with_check(item, obj, "Failed to obtain cv::Scalar");
//             return cv::GMetaArg{cv::descr_of(obj)};
//         }
//         case cv::GShape::GARRAY:
//         {
//             return cv::GMetaArg{cv::empty_array_desc()};
//         }
//         case cv::GShape::GOPAQUE:
//         {
//             return cv::GMetaArg{cv::empty_gopaque_desc()};
//         }
//         case cv::GShape::GFRAME:
//         {
//             // NB: Isn't supported yet.
//             break;
//         }
//     }
//     util::throw_error(std::logic_error("Unsupported output shape"));
// }

// static cv::GMetaArgs extract_meta_args(const cv::GTypesInfo& info, PyObject* py_args)
// {
//     GAPI_Assert(PyList_Check(py_args));

//     cv::GMetaArgs metas;
//     Py_ssize_t list_size = PyList_Size(py_args);
//     metas.reserve(list_size);

//     for (int i = 0; i < list_size; ++i)
//     {
//         metas.push_back(extract_meta_arg(info[i], PyList_GetItem(py_args, i)));
//     }

//     return metas;
// }

// static cv::GRunArgs run_py_kernel(cv::detail::PyObjectHolder kernel,
//                                   const cv::gapi::python::GPythonContext &ctx)
// {
//     const auto& ins      = ctx.ins;
//     const auto& in_metas = ctx.in_metas;
//     const auto& out_info = ctx.out_info;

//     PyGILState_STATE gstate;
//     gstate = PyGILState_Ensure();

//     cv::GRunArgs outs;
//     try
//     {
//         int in_idx = 0;
//         // NB: Doesn't increase reference counter (false),
//         // because PyObject already have ownership.
//         // In case exception decrement reference counter.
//         cv::detail::PyObjectHolder args(PyTuple_New(ins.size()), false);
//         for (size_t i = 0; i < ins.size(); ++i)
//         {
//             // NB: If meta is monostate then object isn't associated with G-TYPE.
//             if (cv::util::holds_alternative<cv::util::monostate>(in_metas[i]))
//             {
//                 PyTuple_SetItem(args.get(), i, pyopencv_from(ins[i]));
//                 continue;
//             }

//             switch (in_metas[i].index())
//             {
//                 case cv::GMetaArg::index_of<cv::GMatDesc>():
//                     PyTuple_SetItem(args.get(), i, pyopencv_from(ins[i].get<cv::Mat>()));
//                     break;
//                 case cv::GMetaArg::index_of<cv::GScalarDesc>():
//                     PyTuple_SetItem(args.get(), i, pyopencv_from(ins[i].get<cv::Scalar>()));
//                     break;
//                 case cv::GMetaArg::index_of<cv::GOpaqueDesc>():
//                     PyTuple_SetItem(args.get(), i, pyopencv_from(ins[i].get<cv::detail::OpaqueRef>()));
//                     break;
//                 case cv::GMetaArg::index_of<cv::GArrayDesc>():
//                     PyTuple_SetItem(args.get(), i, pyopencv_from(ins[i].get<cv::detail::VectorRef>()));
//                     break;
//                 case cv::GMetaArg::index_of<cv::GFrameDesc>():
//                     util::throw_error(std::logic_error("GFrame isn't supported for custom operation"));
//                     break;
//             }
//             ++in_idx;
//         }
//         // NB: Doesn't increase reference counter (false).
//         // In case PyObject_CallObject return NULL, do nothing in destructor.
//         cv::detail::PyObjectHolder result(
//                 PyObject_CallObject(kernel.get(), args.get()), false);

//         if (PyErr_Occurred())
//         {
//             PyErr_PrintEx(0);
//             PyErr_Clear();
//             throw std::logic_error("Python kernel failed with error!");
//         }
//         // NB: In fact it's impossible situation, because errors were handled above.
//         GAPI_Assert(result.get() && "Python kernel returned NULL!");

//         if (out_info.size() == 1)
//         {
//             outs = cv::GRunArgs{extract_run_arg(out_info[0], result.get())};
//         }
//         else if (out_info.size() > 1)
//         {
//             GAPI_Assert(PyTuple_Check(result.get()));

//             Py_ssize_t tuple_size = PyTuple_Size(result.get());
//             outs.reserve(tuple_size);

//             for (int i = 0; i < tuple_size; ++i)
//             {
//                 outs.push_back(extract_run_arg(out_info[i], PyTuple_GetItem(result.get(), i)));
//             }
//         }
//         else
//         {
//             // Seems to be impossible case.
//             GAPI_Assert(false);
//         }
//     }
//     catch (...)
//     {
//         PyGILState_Release(gstate);
//         throw;
//     }
//     PyGILState_Release(gstate);

//     return outs;
// }

// static GMetaArg get_meta_arg(PyObject* obj)
// {
//     cv::GMetaArg arg;
//     if (!pyopencv_to(obj, arg, ArgInfo("arg", false)))
//     {
//         util::throw_error(std::logic_error("Unsupported output meta type"));
//     }
//     return arg;
// }

// static cv::GMetaArgs get_meta_args(PyObject* tuple)
// {
//     size_t size = PyTuple_Size(tuple);

//     cv::GMetaArgs metas;
//     metas.reserve(size);
//     for (size_t i = 0; i < size; ++i)
//     {
//         metas.push_back(get_meta_arg(PyTuple_GetItem(tuple, i)));
//     }

//     return metas;
// }

// static GMetaArgs run_py_meta(cv::detail::PyObjectHolder out_meta,
//                              const cv::GMetaArgs         &meta,
//                              const cv::GArgs             &gargs)
// {
//     PyGILState_STATE gstate;
//     gstate = PyGILState_Ensure();

//     cv::GMetaArgs out_metas;
//     try
//     {
//         // NB: Doesn't increase reference counter (false),
//         // because PyObject already have ownership.
//         // In case exception decrement reference counter.
//         cv::detail::PyObjectHolder args(PyTuple_New(meta.size()), false);
//         size_t idx = 0;
//         for (auto&& m : meta)
//         {
//             switch (m.index())
//             {
//                 case cv::GMetaArg::index_of<cv::GMatDesc>():
//                     PyTuple_SetItem(args.get(), idx, pyopencv_from(cv::util::get<cv::GMatDesc>(m)));
//                     break;
//                 case cv::GMetaArg::index_of<cv::GScalarDesc>():
//                     PyTuple_SetItem(args.get(), idx, pyopencv_from(cv::util::get<cv::GScalarDesc>(m)));
//                     break;
//                 case cv::GMetaArg::index_of<cv::GArrayDesc>():
//                     PyTuple_SetItem(args.get(), idx, pyopencv_from(cv::util::get<cv::GArrayDesc>(m)));
//                     break;
//                 case cv::GMetaArg::index_of<cv::GOpaqueDesc>():
//                     PyTuple_SetItem(args.get(), idx, pyopencv_from(cv::util::get<cv::GOpaqueDesc>(m)));
//                     break;
//                 case cv::GMetaArg::index_of<cv::util::monostate>():
//                     PyTuple_SetItem(args.get(), idx, pyopencv_from(gargs[idx]));
//                     break;
//                 case cv::GMetaArg::index_of<cv::GFrameDesc>():
//                     util::throw_error(std::logic_error("GFrame isn't supported for custom operation"));
//                     break;
//             }
//             ++idx;
//         }
//         // NB: Doesn't increase reference counter (false).
//         // In case PyObject_CallObject return NULL, do nothing in destructor.
//         cv::detail::PyObjectHolder result(
//                 PyObject_CallObject(out_meta.get(), args.get()), false);

//         if (PyErr_Occurred())
//         {
//             PyErr_PrintEx(0);
//             PyErr_Clear();
//             throw std::logic_error("Python outMeta failed with error!");
//         }
//         // NB: In fact it's impossible situation, because errors were handled above.
//         GAPI_Assert(result.get() && "Python outMeta returned NULL!");

//         out_metas = PyTuple_Check(result.get()) ? get_meta_args(result.get())
//                                                 : cv::GMetaArgs{get_meta_arg(result.get())};
//     }
//     catch (...)
//     {
//         PyGILState_Release(gstate);
//         throw;
//     }
//     PyGILState_Release(gstate);

//     return out_metas;
// }

// static PyObject* pyopencv_cv_gapi_kernels(PyObject* , PyObject* py_args, PyObject*)
// {
//     using namespace cv;
//     GKernelPackage pkg;
//     Py_ssize_t size = PyTuple_Size(py_args);

//     for (int i = 0; i < size; ++i)
//     {
//         PyObject* user_kernel = PyTuple_GetItem(py_args, i);

//         PyObject* id_obj = PyObject_GetAttrString(user_kernel, "id");
//         if (!id_obj)
//         {
//             PyErr_SetString(PyExc_TypeError,
//                     "Python kernel should contain id, please use cv.gapi.kernel to define kernel");
//             return NULL;
//         }

//         PyObject* out_meta = PyObject_GetAttrString(user_kernel, "outMeta");
//         if (!out_meta)
//         {
//             PyErr_SetString(PyExc_TypeError,
//                     "Python kernel should contain outMeta, please use cv.gapi.kernel to define kernel");
//             return NULL;
//         }

//         PyObject* run  = PyObject_GetAttrString(user_kernel, "run");
//         if (!run)
//         {
//             PyErr_SetString(PyExc_TypeError,
//                     "Python kernel should contain run, please use cv.gapi.kernel to define kernel");
//             return NULL;
//         }

//         std::string id;
//         if (!pyopencv_to(id_obj, id, ArgInfo("id", false)))
//         {
//             PyErr_SetString(PyExc_TypeError, "Failed to obtain string");
//             return NULL;
//         }

//         using namespace std::placeholders;
//         gapi::python::GPythonFunctor f(id.c_str(),
//                 std::bind(run_py_meta  , cv::detail::PyObjectHolder{out_meta}, _1, _2),
//                 std::bind(run_py_kernel, cv::detail::PyObjectHolder{run}    , _1));
//         pkg.include(f);
//     }
//     return pyopencv_from(pkg);
// }

// static PyObject* pyopencv_cv_gapi_op(PyObject* , PyObject* py_args, PyObject*)
// {
//     using namespace cv;
//     Py_ssize_t size = PyTuple_Size(py_args);
//     std::string id;
//     if (!pyopencv_to(PyTuple_GetItem(py_args, 0), id, ArgInfo("id", false)))
//     {
//         PyErr_SetString(PyExc_TypeError, "Failed to obtain: operation id must be a string");
//         return NULL;
//     }
//     PyObject* outMeta = PyTuple_GetItem(py_args, 1);

//     cv::GArgs args;
//     for (int i = 2; i < size; i++)
//     {
//         PyObject* item = PyTuple_GetItem(py_args, i);
//         if (PyObject_TypeCheck(item,
//                     reinterpret_cast<PyTypeObject*>(pyopencv_GMat_TypePtr)))
//         {
//             args.emplace_back(reinterpret_cast<pyopencv_GMat_t*>(item)->v);
//         }
//         else if (PyObject_TypeCheck(item,
//                            reinterpret_cast<PyTypeObject*>(pyopencv_GScalar_TypePtr)))
//         {
//             args.emplace_back(reinterpret_cast<pyopencv_GScalar_t*>(item)->v);
//         }
//         else if (PyObject_TypeCheck(item,
//                            reinterpret_cast<PyTypeObject*>(pyopencv_GOpaqueT_TypePtr)))
//         {
//             auto&& arg = reinterpret_cast<pyopencv_GOpaqueT_t*>(item)->v.arg();
// #define HC(T, K) case cv::GOpaqueT::Storage:: index_of<cv::GOpaque<T>>(): \
//             args.emplace_back(cv::util::get<cv::GOpaque<T>>(arg));        \
//             break;                                                        \

//             SWITCH(arg.index(), GOPAQUE_TYPE_LIST_G, HC)
// #undef HC
//         }
//         else if (PyObject_TypeCheck(item,
//                            reinterpret_cast<PyTypeObject*>(pyopencv_GArrayT_TypePtr)))
//         {
//             auto&& arg = reinterpret_cast<pyopencv_GArrayT_t*>(item)->v.arg();
// #define HC(T, K) case cv::GArrayT::Storage:: index_of<cv::GArray<T>>(): \
//             args.emplace_back(cv::util::get<cv::GArray<T>>(arg));       \
//             break;                                                      \

//             SWITCH(arg.index(), GARRAY_TYPE_LIST_G, HC)
// #undef HC
//         }
//         else
//         {
//             args.emplace_back(cv::GArg(cv::detail::PyObjectHolder{item}));
//         }
//     }

//     cv::GKernel::M outMetaWrapper = std::bind(run_py_meta,
//                                               cv::detail::PyObjectHolder{outMeta},
//                                               std::placeholders::_1,
//                                               std::placeholders::_2);
//     return pyopencv_from(cv::gapi::wip::op(id, outMetaWrapper, std::move(args)));
// }

// template<>
// bool pyopencv_to(PyObject* obj, cv::detail::ExtractArgsCallback& value, const ArgInfo&)
// {
//     cv::detail::PyObjectHolder holder{obj};
//     value = cv::detail::ExtractArgsCallback{[=](const cv::GTypesInfo& info)
//     {
//         PyGILState_STATE gstate;
//         gstate = PyGILState_Ensure();

//         cv::GRunArgs args;
//         try
//         {
//             args = extract_run_args(info, holder.get());
//         }
//         catch (...)
//         {
//             PyGILState_Release(gstate);
//             throw;
//         }
//         PyGILState_Release(gstate);
//         return args;
//     }};
//     return true;
// }

// template<>
// bool pyopencv_to(PyObject* obj, cv::detail::ExtractMetaCallback& value, const ArgInfo&)
// {
//     cv::detail::PyObjectHolder holder{obj};
//     value = cv::detail::ExtractMetaCallback{[=](const cv::GTypesInfo& info)
//     {
//         PyGILState_STATE gstate;
//         gstate = PyGILState_Ensure();

//         cv::GMetaArgs args;
//         try
//         {
//             args = extract_meta_args(info, holder.get());
//         }
//         catch (...)
//         {
//             PyGILState_Release(gstate);
//             throw;
//         }
//         PyGILState_Release(gstate);
//         return args;
//     }};
//     return true;
// }

// template<typename T>
// struct PyOpenCV_Converter<cv::GArray<T>>
// {
//     static PyObject* from(const cv::GArray<T>& p)
//     {
//         return pyopencv_from(cv::GArrayT(p));
//     }
//     static bool to(PyObject *obj, cv::GArray<T>& value, const ArgInfo& info)
//     {
//         if (PyObject_TypeCheck(obj, reinterpret_cast<PyTypeObject*>(pyopencv_GArrayT_TypePtr)))
//         {
//             auto& array = reinterpret_cast<pyopencv_GArrayT_t*>(obj)->v;
//             try
//             {
//                 value = cv::util::get<cv::GArray<T>>(array.arg());
//             }
//             catch (...)
//             {
//                 return false;
//             }
//             return true;
//         }
//         return false;
//     }
// };

// template<typename T>
// struct PyOpenCV_Converter<cv::GOpaque<T>>
// {
//     static PyObject* from(const cv::GOpaque<T>& p)
//     {
//         return pyopencv_from(cv::GOpaqueT(p));
//     }
//     static bool to(PyObject *obj, cv::GOpaque<T>& value, const ArgInfo& info)
//     {
//         if (PyObject_TypeCheck(obj, reinterpret_cast<PyTypeObject*>(pyopencv_GOpaqueT_TypePtr)))
//         {
//             auto& opaque = reinterpret_cast<pyopencv_GOpaqueT_t*>(obj)->v;
//             try
//             {
//                 value = cv::util::get<cv::GOpaque<T>>(opaque.arg());
//             }
//             catch (...)
//             {
//                 return false;
//             }
//             return true;
//         }
//         return false;
//     }
// };

// template<>
// bool pyopencv_to(PyObject* obj, cv::GProtoInputArgs& value, const ArgInfo& info)
// {
//     try
//     {
//         value = extract_proto_args<cv::GProtoInputArgs>(obj);
//         return true;
//     }
//     catch (...)
//     {
//         failmsg("Can't parse cv::GProtoInputArgs");
//         return false;
//     }
// }

// template<>
// bool pyopencv_to(PyObject* obj, cv::GProtoOutputArgs& value, const ArgInfo& info)
// {
//     try
//     {
//         value = extract_proto_args<cv::GProtoOutputArgs>(obj);
//         return true;
//     }
//     catch (...)
//     {
//         failmsg("Can't parse cv::GProtoOutputArgs");
//         return false;
//     }
// }

// extend cv.gapi methods
// #define PYOPENCV_EXTRA_METHODS_GAPI \
//   {"kernels", CV_PY_FN_WITH_KW(pyopencv_cv_gapi_kernels), "kernels(...) -> GKernelPackage"}, \
//   {"__op", CV_PY_FN_WITH_KW(pyopencv_cv_gapi_op), "__op(...) -> retval\n"},


#endif  // HAVE_OPENCV_GAPI
