//! Device tree driver interfaces
use core::ops::Range;

use alloc::sync::{Arc, Weak};
use device_api::{
    bus::Bus,
    clock::{ClockHandle, ResetHandle},
    device::Device,
    gpio::PinHandle,
    interrupt::{ExternalInterruptController, IrqHandle},
};
use libk::error::Error;

use crate::{driver::DeviceTreeGpioPins, TProp};

use super::{InitSequence, Node};

/// Device tree driver interface
pub trait Driver: Sync {
    /// Constructs a [Device] for a given matching node
    fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>>;
}

/// Device-tree based interrupt mapper/controller interface
pub trait DeviceTreeInterruptController {
    /// Reads interrupt information from `property` at given `offset` and maps it to
    /// the interrupt used by the controller
    fn map_interrupt(self: Arc<Self>, property: &TProp, offset: usize) -> Option<IrqHandle>;

    /// Returns the [ExternalInterruptController] implementor of this node
    fn as_interrupt_controller(self: Arc<Self>) -> Arc<dyn ExternalInterruptController>;
}

/// Device-tree based clock source interface
pub trait DeviceTreeClockController {
    /// Reads clock information from `property` at given `offset` and maps it to a
    /// [ClockHandle], returning the handle + the size of the clock entry.
    fn map_clock(self: Arc<Self>, property: &TProp, offset: usize) -> Option<(ClockHandle, usize)>;
}

/// Device-tree based reset source interface
pub trait DeviceTreeResetController {
    /// Reads reset information from `property` at given `offset` and maps it to a
    /// [ResetHandle], returning the handle + the size of the reset entry.
    fn map_reset(self: Arc<Self>, property: &TProp, offset: usize) -> Option<(ResetHandle, usize)>;
}

/// Context passed to the driver's `probe` function
pub struct ProbeContext {
    /// Parent bus of the node being probed
    pub bus: Option<Weak<dyn Bus>>,
    /// Can be used to set a specific initialization sequence for the device
    pub sequence: Option<InitSequence>,
}

/// `-pinctrl`-type devices, used for configuring pin groups
pub trait DeviceTreePinController {
    /// Configure a pin group
    fn configure_pin_group(self: Arc<Self>, pins: &Arc<Node>) -> Result<(), Error>;
    /// Reads GPIO pin information from `property` at given `offset` and maps it to a
    /// [PinHandle], returning the handle + the size of the GPIO entry.
    fn map_gpio(
        self: Arc<Self>,
        property: &TProp,
        offset: usize,
        info: &DeviceTreeGpioPins,
    ) -> Option<(PinHandle, usize)>;
}

impl ProbeContext {
    /// See [Node::map_range]
    pub fn map_range(&self, range: Range<u64>) -> Option<Range<u64>> {
        if let Some(bus) = self.bus.as_ref().and_then(Weak::upgrade) {
            // Use parent bus to map the address
            bus.map_range(range)
        } else {
            // Identity
            Some(range)
        }
    }
}
