#ifndef SURFACEINTEROPCV_H
#define SURFACEINTEROPCV_H

#ifdef __APPLE__
//MacOS/iOS header
#include <CoreVideo/CoreVideo.h>

#include "SurfaceInterop.h"
#include "NVSurfaceInteropFactory.h"


namespace cv {

NVPixelFormatEx::PixelFormat format_from_cv(int cv);

typedef bool (*NVPixelFormatExToGL)(const NVPixelFormatEx& fmt,int32_t * iformat, uint32_t* mformat, uint32_t* dtype);

extern NVPixelFormatExToGL g_NVPixelFormatExToGL;

typedef uint32_t GLuint; // define here to avoid including gl headers which are not required by decoder
typedef uint32_t GLenum;
typedef int32_t  GLint;

// FIXME: not texture if change from InteropCVOpenGLES to InteropCVPixelBuffer
enum InteropType {
    InteropCVPixelBuffer,   // macOS+ios
    InteropIOSurface,       // macOS
    InteropCVOpenGL,        // macOS, not implemented
    InteropCVOpenGLES,       // ios
    InteropAuto
};

class InteropResource
{
public:
    InteropResource();
    // Must have CreateInteropXXX in each implemention
    static InteropResource* create(InteropType type);
    virtual ~InteropResource() {}
    /*!
     * \brief stridesForWidth
     * The stride used by opengl can be different in some interop because frame display format can change (outFmt), for example we can use rgb for uyvy422. The default value use the origial pixel format to comupte the strides.
     * If the input strides has a valid value, use this value to compute the output strides. Otherwise, use width to compute the output strides without taking care of padded data size.
     */
    virtual bool stridesForWidth(int cvfmt, int width, int* strides/*in_out*/, NVPixelFormatEx::PixelFormat* outFmt);
    virtual bool mapToTexture2D() const { return true;}
    // egl supports yuv extension
    /*!
     * \brief map
     * \param buf vt decoded buffer
     * \param texInOut opengl texture. You can modify it
     * \param w frame width(visual width) without alignment, <= dxva surface width
     * \param h frame height(visual height)
     * \param plane useless now
     * \return true if success
     */
    virtual bool map(CVPixelBufferRef buf, GLuint *texInOut, int w, int h, int plane) = 0;
    virtual bool unmap(CVPixelBufferRef buf, GLuint tex) {
        UNUSED(buf);
        UNUSED(tex);
        return true;
    }
    virtual GLuint createTexture(CVPixelBufferRef, const NVPixelFormatEx &fmt, int plane, int planeWidth, int planeHeight) {
        UNUSED(fmt);
        UNUSED(plane);
        UNUSED(planeWidth);
        UNUSED(planeHeight);
        return 0;
    }
    void getParametersGL(OSType cvpixfmt, GLint* internalFormat, GLenum* format, GLenum* dataType, int plane = 0);
    virtual bool is_need_release_in_uithread() = 0;
private:
    OSType m_cvfmt;
    GLint m_iformat[4];
    GLenum m_format[4];
    GLenum m_dtype[4];
};
typedef std::shared_ptr<InteropResource> InteropResourcePtr;

class SurfaceInteropCV : public VideoSurfaceInterop
{
public:
    SurfaceInteropCV(const InteropResourcePtr& res) : frame_width(0), frame_height(0), m_resource(res) {}
    ~SurfaceInteropCV();
    void setSurface(CVPixelBufferRef buf, int w, int h);
    void* map(SurfaceType type, const NVPixelFormatEx& fmt, void* handle, int plane) override;
    void unmap(void *handle) override;
    bool CopyTo(uint8_t* data[4], int strides[4]) override { return false; }
    bool CopyTo(void* textures[4]) override { return false; }
    virtual void* createHandle(void* handle, SurfaceType type, const NVPixelFormatEx &fmt, int plane, int planeWidth, int planeHeight) override;
    bool is_need_release_in_uithread() override;
protected:
    void* mapToHost(const NVPixelFormatEx &format, void *handle, int plane);
private:
    int frame_width, frame_height;
    InteropResourcePtr m_resource;
    CVPixelBufferRef m_surface;
};
} // namespace cv

namespace NVSurfaceInterop {

    struct CVSurfaceInterOpBuilder: public SurfaceInterOpBuilder{

        //we have a implementation for this.
        //You can override this if needed.
        virtual cv::InteropResource* create(cv::InteropType type);

        //Please provide this in Qt.
        virtual cv::InteropResource* CreateInteropIOSurface() = 0;

        virtual std::string name() const override {return "CVSurfaceInterOpBuilder";}

        virtual SurfaceInterOp* build() override {return nullptr;}

        virtual inline cv::SurfaceInteropCV* build(const cv::InteropResourcePtr& res){
            return new cv::SurfaceInteropCV(res);
        }
    };
}

#endif
#endif //SURFACEINTEROPCV_H
