/**
 *!
 * \file        b_util_memp.c
 * \version     v0.0.1
 * \date        2020/04/01
 * \author      Bean(notrynohigh@outlook.com)
 *******************************************************************************
 * @attention
 *
 * Copyright (c) 2020 Bean
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *******************************************************************************
 */

/*Includes ----------------------------------------------*/
#include "utils/inc/b_util_memp.h"

#include "utils/inc/b_util_log.h"

#if (defined(_MEMP_ENABLE) && (_MEMP_ENABLE == 1))

/**
 * \addtogroup B_UTILS
 * \{
 */

/**
 * \addtogroup MEMP
 * \{
 */

/**
 * \defgroup MEMP_Private_TypesDefinitions
 * \{
 */
#pragma pack(1)
typedef struct head
{
    uint32_t     status : 8;
    uint32_t     size : 24;
    struct head *next;
} bMempUnitHead_t;
#pragma pack()

/**
 * \}
 */

/**
 * \defgroup MEMP_Private_Defines
 * \{
 */
#define MEMP_SIZE (((MEMP_MAX_SIZE) >> 2) << 2)

#define MEMP_UNIT_USED (0xAA)
#define MEMP_UNIT_FREE (0x55)

#if defined(__CC_ARM)
__attribute__((aligned(4))) static uint8_t bMempBuf[MEMP_SIZE];
#elif defined(__ICCARM__)
#pragma data_alignment = 4
static uint8_t bMempBuf[MEMP_SIZE];
#elif defined(__GNUC__)
__attribute__((aligned(4))) static uint8_t bMempBuf[MEMP_SIZE];
#endif

/**
 * \}
 */

/**
 * \defgroup MEMP_Private_Macros
 * \{
 */

/**
 * \}
 */

/**
 * \defgroup MEMP_Private_Variables
 * \{
 */
static uint8_t bMempInitFlag = 0;
/**
 * \}
 */

/**
 * \defgroup MEMP_Private_FunctionPrototypes
 * \{
 */

/**
 * \}
 */

/**
 * \defgroup MEMP_Private_Functions
 * \{
 */

static void _bMempInit()
{
    bMempUnitHead_t *phead = (bMempUnitHead_t *)bMempBuf;
    if (bMempInitFlag == 0)
    {
        memset(bMempBuf, 0, sizeof(bMempBuf));
        phead->status = MEMP_UNIT_FREE;
        phead->size   = MEMP_SIZE - sizeof(bMempUnitHead_t);
        phead->next   = NULL;
        bMempInitFlag = 1;
    }
}

static void *_bMempAlloc(uint32_t size)
{
    size = B_SIZE_ALIGNMENT(size, 4);

    bMempUnitHead_t *phead      = (bMempUnitHead_t *)bMempBuf;
    void            *pret       = NULL;
    uint32_t         real_size  = size + sizeof(bMempUnitHead_t);
    bMempUnitHead_t *p_new_head = NULL;
    while (phead)
    {
        if (phead->status == MEMP_UNIT_USED)
        {
            phead = phead->next;
        }
        else
        {
            if (phead->size == size)
            {
                phead->status = MEMP_UNIT_USED;
                pret          = (void *)(((char *)phead) + sizeof(bMempUnitHead_t));
            }
            else if (phead->size < real_size)
            {
                phead = phead->next;
            }
            else
            {
                phead->status = MEMP_UNIT_USED;
                pret          = (void *)(((char *)phead) + sizeof(bMempUnitHead_t));
                // create a new unit
                p_new_head         = (bMempUnitHead_t *)(((char *)pret) + size);
                p_new_head->status = MEMP_UNIT_FREE;
                p_new_head->size   = phead->size - size - sizeof(bMempUnitHead_t);
                p_new_head->next   = phead->next;
                phead->size        = size;
                phead->next        = p_new_head;
            }
        }
        if (pret != NULL)
        {
            break;
        }
    }
    if (pret == NULL)
    {
        b_log_e("memp alloc fail, size:%d", size);
        bMallocFailedHook();
    }
    return pret;
}

static void _bMempFree(uint32_t addr)
{
    bMempUnitHead_t *phead = (bMempUnitHead_t *)(addr - sizeof(bMempUnitHead_t));
    if (phead->status != MEMP_UNIT_USED ||
        addr < (((uint32_t)bMempBuf) + sizeof(bMempUnitHead_t)) ||
        addr > (((uint32_t)bMempBuf) + MEMP_SIZE - sizeof(bMempUnitHead_t)))
    {
        return;
    }
    phead->status = MEMP_UNIT_FREE;
    phead         = (bMempUnitHead_t *)bMempBuf;

    while (phead != NULL)
    {
        if (phead->status == MEMP_UNIT_FREE)
        {
            while (phead->next != NULL && phead->next->status == MEMP_UNIT_FREE)
            {
                phead->size += phead->next->size + sizeof(bMempUnitHead_t);
                phead->next = phead->next->next;
            }
        }
        phead = phead->next;
    }
}

static uint32_t _bGetFreeSize()
{
    bMempUnitHead_t *phead = (bMempUnitHead_t *)bMempBuf;
    uint32_t         ret   = 0;
    while (phead)
    {
        if (phead->status == MEMP_UNIT_FREE)
        {
            ret += phead->size;
        }
        phead = phead->next;
    }
    return ret;
}

