#include "serial.h"
#include "drawing.h"
#include "mouse.h"
#include "timer.h"
#include "speaker.h"
#include "gdt.h"
#include "idt.h"
#include "heap.h"
#include "pmm.h"
#include "vmm.h"
#include "pic.h"
#include "logger.h"
#include "string.h"
#include "multiboot.h"

uint32_t bg_color = 0x00008080;
uint32_t color_sky_blue   = 0x0087CEEB;
uint32_t color_orange     = 0x00FFA500;
uint32_t color_white      = 0x00FFFFFF;
uint32_t color_black      = 0x00000000;
extern uint8_t _binary_assets_logo_bmp_start[];
extern uint8_t _binary_assets_bg_bmp_start[];
extern uint8_t _binary_assets_cursor_bmp_start[];

uint32_t frame_count = 0;       // Сколько раз отрисовали за секунду
uint32_t last_fps_ticks = 0;    // Когда последний раз обновляли FPS
uint32_t current_fps = 0;       // Результат (число FPS)

extern uint32_t _kernel_end; // Символ 'end' прописан в конце любого стандартного linker.ld
uint32_t kernel_end = (uint32_t)&_kernel_end;

void kernel_main(uint32_t mboot_addr, uint32_t magic) {
	log_info(LOG_NORMAL, "Initializing serial...");
	serial_init();
	if (magic != MULTIBOOT_BOOTLOADER_MAGIC) {
	    log_error(LOG_NORMAL, "fuck, i got invalid multiboot magic. IS THAT REALLY THE MULTIBOOT YOU TALKED ABOUT?");
	    while(1);
	}
	struct multiboot_info* mbi = (struct multiboot_info*)mboot_addr;
	if (!(mbi->flags & (1 << 6))) {
	    log_error(LOG_NORMAL, "fuck, i haven't got a memory map. IS THAT REALLY THE MULTIBOOT YOU TALKED ABOUT?");
	    while(1);
	}
    log_info(LOG_NORMAL, "Initializing GDT...");
    gdt_init();
    log_info(LOG_NORMAL, "Initializing IDT...");
    idt_init();
    log_info(LOG_NORMAL, "Remapping PIC...");
    pic_remap(32, 40);
    log_info(LOG_NORMAL, "Unmasking IRQ 0... (timer)");
    pic_clear_mask(0);
    log_info(LOG_NORMAL, "Unmasking IRQ 1... (keyboard)");
    pic_clear_mask(1);
    log_info(LOG_NORMAL, "Unmasking IRQ 2... (slave pic)");
    pic_clear_mask(2);
    log_info(LOG_NORMAL, "Unmasking IRQ 12... (mouse)");
    pic_clear_mask(12);
    log_info(LOG_NORMAL, "Registering timer at 100Hz");
    timer_init(100);
    log_info(LOG_NORMAL, "Registering mouse");    
    mouse_init();
	kprint_serial("--------------------\n");
    log_info(LOG_NORMAL, "Enabling PMM...");
	pmm_init(mbi, (uint32_t)&_kernel_end);
    kprint_serial("--- Memory Map From GRUB ---");
    struct multiboot_mmap_entry* mmap = (struct multiboot_mmap_entry*)mbi->mmap_addr;
    
    while((uint32_t)mmap < mbi->mmap_addr + mbi->mmap_length) {
        char addr_buf[32];
        char len_buf[32];
        
        itoa((uint32_t)mmap->addr, addr_buf, 16);
        itoa((uint32_t)mmap->len, len_buf, 10);
        
        kprint_serial("Region: Base=0x");
        kprint_serial(addr_buf);
        kprint_serial(" Size=");
        kprint_serial(len_buf);
        
        if (mmap->type == MULTIBOOT_MEMORY_AVAILABLE) {
            kprint_serial(" [FREE RAM]\n");
        } else {
            kprint_serial(" [RESERVED]\n");
        }
        
        // Переходим к следующей записи (учитываем поле size)
        mmap = (struct multiboot_mmap_entry*)((uint32_t)mmap + mmap->size + sizeof(mmap->size));
    }
    kprint_serial("--- trying to allocate some fucking memory... ---\n");
    kprint_serial("page 1... ");
    void* p1 = pmm_alloc_page();
    kprint_serial("page 2...");
    void* p2 = pmm_alloc_page();
    kprint_serial("ok!");
    char buf[32];
    kprint_serial("PMM TEST: Page 1 allocated at 0x");
    itoa((uint32_t)p1, buf, 16);
    kprint_serial(buf);
    kprint_serial("\n");

    kprint_serial("PMM TEST: Page 2 allocated at 0x");
    itoa((uint32_t)p2, buf, 16);
    kprint_serial(buf);
    kprint_serial("\n");

    kprint_serial("freeing the pages back... page 1... ");
    pmm_free_page(p1);
    kprint_serial("page 2... ");
    pmm_free_page(p2);
    kprint_serial("it don't died!\n");

	kprint_serial("\n--- PMM MEMORY STATS ---\n");
    
    pmm_get_stats(); // Просто запускаем сбор, она запишет всё в глобальную переменную
    
    // Берем указатель на глобальную переменную
    pmm_stats_t* my_stats = pmm_get_stats_ptr();
    
    kprint_serial("Total Memory: ");
    itoa(my_stats->total_memory >> 20, buf, 10);
    kprint_serial(buf);
    kprint_serial(" MB\n");

    kprint_serial("Used Memory: ");
    itoa(my_stats->used_memory >> 10, buf, 10);
    kprint_serial(buf);
    kprint_serial(" KB\n");

    kprint_serial("Free Memory: ");
    itoa(my_stats->free_memory >> 20, buf, 10);
    kprint_serial(buf);
    kprint_serial(" MB\n");
    
    kprint_serial("------------------------\n\n");
    log_info(LOG_NORMAL, "Enabling VMM...");
    vmm_init();
    log_info(LOG_NORMAL, "Enabling KHeap...");
	kheap_init();
	
	kprint_serial("--- HEAP TESTING ---\n");
	
	// Выделяем три разных куска памяти
	int* arr1 = (int*)kmalloc(10 * sizeof(int));
	char* str = (char*)kmalloc(64);
	int* arr2 = (int*)kmalloc(5 * sizeof(int));
	char hbuf[32];
	kprint_serial("Allocated arr1 at virtual address: 0x");
	itoa((uint32_t)arr1, hbuf, 16);
	kprint_serial(hbuf);
	kprint_serial("\n");
	kprint_serial("Allocated str at virtual address: 0x");
	itoa((uint32_t)str, hbuf, 16);
	kprint_serial(hbuf);
	kprint_serial("\n");
	// Запишем туда данные, чтобы проверить, что маппинг страниц не падает при записи
	arr1[0] = 1337;
	str[0] = 'H';
	str[1] = 'A';
	str[2] = 'I';
	str[3] = '\0';
	kprint_serial("Data integrity check: str is ");
	kprint_serial(str);
	kprint_serial("\n");
	// Освобождаем память обратно
	kfree(arr1);
	kfree(str);
	kfree(arr2);
	
	kprint_serial("Kfree done. It don't died twice!\n");
	if (mbi->flags & (1 << 12)) {
	    log_info(LOG_NORMAL, "Framebuffer detected!");
	    
	    uint32_t fb_addr = mbi->framebuffer_addr;
	    uint32_t width = mbi->framebuffer_width;
	    uint32_t height = mbi->framebuffer_height;
	    uint32_t bpp = mbi->framebuffer_bpp;
	
	    char buf[16];
	    kprint_serial("FB Addr: "); 
	    kprint_serial_hex(fb_addr);
	    kprint_serial("\nSize: ");
	    kprint_serial_dec(width);
	    kprint_serial("x");
	    kprint_serial_dec(height);
	    kprint_serial("\n");
	    draw_init(mbi);
	} else {
	    log_error(LOG_NORMAL, "No Framebuffer provided by GRUB!");
	}
    log_info(LOG_NORMAL, "Enabling IRQ...");
    __asm__ __volatile__("sti");
    speaker_beep(100, 10);
    speaker_beep(125, 10);
    speaker_beep(150, 10);
    speaker_beep(175, 10);
    speaker_beep(200, 10);
    speaker_beep(225, 10);
    speaker_beep(250, 10);
    speaker_beep(275, 10);
    speaker_beep(300, 10);
    speaker_beep(325, 10);
    speaker_beep(350, 400);
    log_info(LOG_NORMAL, "OK!");
    int old_mouse_x = 0;
    int old_mouse_y = 0;
    char fpsbuf[4];
	while (1) {
	    // 1. Всегда рисуем фон и элементы
	    draw_bmp(_binary_assets_bg_bmp_start, 0, 0);
	    draw_cursor_sprite(_binary_assets_cursor_bmp_start, mouse_x, mouse_y);
	    
	    // 2. Всегда рисуем FPS
	    draw_string("FPS: ", 0, 0, color_white);
	    itoa(current_fps, fpsbuf, 10);
	    draw_string(fpsbuf, 40, 0, color_white);
	    
	    // 3. Считаем кадр
	    frame_count++;
	    
	    // 4. Логика обновления FPS (раз в секунду)
	    if (system_ticks - last_fps_ticks >= 100) {
	        current_fps = frame_count;
	        frame_count = 0;
	        last_fps_ticks = system_ticks;
	    }
	}
}
