// Copyright (c) 2025 shibedrill
// SPDX-License-Identifier: MIT

#![no_std]
#![no_main]
#![feature(abi_x86_interrupt)]

mod arch;
mod boot;
mod constants;
mod interrupt;
#[macro_use]
mod log;
mod memory;
mod panic;
mod process;
mod util;

use core::ptr::addr_of;

use arch::x86_64::interrupts::IDT;
use arch::x86_64::serial::SerialPort;
use boot::{BASE_REVISION, params, *};
use constants::*;
use log::*;
use memory::alloc::{boxed::Box, format, vec};
use params::*;

use lazy_static::lazy_static;
use limine::firmware::{
    FIRMWARE_TYPE_EFI32, FIRMWARE_TYPE_EFI64, FIRMWARE_TYPE_SBI, FIRMWARE_TYPE_X86BIOS,
};
use spin::mutex::Mutex;
use x86_64::{
    VirtAddr,
    registers::rflags::RFlags,
    structures::{
        idt::InterruptStackFrameValue,
        paging::{PageTableFlags, page_table::PageTableEntry},
    },
};

use crate::{
    arch::{
        asm,
        x86_64::{
            cpuid::{CPUID, virt_supported},
            gdt::{SELECTORS, dump_gdt},
            interrupts::get_lapic_addr,
        },
    },
    boot::modules::MODULE_RESPONSE,
    memory::paging::{self, active_root_table, map_page, phys_to_virt},
};

lazy_static! {
    pub static ref SERIAL_3F8: Mutex<SerialPort> = Mutex::new(
        arch::x86_64::serial::SerialPort::try_from_port(0x3f8).expect("Could not create port")
    );
}