static uint32_t _bGetUsableSize(void *addr)
{
    bMempUnitHead_t *phead = (bMempUnitHead_t *)(addr - sizeof(bMempUnitHead_t));
    if (phead->status != MEMP_UNIT_USED ||
        (uint32_t)addr < (((uint32_t)bMempBuf) + sizeof(bMempUnitHead_t)) ||
        (uint32_t)addr > (((uint32_t)bMempBuf) + MEMP_SIZE - sizeof(bMempUnitHead_t)))
    {
        return 0;
    }
    return phead->size;
}

/**
 * \}
 */

/**
 * \addtogroup MEMP_Exported_Functions
 * \{
 */

#if (defined(_MEMP_MONITOR_ENABLE) && (_MEMP_MONITOR_ENABLE == 1))
static void *_bMalloc(uint32_t size)
{
    _bMempInit();
    if (size == 0)
    {
        return NULL;
    }
    return _bMempAlloc(size);
}

static void *_bCalloc(uint32_t num, uint32_t size)
{
    void *p = NULL;
    _bMempInit();
    if (size == 0 || num == 0)
    {
        return NULL;
    }
    p = _bMempAlloc(size * num);
    if (p)
    {
        memset(p, 0, (size * num));
    }
    return p;
}

static void _bFree(void *paddr)
{
    if (paddr == NULL || bMempInitFlag == 0)
    {
        return;
    }
    _bMempFree((uint32_t)paddr);
}

static void *_bRealloc(void *paddr, uint32_t size)
{
    if (paddr == NULL)
    {
        return bMalloc(size);
    }
    if (size == 0)
    {
        bFree(paddr);
        return NULL;
    }
    void *new_ptr = bMalloc(size);
    if (new_ptr == NULL)
    {
        return NULL;
    }
    uint32_t old_size  = _bGetUsableSize(paddr);
    uint32_t copy_size = (size < old_size) ? size : old_size;
    memcpy(new_ptr, paddr, copy_size);
    bFree(paddr);
    return new_ptr;
}

#else
void *bMalloc(uint32_t size)
{
    _bMempInit();
    if (size == 0)
    {
        return NULL;
    }
    return _bMempAlloc(size);
}

void *bCalloc(uint32_t num, uint32_t size)
{
    void *p = NULL;
    _bMempInit();
    if (size == 0 || num == 0)
    {
        return NULL;
    }
    p = _bMempAlloc(size * num);
    if (p)
    {
        memset(p, 0, (size * num));
    }
    return p;
}

void bFree(void *paddr)
{
    if (paddr == NULL || bMempInitFlag == 0)
    {
        return;
    }
    _bMempFree((uint32_t)paddr);
}

void *bRealloc(void *paddr, uint32_t size)
{
    if (paddr == NULL)
    {
        return bMalloc(size);
    }
    if (size == 0)
    {
        bFree(paddr);
        return NULL;
    }
    void *new_ptr = bMalloc(size);
    if (new_ptr == NULL)
    {
        return NULL;
    }
    uint32_t old_size  = _bGetUsableSize(paddr);
    uint32_t copy_size = (size < old_size) ? size : old_size;
    memcpy(new_ptr, paddr, copy_size);
    bFree(paddr);
    return new_ptr;
}

#endif

uint32_t bGetFreeSize()
{
    _bMempInit();
    return _bGetFreeSize();
}

uint32_t bGetTotalSize()
{
    return MEMP_SIZE;
}

#if defined(__WEAKDEF)
__WEAKDEF void bMallocFailedHook()
{
    ;
}
#else
static void (*pMallocFailHook)(void) = NULL;
void bMallocRegisterHook(void (*hook)(void))
{
    pMallocFailHook = hook;
}
void bMallocFailedHook()
{
    if (pMallocFailHook)
    {
        pMallocFailHook();
    }
}
#endif

#if (defined(_MEMP_MONITOR_ENABLE) && (_MEMP_MONITOR_ENABLE == 1))

void *bMallocPlus(uint32_t size, const char *func, int line)
{
    void *ptr = _bMalloc(size);
    b_log("malloc in %s, %d, size %d, address %p\r\n", func, line, size, ptr);
    return ptr;
}

void *bCallocPlus(uint32_t num, uint32_t size, const char *func, int line)
{
    void *ptr = _bCalloc(num, size);
    b_log("calloc in %s, %d, num %d size %d, address %p\r\n", func, line, num, size, ptr);
    return ptr;
}

void bFreePlus(void *ptr, const char *func, int line)
{
    _bFree(ptr);
    b_log("free in %s, %d, address %p\r\n", func, line, ptr);
}

void *bReallocPlus(void *ptr, uint32_t size, const char *func, int line)
{
    void *new_ptr = _bRealloc(ptr, size);
    b_log("realloc in %s, %d, address %p size %d\r\n", func, line, ptr, size);
    return new_ptr;
}

#endif

/**
 * \}
 */

/**
 * \}
 */

/**
 * \}
 */

#endif

/************************ Copyright (c) 2020 Bean *****END OF FILE****/