/*
 * Copyright 2018-2019 NXP
 * All rights reserved.
 *
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "mem_manager.h"
#if (defined(MEM_MANAGER_ENABLE_TRACE) && (MEM_MANAGER_ENABLE_TRACE > 0U))
#include "fsl_debug_console.h"
#endif

/*****************************************************************************
******************************************************************************
* Private macros
******************************************************************************
*****************************************************************************/
#if defined(__IAR_SYSTEMS_ICC__)
#define __mem_get_LR() __get_LR()
#elif defined(__GNUC__)
#define __mem_get_LR() __builtin_return_address(0)
#elif defined(__CC_ARM) || defined(__ARMCC_VERSION)
#define __mem_get_LR() __return_address()
#endif

#if (defined(MEM_MANAGER_PRE_CONFIGURE) && (MEM_MANAGER_PRE_CONFIGURE > 0U))
#undef _block_set_
#undef _eol_

#define _eol_       ;
#define _block_set_ MEM_BLOCK_BUFFER_NONAME_DEFINE

PoolsDetails_c

#undef _block_set_
#undef _number_of_blocks_
#undef _eol_
#undef _pool_id_

#define _eol_       ,
#define _block_set_ MEM_BLOCK_NONAME_BUFFER

    static uint8_t *s_PoolList[] = {PoolsDetails_c};
#endif /*MEM_MANAGER_PRE_CONFIGURE*/

/*****************************************************************************
******************************************************************************
* Private type definitions
******************************************************************************
*****************************************************************************/
/*! @brief Buffer pools structure*/
typedef struct _mem_pool_structure
{
    struct _mem_pool_structure *nextPool;
    uint8_t *pHeap;
    uint32_t heapSize;
#if (defined(MEM_MANAGER_ENABLE_TRACE) && (MEM_MANAGER_ENABLE_TRACE > 0U))
    uint16_t allocatedBlocksPeak;
    uint16_t poolFragmentWaste;
    uint16_t poolTotalFragmentWaste;
    uint16_t poolFragmentWastePeak;
    uint16_t poolFragmentMinWaste;
#endif /*MEM_MANAGER_ENABLE_TRACE*/
    uint16_t poolId;
    uint16_t blockSize;
    uint16_t numBlocks;
    uint16_t allocatedBlocks;
} mem_pool_structure_t;

/*! @brief Header description for buffers.*/
typedef struct _block_list_header
{
    uint16_t allocated;
    uint16_t blockSize;
#if (defined(MEM_MANAGER_ENABLE_TRACE) && (MEM_MANAGER_ENABLE_TRACE > 0U))
    uint32_t caller;
    uint16_t allocatedBytes;
#endif /*MEM_MANAGER_ENABLE_TRACE*/
} block_list_header_t;

/*! @brief State structure for memory manager. */
typedef struct _mem_manager_info
{
    mem_pool_structure_t *pPool;
    uint16_t poolNum;
#if (defined(MEM_MANAGER_ENABLE_TRACE) && (MEM_MANAGER_ENABLE_TRACE > 0U))
    uint16_t allocationFailures;
    uint16_t freeFailures;
#endif /*MEM_MANAGER_ENABLE_TRACE*/
} mem_manager_info_t;

/*****************************************************************************
******************************************************************************
* Public memory declarations
******************************************************************************
*****************************************************************************/
/*****************************************************************************
 *****************************************************************************
 * Private prototypes
 *****************************************************************************
 *****************************************************************************/
/*****************************************************************************
 *****************************************************************************
 * Private memory definitions
 *****************************************************************************
 *****************************************************************************/
static mem_manager_info_t s_memmanager = {0};
/*****************************************************************************
******************************************************************************
* Private API macro define
******************************************************************************
*****************************************************************************/

/*****************************************************************************
******************************************************************************
* Private functions
******************************************************************************
*****************************************************************************/

/*****************************************************************************
******************************************************************************
* Public functions
******************************************************************************
*****************************************************************************/
/*!
 * @brief  Initialises the Memory Manager.
 *
 */
