#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <limine.h>
#include "video/render.h"
#include "video/tga.h"
#include "stdio.h"
#include "arch/x86_64/gdt.h"
#include "arch/x86_64/idt.h"
#include "arch/x86_64/isr.h"
#include "arch/x86_64/irq.h"
#include "mm/memory.h"
#include "mm/pmm.h"
#include "mm/vmm.h"
#include "arch/x86_64/ata.h"
#include "fs/ext2.h"
#include "string.h"
#include "arch/x86_64/io.h"
#include "arch/x86_64/usermode.h"
#include "syscall/syscall.h"
#include "fs/vfs.h"
#include "time/time.h"
#include "arch/x86_64/pit.h"
#include <uacpi/uacpi.h>
#include <uacpi/event.h>
#include <uacpi/sleep.h>
#include "arch/x86_64/pci.h"


uintptr_t g_hhdm_offset;
#define CPU_STACK_SIZE (64 * 1024)

// Set the base revision to 6, this is recommended as this is the latest
// base revision described by the Limine boot protocol specification.
// See specification for further info.

__attribute__((used, section(".limine_requests")))
static volatile uint64_t limine_base_revision[] = LIMINE_BASE_REVISION(6);

// The Limine requests can be placed anywhere, but it is important that
// the compiler does not optimise them away, so, usually, they should
// be made volatile or equivalent, _and_ they should be accessed at least
// once or marked as used with the "used" attribute as done here.

__attribute__((used, section(".limine_requests")))
static volatile struct limine_framebuffer_request framebuffer_request = {
    .id = LIMINE_FRAMEBUFFER_REQUEST_ID,
    .revision = 6
};

__attribute__((used, section(".limine_requests")))
static volatile struct limine_memmap_request memmap_request = {
    .id = LIMINE_MEMMAP_REQUEST_ID,
    .revision = 6
};

__attribute__((used, section(".limine_requests")))
static volatile struct limine_rsdp_request rsdp_request = {
    .id = LIMINE_RSDP_REQUEST_ID,
    .revision = 6
};




__attribute__((used, section(".limine_requests")))
volatile struct limine_stack_size_request stack_size_request = {
	.id = LIMINE_STACK_SIZE_REQUEST_ID,
	.revision = 0,
	.stack_size = CPU_STACK_SIZE,
};



// Finally, define the start and end markers for the Limine requests.
// These can also be moved anywhere, to any .c file, as seen fit.

__attribute__((used, section(".limine_requests_start")))
static volatile uint64_t limine_requests_start_marker[] = LIMINE_REQUESTS_START_MARKER;

__attribute__((used, section(".limine_requests_end")))
static volatile uint64_t limine_requests_end_marker[] = LIMINE_REQUESTS_END_MARKER;

// GCC and Clang reserve the right to generate calls to the following
// 4 functions even if they are not directly called.
// Implement them as the C specification mandates.
// DO NOT remove or rename these functions, or stuff will eventually break!
// They CAN be moved to a different .c file.



// Halt and catch fire function.
static void hcf(void) {
    for (;;) {
        asm volatile ("pause");
    }
}

extern struct kernel_pagemap;
uint64_t g_rsdp_phys;

void shutdown(void) {
    uacpi_status ret2 = uacpi_prepare_for_sleep_state(UACPI_SLEEP_STATE_S5);
    if (uacpi_unlikely_error(ret2)) {
        printf("failed to prepare for sleep: %s", uacpi_status_to_string(ret2));
    }

    ret2 = uacpi_enter_sleep_state(UACPI_SLEEP_STATE_S5);
    if (uacpi_unlikely_error(ret2)) {
        printf("failed to enter sleep: %s", uacpi_status_to_string(ret2));
    }
}

static uacpi_interrupt_ret handle_power_button(uacpi_handle ctx) {
    /*
     * Shut down right here using the helper we have defined above.
     *
     * Note that it's generally terrible practice to run any AML from
     * an interrupt handler, as it's allowed to allocate, map, sleep,
     * stall, acquire mutexes, etc. So, if possible in your kernel,
     * instead schedule the shutdown callback to be run in a normal
     * preemptible context later.
     */
    shutdown();
    return UACPI_INTERRUPT_HANDLED;
}

