///                MentOS, The Mentoring Operating system project
/// @file   kernel.c
/// @brief  Kernel main function.
/// @copyright (c) 2014-2021 This file is distributed under the MIT License.
/// See LICENSE.md for details.

#include "io/proc_modules.h"
#include "mem/vmem_map.h"
#include "fs/procfs.h"
#include "devices/pci.h"
#include "drivers/ata.h"
#include "descriptor_tables/idt.h"
#include "kernel.h"
#include "mem/zone_allocator.h"
#include "descriptor_tables/gdt.h"
#include "system/syscall.h"
#include "version.h"
#include "io/video.h"
#include "hardware/pic8259.h"
#include "io/debug.h"
#include "drivers/fdc.h"
#include "fs/initrd.h"
#include "fs/ext2.h"
#include "klib/irqflags.h"
#include "drivers/keyboard/keyboard.h"
#include "process/scheduler.h"
#include "hardware/timer.h"
#include "fs/vfs.h"
#include "devices/fpu.h"
#include "system/printk.h"
#include "sys/module.h"
#include "drivers/rtc.h"
#include "stdio.h"
#include "assert.h"
#include "io/vga/vga.h"

/// Describe start address of grub multiboot modules.
char *module_start[MAX_MODULES];
/// Describe end address of grub multiboot modules.
char *module_end[MAX_MODULES];

// Everything is defined in kernel.ld.

/// Points at the multiheader grub info, starting address.
extern uint32_t _multiboot_header_start;
/// Points at the multiheader grub info, ending address.
extern uint32_t _multiboot_header_end;
/// Points at the kernel code, starting address.
extern uint32_t _text_start;
/// Points at the kernel code, ending address.
extern uint32_t _text_end;
/// Points at the read-only kernel data, starting address.
extern uint32_t _rodata_start;
/// Points at the read-only kernel data, ending address.
extern uint32_t _rodata_end;
/// Points at the read-write kernel data initialized, starting address.
extern uint32_t _data_start;
/// Points at the read-write kernel data initialized, ending address.
extern uint32_t _data_end;
/// Points at the read-write kernel data uninitialized an kernel stack, starting address.
extern uint32_t _bss_start;
/// Points at the read-write kernel data uninitialized an kernel stack, ending address.
extern uint32_t _bss_end;
/// Points at the top of the kernel stack.
extern uint32_t stack_top;
/// Points at the bottom of the kernel stack.
extern uint32_t stack_bottom;
/// Points at the end of kernel code/data.
extern uint32_t end;

/// Initial ESP.
uintptr_t initial_esp = 0;
/// The boot info.
boot_info_t boot_info;

/// @brief Prints [OK] at the current row and column 60.
static inline void print_ok()
{
    video_move_cursor(75, video_get_y());
    video_puts("[" FG_GREEN_BRIGHT "OK" FG_WHITE "]\n");
}

/// @brief Prints [FAIL] at the current row and column 60.
static inline void print_fail()
{
    video_move_cursor(75, video_get_y());
    video_puts("[" FG_RED_BRIGHT "FAIL" FG_WHITE "]\n");
}