mem_status_t MEM_Init(void)
{
    static bool initialized = false;
    assert(sizeof(mem_pool_structure_t) == MEM_POOL_SIZE);
    assert(sizeof(block_list_header_t) == MEM_BLOCK_SIZE);
    if (!initialized)
    {
        s_memmanager.pPool   = NULL;
        s_memmanager.poolNum = 0;
#if (defined(MEM_MANAGER_ENABLE_TRACE) && (MEM_MANAGER_ENABLE_TRACE > 0U))
        s_memmanager.allocationFailures = 0;
        s_memmanager.freeFailures       = 0;
#endif /*MEM_MANAGER_ENABLE_TRACE*/
#if (defined(MEM_MANAGER_PRE_CONFIGURE) && (MEM_MANAGER_PRE_CONFIGURE > 0U))
        for (uint8_t i = 0; i < (sizeof(s_PoolList) / sizeof(s_PoolList[0])); i++)
        {
            (void)MEM_AddBuffer((uint8_t *)s_PoolList[i]);
        }
#endif /*MEM_MANAGER_PRE_CONFIGURE*/
        initialized = true;
    }
    return kStatus_MemSuccess;
}

/*!
 * @brief Add memory buffer to memory manager buffer list.
 *
 * @note This API should be called when need add memory buffer to memory manager buffer list. First use
 * MEM_BLOCK_BUFFER_DEFINE to
 *        define memory buffer, then call MEM_AddBuffer function with MEM_BLOCK_BUFFER Macro.
 *  @code
 * MEM_BLOCK_BUFFER_DEFINE(app64, 5, 64,0);
 * MEM_BLOCK_BUFFER_DEFINE(app128, 6, 128,0);
 * MEM_BLOCK_BUFFER_DEFINE(app256, 7, 256,0);
 *
 * MEM_AddBuffer(MEM_BLOCK_BUFFER(app64));
 * MEM_AddBuffer(MEM_BLOCK_BUFFER(app128));
 * MEM_AddBuffer(MEM_BLOCK_BUFFER(app256));
 *  @endcode
 *
 * @param buffer                     Pointer the memory pool buffer, use MEM_BLOCK_BUFFER Macro as the input parameter.
 *
 * @retval kStatus_MemSuccess        Memory manager add Buffer succeed.
 * @retval kStatus_MemInitError      Memory manager add Buffer error occurred.
 */
mem_status_t MEM_AddBuffer(uint8_t *buffer)
{
    mem_config_t *memConfig     = (mem_config_t *)(void *)buffer;
    mem_pool_structure_t *pPool = (mem_pool_structure_t *)(void *)memConfig->pbuffer;
    uint8_t *pHeap              = memConfig->pbuffer + sizeof(mem_pool_structure_t);
    mem_pool_structure_t *pPrevPool, *pTempPool;

    assert(buffer);
    assert(memConfig->numberOfBlocks);
    assert(memConfig->blockSize);

    uint32_t regPrimask = DisableGlobalIRQ();
#if (defined(MEM_MANAGER_PRE_CONFIGURE) && (MEM_MANAGER_PRE_CONFIGURE == 0U))
    (void)MEM_Init();
#endif
    pPool->pHeap     = pHeap;
    pPool->numBlocks = memConfig->numberOfBlocks;
    pPool->blockSize = memConfig->blockSize;
    pPool->poolId    = *(uint16_t *)(void *)(&buffer[4]);
    pPool->heapSize =
        (MEM_POOL_SIZE + (uint32_t)memConfig->numberOfBlocks * (MEM_BLOCK_SIZE + (uint32_t)memConfig->blockSize));
#if (defined(MEM_MANAGER_ENABLE_TRACE) && (MEM_MANAGER_ENABLE_TRACE > 0U))
    pPool->allocatedBlocksPeak    = 0;
    pPool->poolTotalFragmentWaste = 0;
    pPool->poolFragmentWaste      = 0;
    pPool->poolFragmentWastePeak  = 0;
    pPool->poolFragmentMinWaste   = 0xffff;
#endif /*MEM_MANAGER_ENABLE_TRACE*/
    if (s_memmanager.pPool == NULL)
    {
        s_memmanager.pPool = pPool;
    }
    else
    {
        pTempPool = s_memmanager.pPool;
        pPrevPool = pTempPool;
        while (NULL != pTempPool)
        {
            if (((pPool->blockSize >= pPrevPool->blockSize) && (pPool->blockSize <= pTempPool->blockSize)) ||
                (pPool->blockSize <= pPrevPool->blockSize))
            {
                if (pTempPool == s_memmanager.pPool)
                {
                    s_memmanager.pPool = pPool;
                }
                else
                {
                    pPrevPool->nextPool = pPool;
                }
                pPool->nextPool = pTempPool;
                break;
            }
            pPrevPool = pTempPool;
            pTempPool = pTempPool->nextPool;
        }
        if (pPool->blockSize > pPrevPool->blockSize)
        {
            pPrevPool->nextPool = pPool;
        }
    }

    s_memmanager.poolNum++;
    EnableGlobalIRQ(regPrimask);
    return kStatus_MemSuccess;
}

