use alloc::sync::Arc;
use device_api::{
    device::{Device, DeviceInitContext},
    gpio::{GpioController, GpioInterruptEvent, PinHandle},
    interrupt::{InterruptHandler, IrqHandle, IrqVector},
};
use device_tree::{
    driver::{
        device_tree_driver, util::generic_gpio_config, DeviceTreeGpioPins, DeviceTreePinController,
        Node, ProbeContext,
    },
    DeviceTreePropertyRead, TProp,
};
use libk_mm::device::DeviceMemoryIo;
use libk_util::sync::IrqSafeSpinlock;
use tock_registers::{
    interfaces::{Readable, Writeable},
    register_structs,
    registers::{ReadOnly, ReadWrite, WriteOnly},
};
use yggdrasil_abi::error::Error;

const PUPD_NONE: u32 = 0b00;

register_structs! {
    #[allow(non_snake_case)]
    Regs {
        // Pin function select
        (0x00 => GPFSEL: [ReadWrite<u32>; 6]),
        (0x18 => _0),
        // Set pin
        (0x1C => GPSET: [WriteOnly<u32>; 2]),
        (0x24 => _1),
        // Clear pin
        (0x28 => GPCLR: [WriteOnly<u32>; 2]),
        (0x30 => _2),
        // Current pin level
        (0x34 => GPLEV: [ReadOnly<u32>; 2]),
        (0x3C => _3),
        // Pin event detect status
        (0x40 => GPEDS: [ReadWrite<u32>; 2]),
        (0x48 => _4),
        // Pin rising edge event enable
        (0x4C => GPREN: [ReadWrite<u32>; 2]),
        (0x54 => _5),
        // Pin falling edge event enable
        (0x58 => GPFEN: [ReadWrite<u32>; 2]),
        (0x60 => _6),
        // Pin high event enable
        (0x64 => GPHEN: [ReadWrite<u32>; 2]),
        (0x6C => _7),
        // Pin low event enable
        (0x70 => GPLEN: [ReadWrite<u32>; 2]),
        (0x78 => _8),
        // Pin async rising edge event enable
        (0x7C => GPAREN: [ReadWrite<u32>; 2]),
        (0x84 => _9),
        // Pin async falling edge event enable
        (0x88 => GPAFEN: [ReadWrite<u32>; 2]),
        (0x90 => _10),
        // Pin pull up/down control
        (0xE4 => GPIO_PUP_PDN_CNTRL_REG: [ReadWrite<u32>; 4]),
        (0xF4 => _11),
        (0x100 => @END),
    }
}

struct Bcm2711Gpio {
    regs: IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>,
    #[allow(unused)]
    irqs: [IrqHandle; 2],
}

impl Regs {
    fn write_fsel(&self, pin: usize, value: u32) {
        let fsel_reg = pin / 10;
        let fsel_shift = (pin % 10) * 3;

        let mut fsel = self.GPFSEL[fsel_reg].get();
        fsel &= !(0x7 << fsel_shift);
        fsel |= (value & 0x7) << fsel_shift;
        self.GPFSEL[fsel_reg].set(fsel);
    }

    fn write_pupd(&self, pin: usize, value: u32) {
        let pupd_reg = pin / 16;
        let pupd_shift = (pin % 16) * 2;

        let mut pupd = self.GPIO_PUP_PDN_CNTRL_REG[pupd_reg].get();
        pupd &= !(0x3 << pupd_shift);
        pupd |= (value & 0x3) << pupd_shift;
        self.GPIO_PUP_PDN_CNTRL_REG[pupd_reg].set(pupd);
    }

    fn configure_pin_interrupts(
        &self,
        pin: usize,
        rising_edge: bool,
        falling_edge: bool,
        level_high: bool,
        level_low: bool,
    ) {
        #[inline]
        fn modify_reg(reg: &ReadWrite<u32>, bit: u32, set: bool) {
            if set {
                reg.set(reg.get() | bit);
            } else {
                reg.set(reg.get() & !bit);
            }
        }

        // TODO use async edge detection (likely have some limitations)?
        let reg = pin / 32;
        let bit = 1 << (pin % 32);

        // Disable async edge events
        modify_reg(&self.GPAREN[reg], bit, false);
        modify_reg(&self.GPAFEN[reg], bit, false);

        modify_reg(&self.GPREN[reg], bit, rising_edge);
        modify_reg(&self.GPFEN[reg], bit, falling_edge);
        modify_reg(&self.GPHEN[reg], bit, level_high);
        modify_reg(&self.GPLEN[reg], bit, level_low);

        // Clear interrupt status
        self.GPEDS[reg].set(bit);
    }

