// 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) 2020 Intel Corporation

#ifndef OPENCV_GAPI_RMAT_HPP
#define OPENCV_GAPI_RMAT_HPP

#include <opencv2/gapi/gmat.hpp>
#include <opencv2/gapi/own/exports.hpp>

namespace cv {

// "Remote Mat", a general class which provides an abstraction layer over the data
// storage and placement (host, remote device etc) and allows to access this data.
//
// The device specific implementation is hidden in the RMat::Adapter class
//
// The basic flow is the following:
// * Backend which is aware of the remote device:
//   - Implements own AdapterT class which is derived from RMat::Adapter
//   - Wraps device memory into RMat via make_rmat utility function:
//         cv::RMat rmat = cv::make_rmat<AdapterT>(args);
//
// * End user:
//   - Writes the code which works with RMats without any knowledge of the remote device:
//     void func(const cv::RMat& in_rmat, cv::RMat& out_rmat) {
//         // Fetch input data from the device, get mapped memory for output
//         cv::RMat::View  in_view =  in_rmat.access(Access::R);
//         cv::RMat::View out_view = out_rmat.access(Access::W);
//         performCalculations(in_view, out_view);
//         // data from out_view is transferred to the device when out_view is destroyed
//     }
class GAPI_EXPORTS RMat
{
public:
    // A lightweight wrapper on image data:
    // - Doesn't own the memory;
    // - Doesn't implement copy semantics (it's assumed that a view is created each time
    // wrapped data is being accessed);
    // - Has an optional callback which is called when the view is destroyed.
    class GAPI_EXPORTS View
    {
    public:
        using DestroyCallback = std::function<void()>;

        View() = default;
        View(const GMatDesc& desc, uchar* data, size_t step = 0u, DestroyCallback&& cb = nullptr)
            : m_desc(desc), m_data(data), m_step(step == 0u ? elemSize()*cols() : step), m_cb(std::move(cb))
        {}

        View(const View&) = delete;
        View& operator=(const View&) = delete;
        View(View&&) = default;
        View& operator=(View&& v);
        ~View() { if (m_cb) m_cb(); }

        cv::Size size() const { return m_desc.size; }
        const std::vector<int>& dims() const { return m_desc.dims; }
        int cols() const { return m_desc.size.width; }
        int rows() const { return m_desc.size.height; }
        int type() const { return CV_MAKE_TYPE(depth(), chan()); }
        int depth() const { return m_desc.depth; }
        int chan() const { return m_desc.chan; }
        size_t elemSize() const { return CV_ELEM_SIZE(type()); }

        template<typename T = uchar> T* ptr(int y = 0, int x = 0) {
            return reinterpret_cast<T*>(m_data + m_step*y + x*CV_ELEM_SIZE(type()));
        }
        template<typename T = uchar> const T* ptr(int y = 0, int x = 0) const {
            return reinterpret_cast<const T*>(m_data + m_step*y + x*CV_ELEM_SIZE(type()));
        }
        size_t step() const { return m_step; }

    private:
        GMatDesc m_desc;
        uchar* m_data = nullptr;
        size_t m_step = 0u;
        DestroyCallback m_cb = nullptr;
    };

    enum class Access { R, W };
    class Adapter
    {
    public:
        virtual ~Adapter() = default;
        virtual GMatDesc desc() const = 0;
        // Implementation is responsible for setting the appropriate callback to
        // the view when accessed for writing, to ensure that the data from the view
        // is transferred to the device when the view is destroyed
        virtual View access(Access) = 0;
    };
    using AdapterP = std::shared_ptr<Adapter>;

    RMat() = default;
    RMat(AdapterP&& a) : m_adapter(std::move(a)) {}
    GMatDesc desc() const { return m_adapter->desc(); }

    // Note: When accessed for write there is no guarantee that returned view
    // will contain actual snapshot of the mapped device memory
    // (no guarantee that fetch from a device is performed). The only
    // guaranty is that when the view is destroyed, its data will be
    // transferred to the device
    View access(Access a) const { return m_adapter->access(a); }

    // Cast underlying RMat adapter to the particular adapter type,
    // return nullptr if underlying type is different
    template<typename T> T* get() const
    {
        static_assert(std::is_base_of<Adapter, T>::value, "T is not derived from Adapter!");
        GAPI_Assert(m_adapter != nullptr);
        return dynamic_cast<T*>(m_adapter.get());
    }

private:
    AdapterP m_adapter = nullptr;
};

template<typename T, typename... Ts>
RMat make_rmat(Ts&&... args) { return { std::make_shared<T>(std::forward<Ts>(args)...) }; }

} //namespace cv

#endif /* OPENCV_GAPI_RMAT_HPP */
