#include "pmm.h"
#include "string.h"
#include "logger.h"
#include "serial.h"

// Указатель на начало нашей битовой карты в памяти
static uint32_t* pmm_bitmap = NULL;
static uint32_t pmm_bitmap_size = 0;
static uint32_t total_pages = 0;

static pmm_stats_t global_stats;

// Вспомогательные инлайны для быстрой работы с битами ядра
static inline void bitmap_set(uint32_t page_index) {
    pmm_bitmap[page_index / 32] |= (1 << (page_index % 32));
}

static inline void bitmap_clear(uint32_t page_index) {
    pmm_bitmap[page_index / 32] &= ~(1 << (page_index % 32));
}

static inline int bitmap_test(uint32_t page_index) {
    return (pmm_bitmap[page_index / 32] & (1 << (page_index % 32))) != 0;
}

void pmm_init(struct multiboot_info* mbi, uint32_t kernel_end) {
    // Рассчитываем менеджер на 128 МБ памяти
    uint32_t mem_size = 128 * 1024 * 1024; 
    total_pages = mem_size / PAGE_SIZE;
    
    // Саму карту пихаем прямо за концом ядра (куда указывает линкер)
    pmm_bitmap = (uint32_t*)kernel_end;
    pmm_bitmap_size = total_pages / 8; // Размер карты в байтах (4096 байт)
    
    uint32_t bitmap_end = kernel_end + pmm_bitmap_size;

    // Шаг 1: Помечаем вообще всё как занятое (1). Безопасность превыше всего.
    memset(pmm_bitmap, 0xFF, pmm_bitmap_size);

    // Шаг 2: Парсим карту памяти от GRUB и открываем доступные регионы
    struct multiboot_mmap_entry* mmap = (struct multiboot_mmap_entry*)mbi->mmap_addr;
    
    while((uint32_t)mmap < mbi->mmap_addr + mbi->mmap_length) {
        // Если GRUB говорит, что этот кусок RAM свободен для юзания
        if (mmap->type == MULTIBOOT_MEMORY_AVAILABLE) {
            uint32_t start_addr = (uint32_t)mmap->addr;
            uint32_t end_addr = start_addr + (uint32_t)mmap->len;

            for (uint32_t addr = start_addr; addr < end_addr; addr += PAGE_SIZE) {
                uint32_t page_idx = addr / PAGE_SIZE;
                if (page_idx < total_pages) {
                    bitmap_clear(page_idx); // Ставим 0 (страница свободна)
                }
            }
        }
        // Шагаем к следующей записи в таблице Multiboot
        mmap = (struct multiboot_mmap_entry*)((uint32_t)mmap + mmap->size + sizeof(mmap->size));
    }

    // Шаг 3: Жестко защищаем саму ОС! 
    // Всё, что лежит от физического адреса 0x0 до bitmap_end (код ядра, стек, сама битовая карта),
    // мы принудительно забиваем единицами, чтобы аллокатор туда никогда не залез.
    for (uint32_t addr = 0; addr < bitmap_end; addr += PAGE_SIZE) {
        bitmap_set(addr / PAGE_SIZE);
    }

    log_info(LOG_NORMAL, "pmm works.");
}

void* pmm_alloc_page(void) {
    // Тупо линейно ищем первый попавшийся бит, равный 0
    for (uint32_t i = 0; i < total_pages; i++) {
        if (!bitmap_test(i)) { 
            bitmap_set(i); // Застолбили страницу (поставили 1)
            return (void*)(i * PAGE_SIZE); // Считаем её реальный физический адрес
        }
    }
    
    log_error(LOG_NORMAL, "PMM: FUCK, WHERE IS ALL OUR FUCKING MEMORY?!");
    return NULL; 
}

void pmm_free_page(void* addr) {
    uint32_t page_idx = ((uint32_t)addr) / PAGE_SIZE;
    if (page_idx < total_pages) {
        bitmap_clear(page_idx); // Сбросили в 0, страница снова чиста
    }
}

void pmm_get_stats(void) {
    kprint_serial("Someone wants memory stats! Running the test!\n");
    uint32_t used = 0;
    uint32_t free = 0;

    for (uint32_t i = 0; i < total_pages; i++) {
        if (bitmap_test(i)) {
            used++;
        } else {
            free++;
        }
    }

    kprint_serial("Okay, i am collected the stats. ");

    // Пишем в ГЛОБАЛЬНУЮ переменную
    global_stats.total_pages = total_pages;
    global_stats.used_pages = used;
    global_stats.free_pages = free;
    global_stats.total_memory = total_pages << 12;
    global_stats.used_memory = used << 12;
    global_stats.free_memory = free << 12;

    kprint_serial("Goodbye!\n");
}

// Добавим функцию-геттер, которая просто вернет указатель на эту глобальную память
pmm_stats_t* pmm_get_stats_ptr(void) {
    return &global_stats;
}
