use core::ptr::NonNull;

use some_serial::{ns16550, pl011};

use crate::{
    console::{DEBUG_BASE, DEBUG_IS_MMIO, EarlySerial, EarlySerialRaw},
    mem::_fixmap_io,
};

pub fn setup_earlycon() -> Option<()> {
    let _ = super::set_cmdline();

    if <<crate::arch::Arch as crate::ArchTrait>::Console as crate::console::ArchConsoleOps>::init()
    {
        Some(())
    } else {
        if crate::console::set_earlycon_by_cmdline().is_ok() {
            return Some(());
        }

        if set_by_stdout().is_some() {
            return Some(());
        }

        Some(())
    }
}

fn set_by_stdout() -> Option<()> {
    let fdt = crate::fdt::fdt_base()?;

    let chosen = fdt.chosen()?;
    let stdout = split_stdout_options(chosen.stdout_path()?);
    let node = fdt.find_by_path(stdout)?;
    let reg = node.reg()?.next()?;
    let address = <crate::arch::Arch as crate::ArchTrait>::canonicalize_paddr(
        fdt.translate_address(stdout, reg.address) as _,
    );

    let addr = NonNull::new(_fixmap_io(address))?;
    let clock = node
        .find_property("clock-frequency")
        .and_then(|prop| prop.as_u32())
        .unwrap_or(0);
    let reg_width = node
        .find_property("reg-io-width")
        .and_then(|prop| prop.as_u32())
        .unwrap_or(1) as usize;

    let mut installed = false;
    for com in node.compatibles() {
        match com {
            "arm,pl011" | "arm,primecell" => {
                let mut serial = pl011::Pl011::new(addr, clock);
                serial.open();
                crate::console::set_earlycon_serial(EarlySerial::new(EarlySerialRaw::Pl011(
                    serial,
                )));
                installed = true;
                break;
            }
            "snps,dw-apb-uart" | "ns16550a" | "ns16550" => {
                let mut serial = ns16550::Ns16550::new_mmio(addr, clock, reg_width);
                serial.open();
                crate::console::set_earlycon_serial(EarlySerial::new(EarlySerialRaw::Ns16550Mmio(
                    serial,
                )));
                installed = true;
                break;
            }
            _ => {
                continue;
            }
        }
    }
    if !installed {
        return None;
    }
    unsafe {
        DEBUG_BASE = address;
        DEBUG_IS_MMIO = true;
    }
    Some(())
}

fn split_stdout_options(path: &str) -> &str {
    path.split_once(':').map_or(path, |(path, _)| path)
}
