/*
 * Copyright (C) 2014 Intel Corporation. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#ifndef v4l2_codecbase_h
#define v4l2_codecbase_h

#include <assert.h>
#include <deque>
#include <vector>
#include <list>
#include "common/condition.h"
#include "VideoPostProcessHost.h"
#include "VideoDecoderInterface.h"
#if defined(__ENABLE_X11__)
#include <X11/Xlib.h>
#endif
#if defined(__ENABLE_EGL__)
#include <EGL/egl.h>
#define EGL_EGLEXT_PROTOTYPES
#include "EGL/eglext.h"
#endif
#include "VideoCommonDefs.h"
#include "vaapi/vaapiptrs.h"
#include "v4l2codec_device_ops.h"

#ifndef V4L2_EVENT_RESOLUTION_CHANGE
    #define V4L2_EVENT_RESOLUTION_CHANGE 5
#endif

#ifndef V4L2_PIX_FMT_VP9
#define V4L2_PIX_FMT_VP9 YAMI_FOURCC('V', 'P', '9', '0')
#endif

#ifndef V4L2_PIX_FMT_VC1
#define V4L2_PIX_FMT_VC1 YAMI_FOURCC('V', 'C', '1', '0')
#endif

#if defined(INPUT) || defined(OUTPUT)
    #error("conflict define for INPUT/OUTPUT")
#else
    #define INPUT 0
    #define OUTPUT 1
#endif
#define GET_PORT_INDEX(_port, _type, _ret) do {                     \
        if (_type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {           \
            _port = INPUT;                                          \
        } else if (_type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {   \
            _port = OUTPUT;                                         \
        } else {                                                    \
            _ret = -1;                                              \
            ERROR("unkown port, type num: %d", _type);              \
            break;                                                  \
        }                                                           \
    } while (0)

using namespace YamiMediaCodec;
class V4l2CodecBase {
  public:
    V4l2CodecBase();
     virtual ~V4l2CodecBase();

    typedef SharedPtr < V4l2CodecBase > V4l2CodecPtr;
    static V4l2CodecPtr createCodec(const char* name, int32_t flags);
    bool close ();
    virtual int32_t setFrameMemoryType(VideoDataMemoryType memory_type) {m_memoryType = memory_type; return 0;} ;
    virtual int32_t setSvcT(bool enable) {m_svct = enable; return 0;};
    virtual int32_t setThumbnailMode(bool enable) {m_isThumbnailMode = enable; return 0;};
    virtual int32_t ioctl(int request, void* arg);
    int32_t poll(bool poll_device, bool* event_pending);
    int32_t setDeviceEvent(int index);
    int32_t clearDeviceEvent(int index);
    virtual void* mmap(void* addr, size_t length,
                         int prot, int flags, unsigned int offset) {return NULL;};
    // virtual int32_t munmap(void* addr, size_t length) {return 0;};
    virtual bool stop() = 0;

#if defined(__ENABLE_WAYLAND__)
    bool setWaylandDisplay(struct wl_display*);
#endif //__ENABLE_WAYLAND__

#if defined(__ENABLE_X11__)
    bool setXDisplay(Display*);
#endif //__ENABLE_X11__

#if defined(__ENABLE_EGL__)
    virtual int32_t useEglImage(EGLDisplay eglDisplay, EGLContext eglContext, uint32_t buffer_index, void* egl_image) {return 0;};
#endif

    bool setDrmFd(int fd);

    void workerThread();
    int32_t fd() { return m_fd[0];};

  protected:
    virtual bool start() = 0;
    virtual bool acceptInputBuffer(struct v4l2_buffer *qbuf) = 0;
    virtual bool giveOutputBuffer(struct v4l2_buffer *dqbuf) = 0;
    virtual bool inputPulse(uint32_t index) = 0;
    virtual bool outputPulse(uint32_t &index) = 0; // index of decode output is decided by libyami, not FIFO of m_framesTodo[OUTPUT]
    virtual bool recycleInputBuffer(struct v4l2_buffer *qbuf) {return true; }
    virtual bool recycleOutputBuffer(int32_t index) {return true;};
    virtual bool hasCodecEvent();
    virtual void setCodecEvent();
    virtual void clearCodecEvent();
    virtual void releaseCodecLock(bool lockable) {};
    virtual void flush() {}

    VideoDataMemoryType m_memoryType;
    uint32_t m_maxBufferCount[2];
    uint32_t m_bufferPlaneCount[2];
    uint32_t m_memoryMode[2];
    uint32_t m_pixelFormat[2]; // (it should be a set)
    bool m_streamOn[2];
    bool m_threadOn[2];
    int32_t m_fd[2]; // 0 for device event, 1 for interrupt
    bool m_started;
    bool m_svct;
    bool m_isThumbnailMode;

    NativeDisplay m_nativeDisplay;

    enum EosState{
        EosStateNormal,
        EosStateInput,
        EosStateOutput,
    };
    // EOS state is detected(EosStateInput) or transit to EosStateOutput in subclass (V4l2Decoder/V4l2Encoder).
    // it is cleared in base class (V4l2Codec) after input thread unblock, and used for special synchronization between INPUT and OUTPUT thread
    // so, we keep its operation func in base class with lock (and m_eosState private).
    virtual EosState eosState() { return m_eosState; };
    virtual void setEosState(EosState eosState);

  private:
    bool m_hasEvent;

    pthread_t m_worker[2];
    // to be processed by codec.
    // encoder: (0:INPUT):filled with input frame data, input worker thread will send them to yami
    //          (1:OUTPUT): empty output buffer, output worker thread will fill it with coded data
    // decoder: (0:INPUT):filled with compressed frame data, input worker thread will send them to yami
    //          (1:OUTPUT): frames at codec side under processing; when output worker get one frame from yami, it should be in this set
    std::list<int> m_framesTodo[2]; // INPUT port FIFO, OUTPUT port in random order
    // processed by codec already, ready for dque
    // (0,INPUT): ready to deque for input buffer.
    // (1, OUTPUT): filled with coded data (encoder) or decoded frame (decoder).
    std::deque<int> m_framesDone[2];  // ready for deque, processed by codec already for input buffer.
    YamiMediaCodec::Lock m_frameLock[2]; // lock for INPUT/OUTPUT frames respectively

    // Condition must be initialized with Lock, but array (without default construct function) doesn't work
    YamiMediaCodec::Condition m_inputThreadCond;
    YamiMediaCodec::Condition m_outputThreadCond;
    YamiMediaCodec::Condition *m_threadCond[2];

    enum ReqBufState {
        RBS_Normal,         // normal running state
        RBS_Request,        // receive REQBUF in I/O thread
        RBS_Acknowledge,    // work thread acknowledge REQBUF (pause buffer processing)
        RBS_Released,       //buffer released, which means cannot call Qbuf or Debuf
        RBS_FormatChanged,  //buffer released, which means can still call Qbuf or Debuf
    };
    ReqBufState m_reqBufState[2];

    YamiMediaCodec::Lock m_codecLock;
    EosState  m_eosState;

    bool open(const char* name, int32_t flags);

#ifdef __ENABLE_DEBUG__
  protected:
    const char* IoctlCommandString(int command);

    uint32_t m_frameCount[2];
#endif
};

uint32_t v4l2PixelFormatFromMime(const char* mime);
const char* mimeFromV4l2PixelFormat(uint32_t pixelFormat);

#endif