/*!
 * @brief Remove memory buffer from memory manager buffer list.
 *
 * @note This API should be called when need remove memory buffer to memory manager buffer list. Use with
 * MEM_BLOCK_BUFFER Macro as input parameter.
 *
 * @param buffer                     Pointer the memory pool buffer, use MEM_BLOCK_BUFFER Macro as the input parameter.
 *
 * @retval kStatus_MemSuccess        Memory manager remove buffer succeed.
 * @retval kStatus_MemUnknownError      Memory manager remove buffer error occurred.
 */
#if (defined(MEM_MANAGER_BUFFER_REMOVE) && (MEM_MANAGER_BUFFER_REMOVE > 0U))
mem_status_t MEM_RemoveBuffer(uint8_t *buffer)
{
    mem_config_t *memConfig     = (mem_config_t *)(void *)buffer;
    mem_pool_structure_t *pPool = (mem_pool_structure_t *)memConfig->pbuffer;
    uint8_t *pHeap              = memConfig->pbuffer + sizeof(mem_pool_structure_t);
    mem_pool_structure_t *pPrevPool, *pTempPool;

    assert(buffer);
    assert(((mem_config_t *)buffer)->numberOfBlocks);
    assert(((mem_config_t *)buffer)->blockSize);

    uint32_t regPrimask = DisableGlobalIRQ();
    pTempPool           = s_memmanager.pPool;
    pPrevPool           = pTempPool;
    while (NULL != pTempPool)
    {
        if (0U != pPool->allocatedBlocks)
        {
            break;
        }
        if (pTempPool->pHeap == pHeap)
        {
            if (pPool == s_memmanager.pPool)
            {
                s_memmanager.pPool = pPool->nextPool;
            }
            else
            {
                pPrevPool->nextPool = pPool->nextPool;
            }
            s_memmanager.poolNum--;
            EnableGlobalIRQ(regPrimask);
            return kStatus_MemSuccess;
        }
        pPrevPool = pTempPool;
        pTempPool = pTempPool->nextPool;
    }
    EnableGlobalIRQ(regPrimask);
    return kStatus_MemUnknownError;
}
#endif // MEM_MANAGER_BUFFER_REMOVE

/*!
 * @brief Allocate a block from the memory pools. The function uses the
 *        numBytes argument to look up a pool with adequate block sizes.
 *
 * @param numBytes           The number of bytes will be allocated.
 * @param poolId             The ID of the pool where to search for a free buffer.
 * @retval Memory buffer address when allocate success, NULL when allocate fail.
 */
