use {limine::SmpRequest, xml::XMLElement};

use embedded_graphics::pixelcolor::Rgb888;

use crate::{arch::x86_64::graphics::DISPLAY, kmain::DEVICE_TREE};

pub mod memory;

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

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);

    static SMP: SmpRequest = SmpRequest::new(0);
    let smp = SMP.get_response().get().unwrap();
    use crate::alloc::string::ToString;

    let cpuinfo = cpuid::master().unwrap();
    let brand_string = cpuinfo.brand_string().unwrap_or("Unknown").to_string();
    DEVICE_TREE.force_unlock();

    pci::init();

    let mut dt = DEVICE_TREE.lock();

    let cpus = dt.devices.get_mut("CPUs").unwrap();
    let mut cpu = XMLElement::new("cpu");
    let core_count = smp.cpu_count.to_string();
    cpu.set_attribute("core count", core_count);
    for x in 0..smp.cpu_count {
        let core_name = alloc::format!("core_{}", x);
        let core = XMLElement::new(core_name);
        cpu.set_child(core);
    }

    cpu.set_attribute("brand string", brand_string);

    let _cpu_speed = 0;

    cpu.set_attribute("speed", "unknown");

    if false {
        // disable()     // disable interrupts (if still not done)
        let _i = 0;
        let start = cpuinfo.time_stamp_counter();

        log::info!("{:?}", start.unwrap().invariant_tsc());
        for _x in 0..1000 {}
        let _end = cpuinfo.time_stamp_counter();
    }

    let mut cpu_features = xml::XMLElement::new("CPU Features");
    {
        let apic = cpuinfo.apic();
        let avx = cpuinfo.avx();
        let avx2 = cpuinfo.avx2();
        let x2 = cpuinfo.x2apic();

        let gb_pages = cpuinfo.gigabyte_pages();
        let rdseed = cpuinfo.rdseed();
        let rdrand = cpuinfo.rdrand();

        cpu_features.set_attribute("apic", apic.to_string());
        cpu_features.set_attribute("avx", avx.to_string());
        cpu_features.set_attribute("avx2", avx2.to_string());
        cpu_features.set_attribute("gigabyte pages", gb_pages.to_string());
        cpu_features.set_attribute("rdrand", rdrand.to_string());
        cpu_features.set_attribute("rdseed", rdseed.to_string());
        cpu_features.set_attribute("x2apic", x2.to_string());
    }
    if cpuinfo.digital_temperature_sensor() {
        let mut temperature_child = XMLElement::new("Temperature");

        temperature_child.set_attribute("degrees", "Unknown");
        cpu.set_child(temperature_child);
    }

    cpu.set_child(cpu_features);
    cpus.push(cpu);
    drop(dt);

    // 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");

    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(),
        MOD_REQ
            .get_response()
            .get()
            .and_then(|m| m.modules().get(0))
            .map(|file| unsafe {
                core::slice::from_raw_parts(
                    file.base.as_ptr().expect("invalid initrd"),
                    file.length as usize,
                )
            }),
    )
}

/// Spin loop
pub fn sloop() -> ! {
    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() {}