#include <memory/heap.h>
#include <memory/pmm.h>
#include <serial.h>
#include <string_utils.h>

struct heap_block {
    UINTN  size;   // includes header; bit 0 = 1 used, 0 free
    struct heap_block* next;  // free list link (only valid when free)
};

#define HEAP_ALIGN       16
#define HEADER_SIZE      ((UINTN)sizeof(struct heap_block))
#define MIN_BLOCK_SIZE   (HEADER_SIZE + HEAP_ALIGN)

#define HEAP_INIT_PAGES  4

#define BLOCK_SIZE(block)   ((block)->size & ~(UINTN)1)
#define IS_USED(block)      ((block)->size & 1)
#define IS_FREE(block)      (!IS_USED(block))

static struct heap_block* g_heap_free_list = NULL;
static void* g_heap_start  = NULL;
static void* g_heap_end    = NULL;

static UINTN align_up(UINTN val, UINTN align) {
    return (val + align - 1) & ~(align - 1ULL);
}

static struct heap_block* next_block(struct heap_block* block) {
    return (struct heap_block*)((UINT8*)block + BLOCK_SIZE(block));
}

static void heap_expand(UINTN min_size) {
    UINTN pages = (min_size + PAGE_SIZE - 1) / PAGE_SIZE;
    void* mem = pmm_alloc_pages(pages);
    if (!mem) {
        serial_write("HEAP: expand failed!\n");
        return;
    }

    struct heap_block* new_block = (struct heap_block*)mem;
    new_block->size = pages * PAGE_SIZE;
    new_block->next = NULL;

    // 添加到空闲链表（按地址排序以便合并）
    struct heap_block** prev = &g_heap_free_list;
    while (*prev && (UINT8*)*prev < (UINT8*)new_block) {
        prev = &(*prev)->next;
    }
    new_block->next = *prev;
    *prev = new_block;

    // 尝试与前一个空闲块合并（如果相邻）
    if (prev != &g_heap_free_list) {
        struct heap_block* prev_block = g_heap_free_list;
        while (prev_block->next != new_block) {
            prev_block = prev_block->next;
        }
        if ((UINT8*)prev_block + BLOCK_SIZE(prev_block) == (UINT8*)new_block) {
            prev_block->size += new_block->size;
            prev_block->next = new_block->next;
            new_block = prev_block;
        }
    }

    if ((UINT8*)new_block + BLOCK_SIZE(new_block) > (UINT8*)g_heap_end) {
        g_heap_end = (UINT8*)new_block + BLOCK_SIZE(new_block);
    }

    serial_write("HEAP: expanded by ");
    serial_write_hex(pages * PAGE_SIZE);
    serial_write(" bytes\n");
}

void init_heap() {
    void* mem = pmm_alloc_pages(HEAP_INIT_PAGES);
    if (!mem) {
        serial_write("HEAP: init failed!\n");
        return;
    }

    g_heap_start = mem;
    g_heap_end   = (void*)((UINT8*)mem + HEAP_INIT_PAGES * PAGE_SIZE);

    struct heap_block* initial = (struct heap_block*)mem;
    initial->size = HEAP_INIT_PAGES * PAGE_SIZE;
    initial->next = NULL;
    g_heap_free_list = initial;

    serial_write("HEAP: init OK, ");
    serial_write_hex(HEAP_INIT_PAGES * PAGE_SIZE);
    serial_write(" bytes @ ");
    serial_write_hex((UINTN)mem);
    serial_write("\n");
}

void* kmalloc(UINTN size) {
    if (size == 0) return NULL;

    UINTN alloc_size = align_up(size + HEADER_SIZE, HEAP_ALIGN);
    if (alloc_size < MIN_BLOCK_SIZE) alloc_size = MIN_BLOCK_SIZE;

    struct heap_block** prev = &g_heap_free_list;
    while (*prev) {
        UINTN block_sz = BLOCK_SIZE(*prev);
        if (block_sz >= alloc_size) {
            // 找到合适的块
            struct heap_block* block = *prev;

            // 如果剩余空间足够则分割
            if (block_sz >= alloc_size + MIN_BLOCK_SIZE) {
                struct heap_block* split = (struct heap_block*)((UINT8*)block + alloc_size);
                split->size = block_sz - alloc_size;
                // 将分割后的块插入空闲链表
                split->next = block->next;
                block->size = alloc_size | 1;
                *prev = split;
            } else {
                // 使用整个块
                *prev = block->next;
                block->size = block_sz | 1;  // 标记为已使用
            }

            if (size > 1024) {
                serial_write("HEAP: kmalloc ");
                serial_write_hex(size);
                serial_write(" -> ");
                serial_write_hex((UINTN)(block + 1));
                serial_write("\n");
            }

            return (void*)(block + 1);
        }
        prev = &(*prev)->next;
    }

    // 当前堆空间不足，扩展堆
    UINTN expand_size = alloc_size > PAGE_SIZE ? alloc_size : PAGE_SIZE;
    heap_expand(expand_size);

    // 扩展后重试
    return kmalloc(size);
}

void kfree(void* ptr) {
    if (!ptr) return;

    struct heap_block* block = (struct heap_block*)ptr - 1;
    if (IS_FREE(block)) {
        serial_write("HEAP: double free detected!\n");
        return;
    }

    // 标记为空闲
    block->size &= ~(UINTN)1;

    // 与下一个空闲块合并
    struct heap_block* next = next_block(block);
    if ((UINT8*)next < (UINT8*)g_heap_end) {
        if (IS_FREE(next)) {
            // 从空闲链表中移除 next 并合并
            block->size += next->size;
            struct heap_block** prev = &g_heap_free_list;
            while (*prev && *prev != next) {
                prev = &(*prev)->next;
            }
            if (*prev) *prev = next->next;
        }
    }

    // 将块插入空闲链表
    struct heap_block** prev = &g_heap_free_list;
    while (*prev && (UINT8*)*prev < (UINT8*)block) {
        prev = &(*prev)->next;
    }
    block->next = *prev;
    *prev = block;

    serial_write("HEAP: kfree @ ");
    serial_write_hex((UINTN)ptr);
    serial_write("\n");
}

void* kcalloc(UINTN num, UINTN size) {
    UINTN total = num * size;
    void* ptr = kmalloc(total);
    if (ptr) {
        mem_set(ptr, 0, total);
    }
    return ptr;
}

void* krealloc(void* ptr, UINTN new_size) {
    if (!ptr) return kmalloc(new_size);
    if (new_size == 0) {
        kfree(ptr);
        return NULL;
    }

    struct heap_block* block = (struct heap_block*)ptr - 1;
    UINTN old_size = BLOCK_SIZE(block) - HEADER_SIZE;

    if (old_size >= new_size) {
        // 能否分割缩小的部分？
        UINTN shrink = old_size - new_size;
        if (shrink >= MIN_BLOCK_SIZE) {
            block->size = (new_size + HEADER_SIZE) | 1;
            struct heap_block* split = (struct heap_block*)((UINT8*)ptr + new_size);
            split->size = shrink;
            kfree(split + 1);
        }
        return ptr;
    }

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