//! GPIO drivers

use abi::error::Error;
use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec};
use device_api::{
    device::{Device, DeviceInitContext},
    gpio::{GpioInterrupt, GpioInterruptMode, GpioOutput, GpioPinLevel, PinHandle},
};
use device_tree::driver::{device_tree_driver, DeviceTreeGpioPins, Node, ProbeContext};
use libk::event::{self, GpioEvent};

enum LedFunction {
    Heartbeat,
    Other,
}

struct LedPin {
    name: String,
    function: LedFunction,
    handle: PinHandle,
}

struct KeyPin {
    name: String,
    handle: PinHandle,
}

trait Pin: Sized + Send + Sync + 'static {
    fn from_device_tree(node: &Arc<Node>) -> Option<Arc<Self>>;
    fn configure(self: &Arc<Self>) -> Result<(), Error>;
    fn name(&self) -> &str;
}

struct PinGroup<P: Pin> {
    name: String,
    pins: Vec<Arc<P>>,
}

impl<P: Pin> PinGroup<P> {
    fn from_device_tree(node: &Arc<Node>) -> Option<Arc<dyn Device>> {
        let pins = node
            .children()
            .filter_map(P::from_device_tree)
            .collect::<Vec<_>>();
        if pins.is_empty() {
            return None;
        }
        let name = node.name().unwrap_or("pin-group");
        Some(Arc::new(Self {
            pins,
            name: name.into(),
        }))
    }
}

impl<P: Pin> Device for PinGroup<P> {
    unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
        for pin in self.pins.iter() {
            if let Err(error) = pin.configure() {
                log::error!("gpio {} setup error: {:?}", pin.name(), error);
            }
        }
        Ok(())
    }

    fn display_name(&self) -> &str {
        &self.name
    }
}

impl Pin for LedPin {
    fn from_device_tree(node: &Arc<Node>) -> Option<Arc<Self>> {
        let handle = node.gpio(
            &DeviceTreeGpioPins {
                input: false,
                output: true,
                initial_level: GpioPinLevel::Low,
            },
            0,
        )?;

        let function = match node.prop_str("function") {
            Some("heartbeat") => LedFunction::Heartbeat,
            _ => LedFunction::Other,
        };

        let name = node.name().unwrap_or("gpio-led").into();
        Some(Arc::new(Self {
            name,
            function,
            handle,
        }))
    }

    fn configure(self: &Arc<Self>) -> Result<(), Error> {
        self.handle.configure()?;

        match self.function {
            LedFunction::Heartbeat => {
                log::info!("Register led {:?} as heartbeat indicator", self.name);
                event::register_heartbeat_gpio(self.clone())
            }
            _ => (),
        }

        Ok(())
    }

    fn name(&self) -> &str {
        &self.name
    }
}

impl GpioOutput for LedPin {
    fn write(&self, value: bool) -> Result<(), Error> {
        self.handle.write(value)
    }
}

impl Pin for KeyPin {
    fn from_device_tree(node: &Arc<Node>) -> Option<Arc<Self>> {
        let handle = node.gpio(
            &DeviceTreeGpioPins {
                input: true,
                output: false,
                initial_level: GpioPinLevel::Low,
            },
            0,
        )?;
        let name = node.name().unwrap_or("gpio-key").into();
        Some(Arc::new(Self { name, handle }))
    }

    fn configure(self: &Arc<Self>) -> Result<(), Error> {
        let key = self.clone();
        self.handle.configure_with_interrupt(GpioInterrupt {
            mode: GpioInterruptMode::FallingEdge,
            handler: Box::new(move || {
                log::info!("GPIO key triggered: {:?}", key.name);
            }),
        })
    }

    fn name(&self) -> &str {
        &self.name
    }
}

device_tree_driver! {
    compatible: ["gpio-leds"],
    driver: {
        fn probe(&self, node: &Arc<Node>, _context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
            PinGroup::<LedPin>::from_device_tree(node)
        }
    }
}

device_tree_driver! {
    compatible: ["gpio-keys"],
    driver: {
        fn probe(&self, node: &Arc<Node>, _context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
            PinGroup::<KeyPin>::from_device_tree(node)
        }
    }
}