/// @brief Entry point of the kernel.
/// @param boot_informations Information concerning the boot.
/// @return The exit status of the kernel.
int kmain(boot_info_t *boot_informations)
{
    pr_notice("Booting...\n");
    // Make a copy for when paging is enabled
    boot_info = *boot_informations;
    // Am I booted by a Multiboot-compliant boot loader?
    if (boot_info.magic != MULTIBOOT_BOOTLOADER_MAGIC) {
        printf("Invalid magic number: 0x%x\n", (unsigned)boot_info.magic);
        return 1;
    }
    // Set the initial esp.
    initial_esp = boot_info.stack_base;
    // Dump the multiboot structure.
    dump_multiboot(boot_info.multiboot_header);
    //==========================================================================
    pr_notice("Initialize the video...\n");
    vga_initialize();
    video_init();

    //==========================================================================
    printf(OS_NAME " " OS_VERSION);
    printf("\nSite:");
    printf(OS_SITEURL);
    printf("\n\n");

    //==========================================================================
    pr_notice("Initialize modules...\n");
    printf("Initialize modules...");
    if (!init_modules(boot_info.multiboot_header)) {
        print_fail();
        return 1;
    }
    print_ok();
    pr_debug("End of modules: 0x%09p\n", get_address_after_modules());

    //==========================================================================
    pr_notice("Initialize physical memory manager...\n");
    printf("Initialize physical memory manager...");
    if (!pmmngr_init(&boot_info)) {
        print_fail();
        return 1;
    }
    print_ok();

    //==========================================================================
    pr_notice("Initialize slab allocator.\n");
    printf("Initialize slab...");
    kmem_cache_init();
    print_ok();

    //==========================================================================
    // The Global Descriptor Table (GDT) is a data structure used by Intel
    // x86-family processors starting with the 80286 in order to define the
    // characteristics of the various memory areas used during program execution,
    // including the base address, the size, and access privileges like
    // executability and writability. These memory areas are called segments in
    // Intel terminology.
    pr_notice("Initialize Global Descriptor Table (GDT)...\n");
    printf("Initialize GDT...");
    init_gdt();
    print_ok();
    // The IDT is used to show the processor what Interrupt Service Routine
    // (ISR) to call to handle an exception. IDT entries are also called
    // Interrupt requests whenever a device has completed a request and needs to
    // be serviced.
    // ISRs are used to save the current processor state and set up the
    // appropriate segment registers needed for kernel mode before the kernel’s
    // C-level interrupt handler is called. To handle the right exception, the
    // correct entry in the IDT should be pointed to the correct ISR.
    pr_notice("Initialize Interrupt Service Routine(ISR)...\n");
    printf("Initialize IDT...");
    init_idt();
    print_ok();

    //==========================================================================
    pr_notice("Initialize system calls...\n");
    printf("Initialize system calls...");
    syscall_init();
    print_ok();

    //==========================================================================
    pr_notice("Initialize IRQ...\n");
    printf("Initialize IRQ...");
    pic8259_init_irq();
    print_ok();

    //==========================================================================
    pr_notice("Relocate modules.\n");
    printf("Relocate modules...");
    relocate_modules();
    print_ok();

    //==========================================================================
    pr_notice("Initialize paging.\n");
    printf("Initialize paging...");
    paging_init(&boot_info);
    print_ok();

    //==========================================================================
    pr_notice("Initialize virtual memory mapping.\n");
    printf("Initialize virtual memory mapping...");
    virt_init();
    print_ok();

    //==========================================================================
    pr_notice("Install the timer.\n");
    printf("Setting up timer...");
    timer_install();
    print_ok();

    //==========================================================================
    pr_notice("Install RTC.\n");
    printf("Setting up RTC...");
    rtc_initialize();
    print_ok();

    //==========================================================================
    pr_notice("Initialize the filesystem.\n");
    printf("Initialize the filesystem...");
    vfs_init();
    print_ok();

    //==========================================================================
    pr_notice("    Initialize 'initrd'...\n");
    printf("    Initialize 'initrd'...");
    if (initrd_init_module()) {
        print_fail();
        pr_emerg("Failed to register `initrd`!\n");
        return 1;
    }
    print_ok();
    if (do_mount("initrd", "/", "/dev/ram0")) {
        pr_emerg("Failed to mount root `/`!\n");
        return 1;
    }

    //==========================================================================
    pr_notice("    Initialize 'procfs'...\n");
    printf("    Initialize 'procfs'...");
    if (procfs_module_init()) {
        print_fail();
        pr_emerg("Failed to register `procfs`!\n");
        return 1;
    }
    print_ok();
    if (do_mount("procfs", "/proc", NULL)) {
        pr_emerg("Failed to mount procfs at `/proc`!\n");
        return 1;
    }

    //==========================================================================
    pr_notice("Initialize video procfs file...\n");
    printf("Initialize video procfs file...");
    if (procv_module_init()) {
        print_fail();
        pr_emerg("Failed to initialize `/proc/video`!\n");
        return 1;
    }
    print_ok();

    //==========================================================================
    pr_notice("Initialize system procfs file...\n");
    printf("Initialize system procfs file...");
    if (procs_module_init()) {
        print_fail();
        pr_emerg("Failed to initialize proc system entries!\n");
        return 1;
    }
    print_ok();

    //==========================================================================
    // Scan for ata devices.
    pr_notice("Initialize ATA devices...\n");
    printf("Initialize ATA devices...\n");
    if (ata_initialize()) {
        pr_emerg("Failed to initialize ATA devices!\n");
        return 1;
    }

    ext2_initialize();
    if (do_mount("ext2", "/mnt", "/dev/hda")) {
        pr_warning("Failed to mount ext at `/dev/hda`!\n");
    }

    //==========================================================================
    pr_notice("Setting up keyboard driver...\n");
    printf("Setting up keyboard driver...");
    keyboard_initialize();
    print_ok();

    //==========================================================================
#if 0
     pr_notice("Install the mouse.\n");
     printf(" * Setting up mouse driver...");
     mouse_install();     // Install the mouse.
     print_ok();
#endif

    //==========================================================================
    pr_notice("Initialize the scheduler.\n");
    printf("Initialize the scheduler...");
    scheduler_initialize();
    print_ok();

    //==========================================================================
    pr_notice("Init process management...\n");
    printf("Init process management...");
    if (!init_tasking()) {
        print_fail();
        return 1;
    }
    print_ok();

    //==========================================================================
    pr_notice("Creating init process...\n");
    printf("Creating init process...");
    task_struct *init_p = process_create_init("/bin/init");
    if (!init_p) {
        print_fail();
        return 1;
    }
    print_ok();

    //==========================================================================
    pr_notice("Initialize floating point unit...\n");
    printf("Initialize floating point unit...");
    if (!fpu_install()) {
        print_fail();
        return 1;
    }
    print_ok();

    //==========================================================================
    pr_notice("Initialize signals...\n");
    printf("Initialize signals...");
    if (!signals_init()) {
        print_fail();
        return 1;
    }
    print_ok();

    // We have completed the booting procedure.
    pr_notice("Booting done, jumping into init process.\n");
    // Print the welcome message.
    printf("\n                .: Welcome to MentOS :.\n\n");
    // Switch to the page directory of init.
    paging_switch_directory_va(init_p->mm->pgd);
    // Jump into init process.
    scheduler_enter_user_jmp(
        // Entry point.
        init_p->thread.regs.eip,
        // Stack pointer.
        init_p->thread.regs.useresp);
    // Enable interrupt requests.
    sti();
    for (;;) {}
    // We should not be here.
    pr_emerg("Dear developer, we have to talk...\n");
    return 1;
}