void *MEM_BufferAllocWithId(uint32_t numBytes, uint8_t poolId)
{
#if (defined(MEM_MANAGER_ENABLE_TRACE) && (MEM_MANAGER_ENABLE_TRACE > 0U))
    uint32_t fragmentWaste = 0;
#endif /*MEM_MANAGER_ENABLE_TRACE*/
    mem_pool_structure_t *pPool = s_memmanager.pPool;
    block_list_header_t *pBlock;

    uint32_t regPrimask = DisableGlobalIRQ();

    while (0U != numBytes)
    {
        if ((numBytes <= pPool->blockSize) && (pPool->poolId == poolId))
        {
            for (uint32_t i = 0; i < pPool->numBlocks; i++)
            {
                pBlock = (block_list_header_t *)(void *)(pPool->pHeap + i * ((uint32_t)pPool->blockSize + (uint32_t)sizeof(block_list_header_t)));
                if ((NULL != pBlock) && (0U == pBlock->allocated))
                {
#if (defined(MEM_MANAGER_ENABLE_TRACE) && (MEM_MANAGER_ENABLE_TRACE > 0U))
                    pBlock->allocatedBytes = (uint16_t)numBytes;
                    pBlock->caller         = (uint32_t)((uint32_t *)__mem_get_LR());
#endif /*MEM_MANAGER_ENABLE_TRACE*/
                    pBlock->allocated = 1;
                    pBlock->blockSize = pPool->blockSize;
                    pBlock++;
                    pPool->allocatedBlocks++;
#if (defined(MEM_MANAGER_ENABLE_TRACE) && (MEM_MANAGER_ENABLE_TRACE > 0U))
                    if (pPool->allocatedBlocks > pPool->allocatedBlocksPeak)
                    {
                        pPool->allocatedBlocksPeak = pPool->allocatedBlocks;
                    }
                    fragmentWaste = pPool->blockSize - numBytes;
                    if (fragmentWaste > pPool->poolFragmentWastePeak)
                    {
                        pPool->poolFragmentWastePeak = (uint16_t)fragmentWaste;
                    }
                    pPool->poolFragmentWaste = (uint16_t)fragmentWaste;
                    pPool->poolTotalFragmentWaste += (uint16_t)fragmentWaste;
                    if (fragmentWaste < pPool->poolFragmentMinWaste)
                    {
                        pPool->poolFragmentMinWaste = (uint16_t)fragmentWaste;
                    }
#endif /*MEM_MANAGER_ENABLE_TRACE*/
                    EnableGlobalIRQ(regPrimask);
                    return pBlock;
                }
            }
        }
        /* Try next pool*/
        pPool = pPool->nextPool;
        if (NULL == pPool)
        {
            break;
        }
    }
#if (defined(MEM_MANAGER_ENABLE_TRACE) && (MEM_MANAGER_ENABLE_TRACE > 0U))
    s_memmanager.allocationFailures++;
#endif /*MEM_MANAGER_ENABLE_TRACE*/
    EnableGlobalIRQ(regPrimask);
    return NULL;
}

/*!
 * @brief Memory buffer free.
 *
 * @param buffer                     The memory buffer address will be free.
 * @retval kStatus_MemSuccess        Memory free succeed.
 * @retval kStatus_MemFreeError      Memory free error occurred.
 */
mem_status_t MEM_BufferFree(void *buffer /* IN: Block of memory to free*/
)
{
    block_list_header_t *pBlock;
    uint32_t regPrimask = DisableGlobalIRQ();

    do
    {
        if (NULL == buffer)
        {
            break;
        }
        pBlock = (block_list_header_t *)buffer - 1;
        assert(pBlock);
        if (1U == pBlock->allocated)
        {
            (void)memset(pBlock, 0x0, sizeof(block_list_header_t));
            EnableGlobalIRQ(regPrimask);
            return kStatus_MemSuccess;
        }

#if (defined(MEM_MANAGER_ENABLE_TRACE) && (MEM_MANAGER_ENABLE_TRACE > 0U))
        s_memmanager.freeFailures++;
#endif /*MEM_MANAGER_ENABLE_TRACE*/

    } while (false);

    EnableGlobalIRQ(regPrimask);
    return kStatus_MemFreeError;
}

/*!
 * @brief Returns the size of a given buffer.
 *
 * @param buffer                     The memory buffer address will be free.
 * @retval The size of a given buffer.
 */
uint16_t MEM_BufferGetSize(void *buffer) /* IN: Block of memory to get size*/
{
    block_list_header_t *pBlock;
    uint32_t regPrimask = DisableGlobalIRQ();

    pBlock = (block_list_header_t *)buffer - 1;

    if (buffer != NULL)
    {
        EnableGlobalIRQ(regPrimask);
        return pBlock->blockSize;
    }
    EnableGlobalIRQ(regPrimask);
    return 0;
}

/*!
 * @brief Frees all allocated blocks by selected source and in selected pool.
 *
 * @param poolId                     Selected pool Id (4 LSBs of poolId parameter) and selected
 *                                   source Id (4 MSBs of poolId parameter).
 * @retval kStatus_MemSuccess        Memory free succeed.
 * @retval kStatus_MemFreeError      Memory free error occurred.
 */