#[unsafe(no_mangle)]
unsafe extern "C" fn main() -> ! {
    // CRITICAL AREA
    // Nothing non-essential should be done until we can initialize logging
    // Otherwise it will fail silently

    // Assert supported bootloader version
    assert!(BASE_REVISION.is_supported());

    // Set up logging level from params
    // Nothing we can do here if this fails since no log subscribers are initialized yet
    if let Some(level) = PARAMS.get("-loglevel")
        && let Ok(parsed_level) = LogLevel::try_from(level.as_str())
    {
        LOGGER.lock().level = parsed_level;
    }
    // Add subscribers to logger
    if let Some(device) = PARAMS.get("-logdev") {
        let log_device_list: vec::Vec<&str> = device.split(',').collect();
        if log_device_list.contains(&"display") {
            // Append display console to log subs
        }
        if log_device_list.contains(&"serial") {
            // TODO: Set up device discovery
            let serial_logger = |msg: &str| SERIAL_3F8.lock().log_writeln(msg);
            LOGGER.lock().add_device(Box::new(serial_logger));
        }
        log_trace!("Configured kernel logging devices");
    }

    // END OF CRITICAL AREA
    // Fallible code can be placed below this comment

    // Initialize all statics so the bootloader memory can be reclaimed
    memory::init_statics();

    log_info!(
        "Long mode: {}",
        if asm::long_mode() {
            "enabled"
        } else {
            "disabled"
        }
    );
    // Ensure IDT exists
    IDT.load();
    log_info!("IDT loaded!");
    SELECTORS.gdt.load();
    log_info!("GDT loaded!");

    log_info!(
        "Kernel cmdline: {}",
        EXECUTABLE_FILE_RESPONSE.executable_file().cmdline()
    );
    log_info!(
        "Kernel file path: {}",
        EXECUTABLE_FILE_RESPONSE.executable_file().path()
    );

    let log_level = log::LOGGER.lock().level;
    log_info!("Log level: {:?}", log_level);

    // Branding
    for line in ASCII_LOGO {
        let mut locked = SERIAL_3F8.lock();
        locked.log_write("\t\t");
        locked.log_writeln(line);
    }
    SERIAL_3F8
        .lock()
        .log_writeln("\tWelcome to the Gila microkernel!\n");
    log_info!("Booting gila version {}", kernel_version_string());

    match boot::FIRMWARE_TYPE_REQUEST.response() {
        Some(resp) => log_info!(
            "Firmware type: {}",
            match resp.firmware_type {
                FIRMWARE_TYPE_SBI => "SBI",
                FIRMWARE_TYPE_EFI32 => "UEFI (32-bit)",
                FIRMWARE_TYPE_EFI64 => "UEFI (64-bit)",
                FIRMWARE_TYPE_X86BIOS => "x86 BIOS",
                _ => "Unknown",
            }
        ),
        None => log_warning!("Firmware type: No response"),
    }

    log_info!("Trans rights!");

    if let Some(framebuffer_response) = FRAMEBUFFER_REQUEST.response() {
        let fb = framebuffer_response.framebuffers_rev1().unwrap()[0];
        log_info!("Framebuffer response received");
        for mode in fb.modes() {
            log_trace!(
                "Mode: {}x{}, {} bits per pixel",
                mode.width,
                mode.height,
                mode.bpp
            );
        }
    } else {
        log_info!("Framebuffer response absent, graphics disabled",);
    }

    let smp_response = MP_REQUEST.response();
    match smp_response {
        None => log_info!("Multiprocessing response not received"),
        Some(resp) => {
            log_info!("Multiprocessing response received");
            log_info!("{} CPUs found", resp.cpus().len());
        }
    }

    let rsdp_response = RSDP_REQUEST.response();
    match rsdp_response {
        None => log_info!("RDSP response not received"),
        Some(resp) => {
            log_info!("RSDP response received");
            log_trace!("RSDP address: 0x{:x}", resp.address as usize)
        }
    }

    boot::modules::log_modules();

    memory::log_memory();
    memory::log_address();

    if let Some(string) = CPUID.get_processor_brand_string() {
        log_info!("CPU brand string: {}", string.as_str());
    }
    if let Some(string) = CPUID.get_vendor_info() {
        log_info!("CPU vendor: {}", string.as_str());
    }
    if let Some(string) = CPUID.get_hypervisor_info() {
        log_info!("Hypervisor: {:?}", string.identify());
    }
    log_info!("Virtualization provider: {:?}", virt_supported());

    {
        let root_ptable_info = unsafe { active_root_table() };
        log_info!(
            "Root page table is Level {} at 0x{:x}",
            root_ptable_info.1,
            addr_of!(*root_ptable_info.0) as usize
        );
        // This is an expensive function. Only run it if we're actually logging the results.
        let loglevel = LOGGER.lock().level;
        if loglevel <= LogLevel::Trace {
            paging::walk_tables();
        }
    }

    dump_gdt();

    let lapic = get_lapic_addr();
    let mapping = {
        let mut m = PageTableEntry::new();
        m.set_addr(lapic, PageTableFlags::PRESENT);
        m
    };
    let res = map_page(phys_to_virt(lapic), mapping);
    log_info!("Map status: {:?}", res);

    if let Some(modules_list) = *MODULE_RESPONSE
        && modules_list.modules()[0].path().ends_with("/userboot.bin")
    {
        let ub = modules_list.modules()[0];
        log_info!("Found userboot: {} {}", ub.path(), ub.cmdline());

        //let start_addr = VirtAddr::from_ptr(ub.data() as *const [u8]);
        let start_addr = VirtAddr::from_ptr(usermode_fn as *const fn());
        log_info!("Start address: 0x{:x}", start_addr);

        // This does not work :/
        /*unsafe {
            InterruptStackFrameValue::new(
                start_addr,
                GDT_SELECTOR_USER_CODE,
                RFlags::empty(),
                phys_to_virt(PhysAddr::new(0x1000)),
                GDT_SELECTOR_USER_DATA,
            )
            .iretq()
        };*/
        log_trace!("Segment offsets:");
        log_trace!("GDT_SELECTOR_USER_CODE: {}", SELECTORS.sel_user_code.0);
        log_trace!("GDT_SELECTOR_USER_DATA: {}", SELECTORS.sel_user_stack.0);
        log_trace!("GDT_SELECTOR_KERNEL_CODE: {}", SELECTORS.sel_kernel_code.0);
        log_trace!("GDT_SELECTOR_KERNEL_DATA: {}", SELECTORS.sel_kernel_stack.0);
        x86_64::registers::model_specific::Star::write(
            SELECTORS.sel_user_code,
            SELECTORS.sel_user_stack,
            SELECTORS.sel_kernel_code,
            SELECTORS.sel_kernel_stack,
        )
        .expect("fuck");
        unsafe {
            InterruptStackFrameValue::new(
                VirtAddr::from_ptr(usermode_fn as *const fn()),
                SELECTORS.sel_user_code,
                RFlags::INTERRUPT_FLAG,
                VirtAddr::new(0xffffffff8f000000),
                SELECTORS.sel_user_stack,
            )
            .iretq();
        }
    }

    // TODO:
    // - Map APIC
    // - Create an interrupt stack frame, push it to stack, and iret to userboot

    panic!("Finished boot, but cannot start init because processes not implemented!");
}

fn usermode_fn() -> ! {
    log_info!("In usermode!");
    loop {
        arch::asm::nop();
    }
}
