use core::sync::atomic::{AtomicU64, Ordering};

use alloc::{sync::Arc, vec::Vec};
use device_api::{
    clock::{ClockHandle, ResetHandle},
    device::{Device, DeviceInitContext},
    gpio::{
        GpioController, GpioInterruptEvent, GpioInterruptMode, GpioPinLevel, PinHandle,
        SinglePinDirection,
    },
    interrupt::{InterruptHandler, IrqHandle, IrqVector},
};
use device_tree::{
    driver::{
        device_tree_driver, util::generic_gpio_config, DeviceTreeGpioPins, DeviceTreePinController,
        Node, ProbeContext,
    },
    DeviceTreePropertyRead, TProp,
};
use libk::event::signal_gpio_event;
use libk_mm::device::DeviceMemoryIo;
use libk_util::{bit::BitField, sync::IrqSafeSpinlock};
use tock_registers::{
    interfaces::{Readable, Writeable},
    register_structs,
    registers::{ReadOnly, ReadWrite, WriteOnly},
};
use yggdrasil_abi::error::Error;

register_structs! {
    #[allow(non_snake_case)]
    Regs {
        (0x0000 => GPIODATA: [ReadWrite<u32>; 256]),
        (0x0400 => GPIODIR: ReadWrite<u32>),
        (0x0404 => GPIOIS: ReadWrite<u32>),
        (0x0408 => GPIOIBE: ReadWrite<u32>),
        (0x040C => GPIOIEV: ReadWrite<u32>),
        (0x0410 => GPIOIE: ReadWrite<u32>),
        (0x0414 => GPIORIS: ReadOnly<u32>),
        (0x0418 => GPIOMIS: ReadOnly<u32>),
        (0x041C => GPIOIC: WriteOnly<u32>),
        (0x0420 => GPIOAFSEL: ReadWrite<u32>),
        (0x0424 => _0),
        (0x0FE0 => GPIOPERIPHID: [ReadOnly<u32>; 4]),
        (0x0FF0 => GPIOPCELLID: [ReadOnly<u32>; 4]),
        (0x1000 => @END),
    }
}

struct Pl061 {
    #[allow(unused)]
    regs: IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>,
    irq: IrqHandle,
    clocks: Vec<ClockHandle>,
    resets: Vec<ResetHandle>,
    gpio_events: [AtomicU64; 8],
}

impl Device for Pl061 {
    unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
        for clock in self.clocks.iter() {
            clock.enable()?;
        }
        for reset in self.resets.iter() {
            reset.deassert()?;
        }

        Ok(())
    }

    unsafe fn init_irq(self: Arc<Self>) -> Result<(), Error> {
        self.irq.register(self.clone())?;
        self.irq.enable()?;
        Ok(())
    }

    fn display_name(&self) -> &str {
        "PL061 GPIO Controller"
    }
}

impl InterruptHandler for Pl061 {
    fn handle_irq(self: Arc<Self>, _vector: IrqVector) -> bool {
        let status = {
            let lock = self.regs.lock();
            let val = lock.GPIOMIS.get();
            lock.GPIOIC.set(0xFF);
            val
        };

        for bit in 0..8 {
            let ev = self.gpio_events[bit].load(Ordering::Acquire);
            if ev != 0 {
                signal_gpio_event(ev);
            }
        }

        status != 0
    }
}

impl GpioController for Pl061 {
    fn write_gpio(&self, _pin: &PinHandle, _value: bool) -> Result<(), Error> {
        todo!()
    }

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

    fn setup_gpio(&self, pin: &PinHandle, event: Option<GpioInterruptEvent>) -> Result<(), Error> {
        let regs = self.regs.lock();

        let direction = pin
            .config
            .force_single_direction()
            .ok_or(Error::InvalidArgument)
            .inspect_err(|_| {
                log::warn!(
                    "pl061: gpio #{} has invalid direction input={}, output={}",
                    pin.index,
                    pin.config.input,
                    pin.config.output
                )
            })?;

        // Enable software control
        regs.GPIOAFSEL.set(regs.GPIOAFSEL.get() & !(1 << pin.index));

        match direction {
            SinglePinDirection::Output => {
                log::info!("pl061: gpio #{} set as output", pin.index);
                regs.GPIODIR.set(regs.GPIODIR.get() | (1 << pin.index));
                // Disable interrupt
                regs.GPIOIE.set(regs.GPIOIE.get() & !(1 << pin.index));

                let level = match pin.config.initial_level {
                    GpioPinLevel::Low => 0,
                    GpioPinLevel::High => 1,
                };
                let mut val = regs.GPIODATA[0].get();
                val &= !(1 << pin.index);
                val |= level << pin.index;
                regs.GPIODATA[0].set(val);
            }
            SinglePinDirection::Input => {
                log::info!("pl061: gpio #{} set as input", pin.index);
                regs.GPIODIR.set(regs.GPIODIR.get() & !(1 << pin.index));

                if let Some(event) = event {
                    let ibe = event.mode == GpioInterruptMode::BothEdges;
                    let is = event.mode == GpioInterruptMode::HighLevel
                        || event.mode == GpioInterruptMode::LowLevel;
                    let iev = event.mode == GpioInterruptMode::HighLevel
                        || event.mode == GpioInterruptMode::RisingEdge;

                    self.gpio_events[pin.index as usize].store(event.event, Ordering::Release);

                    regs.GPIOIS
                        .set(regs.GPIOIS.get().modify_bit(pin.index as usize, is));
                    regs.GPIOIBE
                        .set(regs.GPIOIBE.get().modify_bit(pin.index as usize, ibe));
                    regs.GPIOIEV
                        .set(regs.GPIOIEV.get().modify_bit(pin.index as usize, iev));
                    regs.GPIOIE.set(regs.GPIOIE.get() | (1 << pin.index));
                    regs.GPIOIC.set(1 << pin.index);
                }
            }
        }

        Ok(())
    }
}

impl DeviceTreePinController for Pl061 {
    fn configure_pin_group(self: Arc<Self>, _pins: &Arc<Node>) -> Result<(), Error> {
        // TODO implement this when I get some board with this pinctrl
        todo!()
    }

    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: ["arm,pl061"],
    driver: {
        fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
            let clocks = if let Some(clocks) = node.clocks() {
                clocks.collect()
            } else {
                Vec::new()
            };
            let resets = if let Some(resets) = node.resets() {
                resets.collect()
            } else {
                Vec::new()
            };
            let irq = node.interrupt(0)?;

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

            let pinctrl = Arc::new(Pl061 {
                regs: IrqSafeSpinlock::new(regs),
                irq,
                clocks,
                resets,
                gpio_events: [const { AtomicU64::new(0) }; 8]
            });

            node.make_pin_controller(pinctrl.clone());

            Some(pinctrl)
        }
    }
}