    fn configure_pin_function(&self, pin: usize, function: u32, pull: u32) {
        self.write_fsel(pin, function);
        self.write_pupd(pin, pull);
    }
}

impl Device for Bcm2711Gpio {
    unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
        let regs = self.regs.lock();

        // Disable all interrupts by default
        for pin in 0..58 {
            regs.configure_pin_interrupts(pin, false, false, false, false);
        }

        for irq in self.irqs.iter() {
            irq.register(self.clone())?;
            irq.enable()?;
        }

        Ok(())
    }

    fn display_name(&self) -> &str {
        "bcm2711-gpio"
    }
}

impl InterruptHandler for Bcm2711Gpio {
    fn handle_irq(self: Arc<Self>, _vector: IrqVector) -> bool {
        log::warn!("TODO: handle bcm2711-gpio interrupts");
        false
    }
}

impl GpioController for Bcm2711Gpio {
    fn setup_gpio(&self, pin: &PinHandle, _event: Option<GpioInterruptEvent>) -> Result<(), Error> {
        log::warn!(
            "TOOD: bcm2711 gpio pin #{} setup: input={}, output={}",
            pin.index,
            pin.config.input,
            pin.config.output
        );
        Ok(())
    }

    fn read_gpio(&self, _pin: &PinHandle) -> Result<bool, Error> {
        Ok(false)
    }

    fn write_gpio(&self, _pin: &PinHandle, _value: bool) -> Result<(), Error> {
        Ok(())
    }
}

impl DeviceTreePinController for Bcm2711Gpio {
    fn configure_pin_group(self: Arc<Self>, group: &Arc<Node>) -> Result<(), Error> {
        let pins = group.property("brcm,pins").ok_or(Error::InvalidArgument)?;
        let function = group.property("brcm,function").ok_or(Error::DoesNotExist)?;
        let pull = group.property("brcm,pull");

        if function.is_empty() || pins.is_empty() {
            return Ok(());
        }

        let function = function.read_cell(0, 1).ok_or(Error::InvalidArgument)? as u32;
        let regs = self.regs.lock();

        for i in 0..pins.len() / 4 {
            let pin = pins.read_cell(i, 1).ok_or(Error::InvalidArgument)? as u32;
            let pull = if let Some(pull) = pull.as_ref().and_then(|p| p.read_cell(i, 1)) {
                pull as u32
            } else {
                PUPD_NONE
            };
            log::info!("bcm2711-gpio: gpio{pin} function={function}");
            regs.configure_pin_function(pin as usize, function, pull);
        }

        Ok(())
    }

    fn map_gpio(
        self: Arc<Self>,
        property: &TProp,
        offset: usize,
        info: &DeviceTreeGpioPins,
    ) -> Option<(PinHandle, usize)> {
        let (pin, options) = property.read_cells_at(offset, (1, 1))?;
        let config = generic_gpio_config(options as u32, info);
        Some((
            PinHandle {
                index: pin as u32,
                config,
                parent: self,
            },
            2,
        ))
    }
}

device_tree_driver! {
    compatible: ["brcm,bcm2711-gpio"],
    driver: {
        fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
            let irq0 = node.interrupt(0)?;
            let irq1 = node.interrupt(1)?;

            let base = node.map_base(context, 0)?;
            let regs = unsafe { DeviceMemoryIo::map(base, Default::default()) }.ok()?;

            let gpio = Arc::new(Bcm2711Gpio {
                regs: IrqSafeSpinlock::new(regs),
                irqs: [irq0, irq1]
            });

            node.make_pin_controller(gpio.clone());

            Some(gpio)
        }
    }
}
