use alloc::{sync::Arc, vec::Vec};
use device_api::{
    clock::{ClockHandle, Hertz, ResetHandle},
    device::Device,
    interrupt::{InterruptHandler, IrqHandle},
};
use device_tree::driver::{Node, ProbeContext, device_tree_driver};
use libk::error::Error;
use libk_mm::{address::PhysicalAddress, device::RawDeviceMemoryMapping};

use crate::{Access, Config, PortConfig, Uart8250, Vendor};

pub struct Fdt8250Access {
    mapping: RawDeviceMemoryMapping,
    offset: usize,
    shift: usize,
    io_width: usize,
}

pub struct Fdt8250Config {
    irq: IrqHandle,
    ports: [Fdt8250PortConfig; 1],
}

pub struct Fdt8250PortConfig {
    base: PhysicalAddress,
    reg_offset: usize,
    reg_shift: usize,
    reg_io_width: usize,

    resets: Vec<ResetHandle>,
    clock_frequency: Option<Hertz>,
    clk_baud: Option<ClockHandle>,
    clk_apb: Option<ClockHandle>,
}

impl Access for Fdt8250Access {
    type Config = Fdt8250Config;

    unsafe fn map(config: &Fdt8250PortConfig) -> Result<Self, Error> {
        let mapping = unsafe {
            RawDeviceMemoryMapping::map(config.base.into_u64(), 0x1000, Default::default())
        }?;
        Ok(Self {
            offset: config.reg_offset,
            shift: config.reg_shift,
            io_width: config.reg_io_width,
            mapping,
        })
    }

    fn read(&self, reg: u8) -> u8 {
        let addr = ((reg as usize) << self.shift) + self.offset;
        assert!(addr + self.io_width <= 0x1000);
        let ptr = unsafe { self.mapping.as_non_null::<u8>().add(addr) };
        match self.io_width {
            4 => unsafe { ptr.cast::<u32>().read_volatile() as u8 },
            1 => unsafe { ptr.cast::<u8>().read_volatile() },
            _ => unimplemented!(),
        }
    }

    fn write(&mut self, reg: u8, val: u8) {
        let addr = ((reg as usize) << self.shift) + self.offset;
        assert!(addr + self.io_width <= 0x1000);
        let ptr = unsafe { self.mapping.as_non_null::<u8>().add(addr) };
        match self.io_width {
            4 => unsafe { ptr.cast::<u32>().write_volatile(val as u32) },
            1 => unsafe { ptr.cast::<u8>().write_volatile(val) },
            _ => unimplemented!(),
        }
    }

    // fn read(&self, reg: usize) -> u32 {
    // }

    // fn write(&mut self, reg: usize, val: u32) {
    // }
}

impl PortConfig for Fdt8250PortConfig {
    fn input_clock_frequency(&self) -> Result<Hertz, Error> {
        if let Some(clock_frequency) = self.clock_frequency {
            Ok(clock_frequency)
        } else if let Some(clk_baud) = self.clk_baud.as_ref() {
            clk_baud.rate()
        } else {
            Err(Error::InvalidArgument)
        }
    }

    fn deassert_resets(&self) -> Result<(), Error> {
        for reset in self.resets.iter() {
            reset.deassert()?;
        }
        Ok(())
    }

    fn enable_clocks(&self) -> Result<(), Error> {
        if let Some(clk_apb) = self.clk_apb.as_ref() {
            clk_apb.enable()?;
        }
        if let Some(clk_baud) = self.clk_baud.as_ref() {
            clk_baud.enable()?;
        }
        Ok(())
    }
}

impl Config for Fdt8250Config {
    type Port = Fdt8250PortConfig;

    fn ports(&self) -> &[Self::Port] {
        &self.ports[..]
    }

    fn enable_interrupt(&self, handler: Arc<dyn InterruptHandler>) -> Result<(), Error> {
        self.irq.register(handler)?;
        self.irq.enable()?;
        Ok(())
    }
}

// uart1: serial@7e215040 {
// 	compatible = "brcm,bcm2835-aux-uart";
// 	reg = <0x7e215040 0x40>;
// 	interrupts = <GIC_SPI 93 IRQ_TYPE_LEVEL_HIGH>;
// 	clocks = <&aux 0x00>;
// 	status = "okay";
// 	pinctrl-names = "default";
// 	pinctrl-0 = <&uart1_gpio14>;
// 	skip-init;
// 	bootph-all;
// };

impl Fdt8250Config {
    fn from_device_tree(node: &Arc<Node>, context: &mut ProbeContext) -> Option<(Self, Vendor)> {
        let (vendor, reg_io_width, reg_shift) = if node.is_compatible("snps,dw-apb-uart") {
            (Vendor::DwApbUart, 1, 0)
        } else if node.is_compatible("ns16550a") {
            (Vendor::Generic16550, 1, 0)
        } else if node.is_compatible("brcm,bcm2835-aux-uart") {
            (Vendor::Generic8250, 4, 2)
        } else {
            (Vendor::Generic8250, 1, 0)
        };

        let base = node.map_base(context, 0)?;
        let irq = node.interrupt(0)?;
        let resets: Vec<_> = node.resets().map(|r| r.collect()).unwrap_or_default();
        let clock_frequency = node
            .prop_usize("clock-frequency")
            .map(|clk| Hertz(clk as _));
        let mut clk_baud = node.named_clock("baudclk");
        let clk_apb = node.named_clock("apb_pclk");
        if clk_baud.is_none() && clk_apb.is_none() {
            clk_baud = node.clock(0);
        }

        let reg_io_width = node.prop_usize("reg-io-width").unwrap_or(reg_io_width);
        let reg_shift = node.prop_usize("reg-shift").unwrap_or(reg_shift);
        let reg_offset = node.prop_usize("reg-offset").unwrap_or(0);

        Some((
            Self {
                irq,
                ports: [Fdt8250PortConfig {
                    clk_apb,
                    clk_baud,
                    resets,
                    clock_frequency,

                    reg_io_width,
                    reg_offset,
                    reg_shift,

                    base,
                }],
            },
            vendor,
        ))
    }
}

device_tree_driver! {
    compatible: [
        "snps,dw-apb-uart",
        "brcm,bcm2835-aux-uart",
        "ns16550a",
    ],
    driver: {
        fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
            let (config, vendor) = Fdt8250Config::from_device_tree(node, context)?;
            let uart = Arc::new(Uart8250::<Fdt8250Access>::new(config, vendor));
            Some(uart)
        }
    }
}
