#include "malloc.h"
#include "util.h"
#include "fb.h"

/* Simple bump allocator with free list */

#define HEAP_SIZE (1024 * 1024) /* 1 MB */
static uint8_t heap[HEAP_SIZE] __attribute__((aligned(16)));
static uint64_t heap_base;
static uint64_t heap_end;
static uint64_t heap_ptr;

/* Free list block header */
struct block_header {
    uint64_t size;          /* including header */
    int      free;
    struct block_header *next;
};

static struct block_header *free_list;

void kfree_init(uint64_t base, uint64_t size)
{
    heap_base = (uint64_t)heap;
    heap_end = heap_base + HEAP_SIZE;
    heap_ptr = heap_base;

    /* Single large free block covering everything */
    free_list = (struct block_header *)heap_base;
    free_list->size = HEAP_SIZE;
    free_list->free = 1;
    free_list->next = NULL;
}

/* Align up to 16 bytes */
static uint64_t align16(uint64_t v) { return (v + 15) & ~15ULL; }

void *kmalloc(uint64_t size)
{
    if(size == 0) return NULL;
    size = align16(size + sizeof(struct block_header));

    /* First fit */
    struct block_header *cur = free_list;
    while(cur) {
        if(cur->free && cur->size >= size) {
            /* Split if remainder is big enough */
            if(cur->size >= size + sizeof(struct block_header) + 16) {
                struct block_header *new_blk =
                    (struct block_header *)((uint8_t *)cur + size);
                new_blk->size = cur->size - size;
                new_blk->free = 1;
                new_blk->next = cur->next;
                cur->next = new_blk;
                cur->size = size;
            }
            cur->free = 0;
            return (void *)((uint8_t *)cur + sizeof(struct block_header));
        }
        cur = cur->next;
    }

    /* Fallback: bump allocator from end of heap */
    uint64_t total = align16(size);
    if(heap_ptr + total <= heap_end) {
        void *ptr = (void *)heap_ptr;
        heap_ptr += total;
        return ptr;
    }

    return NULL;
}

void *kcalloc(uint64_t count, uint64_t size)
{
    uint64_t total = count * size;
    void *p = kmalloc(total);
    if(p) memset(p, 0, total);
    return p;
}

void *krealloc(void *ptr, uint64_t old_size, uint64_t new_size)
{
    if(!ptr) return kmalloc(new_size);
    if(new_size <= old_size) return ptr;

    void *new_ptr = kmalloc(new_size);
    if(new_ptr) {
        memcpy(new_ptr, ptr, old_size);
        kfree(ptr);
    }
    return new_ptr;
}

void kfree(void *ptr)
{
    if(!ptr) return;
    struct block_header *hdr =
        (struct block_header *)((uint8_t *)ptr - sizeof(struct block_header));

    hdr->free = 1;

    /* Coalesce with next block if free */
    if(hdr->next && hdr->next->free) {
        hdr->size += hdr->next->size;
        hdr->next = hdr->next->next;
    }
}

uint64_t kheap_used(void)
{
    return heap_ptr - heap_base;
}
