/*
 * This file is part of the PikaPython project.
 * http://github.com/pikastech/pikapython
 *
 * MIT License
 *
 * Copyright (c) 2021 lyon liang6516@outlook.com
 *
 * 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.
 */

#define __DATA_MEMORY_CLASS_IMPLEMENT__
#include "dataMemory.h"
#include "PikaPlatform.h"

volatile PikaMemInfo g_PikaMemInfo = {0};

void* pikaMalloc(uint32_t size) {
    /* pika memory lock */
    if (0 != pika_is_locked_pikaMemory()) {
        pika_platform_wait();
    }

//! if you unsure about the __impl_pikaMalloc, uncomment this to force alignment
#if PIKA_ARG_ALIGN_ENABLE
    /* force alignment to avoid unaligned access */
    size = mem_align(size);
#endif

    g_PikaMemInfo.heapUsed += size;
    if (g_PikaMemInfo.heapUsedMax < g_PikaMemInfo.heapUsed) {
        g_PikaMemInfo.heapUsedMax = g_PikaMemInfo.heapUsed;
    }
    pika_platform_disable_irq_handle();
    void* mem = pika_user_malloc(size);
    pika_platform_enable_irq_handle();
    if (NULL == mem) {
        pika_platform_printf(
            "Error: No heap space! Please reset the device.\r\n");
        while (1) {
        }
    }
    return mem;
}

void pikaFree(void* mem, uint32_t size) {
    pika_assert(mem != NULL);
    if (0 != pika_is_locked_pikaMemory()) {
        pika_platform_wait();
    }

//! if you unsure about the __impl_pikaMalloc, uncomment this to force alignment
#if PIKA_ARG_ALIGN_ENABLE
    /* force alignment to avoid unaligned access */
    size = mem_align(size);
#endif

    pika_platform_disable_irq_handle();
    pika_user_free(mem, size);
    pika_platform_enable_irq_handle();
    g_PikaMemInfo.heapUsed -= size;
}

uint32_t pikaMemNow(void) {
    return g_PikaMemInfo.heapUsed;
    // return 0;
}

uint32_t pikaMemMax(void) {
    return g_PikaMemInfo.heapUsedMax;
}

void pikaMemMaxReset(void) {
    g_PikaMemInfo.heapUsedMax = 0;
}

#if PIKA_POOL_ENABLE
uint32_t pool_getBlockIndex_byMemSize(Pool* pool, uint32_t size) {
    if (0 == size) {
        return 0;
    }
    return (size - 1) / pool->aline + 1;
}

uint32_t pool_aline(Pool* pool, uint32_t size) {
    return pool_getBlockIndex_byMemSize(pool, size) * pool->aline;
}

Pool pool_init(uint32_t size, uint8_t aline) {
    Pool pool;
    pool.aline = aline;
    uint32_t block_size = pool_getBlockIndex_byMemSize(&pool, size);
    pool.size = pool_aline(&pool, size);
    pool.bitmap = bitmap_init(block_size);
    pool.mem = pika_platform_malloc(pool_aline(&pool, pool.size));
    pool.first_free_block = 0;
    pool.purl_free_block_start = 0;
    pool.inited = pika_true;
    return pool;
}

void pool_deinit(Pool* pool) {
    pika_platform_free(pool->mem);
    pool->mem = NULL;
    bitmap_deinit(pool->bitmap);
}

void* pool_getBytes_byBlockIndex(Pool* pool, uint32_t block_index) {
    return pool->mem + block_index * pool->aline;
}

uint32_t pool_getBlockIndex_byMem(Pool* pool, void* mem) {
    uint32_t mem_size = (uintptr_t)mem - (uintptr_t)pool->mem;
    return pool_getBlockIndex_byMemSize(pool, mem_size);
}

void pool_printBlocks(Pool* pool, uint32_t size_min, uint32_t size_max) {
    uint32_t block_index_min = pool_getBlockIndex_byMemSize(pool, size_min);
    uint32_t block_index_max = pool_getBlockIndex_byMemSize(pool, size_max);
    pika_platform_printf("[bitmap]\r\n");
    uint8_t is_end = 0;
    for (uint32_t i = block_index_min; i < block_index_max; i += 16) {
        if (is_end) {
            break;
        }
        pika_platform_printf("0x%x\t: 0x%d", i * pool->aline,
                             (i + 15) * pool->aline);
        for (uint32_t j = i; j < i + 16; j += 4) {
            if (is_end) {
                break;
            }
            for (uint32_t k = j; k < j + 4; k++) {
                if (k >= block_index_max) {
                    is_end = 1;
                    break;
                }
                pika_platform_printf("%d", bitmap_get(pool->bitmap, k));
            }
            pika_platform_printf(" ");
        }
        pika_platform_printf("\r\n");
    }
}

