use alloc::{boxed::Box, sync::Arc};
use yggdrasil_abi::error::Error;

use crate::device::Device;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputPinBias {
    PullUp,
    PullDown,
    Floating,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputPinBias {
    PushPull,
    OpenDrain,
    HighZ,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GpioPinLevel {
    High,
    Low,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SinglePinDirection {
    Output,
    Input,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GpioInterruptMode {
    RisingEdge,
    FallingEdge,
    BothEdges,
    HighLevel,
    LowLevel,
}

pub struct GpioInterruptEvent {
    pub mode: GpioInterruptMode,
    pub event: u64,
}

pub struct GpioInterrupt {
    pub mode: GpioInterruptMode,
    pub handler: Box<dyn Fn() + Send>,
}

pub struct GpioPinConfig {
    pub input: bool,
    pub output: bool,
    pub input_bias: Option<InputPinBias>,
    pub output_bias: Option<OutputPinBias>,
    pub active_level: GpioPinLevel,
    pub initial_level: GpioPinLevel,
}

pub struct PinHandle {
    pub index: u32,
    pub config: GpioPinConfig,
    pub parent: Arc<dyn GpioController>,
}

pub trait GpioController: Device {
    fn write_gpio(&self, pin: &PinHandle, value: bool) -> Result<(), Error>;
    fn read_gpio(&self, pin: &PinHandle) -> Result<bool, Error>;

    fn setup_gpio(&self, pin: &PinHandle, event: Option<GpioInterruptEvent>) -> Result<(), Error>;
}

pub trait GpioOutput: Sync + Send {
    fn write(&self, value: bool) -> Result<(), Error>;
}

impl PinHandle {
    pub fn configure(&self) -> Result<(), Error> {
        self.parent.setup_gpio(self, None)
    }

    pub fn write(&self, value: bool) -> Result<(), Error> {
        self.parent.write_gpio(self, value)
    }
}

impl GpioPinConfig {
    pub fn force_single_direction(&self) -> Option<SinglePinDirection> {
        match (self.input, self.output) {
            (true, false) => Some(SinglePinDirection::Input),
            (false, true) => Some(SinglePinDirection::Output),
            (false, false) => None,
            (true, true) => None,
        }
    }
}
