#ifndef __REF_MEMORY_H__
#define __REF_MEMORY_H__

#include <maque_type.h>
#include <maque_mem.h>
#include <pthread.h>
#include <spdlog/spdlog.h>
#include <evpacket.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef struct {
    XM_U8 *pBuffer;
    XM_U32 nBufSize;
    XM_U32 nUsedBytes;
    XM_U32 nRefCount;
    XM_S32 index;
} DemoMemory_s;

static pthread_mutex_t g_mutexMem;
static pthread_mutexattr_t Attr;

// = PTHREAD_MUTEX_INITIALIZER;

void MaQue_Demo_Mem_Init(){
    pthread_mutexattr_init(&Attr);
    pthread_mutexattr_settype(&Attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&g_mutexMem, &Attr);
}

XM_S32 MaQue_Demo_Mem_setLength(XM_HANDLE handle, XM_U32 len)
{
    pthread_mutex_lock(&g_mutexMem);
    DemoMemory_s *pstMem = (DemoMemory_s *)handle;
    pstMem->nUsedBytes = len;

    pthread_mutex_unlock(&g_mutexMem);
    return 0;
}

XM_S32 MaQue_Demo_Mem_alloc(XM_HANDLE *pHandle, MaQueMemAllocParam_s *pstAllocParam)
{
    DemoMemory_s *pstMem = NULL;
    pstMem = (DemoMemory_s *)malloc(sizeof(DemoMemory_s));
    if (!pstMem) {
        spdlog::error("malloc failed!");
        return -1;
    }
    memset(pstMem, 0, sizeof(*pstMem));
    
    pthread_mutex_lock(&g_mutexMem);

    switch(pstAllocParam->eMemType) {
    case MAQUE_MEM_TYPE_VIDEO_ENC:
    case MAQUE_MEM_TYPE_JPEG_ENC:
    case MAQUE_MEM_TYPE_YUV_GET:
    {
        /// NOTE by Bruce: ATTENTION! we place the extr evpacket_t ahead for saving memory ops later
        #ifdef RAW_FORMAT
        XM_U8 * raw = (XM_U8 *)malloc(pstAllocParam->nBufSize);
        pstMem->pBuffer =  raw;
        #else
        XM_U8 * raw = (XM_U8 *)malloc(pstAllocParam->nBufSize + sizeof(evpacket_t));
        pstMem->pBuffer =  raw + sizeof(evpacket_t);
        #endif
        //printf("malloc. raw: %08X, shifted: %08X\n", (uint32_t)raw, (uint32_t)pstMem->pBuffer);
        if (raw) {
            pstMem->index = 0xff;
            pstMem->nBufSize = pstAllocParam->nBufSize;
            pstMem->nRefCount = 1;
        }else {
            spdlog::error("memory allocate failure");
            free(pstMem);
            pstMem = NULL;
        }
    }
    break;
    default:
        spdlog::error("Invalid eMemType: {}", pstAllocParam->eMemType);
        break;
    }

    if (!pstMem) {
        spdlog::error("alloc failed");
        pthread_mutex_unlock(&g_mutexMem);
        return -1;
    }

    *pHandle = (XM_HANDLE)pstMem;
    pstAllocParam->pBuffer = pstMem->pBuffer;
    pthread_mutex_unlock(&g_mutexMem);

    return 0;
}

XM_S32 MaQue_Demo_Mem_release(XM_HANDLE handle)
{
    if(handle == 0){
        return 0;
    }
    pthread_mutex_lock(&g_mutexMem);
    DemoMemory_s *pstMem = (DemoMemory_s *)handle;    
    if (!handle) {
        pthread_mutex_unlock(&g_mutexMem);
        return 0;
    }

    #ifdef RAW_FORMAT
    if(pstMem == nullptr || pstMem->pBuffer == nullptr ){
        pthread_mutex_unlock(&g_mutexMem);
        return 0;
    }

    XM_U8* raw = pstMem->pBuffer;
    //printf("release. h:%08X, raw: %08X, shifted: %08X, %uref\n", (int)handle, (int)raw, (int)shifted, pstMem->nRefCount);
    if (pstMem->nRefCount > 1) {
        pstMem->nRefCount--;
    }
    else if (raw) {
        /// NOTE by Bruce: ATTENTION!
        free(raw);
        free(pstMem);
    }else if(pstMem->nRefCount < 0){
        spdlog::error("shouldn't be here. refcnt:{}", pstMem->nRefCount);
    }
    else{
        spdlog::error("shouldn't be here");
    }
    #else
    if(pstMem == nullptr || pstMem->pBuffer == nullptr || pstMem->pBuffer - sizeof(evpacket_t) == nullptr || pstMem->nRefCount == 0){
        //printf("invalid free: %08X, %08X, %uref\n", (int)pstMem, (int)pstMem->pBuffer, pstMem->nRefCount);
        pthread_mutex_unlock(&g_mutexMem);
        return 0;
    }

    XM_U8* shifted = pstMem->pBuffer;
    XM_U8* raw = shifted - sizeof(evpacket_t);
    //printf("release. h:%08X, raw: %08X, shifted: %08X, %uref\n", (int)handle, (int)raw, (int)shifted, pstMem->nRefCount);
    if (pstMem->nRefCount > 1) {
        pstMem->nRefCount--;
    }
    else if (shifted && raw) {
        /// NOTE by Bruce: ATTENTION!
        free(raw);
        free(pstMem);
    }else if(pstMem->nRefCount < 0){
        spdlog::error("shouldn't be here. refcnt:{}", pstMem->nRefCount);
    }
    else if(raw == nullptr || shifted == nullptr){
        printf("BOOM! r: %08X, s: %08X\n", (int)raw, (int)shifted);
    }else{
        spdlog::error("shouldn't be here");
    }
    #endif

    pthread_mutex_unlock(&g_mutexMem);
    return 0;
}

XM_S32 MaQue_Demo_Mem_addRef(XM_HANDLE handle)
{
    pthread_mutex_lock(&g_mutexMem);
    DemoMemory_s *pstMem = (DemoMemory_s *)handle;
    // memory already released
    if(!handle) {
        pthread_mutex_unlock(&g_mutexMem);
        spdlog::error("BOOM! ar");
        return -1;
    }

    pstMem->nRefCount += 1;

    pthread_mutex_unlock(&g_mutexMem);

    return 0;
}

#ifdef __cplusplus
}
#endif

#endif