// The following will be our kernel's entry point.
// If renaming kmain() to something else, make sure to change the
// linker script accordingly.
void kmain(void) {
    // Ensure the bootloader actually understands our base revision (see spec).
    if (LIMINE_BASE_REVISION_SUPPORTED(limine_base_revision) == false) {
        hcf();
    }

    // Ensure we got a framebuffer.
    if (framebuffer_request.response == NULL
     || framebuffer_request.response->framebuffer_count < 1) {
        hcf();
    }

    // Fetch the first framebuffer.
    struct limine_framebuffer *framebuffer = framebuffer_request.response->framebuffers[0];

    struct limine_memmap_response *memmap_response = memmap_request.response;

    struct limine_rsdp_response *rsdp_response = rsdp_request.response;

    g_rsdp_phys = (uint64_t)rsdp_response->address - MEM_PHYS_OFFSET;


    
    

    if (!memmap_response) {
        hcf();
    }


    fb = framebuffer->address;
    fb_width = framebuffer->width;
    fb_height = framebuffer->height;
    fb_pitch = framebuffer->pitch / 4;
    


    printf("Hello, Kernel!\n");
    printf("Number: %d\n", 1234);
    printf("Hex: %x\n", 0xBEEF);

    for (size_t i = 0; i < memmap_response->entry_count; i++) {
        struct limine_memmap_entry *entry = memmap_response->entries[i];

        printf("Base: 0x%x%x, Length: 0x%x%x, Type: %d\n",
            (uint32_t)(entry->base >> 32), (uint32_t)entry->base,
            (uint32_t)(entry->length >> 32), (uint32_t)entry->length,
            entry->type);
    }
    x86_64_DisableInterrupts();
    


    uint32_t msr_lo, msr_hi;
    asm volatile("rdmsr" : "=a"(msr_lo), "=d"(msr_hi) : "c"(0x1B));
    msr_lo &= ~(1 << 11);
    asm volatile("wrmsr" : : "a"(msr_lo), "d"(msr_hi), "c"(0x1B));
    printf("rsdp: 0x%x\n", g_rsdp_phys);
    printf("init pmm\n");
    pmm_init(memmap_response->entries, memmap_response->entry_count);
    printf("init slab\n");
    slab_init();
    printf("init vmm\n");
    vmm_init(memmap_response->entries, memmap_response->entry_count);
    
    printf("init gdt");
    x86_64_GDT_Initialize();
    x86_64_IDT_Initialize();
    x86_64_ISR_Initialize();
    x86_64_IRQ_Initialize();
    x86_64_PIT_Initialize(1000);
    asm volatile("sti");
    calibrate_tsc();


    //while (1) asm volatile("hlt");

    ata_init();
    ata_identify();

    if (!ext2_read_superblock()) {
        printf("EXT2 failed\n");
        hcf();
    }

    if (!ext2_read_group_desc_table()) {
        printf("GDT failed\n");
        hcf();
    }

    ext2_read_root_dir();
    printf("test");


    uint8_t* file_buffer = kmalloc(4096); // adjust if needed
    uint32_t file_size = 0;
    
    if (ext2_read_file_from_root("a.txt", file_buffer, &file_size)) {
        printf("Read file (size %d):\n", file_size);

        for (uint32_t i = 0; i < file_size; i++) {
            printf("%c", file_buffer[i]); // or printf("%c", ...)
        }

        printf("\n");
    } else {
        printf("Failed to read file\n");
    }

    kfree(file_buffer);

    uint8_t* file2_buffer = kmalloc(4096); // adjust if needed
    uint32_t file2_size = 0;

    if (ext2_read_file_from_root("dogcatman.txt", file2_buffer, &file2_size)) {
        printf("Read file (size %d):\n", file2_size);

        for (uint32_t i = 0; i < file2_size; i++) {
            printf("%c", file2_buffer[i]); // or printf("%c", ...)
        }

        printf("\n");
    } else {
        printf("Failed to read file\n");
    }

    kfree(file2_buffer);

    uint8_t* file3_buffer = kmalloc(4096); // adjust if needed
    uint32_t file3_size = 0;

    if (ext2_read_file_from_root("test.txt", file3_buffer, &file3_size)) {
        printf("Read file (size %d):\n", file3_size);

        for (uint32_t i = 0; i < file3_size; i++) {
            printf("%c", file3_buffer[i]); // or printf("%c", ...)
        }

        printf("\n");
    } else {
        printf("Failed to read file\n");
    }

    kfree(file3_buffer);

    uint8_t* file4_buffer = kmalloc(4096); // adjust if needed
    uint32_t file4_size = 0;

    if (ext2_read_file_from_root("qwerty.txt", file4_buffer, &file4_size)) {
        printf("Read file (size %d):\n", file4_size);

        for (uint32_t i = 0; i < file4_size; i++) {
            printf("%c", file4_buffer[i]); // or printf("%c", ...)
        }

        printf("\n");
    } else {
        printf("Failed to read file\n");
    }



    kfree(file4_buffer);

    const char* msg = "hello i wrote from kernel!";

    ext2_write_file_from_root("qwerty.txt", (const uint8_t*)msg, strlen(msg), EXT2_WRITE_APPEND);

    uint8_t* file5_buffer = kmalloc(4096); // adjust if needed
    uint32_t file5_size = 0;

    if (ext2_read_file_from_root("qwerty.txt", file5_buffer, &file5_size)) {
        printf("Read file (size %d):\n", file5_size);

        for (uint32_t i = 0; i < file5_size; i++) {
            printf("%c", file5_buffer[i]); // or printf("%c", ...)
        }

        printf("\n");
    } else {
        printf("Failed to read file\n");
    }



    kfree(file5_buffer);

    uint32_t inum = ext2_resolve_path("charlie.tga");
    if (!inum) {
        printf("file not found\n");
        hcf();
    }

    ext2_inode_t inode;
    if (!ext2_read_inode(inum, &inode)) {
        printf("inode read failed\n");
        hcf();
    }

    uint32_t tga_size = inode.i_size;
    if (!tga_size) {
        printf("tga file is empty\n");
        hcf();
    }

    uint8_t *file_buf = kmalloc(tga_size);
    if (!file_buf) {
        printf("OOM allocating tga buffer (%u bytes)\n", tga_size);
        hcf();
    }

    if (!ext2_read_file(&inode, file_buf)) {
        printf("read failed\n");
        kfree(file_buf);
        hcf();
    }

    uint32_t *img = tga_parse(file_buf, tga_size);
    kfree(file_buf);   // free the raw file buffer as soon as parse is done

    if (!img) {
        printf("tga parse failed\n");
        hcf();
    }

    int x = 0;
    int y = 0;
    int new_w = img[0] * 5;
    int new_h = img[1] * 5;
    clear_screen(0xFF1E1E1E);
    draw_image_bilinear(img, x, y, new_w, new_h);
    kfree(img);

    //clear_screen(0xFF1E1E1E);

    fd_t fd = VFS_Open("/a.txt");
    if (fd < 0) {
        printf("asudg");
    }
    uint8_t buf[128];
    int n;

    while ((n = VFS_Read(fd, buf, sizeof(buf))) > 0)
    {
        VFS_Write(VFS_FD_STDOUT, buf, n);
    }

    VFS_Close(fd);
    printf("\nKirkOS %s\n", KIRKOS_VERSION);

    x86_64_EnableInterrupts();



    /*
     * Start with this as the first step of the initialization. This loads all
     * tables, brings the event subsystem online, and enters ACPI mode. We pass
     * in 0 as the flags as we don't want to override any default behavior for now.
     */
    uacpi_status ret = uacpi_initialize(0);
    if (uacpi_unlikely_error(ret)) {
        printf("uacpi_initialize error: %s", uacpi_status_to_string(ret));
    }

    /*
     * Load the AML namespace. This feeds DSDT and all SSDTs to the interpreter
     * for execution.
     */
    ret = uacpi_namespace_load();
    if (uacpi_unlikely_error(ret)) {
        printf("uacpi_namespace_load error: %s", uacpi_status_to_string(ret));
    }

    /*
     * Initialize the namespace. This calls all necessary _STA/_INI AML methods,
     * as well as _REG for registered operation region handlers.
     */
    ret = uacpi_namespace_initialize();
    if (uacpi_unlikely_error(ret)) {
        printf("uacpi_namespace_initialize error: %s", uacpi_status_to_string(ret));
    }

    /*
     * Tell uACPI that we have marked all GPEs we wanted for wake (even though we haven't
     * actually marked any, as we have no power management support right now). This is
     * needed to let uACPI enable all unmarked GPEs that have a corresponding AML handler.
     * These handlers are used by the firmware to dynamically execute AML code at runtime
     * to e.g. react to thermal events or device hotplug.
     */
    ret = uacpi_finalize_gpe_initialization();
    if (uacpi_unlikely_error(ret)) {
        printf("uACPI GPE initialization error: %s", uacpi_status_to_string(ret));
    }


    pci_init();

    pci_device_t hda;
    if (pci_find_hda(&hda)) {
        printf("[PCI] Found HDA controller at %02x:%02x.%x  %04x:%04x\n",
            hda.addr.bus, hda.addr.device, hda.addr.function,
            hda.vendor_id, hda.device_id);
        // BAR0 is usually at hda.bar[0] & ~0xF  (MMIO)
    } else {
        printf("[PCI] No HDA controller found!\n");
    }


    //syscall_init();
    //start_userspace();
    printf("tst");


    // We're done, just hang...
    hcf();
}