#include "heap.h"
#include "vmm.h"
#include "pmm.h"
#include "serial.h"

static heap_chunk_t* heap_start_chunk = NULL;

void kheap_init(void) {
    kprint_serial("HEAP: Initializing Kernel Heap...\n");

    // 1. Выделяем одну физ. страницу под старт кучи
    void* phys_page = pmm_alloc_page();
    
    // 2. Мапим её на виртуальный адрес HEAP_START
    // Нам нужны флаги Present (1) + Write (2) = 3
    vmm_map_page((uint32_t)phys_page, HEAP_START, 3);

    // 3. Создаем базовый блок на всю эту страницу
    heap_start_chunk = (heap_chunk_t*)HEAP_START;
    heap_start_chunk->size = PAGE_SIZE - sizeof(heap_chunk_t);
    heap_start_chunk->is_free = 1;
    heap_start_chunk->next = NULL;

    kprint_serial("HEAP: Kernel Heap initialized successfully at 0x00400000!\n");
}

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

    // Выравниваем размер по 4 байта для стабильности на x86
    size = (size + 3) & ~3;

    heap_chunk_t* current = heap_start_chunk;

    // Ищем подходящий свободный блок (First Fit)
    while (current != NULL) {
        if (current->is_free && current->size >= size) {
            
            // Если блок сильно больше, чем надо, дробим его
            // sizeof(heap_chunk_t) + 4 байта минимально на остаток
            if (current->size >= size + sizeof(heap_chunk_t) + 4) {
                heap_chunk_t* new_chunk = (heap_chunk_t*)((uint32_t)current + sizeof(heap_chunk_t) + size);
                new_chunk->size = current->size - size - sizeof(heap_chunk_t);
                new_chunk->is_free = 1;
                new_chunk->next = current->next;

                current->size = size;
                current->next = new_chunk;
            }

            current->is_free = 0;
            
            // Возвращаем указатель на память СРАЗУ ЗА заголовком
            return (void*)((uint32_t)current + sizeof(heap_chunk_t));
        }
        current = current->next;
    }

    // По-хорошему, если памяти не хватило, нужно выделить еще страницу из PMM 
    // и расширить кучу. Но для первого теста выкинем панику.
    kprint_serial("HEAP ERROR: OUT OF KERNEL HEAP MEMORY!\n");
    return NULL;
}

void kfree(void* ptr) {
    if (ptr == NULL) return;

    // Смещаемся назад, чтобы получить доступ к заголовку блока
    heap_chunk_t* chunk = (heap_chunk_t*)((uint32_t)ptr - sizeof(heap_chunk_t));
    chunk->is_free = 1;

    // Склеиваем последовательные свободные блоки (Coalescing), чтобы не было фрагментации
    heap_chunk_t* current = heap_start_chunk;
    while (current != NULL) {
        if (current->is_free && current->next && current->next->is_free) {
            current->size += sizeof(heap_chunk_t) + current->next->size;
            current->next = current->next->next;
            // Не шагаем вперед, проверяем текущий блок еще раз с новым next
            continue; 
        }
        current = current->next;
    }
}
