/*############################################################################
  # Copyright (C) Intel Corporation
  #
  # SPDX-License-Identifier: MIT
  ############################################################################*/

#ifndef __BASE_ALLOCATOR_H__
#define __BASE_ALLOCATOR_H__

#include <string.h>
#include <functional>
#include <list>
#include <memory>
#include <mutex>
#include "vpl/mfxvideo.h"

struct mfxAllocatorParams {
    virtual ~mfxAllocatorParams(){};
};

// this class implements methods declared in mfxFrameAllocator structure
// simply redirecting them to virtual methods which should be overridden in derived classes
class MFXFrameAllocator : public mfxFrameAllocator {
public:
    MFXFrameAllocator();
    virtual ~MFXFrameAllocator();

    // optional method, override if need to pass some parameters to allocator from application
    virtual mfxStatus Init(mfxAllocatorParams *pParams) = 0;
    virtual mfxStatus Close()                           = 0;

    virtual mfxStatus AllocFrames(mfxFrameAllocRequest *request,
                                  mfxFrameAllocResponse *response) = 0;
    virtual mfxStatus ReallocFrame(mfxMemId midIn,
                                   const mfxFrameInfo *info,
                                   mfxU16 memType,
                                   mfxMemId *midOut)               = 0;
    virtual mfxStatus LockFrame(mfxMemId mid, mfxFrameData *ptr)   = 0;
    virtual mfxStatus UnlockFrame(mfxMemId mid, mfxFrameData *ptr) = 0;
    virtual mfxStatus GetFrameHDL(mfxMemId mid, mfxHDL *handle)    = 0;
    virtual mfxStatus FreeFrames(mfxFrameAllocResponse *response)  = 0;

private:
    static mfxStatus MFX_CDECL Alloc_(mfxHDL pthis,
                                      mfxFrameAllocRequest *request,
                                      mfxFrameAllocResponse *response);
    static mfxStatus MFX_CDECL Lock_(mfxHDL pthis, mfxMemId mid, mfxFrameData *ptr);
    static mfxStatus MFX_CDECL Unlock_(mfxHDL pthis, mfxMemId mid, mfxFrameData *ptr);
    static mfxStatus MFX_CDECL GetHDL_(mfxHDL pthis, mfxMemId mid, mfxHDL *handle);
    static mfxStatus MFX_CDECL Free_(mfxHDL pthis, mfxFrameAllocResponse *response);
};

// This class implements basic logic of memory allocator
// Manages responses for different components according to allocation request type
// External frames of a particular component-related type are allocated in one call
// Further calls return previously allocated response.
// Ex. Preallocated frame chain with type=FROM_ENCODE | FROM_VPPIN will be returned when
// request type contains either FROM_ENCODE or FROM_VPPIN

// This class does not allocate any actual memory

class BaseFrameAllocator : public MFXFrameAllocator {
public:
    BaseFrameAllocator();
    virtual ~BaseFrameAllocator();

    virtual mfxStatus Init(mfxAllocatorParams *pParams) = 0;
    virtual mfxStatus Close();
    virtual mfxStatus AllocFrames(mfxFrameAllocRequest *request, mfxFrameAllocResponse *response);
    virtual mfxStatus ReallocFrame(mfxMemId midIn,
                                   const mfxFrameInfo *info,
                                   mfxU16 memType,
                                   mfxMemId *midOut);
    virtual mfxStatus FreeFrames(mfxFrameAllocResponse *response);

protected:
    std::mutex mtx;
    typedef std::list<mfxFrameAllocResponse>::iterator Iter;
    static const mfxU32 MEMTYPE_FROM_MASK = MFX_MEMTYPE_FROM_ENCODE | MFX_MEMTYPE_FROM_DECODE |
                                            MFX_MEMTYPE_FROM_VPPIN | MFX_MEMTYPE_FROM_VPPOUT |
                                            MFX_MEMTYPE_FROM_ENC | MFX_MEMTYPE_FROM_PAK;

    static const mfxU32 MEMTYPE_FROM_MASK_INT_EXT =
        MEMTYPE_FROM_MASK | MFX_MEMTYPE_INTERNAL_FRAME | MFX_MEMTYPE_EXTERNAL_FRAME;

    struct UniqueResponse {
        mfxFrameAllocResponse m_resp;
        mfxU16 m_width;
        mfxU16 m_height;
        mfxU32 m_refCount;
        mfxU16 m_type;

        UniqueResponse() : m_resp({ 0 }), m_width(0), m_height(0), m_refCount(0), m_type(0) {}

        // compare responses by actual frame size, alignment (w and h) is up to application
        UniqueResponse(const mfxFrameAllocResponse &response,
                       mfxU16 width,
                       mfxU16 height,
                       mfxU16 type)
                : m_resp(response),
                  m_width(width),
                  m_height(height),
                  m_refCount(1),
                  m_type(type) {}

        //compare by resolution (and memory type for FEI ENC / PAK)
        bool operator()(const UniqueResponse &response) const {
            if (m_width <= response.m_width && m_height <= response.m_height) {
                // For FEI ENC and PAK we need to distinguish between INTERNAL and EXTERNAL frames

                if (m_type & response.m_type & (MFX_MEMTYPE_FROM_ENC | MFX_MEMTYPE_FROM_PAK)) {
                    return !!((m_type & response.m_type) & 0x000f);
                }
                else {
                    return !!(m_type & response.m_type & MFX_MEMTYPE_FROM_DECODE);
                }
            }
            else {
                return false;
            }
        }

