use embedded_graphics::pixelcolor::Rgb888;

use crate::{arch::x86_64::graphics::DISPLAY, bootmodules::BootModule};

pub mod memory;

mod cpuid;
mod gdt;
pub mod graphics;
pub(crate) mod interrupts;
pub mod logging;
pub mod pci;
pub mod virtio;
mod device_info_collector;

pub use {logging::log, memory::PAGE_SIZE};

use {
    crate::allocator,
    limine::{HhdmRequest, KernelFileRequest, MemmapRequest, ModuleRequest},
    x86_64::VirtAddr,
};

extern "C" {
    fn _initial_kernel_heap_start();
    fn _initial_kernel_heap_size();
}

const INITIAL_KERNEL_HEAP_START: *mut u8 = _initial_kernel_heap_start as _;
const INITIAL_KERNEL_HEAP_SIZE: *const () = _initial_kernel_heap_size as _;

#[no_mangle]
unsafe extern "C" fn _kernel_start() -> ! {
    logging::init();
    crate::logger::init().expect("failed to set logger");
    log::info!("Initialising AKern {}", crate::VERSION);

    static HDHM_REQ: HhdmRequest = HhdmRequest::new(0);
    memory::init_pt(VirtAddr::new(
        HDHM_REQ
            .get_response()
            .get()
            .expect("tried to get physical memory mapping offset from Limine")
            .offset,
    ));

    allocator::init(INITIAL_KERNEL_HEAP_START, INITIAL_KERNEL_HEAP_SIZE as _);

    static MMAP_REQ: MemmapRequest = MemmapRequest::new(0);
    memory::initialize(
        MMAP_REQ
            .get_response()
            .get()
            .expect("tried to get memory map from Limine")
            .memmap(),
    );

    gdt::init();
    interrupts::init();

    static KFILE_REQ: KernelFileRequest = KernelFileRequest::new(0);
    static MOD_REQ: ModuleRequest = ModuleRequest::new(0);

    device_info_collector::collect_device_info();

    // Graphics test
    {
        graphics::init();
        let mut dis = DISPLAY.lock();
        use embedded_graphics::prelude::RgbColor;

        let _ = dis.set_color(Rgb888::YELLOW);
        let thick = 6;
        let p1 = (400, 30);
        let p2 = (200, 150);
        let p3 = (600, 150);
        let p4 = (200, 350);
        let p5 = (600, 350);
        let p6 = (400, 470);

        {
            //HEXAGON

            let _ = dis.line(p1.0, p1.1, p2.0, p2.1, thick);
            let _ = dis.line(p1.0, p1.1, p3.0, p3.1, thick);
            let _ = dis.line(p2.0, p2.1, p4.0, p4.1, thick);
            let _ = dis.line(p3.0, p3.1, p5.0, p5.1, thick);
            let _ = dis.line(p6.0, p6.1, p4.0, p4.1, thick);
            let _ = dis.line(p6.0, p6.1, p5.0, p5.1, thick);
        }
        {
            let _ = dis.line(600, 150, 200, 350, thick);
            let _ = dis.line(600, 350, 400, 250, thick);
        }

        {
            let _ = dis.set_color(Rgb888::WHITE);
            let hp1 = (350, 150);
            let hp2 = (350, 350);
            let hp3 = (450, 250);
            let hp4 = (350, 250);
            let hp5 = (450, 150);
            let hp6 = (450, 350);

            let _ = dis.line(hp1.0, hp1.1, hp2.0, hp2.1, thick);
            let _ = dis.line(hp3.0, hp3.1, hp4.0, hp4.1, thick);
            let _ = dis.line(hp5.0, hp5.1, hp6.0, hp6.1, thick);
        }

        dis.swap_buffers();
    };

    // TODO: Add in rdseed and rdrand as sources for randomness
    let _rand = xml::XMLElement::new("Random");

    let bm = MOD_REQ.get_response().get();

    let mut bootmodules = alloc::vec::Vec::new();

    if bm.is_some() {
        let bm = bm.unwrap();
        for x in 0..bm.module_count {
            let file = bm.modules().get(x as usize);
            if file.is_some() {
                let file = file.unwrap();
                let raw_bytes = core::slice::from_raw_parts(
                    file.base.as_ptr().expect("invalid initrd"),
                    file.length as usize,
                )
                .to_vec();

                let file_path = alloc::string::String::from_utf8(
                    file.path.to_str().unwrap().to_bytes().to_vec(),
                );
                if file_path.is_err() {
                    panic!("invalid file path: {:?}", file_path);
                }
                let file_cmd = alloc::string::String::from_utf8(
                    file.cmdline.to_str().unwrap().to_bytes().to_vec(),
                );
                if file_cmd.is_err() {
                    panic!("invalid module cmd: {:?}", file_cmd);
                }

                log::trace!("module path: {:?}", file_path);
                log::trace!("module cmd: {:?}", file_cmd);

                bootmodules.push(BootModule::new(
                    file_path.unwrap(),
                    raw_bytes,
                    file_cmd.unwrap(),
                ));
            } else {
                log::error!("You should not be here");
                break;
            }
        }
        log::info!("Boot module count: {:?}", bootmodules.len());
        assert_eq!(bm.module_count, bootmodules.len() as u64);
    }

    crate::kmain::kmain(
        KFILE_REQ
            .get_response()
            .get()
            .and_then(|r| r.kernel_file.get())
            .expect("failed to get kernel file from Limine")
            .cmdline
            .to_str()
            .map(core::ffi::CStr::to_str)
            .transpose()
            .expect("expected valid cmdline string")
            .unwrap_or_default(),
        bootmodules,
    )
}

/// Spin loop
pub fn spin_loop() -> ! {
    loop {
        x86_64::instructions::hlt();
    }
}

pub fn hardware_random_u64() -> u64 {
    use {log::trace, rdrand::RdRand};
    let gen = RdRand::new().unwrap();
    let ret = gen.try_next_u64().unwrap();
    trace!("Random {}", ret);

    ret
}

pub fn get_edid() {}