// SPDX-FileCopyrightText: © 2023 Allegro DVT <github-ip@allegrodvt.com>
// SPDX-License-Identifier: MIT

/****************************************************************************
   -----------------------------------------------------------------------------
 **************************************************************************//*!
   \addtogroup lib_base
   @{
   \file
 *****************************************************************************/
#pragma once

#ifdef __cplusplus
extern "C" {
#endif

#include "lib_common/Allocator.h"
#include "lib_common/BufferAPI.h"
#include "lib_common/BufferMeta.h"

/*************************************************************************//*!
   \brief AL_TBufPoolConfig: Used to configure the AL_TBufPool
*****************************************************************************/
typedef struct al_t_BufPoolConfig
{
  uint32_t uNumBuf; /*!< number of buffer in the pool */
  size_t zBufSize;/*!< Size of the buffers that will fill the pool */
  char const* debugName;
  AL_TMetaData* pMetaData;/*!< Metadata of the buffer that will fill the pool */
}AL_TBufPoolConfig;

typedef struct
{
  size_t m_zMaxElem;
  size_t m_zTail;
  size_t m_zHead;
  void** m_ElemBuffer;
  AL_MUTEX hMutex;
  AL_EVENT hEvent;
  int m_iBufNumber;
  bool m_isDecommited;
  AL_SEMAPHORE hSpaceSem;
}App_Fifo;

/*************************************************************************//*!
   \brief Buffer Access mode: Do we want to wait if no buffer is available or to fail fast.
*****************************************************************************/
typedef enum
{
  AL_BUF_MODE_BLOCK,
  AL_BUF_MODE_NONBLOCK,
  /* sentinel */
  AL_BUF_MODE_MAX
}AL_EBufMode;

uint32_t AL_GetWaitMode(AL_EBufMode eMode);

/*************************************************************************//*!
   \brief AL_TBufPool: Pool of buffer
*****************************************************************************/
typedef struct
{
  AL_TAllocator* pAllocator; /*! Allocator used to allocate the buffers */

  AL_TBuffer** pPool; /*! pool of allocated buffers */
  uint32_t uNumBuf; /*! Number of buffer in the pool */

  size_t zBufSize; /*! Size of the buffers in the pool */
  AL_TMetaData* pCreationMeta; /*! Metadata added at buffers creation */

  App_Fifo fifo;
}AL_TBufPool;

/*************************************************************************//*!
   \brief AL_BufPool_Init Initialize the AL_TBufPool
   \param[in] pBufPool Pointer to an AL_TBufPool
   \param[in] pAllocator Pointer to a real allocator
   \param[in] pConfig Pointer to an AL_TBufPoolConfig object
   \return return true on success, false on failure
*****************************************************************************/
bool AL_BufPool_Init(AL_TBufPool* pBufPool, AL_TAllocator* pAllocator, AL_TBufPoolConfig* pConfig);

/*************************************************************************//*!
   \brief AL_BufPool_Deinit Deiniatilize the AL_TBufPool
   \param[in] pBufPool Pointer to an AL_TBufPool
*****************************************************************************/
void AL_BufPool_Deinit(AL_TBufPool* pBufPool);

/*************************************************************************//*!
   \brief AL_BufPool_GetBuffer Get a buffer from the pool
   \param[in] pBufPool Pointer to an AL_TBufPool
   \param[in] eMode Get mode. blocking or non blocking
   \return return the buffer or NULL in case of failure in the non blocking case
*****************************************************************************/
AL_TBuffer* AL_BufPool_GetBuffer(AL_TBufPool* pBufPool, AL_EBufMode eMode);
/*************************************************************************//*!
   \brief AL_BufPool_AddMetaData creates and adds a metadata on all buffers (even if referenced)
   \param[in] pBufPool Pointer to an AL_TBufPool
   \param[in] pMeta Pointer to a metadata
   \return return true on success, false on failure
*****************************************************************************/
bool AL_BufPool_AddMetaData(AL_TBufPool* pBufPool, AL_TMetaData* pMeta);
/*************************************************************************//*!
   \brief AL_BufPool_Decommit Decommit the pool. This deblocks all the blocking
   call to AL_BufPool_GetBuffer.
   \param[in] pBufPool Pointer to an AL_TBufPool.
*****************************************************************************/
void AL_BufPool_Decommit(AL_TBufPool* pBufPool);
void AL_BufPool_Commit(AL_TBufPool* pBufPool);

/*****************************************************************************/

/*@}*/

#ifdef __cplusplus
}

#include <stdexcept>
class bufpool_decommited_error : public std::runtime_error
{
public:
  explicit bufpool_decommited_error() : std::runtime_error("bufpool_decommited_error")
  {
  }
};

/************************    RAII wrappers    *****************************/
struct BaseBufPool
{
  virtual ~BaseBufPool()
  {
    AL_BufPool_Deinit(&m_pool);
  }

  bool AddMetaData(AL_TMetaData* pMeta)
  {
    return AL_BufPool_AddMetaData(&m_pool, pMeta);
  }

  AL_TBuffer* GetBuffer(AL_EBufMode mode = AL_BUF_MODE_BLOCK)
  {
    AL_TBuffer* pBuf = AL_BufPool_GetBuffer(&m_pool, mode);

    if(mode == AL_BUF_MODE_BLOCK && pBuf == nullptr)
      throw bufpool_decommited_error();
    return pBuf;
  }

  void Decommit()
  {
    AL_BufPool_Decommit(&m_pool);
  }

  void Commit()
  {
    AL_BufPool_Commit(&m_pool);
  }

  AL_TBufPool m_pool {};

protected:
  bool InitStructure(AL_TAllocator* pAllocator, uint32_t uNumBuf, size_t zBufSize, AL_TMetaData* pCreationMeta);
  bool AddBuf(AL_TBuffer* pBuf);
  static void FreeBufInPool(AL_TBuffer* pBuf);
};

struct BufPool : public BaseBufPool
{
  BufPool() = default;

  BufPool(AL_TAllocator* pAllocator, AL_TBufPoolConfig& config)
  {
    AL_BufPool_Init(&m_pool, pAllocator, &config);
  }

  int Init(AL_TAllocator* pAllocator, AL_TBufPoolConfig& config)
  {
    return AL_BufPool_Init(&m_pool, pAllocator, &config);
  }
};

#endif
