//
// Created by moqi on 2018/8/10.
//

#ifndef FRAMEWORK_VIDEO_DECODER_H
#define FRAMEWORK_VIDEO_DECODER_H

#include <vector>
#include <utils/AFMediaType.h>
#include <string>
#include <mutex>
#include <functional>
#include <af_config.h>
#include <base/media/IAFPacket.h>
#include <drm/DrmHandler.h>
#include "IVideoFrame.h"

enum decoder_status {
    got_pic,
    retry_input,
    new_format,
    decoder_eos,
    decoder_have_error,
    vt_session_invalid,
    decoder_background,
    decoder_create_fail,
    decoder_drm_error,
};

typedef enum DECODER_FRAME_STATUS {
    DECODER_FRAME_INPUT_NORMAL,
    DECODER_FRAME_INPUT_EOF,
    DECODER_FRAME_INPUT_FLUSH,
    DECODER_FRAME_OUTPUT_EOF,
} DECODER_FRAME_STATUS;

#define STATUS_GOT_PIC    (1 << got_pic)
#define STATUS_RETRY_IN   1 << retry_input
#define STATUS_NEWFORMAT  1 << new_format
#define STATUS_EOS        1 << decoder_eos
#define STATUS_HAVE_ERROR 1 << decoder_have_error
#define STATUS_VT_SESSION_INVALID  1 << vt_session_invalid
#define STATUS_VT_DECODER_BACKGROUND  1 << decoder_background
#define STATUS_CREATE_FAIL 1 << decoder_create_fail
#define STATUS_DRM_ERROR 1 << decoder_drm_error


namespace Cicada {

    class IDecoder {
    public:
        typedef struct decoder_error_info_t {
            int error;
            int64_t pts;
        } decoder_error_info;
        enum status {
            OK,
            EOS,
            RETRY = EAGAIN
        };

        IDecoder() = default;

        virtual ~IDecoder()
        {
            clean_error();
        }

        virtual int open(const Stream_meta *meta, void *voutObsr, uint64_t flags , const DrmInfo *drmInfo ) = 0;

        virtual void flush() = 0;

        virtual void updateMetaData(const Stream_meta *meta){};

        virtual void close() = 0;

        virtual void prePause() = 0;

        virtual void pause(bool pause) = 0;

        virtual void setEOF()
        {

        }

        /*
         * @param packet the packet send to decoder, use null to signals eos
         * @param timeOut the max time us to wait when packet is full in decoder
         *
         * @return
         *       -AGAIN time out was reached
         */

        virtual int send_packet(std::unique_ptr<IAFPacket> &packet, uint64_t timeOut)
        {
            return -ENOTSUP;
        };

        /*
         * @param[out] frame output the frame decoded
         * @param timeOut the max time us to wait when no frame in decoder
         * @return
         *        -AGAIN time out was reached
         */

        virtual int getFrame(std::unique_ptr<IAFFrame> &frame, uint64_t timeOut) = 0;

#ifdef ENABLE_HWDECODER
        virtual void dec_set_hw_device_content(CICADAHWDeviceType type, void *cont)
        {
            mHw_device_cont = cont;
            mHwDevicetype = type;
        }
#endif

        /*
         * return the number of error packet in decoding progress
         * you can use this to decide stop or continue the decoding
         * it will be reset to zero when flush or close
         */
        virtual int get_error_frame_no()
        {
#if AF_HAVE_PTHREAD
            std::lock_guard<std::mutex> lock(mVideoMutex);
#endif
            return static_cast<int>(mErrorQueue.size());
        }

        /*
         *  get the error info of one packet
         */
        virtual decoder_error_info get_error_frame_info(int index)
        {
#if AF_HAVE_PTHREAD
            std::lock_guard<std::mutex> lock(mVideoMutex);
#endif
            return mErrorQueue[index];
        }

        /*
         * clean the errors
         *
         */
        virtual void clean_error()
        {
#if AF_HAVE_PTHREAD
            std::lock_guard<std::mutex> lock(mVideoMutex);
#endif
            mErrorQueue.clear();
        }

        /*
         * get the decoder flags after open, not equal to the flags passed by open
         */

        virtual int getFlags()
        {
            return mFlags;
        }

        // return true if could recover
        virtual bool enterBackground(bool back)
        {
            mInBackground = back;
            return true;
        }

        std::string getName()
        {
            return mName;
        }

        virtual int holdOn(bool hold) = 0;

        virtual int getRecoverQueueSize() = 0;

        virtual uint32_t getInputPaddingSize() = 0;

        virtual bool supportReuse()
        {
            return true;
        }

        void setRequireDrmHandlerCallback(std::function<std::shared_ptr<DrmHandler>(const DrmInfo &drmInfo)> callback)
        {
            mRequireDrmHandlerCallback  = callback;
        }

    protected:
        std::string mName;
        int mFlags = 0; // VFLAG_HW,VFLAG_OUT
#if AF_HAVE_PTHREAD
        std::mutex mVideoMutex;
#endif
        std::vector<decoder_error_info> mErrorQueue;
        bool mInBackground = false;
        bool bNeedKeyFrame{true};
        int64_t keyPts = INT64_MIN;

        std::function<std::shared_ptr<DrmHandler>(const DrmInfo &drmInfo)> mRequireDrmHandlerCallback{nullptr};
    };
}


#endif //FRAMEWORK_VIDEO_DECODER_H