mem_status_t MEM_BufferFreeAllWithId(uint8_t poolId)
{
    mem_pool_structure_t *pPool = s_memmanager.pPool;
    block_list_header_t *pBlock;

    uint32_t regPrimask = DisableGlobalIRQ();

    while (pPool != NULL)
    {
        if (pPool->poolId == poolId)
        {
            for (uint16_t i = 0; i < pPool->numBlocks; i++)
            {
                pBlock = (block_list_header_t *)(void *)(pPool->pHeap + i * (pPool->blockSize + sizeof(block_list_header_t)));
                (void)memset(pBlock, 0x0, sizeof(block_list_header_t));
            }
#if (defined(MEM_MANAGER_ENABLE_TRACE) && (MEM_MANAGER_ENABLE_TRACE > 0U))
            pPool->allocatedBlocksPeak    = 0;
            pPool->poolTotalFragmentWaste = 0;
            pPool->poolFragmentWaste      = 0;
            pPool->poolFragmentWastePeak  = 0;
            pPool->poolFragmentMinWaste   = 0xffff;
#endif /*MEM_MANAGER_ENABLE_TRACE*/
        }
        pPool = pPool->nextPool;
    }

    EnableGlobalIRQ(regPrimask);
    return kStatus_MemSuccess;
}

/*!
 * @brief Memory buffer realloc.
 *
 * @param buffer                     The memory buffer address will be reallocated.
 * @param new_size                   The number of bytes will be reallocated
 * @retval kStatus_MemSuccess        Memory free succeed.
 * @retval kStatus_MemFreeError      Memory free error occurred.
 */
void *MEM_BufferRealloc(void *buffer, uint32_t new_size)
{
    void *realloc_buffer = NULL;
    uint16_t block_size  = 0U;

    if (new_size == 0U)
    {
        /* new requested size is 0, free old buffer */
        (void)MEM_BufferFree(buffer);
        realloc_buffer = NULL;
    }
    else if (buffer == NULL)
    {
        /* input buffer is NULL simply allocate a new buffer and return it */
        realloc_buffer = MEM_BufferAllocWithId(new_size, 0U);
    }
    else
    {
        block_size = MEM_BufferGetSize(buffer);

        if ((uint16_t)new_size <= block_size)
        {
            /* current buffer is large enough for the new requested size
               we can still use it */
            realloc_buffer = buffer;
        }
        else
        {
            /* not enough space in the current block, creating a new one */
            realloc_buffer = MEM_BufferAllocWithId(new_size, 0U);

            if (realloc_buffer != NULL)
            {
                /* copy input buffer data to new buffer */
                (void)memcpy(realloc_buffer, buffer, (uint32_t)block_size);

                /* free old buffer */
                (void)MEM_BufferFree(buffer);
            }
        }
    }

    return realloc_buffer;
}

/*!
 * @brief Trace memory manager all information to use debug.
 *
 */
#if (defined(MEM_MANAGER_ENABLE_TRACE) && (MEM_MANAGER_ENABLE_TRACE > 0U))
void MEM_Trace(void)
{
    mem_pool_structure_t *pPool = s_memmanager.pPool;
    block_list_header_t *pBlock;
    (void)PRINTF("MEM_Trace debug information, Pools Number:%d   allocationFailures: %d  freeFailures:%d \r\n",
                 s_memmanager.poolNum, s_memmanager.allocationFailures, s_memmanager.allocationFailures,
                 s_memmanager.freeFailures);
    while (NULL != pPool)
    {
        (void)PRINTF("POOL: ID %d  blockSize:%d   status:\r\n", pPool->poolId, pPool->blockSize);
        (void)PRINTF(
            "numBlocks allocatedBlocks  allocatedBlocksPeak  poolFragmentWaste poolFragmentWastePeak "
            "poolFragmentMinWaste poolTotalFragmentWaste\r\n");
        (void)PRINTF(
            "    %d          %d                %d                %d                  %d                       %d       "
            "              %d           \r\n",
            pPool->numBlocks, pPool->allocatedBlocks, pPool->allocatedBlocksPeak, pPool->poolFragmentWaste,
            pPool->poolFragmentWastePeak, pPool->poolFragmentMinWaste, pPool->poolTotalFragmentWaste);
        (void)PRINTF("Currently pool meory block allocate status: \r\n");
        for (uint32_t i = 0; i < pPool->numBlocks; i++)
        {
            pBlock = (block_list_header_t *)(pPool->pHeap + i * (pPool->blockSize + sizeof(block_list_header_t)));

            (void)PRINTF("Block %d caller : 0x%x Allocated %d bytes: %d  \r\n", i, pBlock->caller, pBlock->allocated,
                         pBlock->allocatedBytes);
        }
        /* Try next pool*/
        pPool = pPool->nextPool;
    }
}
#endif /*MEM_MANAGER_ENABLE_TRACE*/
