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

#ifndef __D3D11_ALLOCATOR_H__
#define __D3D11_ALLOCATOR_H__

#include <limits>
#include "base_allocator.h"

#ifdef __gnu_linux__
    #include <stdint.h> // for uintptr_t on Linux
#endif

//application can provide either generic mid from surface or this wrapper
//wrapper distinguishes from generic mid by highest 1 bit
//if it set then remained pointer points to extended structure of memid
//64 bits system layout
/*----+-----------------------------------------------------------+
|b63=1|63 bits remained for pointer to extended structure of memid|
|b63=0|63 bits from original mfxMemId                             |
+-----+----------------------------------------------------------*/
//32 bits system layout
/*--+---+--------------------------------------------+
|b31=1|31 bits remained for pointer to extended memid|
|b31=0|31 bits remained for surface pointer          |
+---+---+-------------------------------------------*/
//#pragma warning (disable:4293)
class MFXReadWriteMid {
    static const uintptr_t bits_offset = std::numeric_limits<uintptr_t>::digits - 1;
    static const uintptr_t clear_mask  = ~((uintptr_t)1 << bits_offset);

public:
    enum {
        //if flag not set it means that read and write
        not_set = 0,
        reuse   = 1,
        read    = 2,
        write   = 4,
    };
    //here mfxmemid might be as MFXReadWriteMid or mfxMemId memid
    MFXReadWriteMid(mfxMemId mid, mfxU8 flag = not_set) {
        //setup mid
        m_mid_to_report = (mfxMemId)((uintptr_t)&m_mid | ((uintptr_t)1 << bits_offset));
        if (0 != ((uintptr_t)mid >> bits_offset)) {
            //it points to extended structure
            mfxMedIdEx* pMemIdExt = reinterpret_cast<mfxMedIdEx*>((uintptr_t)mid & clear_mask);
            m_mid.pId             = pMemIdExt->pId;
            if (reuse == flag) {
                m_mid.read_write = pMemIdExt->read_write;
            }
            else {
                m_mid.read_write = flag;
            }
        }
        else {
            m_mid.pId = mid;
            if (reuse == flag)
                m_mid.read_write = not_set;
            else
                m_mid.read_write = flag;
        }
    }
    bool isRead() const {
        return 0 != (m_mid.read_write & read) || !m_mid.read_write;
    }
    bool isWrite() const {
        return 0 != (m_mid.read_write & write) || !m_mid.read_write;
    }
    /// returns original memid without read write flags
    mfxMemId raw() const {
        return m_mid.pId;
    }
    operator mfxMemId() const {
        return m_mid_to_report;
    }

private:
    struct mfxMedIdEx {
        mfxMemId pId;
        mfxU8 read_write;
    };

    mfxMedIdEx m_mid;
    mfxMemId m_mid_to_report;
};

#if (defined(_WIN32) || defined(_WIN64))

    #include <d3d11.h>
    #include <map>
    #include <vector>

struct ID3D11VideoDevice;
struct ID3D11VideoContext;

struct D3D11AllocatorParams : mfxAllocatorParams {
    ID3D11Device* pDevice;
    bool bUseSingleTexture;
    DWORD uncompressedResourceMiscFlags;

    D3D11AllocatorParams() : pDevice(), bUseSingleTexture(), uncompressedResourceMiscFlags() {}
};

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

    virtual mfxStatus Init(mfxAllocatorParams* pParams);
    virtual mfxStatus Close();
    virtual ID3D11Device* GetD3D11Device() {
        return m_initParams.pDevice;
    };
    virtual mfxStatus LockFrame(mfxMemId mid, mfxFrameData* ptr);
    virtual mfxStatus UnlockFrame(mfxMemId mid, mfxFrameData* ptr);
    virtual mfxStatus GetFrameHDL(mfxMemId mid, mfxHDL* handle);

protected:
    static DXGI_FORMAT ConverColortFormat(mfxU32 fourcc);
    virtual mfxStatus CheckRequestType(mfxFrameAllocRequest* request);
    virtual mfxStatus ReleaseResponse(mfxFrameAllocResponse* response);
    virtual mfxStatus AllocImpl(mfxFrameAllocRequest* request, mfxFrameAllocResponse* response);
    virtual mfxStatus ReallocImpl(mfxMemId midIn,
                                  const mfxFrameInfo* info,
                                  mfxU16 memType,
                                  mfxMemId* midOut);

    D3D11AllocatorParams m_initParams;
    ID3D11DeviceContext* m_pDeviceContext;

    struct TextureResource {
        std::vector<mfxMemId> outerMids;
        std::vector<ID3D11Texture2D*> textures;
        std::vector<ID3D11Texture2D*> stagingTexture;
        bool bAlloc;

        TextureResource() : outerMids(), textures(), stagingTexture(), bAlloc(true) {}

        static bool isAllocated(TextureResource& that) {
            return that.bAlloc;
        }
        ID3D11Texture2D* GetTexture(mfxMemId id) {
            if (outerMids.empty())
                return NULL;

            return textures[((uintptr_t)id - (uintptr_t)outerMids.front()) % textures.size()];
        }
        UINT GetSubResource(mfxMemId id) {
            if (outerMids.empty())
                return NULL;

            return (UINT)(((uintptr_t)id - (uintptr_t)outerMids.front()) / textures.size());
        }
        void Release() {
            size_t i = 0;
            for (i = 0; i < textures.size(); i++) {
                textures[i]->Release();
            }
            textures.clear();

            for (i = 0; i < stagingTexture.size(); i++) {
                stagingTexture[i]->Release();
            }
            stagingTexture.clear();

            //marking texture as deallocated
            bAlloc = false;
        }
    };
    class TextureSubResource {
        TextureResource* m_pTarget;
        ID3D11Texture2D* m_pTexture;
        ID3D11Texture2D* m_pStaging;
        UINT m_subResource;

    public:
        TextureSubResource(TextureResource* pTarget = NULL, mfxMemId id = 0)
                : m_pTarget(pTarget),
                  m_pTexture(),
                  m_subResource(),
                  m_pStaging(NULL) {
            if (NULL != m_pTarget && !m_pTarget->outerMids.empty()) {
                ptrdiff_t idx =
                    (uintptr_t)MFXReadWriteMid(id).raw() - (uintptr_t)m_pTarget->outerMids.front();
                m_pTexture    = m_pTarget->textures[idx % m_pTarget->textures.size()];
                m_subResource = (UINT)(idx / m_pTarget->textures.size());
                m_pStaging =
                    m_pTarget->stagingTexture.empty() ? NULL : m_pTarget->stagingTexture[idx];
            }
        }
        ID3D11Texture2D* GetStaging() const {
            return m_pStaging;
        }
        ID3D11Texture2D* GetTexture() const {
            return m_pTexture;
        }
        UINT GetSubResource() const {
            return m_subResource;
        }
        void Release() {
            if (NULL != m_pTarget)
                m_pTarget->Release();
        }
    };

    TextureSubResource GetResourceFromMid(mfxMemId);

    std::list<TextureResource> m_resourcesByRequest; //each alloc request generates new item in list

    typedef std::list<TextureResource>::iterator referenceType;
    std::vector<referenceType> m_memIdMap;
};

#endif // #if defined(_WIN32) || defined(_WIN64)
#endif // __D3D11_ALLOCATOR_H__