        static mfxU16 CropMemoryTypeToStore(mfxU16 type) {
            // Remain INTERNAL / EXTERNAL flag for FEI ENC / PAK
            switch (type & 0xf000) {
                case MFX_MEMTYPE_FROM_ENC:
                case MFX_MEMTYPE_FROM_PAK:
                case (MFX_MEMTYPE_FROM_ENC | MFX_MEMTYPE_FROM_PAK):
                    return type & MEMTYPE_FROM_MASK_INT_EXT;
                    break;
                default:
                    return type & MEMTYPE_FROM_MASK;
                    break;
            }
        }
    };

    std::list<mfxFrameAllocResponse> m_responses;
    std::list<UniqueResponse> m_ExtResponses;

    struct IsSame
            : public std::binary_function<mfxFrameAllocResponse, mfxFrameAllocResponse, bool> {
        bool operator()(const mfxFrameAllocResponse &l, const mfxFrameAllocResponse &r) const {
            return r.mids != 0 && l.mids != 0 && r.mids[0] == l.mids[0] &&
                   r.NumFrameActual == l.NumFrameActual;
        }
    };

    struct IsUniqueSame : public std::binary_function<mfxFrameAllocResponse, UniqueResponse, bool> {
        bool operator()(const mfxFrameAllocResponse &l, const UniqueResponse &r) const {
            return r.m_resp.mids != 0 && l.mids != 0 && r.m_resp.mids[0] == l.mids[0] &&
                   r.m_resp.NumFrameActual == l.NumFrameActual;
        }
    };

    // checks if request is supported
    virtual mfxStatus CheckRequestType(mfxFrameAllocRequest *request);

    // frees memory attached to response
    virtual mfxStatus ReleaseResponse(mfxFrameAllocResponse *response) = 0;
    // allocates memory
    virtual mfxStatus AllocImpl(mfxFrameAllocRequest *request, mfxFrameAllocResponse *response) = 0;
    virtual mfxStatus ReallocImpl(mfxMemId midIn,
                                  const mfxFrameInfo *info,
                                  mfxU16 memType,
                                  mfxMemId *midOut)                                             = 0;
};

#if (MFX_VERSION < 2000)
class MFXBufferAllocator : public mfxBufferAllocator {
public:
    MFXBufferAllocator();
    virtual ~MFXBufferAllocator();

    virtual mfxStatus AllocBuffer(mfxU32 nbytes, mfxU16 type, mfxMemId *mid) = 0;
    virtual mfxStatus LockBuffer(mfxMemId mid, mfxU8 **ptr)                  = 0;
    virtual mfxStatus UnlockBuffer(mfxMemId mid)                             = 0;
    virtual mfxStatus FreeBuffer(mfxMemId mid)                               = 0;

private:
    static mfxStatus MFX_CDECL Alloc_(mfxHDL pthis, mfxU32 nbytes, mfxU16 type, mfxMemId *mid);
    static mfxStatus MFX_CDECL Lock_(mfxHDL pthis, mfxMemId mid, mfxU8 **ptr);
    static mfxStatus MFX_CDECL Unlock_(mfxHDL pthis, mfxMemId mid);
    static mfxStatus MFX_CDECL Free_(mfxHDL pthis, mfxMemId mid);
};

#else
class MFXBufferAllocator {
public:
    MFXBufferAllocator();
    virtual ~MFXBufferAllocator();

    virtual mfxStatus AllocBuffer(mfxU32 nbytes, mfxU16 type, mfxMemId *mid) = 0;
    virtual mfxStatus LockBuffer(mfxMemId mid, mfxU8 **ptr)                  = 0;
    virtual mfxStatus UnlockBuffer(mfxMemId mid)                             = 0;
    virtual mfxStatus FreeBuffer(mfxMemId mid)                               = 0;
    mfxHDL pthis;
    mfxStatus(MFX_CDECL *Alloc)(mfxHDL pthis, mfxU32 nbytes, mfxU16 type, mfxMemId *mid);
    mfxStatus(MFX_CDECL *Lock)(mfxHDL pthis, mfxMemId mid, mfxU8 **ptr);
    mfxStatus(MFX_CDECL *Unlock)(mfxHDL pthis, mfxMemId mid);
    mfxStatus(MFX_CDECL *Free)(mfxHDL pthis, mfxMemId mid);

private:
    static mfxStatus MFX_CDECL Alloc_(mfxHDL pthis, mfxU32 nbytes, mfxU16 type, mfxMemId *mid);
    static mfxStatus MFX_CDECL Lock_(mfxHDL pthis, mfxMemId mid, mfxU8 **ptr);
    static mfxStatus MFX_CDECL Unlock_(mfxHDL pthis, mfxMemId mid);
    static mfxStatus MFX_CDECL Free_(mfxHDL pthis, mfxMemId mid);
};
#endif
#endif // __BASE_ALLOCATOR_H__
