#![no_std]
#![feature(abi_x86_interrupt)]
#![feature(impl_trait_in_bindings)]
#![warn(
    clippy::correctness,
    clippy::nursery,
    clippy::unnecessary_cast,
    clippy::all,
    clippy::suspicious,
    clippy::perf,
    rustdoc::missing_errors_doc,
    rustdoc::missing_panics_doc
)]

extern crate alloc;

use crate::{
    arch::x86_64::{
        cpu::apic::enable_apic,
        drivers::{
            ascii::screensize_chars, framebuffer::display::screensize_px,
        },
        memory::{
            FRAME_ALLOCATOR,
            allocation::{
                heap_alloc::init_heap, page_alloc::FoundryOSFrameAllocator,
            },
            init_page_table, mapping,
            units::MemoryUnits,
        },
    },
    prelude::*,
};

use alloc::{boxed::Box, format};
use core::arch::asm;
use limine::BaseRevision;
use std::unwind::UNWINDER;
use x86_64::VirtAddr;

pub mod arch;
/// Commonly used re-exports.
pub mod prelude;
pub mod resources;
// We aren't using much of this right now.
#[allow(unused)]
pub mod std;
mod step;
pub mod util;

/// Sets the base revision to the latest revision supported by the crate.
/// See specification for further info.
/// Be sure to mark all limine requests with #[used], otherwise they may be
/// removed by the compiler.
#[used]
// The .requests section allows limine to find the requests faster and more
// safely.
#[unsafe(link_section = ".requests")]
static BASE_REVISION: BaseRevision = BaseRevision::new();

pub fn hcf() -> ! {
    loop {
        unsafe {
            #[cfg(target_arch = "x86_64")]
            asm!("hlt");
        }
    }
}

#[derive(Debug)]
pub struct NoError {}

impl core::fmt::Display for NoError {
    fn fmt(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        Ok(())
    }
}

pub struct NoTags;

impl core::error::Error for NoError {}

/// Panicking before this is initialised is unwise. We should probably extract
/// very early init into it's own function because Stack Traces may require
/// allocations etc.
pub fn boot() -> Result<(), Box<dyn core::error::Error>> {
    if !BASE_REVISION.is_supported() {
        return Err("Base revision not supported.".into());
    }

    use arch::x86_64::{gdt, interrupts};

    let memory_map = mapping::get_memory_map();

    print_log!("    Initialising Serial... ");
    let res = arch::x86_64::drivers::serial::init();
    serial_print!("    Initialising Serial... ");
    if res.is_err() {
        debugln!("[Not Detected]")
    } else {
        debugln!("[Success]");
    }

    debugln!("    Display...");
    let dimensions = screensize_chars();
    let dimensions2 = screensize_px();
    debugln!("      => (px)    : {}x{} ", dimensions2.0, dimensions2.1);
    debugln!("      => (chars) : {}x{} ", dimensions.0, dimensions.1);
    debugln!("    [Success]");

    debug!("    Setting Up Global Descriptor Table... ");
    gdt::init();
    debugln!("[Success]");

    debug!("    Setting Up Interrupt Descriptor Table... ");
    interrupts::init_idt();
    debugln!("[Success]");

    debugln!("    Initialising Memory Subsystem... ");
    let physical_memory_offset =
        VirtAddr::new(*mapping::PHYSICAL_MEMORY_OFFSET);
    init_page_table(physical_memory_offset);
    FoundryOSFrameAllocator::init(memory_map);
    let available_bytes =
        FRAME_ALLOCATOR.get().unwrap().lock().available_memory();

    debugln!(
        "      => Available Memory: {}",
        MemoryUnits::from_bytes(available_bytes as usize)
    );

    // Allocations should be all fine past this point.
    debugln!("    Initialising Heap... ");
    match unsafe { init_heap() } {
        Ok(_) => debugln!("    [Success]"),
        Err(why) => return Err(format!("{:?}", why).into()),
    }

    debug!("    Enabling PICs... ");
    interrupts::enable_pic();
    debugln!("[Success]");

    debug!("    Disabling PICs... ");
    interrupts::disable_pic();
    debugln!("[Success]");

    debug!("    Initialising APIC... ");
    enable_apic();
    debugln!("[Success]");

    debug!("    Enabling Interrupts... ");
    x86_64::instructions::interrupts::enable();
    debugln!("[Success]");

    // Initialises the Unwinder once and only once because this makes a heap
    // allocation.
    UNWINDER.lock();

    Ok(())
}