void* pool_malloc(Pool* pool, uint32_t size) {
    uint32_t block_index_max = pool_getBlockIndex_byMemSize(pool, pool->size);
    uint32_t block_num_need = pool_getBlockIndex_byMemSize(pool, size);
    uint32_t block_num_found = 0;
    uint8_t found_first_free = 0;
    uint32_t block_index;
    /* high speed malloc */
    block_index = pool->purl_free_block_start + block_num_need - 1;
    if (block_index < block_index_max) {
        goto found;
    }
    /* low speed malloc */
    for (block_index = pool->first_free_block;
         block_index < pool->purl_free_block_start; block_index++) {
        /* 8 bit is not free */
        uint8_t bitmap_byte = bitmap_getByte(pool->bitmap, block_index);
        if (0xFF == bitmap_byte) {
            block_index = 8 * (block_index / 8) + 7;
            block_num_found = 0;
            continue;
        }
        /* found a free block */
        if (0 == bitmap_get(pool->bitmap, block_index)) {
            /* save the first free */
            if (!found_first_free) {
                pool->first_free_block = block_index;
                found_first_free = 1;
            }
            block_num_found++;
        } else {
            /* a used block appeared, find again */
            block_num_found = 0;
        }
        /* found all request blocks */
        if (block_num_found >= block_num_need) {
            goto found;
        }
    }
    /* malloc for purl free blocks */
    block_index = pool->purl_free_block_start + block_num_need - 1;
    if (block_index < block_index_max) {
        goto found;
    }

    /* no found */
    return NULL;
found:
    /* set 1 for found blocks */
    for (uint32_t i = 0; i < block_num_need; i++) {
        bitmap_set(pool->bitmap, block_index - i, 1);
    }
    /* save last used block */
    if (block_index >= pool->purl_free_block_start) {
        pool->purl_free_block_start = block_index + 1;
    }
    /* return mem by block index */
    return pool_getBytes_byBlockIndex(pool, block_index - block_num_need + 1);
}

void pool_free(Pool* pool, void* mem, uint32_t size) {
    uint32_t block_num = pool_getBlockIndex_byMemSize(pool, size);
    uint32_t block_index = pool_getBlockIndex_byMem(pool, mem);
    for (uint32_t i = 0; i < block_num; i++) {
        bitmap_set(pool->bitmap, block_index + i, 0);
    }
    /* save min free block index to add speed */
    if (block_index < pool->first_free_block) {
        pool->first_free_block = block_index;
    }
    /* save last free block index to add speed */
    uint32_t block_end = block_index + block_num - 1;
    if (block_end == pool->purl_free_block_start - 1) {
        uint32_t first_pure_free_block = block_index;
        /* back to first used block */
        if (0 != first_pure_free_block) {
            while (0 == bitmap_get(pool->bitmap, first_pure_free_block - 1)) {
                first_pure_free_block--;
                if (0 == first_pure_free_block) {
                    break;
                }
            }
        }
        pool->purl_free_block_start = first_pure_free_block;
    }
    return;
}

BitMap bitmap_init(uint32_t size) {
    BitMap mem_bit_map =
        (BitMap)pika_platform_malloc(((size - 1) / 8 + 1) * sizeof(char));
    if (mem_bit_map == NULL) {
        return NULL;
    }
    uint32_t size_mem_bit_map = (size - 1) / 8 + 1;
    pika_platform_memset(mem_bit_map, 0x0, size_mem_bit_map);
    return mem_bit_map;
}

void bitmap_set(BitMap bitmap, uint32_t index, uint8_t bit) {
    uint32_t index_byte = index / 8;
    uint8_t index_bit = index % 8;
    uint8_t x = (0x1 << index_bit);
    /* set 1 */
    if (bit) {
        bitmap[index_byte] |= x;
        return;
    }
    /* set 0 */
    bitmap[index_byte] &= ~x;
    return;
}

uint8_t bitmap_getByte(BitMap bitmap, uint32_t index) {
    uint32_t index_byte = (index) / 8;
    uint8_t byte;
    byte = bitmap[index_byte];
    return byte;
}

uint8_t bitmap_get(BitMap bitmap, uint32_t index) {
    uint32_t index_byte = (index) / 8;
    uint8_t index_bit = (index) % 8;
    uint8_t x = (0x1 << index_bit);
    uint8_t bit;
    bit = bitmap[index_byte] & x;
    return bit > 0 ? 1 : 0;
}

void bitmap_deinit(BitMap bitmap) {
    pika_platform_free(bitmap);
}

Pool pikaPool = {0};
void* pika_user_malloc(size_t size) {
    return pool_malloc(&pikaPool, size);
}
void pika_user_free(void* ptrm, size_t size) {
    pool_free(&pikaPool, ptrm, size);
}
#endif

void mem_pool_init(void) {
#if PIKA_POOL_ENABLE
    if (!pikaPool.inited) {
        pikaPool = pool_init(PIKA_POOL_SIZE, PIKA_POOL_ALIGN);
    }
#endif
}

void _mem_cache_deinit(void) {
#if PIKA_ARG_CACHE_ENABLE
    while (g_PikaMemInfo.cache_pool_top) {
        pika_user_free(
            g_PikaMemInfo.cache_pool[g_PikaMemInfo.cache_pool_top - 1], 0);
        g_PikaMemInfo.cache_pool_top--;
    }
#endif
}

void mem_pool_deinit(void) {
    _mem_cache_deinit();
#if PIKA_POOL_ENABLE
    pool_deinit(&pikaPool);
#endif
}
