#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <limine.h>
#include <lib.h>

static volatile struct limine_module_request module_request = {
    .id = LIMINE_MODULE_REQUEST,
    .revision = 0
};

static volatile struct limine_hhdm_request hhdm_request = {
    .id = LIMINE_HHDM_REQUEST,
    .revision = 0
};

static volatile struct limine_memmap_request memmap_request = {
    .id = LIMINE_MEMMAP_REQUEST,
    .revision = 0
};

static volatile struct limine_framebuffer_request framebuffer_request = {
    .id = LIMINE_FRAMEBUFFER_REQUEST,
    .revision = 0
};

static volatile struct limine_smbios_request smbios_request = {
    .id = LIMINE_SMBIOS_REQUEST,
    .revision = 0
};

static volatile struct limine_efi_system_table_request efi_request = {
    .id = LIMINE_EFI_SYSTEM_TABLE_REQUEST,
    .revision = 0
};

struct CZXE {
    uint16_t jmp;
    uint8_t module_align_bits;
    uint8_t reserved;
    uint32_t signature;
    int64_t org;
    int64_t patch_table_offset;
    int64_t file_size;
} __attribute__((packed));

struct CDate {
    uint32_t time;
    int32_t date;
} __attribute__((packed));

#define MEM_E820_ENTRIES_NUM 256

#define MEM_E820T_USABLE 1
#define MEM_E820T_RESERVED 2
#define MEM_E820T_ACPI 3
#define MEM_E820T_ACPI_NVS 4
#define MEM_E820T_BAD_MEM 5
#define MEM_E820T_PERM_MEM 7

struct CMemE820 {
    uint8_t *base;
    int64_t len;
    uint8_t type, pad[3];
} __attribute__((packed));

struct CGDTEntry {
    uint64_t lo, hi;
} __attribute__((packed));

#define MP_PROCESSORS_NUM 128

struct CGDT {
    struct CGDTEntry null;
    struct CGDTEntry boot_ds;
    struct CGDTEntry boot_cs;
    struct CGDTEntry cs32;
    struct CGDTEntry cs64;
    struct CGDTEntry cs64_ring3;
    struct CGDTEntry ds;
    struct CGDTEntry ds_ring3;
    struct CGDTEntry tr[MP_PROCESSORS_NUM];
    struct CGDTEntry tr_ring3[MP_PROCESSORS_NUM];
} __attribute__((packed));

struct CSysLimitBase {
    uint16_t limit;
    uint8_t *base;
} __attribute__((packed));

struct CVideoInfo {
    uint16_t width;
    uint16_t height;
} __attribute__((packed));

#define VBE_MODES_NUM 32

#define ZEALBOOTER_LIMINE_SIGNATURE_1 0xaa23c08ed10bd4d7
#define ZEALBOOTER_LIMINE_SIGNATURE_2 0xf6ceba7d4b74179a

struct CKernel {
    struct CZXE h;
    uint32_t jmp;
    uint32_t boot_src;
    uint32_t boot_blk;
    uint32_t boot_patch_table_base;
    uint32_t sys_run_level;
    struct CDate compile_time;
    // U0 start
    uint32_t boot_base;
    uint16_t mem_E801[2];
    struct CMemE820 mem_E820[MEM_E820_ENTRIES_NUM];
    uint64_t mem_physical_space;
    struct CSysLimitBase sys_gdt_ptr;
    uint16_t sys_pci_buses;
    struct CGDT sys_gdt __attribute__((aligned(16)));
    uint64_t sys_framebuffer_addr;
    uint64_t sys_framebuffer_width;
    uint64_t sys_framebuffer_height;
    uint64_t sys_framebuffer_pitch;
    uint8_t sys_framebuffer_bpp;
    uint64_t sys_smbios_entry;
    uint64_t sys_disk_uuid[2];
	uint32_t sys_boot_stack;
	uint8_t sys_is_uefi_booted;
	struct CVideoInfo sys_framebuffer_list[VBE_MODES_NUM];
} __attribute__((packed));

#define BOOT_SRC_RAM 2
#define BOOT_SRC_HDD 3
#define BOOT_SRC_DVD 4
#define RLF_16BIT 0b001
#define RLF_VESA  0b010
#define RLF_32BIT 0b100

extern symbol trampoline, trampoline_end;

