/*
 ******************************************************************************
 Project:      OWA EPANET
 Version:      2.3
 Module:       mempool.c
 Description:  a simple fast poooled memory allocation package
 Authors:      see AUTHORS
 Copyright:    see AUTHORS
 License:      see LICENSE
 Last Updated: 08/02/2023

 This module is based on code by Steve Hill in Graphics Gems III,
 David Kirk (ed.), Academic Press, Boston, MA, 1992
 ******************************************************************************
*/

#include <stdlib.h>

#include "mempool.h"

/*
**  ALLOC_BLOCK_SIZE - adjust this size to suit your installation - it
**  should be reasonably large otherwise you will be mallocing a lot.
*/

#define ALLOC_BLOCK_SIZE   64000       /*(62*1024)*/

struct MemBlock
{
    struct MemBlock *next;   // Next block
    char            *block,  // Start of block
                    *free,   // Next free position in block
                    *end;    // block + block size
};

struct Mempool
{
    struct MemBlock *first;
    struct MemBlock *current;
};

static struct MemBlock* createMemBlock()
{
    struct MemBlock* memBlock = malloc(sizeof(struct MemBlock));
    if (memBlock)
    {
        memBlock->block = malloc(ALLOC_BLOCK_SIZE * sizeof(char));
        if (memBlock->block == NULL)
        {
            free(memBlock);
            return NULL;
        }
        memBlock->free = memBlock->block;
        memBlock->next = NULL;
        memBlock->end = memBlock->block + ALLOC_BLOCK_SIZE;
    }
    return memBlock;
}


static void deleteMemBlock(struct MemBlock* memBlock)
{
    free(memBlock->block);
    free(memBlock);
}


struct Mempool * mempool_create()
{
    struct Mempool *mempool;
    mempool = (struct Mempool *)malloc(sizeof(struct Mempool));
    if (mempool == NULL) return NULL;
    mempool->first = createMemBlock();
    mempool->current = mempool->first;
    if (mempool->first == NULL)
    {
        free(mempool);
        return NULL;
    }
    return mempool;
}

void mempool_delete(struct Mempool *mempool)
{
    if (mempool == NULL) return;
    while (mempool->first)
    {
        mempool->current = mempool->first->next;
        deleteMemBlock(mempool->first);
        mempool->first = mempool->current;
    }
    free(mempool);
    mempool = NULL;
}

void mempool_reset(struct Mempool *mempool)
{
    mempool->current = mempool->first;
    mempool->current->free = mempool->current->block;
}


char * mempool_alloc(struct Mempool *mempool, size_t size)
{
    char* ptr;

    /*
    **  Align to 4 byte boundary - should be ok for most machines.
    **  Change this if your machine has weird alignment requirements.
    */
    size = (size + 3) & 0xfffffffc;

    if (!mempool->current) return NULL;
    ptr = mempool->current->free;
    mempool->current->free += size;

    // Check if the current block is exhausted

    if (mempool->current->free >= mempool->current->end)
    {
        // Is the next block already allocated?

        if (mempool->current->next)
        {
            // re-use block
            mempool->current->next->free = mempool->current->next->block;
            mempool->current = mempool->current->next;
        }
        else
        {
            // extend the pool with a new block
            mempool->current->next = createMemBlock();
            if (!mempool->current->next) return NULL;
            mempool->current = mempool->current->next;
        }

        // set ptr to the first location in the next block

        ptr = mempool->current->free;
        mempool->current->free += size;
    }

    // Return pointer to allocated memory

    return ptr;
}