struct E801 {
    size_t lowermem;
    size_t uppermem;
};

static struct E801 get_E801(void) {
    struct E801 E801 = {0};

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

        if (entry->type == LIMINE_MEMMAP_USABLE) {
            if (entry->base == 0x100000) {
                if (entry->length > 0xf00000) {
                    E801.lowermem = 0x3c00;
                } else {
                    E801.lowermem = entry->length / 1024;
                }
            }
            if (entry->base <= 0x1000000 && entry->base + entry->length > 0x1000000) {
                E801.uppermem = ((entry->length - (0x1000000 - entry->base)) / 1024) / 64;
            }
        }
    }

    return E801;
}

void _start(void) {
    printf("ZealBooter prekernel\n");
    printf("____________________\n\n");

    struct limine_file *module_kernel = module_request.response->modules[0];
    struct CKernel *kernel = module_kernel->address;

    const size_t trampoline_size = (uintptr_t)trampoline_end - (uintptr_t)trampoline;
    const size_t boot_stack_size = 32768;
    const size_t final_size = align_up_u64(module_kernel->size + trampoline_size, 16) + boot_stack_size;

    uintptr_t final_address = (uintptr_t)-1;
    for (size_t i = 0; i < memmap_request.response->entry_count; i++) {
        struct limine_memmap_entry *entry = memmap_request.response->entries[i];

        if (entry->type != LIMINE_MEMMAP_USABLE) {
            continue;
        }

        if (entry->length >= final_size) {
            final_address = entry->base;
            break;
        }
    }
    if (final_address == (uintptr_t)-1) {
        printf("ERROR: could not find valid final address");
        for (;;) { asm("hlt"); }
    }

	printf("final_address: 0x%X\n", final_address);

    struct limine_framebuffer *fb = framebuffer_request.response->framebuffers[0];
    kernel->sys_framebuffer_pitch = fb->pitch;
    kernel->sys_framebuffer_width = fb->width;
    kernel->sys_framebuffer_height = fb->height;
    kernel->sys_framebuffer_bpp = fb->bpp;
    kernel->sys_framebuffer_addr = (uintptr_t)fb->address - hhdm_request.response->offset;

    struct limine_video_mode *mode;
    for (size_t i = 0, j = 0; i < fb->mode_count && i < VBE_MODES_NUM; i++)
    {
        mode = fb->modes[i];
        if (mode->bpp == 32)
        {
            kernel->sys_framebuffer_list[j].height = mode->height;
            kernel->sys_framebuffer_list[j].width = mode->width;
            j++;
        }
    }

    void *entry_point; // to CORE0_32BIT_INIT
    for (uint64_t *p = (uint64_t *)kernel; ; p++) {
        if (*p != ZEALBOOTER_LIMINE_SIGNATURE_1) {
            continue;
        }
        p++;
        if (*p != ZEALBOOTER_LIMINE_SIGNATURE_2) {
            continue;
        }
        p++;
        entry_point = p;
        break;
    }

    entry_point -= (uintptr_t)module_kernel->address;
    entry_point += final_address;

    printf("entry_point: 0x%X\n", entry_point);

    if (module_kernel->media_type == LIMINE_MEDIA_TYPE_OPTICAL)
        kernel->boot_src = BOOT_SRC_DVD;
    else if (module_kernel->media_type == LIMINE_MEDIA_TYPE_GENERIC)
        kernel->boot_src = BOOT_SRC_HDD;
    else
        kernel->boot_src = BOOT_SRC_RAM;
    kernel->boot_blk = 0;
    kernel->boot_patch_table_base = (uintptr_t)kernel + kernel->h.patch_table_offset;
    kernel->boot_patch_table_base -= (uintptr_t)module_kernel->address;
    kernel->boot_patch_table_base += final_address;

    printf("kernel->boot_patch_table_base: 0x%X\n", kernel->boot_patch_table_base);

    kernel->sys_run_level = RLF_VESA | RLF_16BIT | RLF_32BIT;

    kernel->boot_base = (uintptr_t)&kernel->jmp - (uintptr_t)module_kernel->address;
    kernel->boot_base += final_address;

    printf("kernel->boot_base: 0x%X\n", kernel->boot_base);

    kernel->sys_gdt_ptr.limit = sizeof(kernel->sys_gdt) - 1;
    kernel->sys_gdt_ptr.base = (void *)&kernel->sys_gdt - (uintptr_t)module_kernel->address;
    kernel->sys_gdt_ptr.base += final_address;

    printf("kernel->sys_gdt_ptr.limit: 0x%X\n", kernel->sys_gdt_ptr.limit);
    printf("kernel->sys_gdt_ptr.base: 0x%X\n", kernel->sys_gdt_ptr.base);

    kernel->sys_pci_buses = 256;

    struct E801 E801 = get_E801();
    kernel->mem_E801[0] = E801.lowermem;
    kernel->mem_E801[1] = E801.uppermem;

    kernel->mem_physical_space = 0;

    printf("memory map:\n");
    size_t mem_count = memmap_request.response->entry_count; // MEM_E820_ENTRIES_NUM now == 256, which is also Limine's memmap entry count max
    for (size_t i = 0; i < mem_count; i++) {
        struct limine_memmap_entry *entry = memmap_request.response->entries[i];
        int zeal_mem_type;

        printf("    ");
        switch (entry->type) {
            case LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE:
            case LIMINE_MEMMAP_KERNEL_AND_MODULES:
            case LIMINE_MEMMAP_USABLE:
                zeal_mem_type = MEM_E820T_USABLE;
                printf("  USABLE: ");
                break;
            case LIMINE_MEMMAP_ACPI_RECLAIMABLE:
                zeal_mem_type = MEM_E820T_ACPI;
                printf("    ACPI: ");
                break;
            case LIMINE_MEMMAP_ACPI_NVS:
                zeal_mem_type = MEM_E820T_ACPI_NVS;
                printf("     NVS: ");
                break;
            case LIMINE_MEMMAP_BAD_MEMORY:
                zeal_mem_type = MEM_E820T_BAD_MEM;
                printf("     BAD: ");
                break;
            case LIMINE_MEMMAP_RESERVED:
            default:
                zeal_mem_type = MEM_E820T_RESERVED;
                printf("RESERVED: ");
                break;
        }

        kernel->mem_E820[i].base = (void *)entry->base;
        kernel->mem_E820[i].len = entry->length;
        kernel->mem_E820[i].type = zeal_mem_type;

        if (kernel->mem_physical_space < entry->base + entry->length) {
            kernel->mem_physical_space = entry->base + entry->length;
        }

        printf("0x%08X-0x%08X", entry->base, entry->base + entry->length - 1);

        if (i % 3 == 0)
        {
            printf("\n");
        }

    }
    printf("\n");

    kernel->mem_E820[mem_count].type = 0;

    kernel->mem_physical_space = align_up_u64(kernel->mem_physical_space, 0x200000);

    void *sys_gdt_ptr = (void *)&kernel->sys_gdt_ptr - (uintptr_t)module_kernel->address;
    sys_gdt_ptr += final_address;

    printf("sys_gdt_ptr: 0x%X\n", sys_gdt_ptr);

    void *sys_smbios_entry = smbios_request.response->entry_32;
    if (sys_smbios_entry != NULL) {
        kernel->sys_smbios_entry = (uintptr_t)sys_smbios_entry - hhdm_request.response->offset;
    }

    memcpy(kernel->sys_disk_uuid, &module_kernel->gpt_disk_uuid, sizeof(kernel->sys_disk_uuid));

    void *const trampoline_phys = (void *)final_address + module_kernel->size;

	printf("trampoline_phys: 0x%X\n", trampoline_phys);

    const uintptr_t boot_stack = final_address + final_size;

	printf("boot_stack: 0x%X\n", boot_stack);

	kernel->sys_boot_stack = boot_stack;

    if (efi_request.response)
    {
        kernel->sys_is_uefi_booted = true;
    }

    memcpy(trampoline_phys, trampoline, trampoline_size);
    memcpy((void *)final_address, kernel, module_kernel->size);

//    printf("\nDEBUG: halting."); for (;;);
    asm volatile (
        "jmp *%0"
        :
        : "a"(trampoline_phys), "b"(entry_point),
          "c"(sys_gdt_ptr), "d"(boot_stack),
          "S"(kernel->boot_patch_table_base), "D"(kernel->boot_base)
        : "memory");

    __builtin_unreachable();
}