//! Device-tree handling for controllers of various types: interrupt, clock etc.
use alloc::sync::Arc;
use device_api::{
    clock::{ClockHandle, ResetHandle},
    gpio::PinHandle,
    interrupt::IrqHandle,
};
use fdt_rs::spec::Phandle;

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

use super::{lookup_phandle, Node};

pub(crate) struct ClockIter<'dt> {
    pub(crate) clocks: TProp<'dt>,
    pub(crate) offset: usize,
}

pub(crate) struct ResetIter<'dt> {
    pub(crate) resets: TProp<'dt>,
    pub(crate) offset: usize,
}

pub(crate) struct GpioIter<'o, 'dt> {
    pub(crate) options: &'o DeviceTreeGpioPins,
    pub(crate) gpios: TProp<'dt>,
    pub(crate) offset: usize,
}

impl Iterator for ClockIter<'_> {
    type Item = ClockHandle;

    fn next(&mut self) -> Option<Self::Item> {
        if self.offset >= self.clocks.len() {
            return None;
        }
        let phandle = self.clocks.read_cell(self.offset, 1)? as Phandle;
        let clkc = lookup_phandle(phandle, true)?;
        let clkc = clkc.clock_controler.try_get()?;
        let (clock, len) = clkc.clone().map_clock(&self.clocks, self.offset + 1)?;
        self.offset += len + 1;
        Some(clock)
    }
}

impl Iterator for ResetIter<'_> {
    type Item = ResetHandle;

    fn next(&mut self) -> Option<Self::Item> {
        if self.offset >= self.resets.len() {
            return None;
        }
        let phandle = self.resets.read_cell(self.offset, 1)? as Phandle;
        let rstc = lookup_phandle(phandle, true)?;
        let rstc = rstc.reset_controller.try_get()?;
        let (reset, len) = rstc.clone().map_reset(&self.resets, self.offset + 1)?;
        self.offset += len + 1;
        Some(reset)
    }
}

impl Iterator for GpioIter<'_, '_> {
    type Item = PinHandle;

    fn next(&mut self) -> Option<Self::Item> {
        if self.offset >= self.gpios.len() {
            return None;
        }
        let phandle = self.gpios.read_cell(self.offset, 1)? as Phandle;
        let gpioc = lookup_phandle(phandle, true)?;
        let gpioc = gpioc.as_pin_controller()?;
        let (pin, len) = gpioc.map_gpio(&self.gpios, self.offset + 1, self.options)?;
        self.offset += len + 1;
        Some(pin)
    }
}

// Interrupt controller handling

/// Reads interrupt information, as interpreted by `interrupt_controller`, from `property` at a
/// given `offset`.
pub fn map_interrupt_at(
    interrupt_controller: &Arc<Node>,
    property: &TProp,
    offset: usize,
) -> Option<IrqHandle> {
    let interrupt_controller = interrupt_controller.interrupt_controller.try_get()?;
    interrupt_controller.clone().map_interrupt(property, offset)
}

/// Same as [map_interrupt_at], but uses a phandle to address the `interrupt-controller` node
/// and a scaled `index`.
pub fn map_interrupt(phandle: Phandle, property: &TProp, index: usize) -> Option<IrqHandle> {
    let interrupt_controller = lookup_phandle(phandle, true)?;
    let interrupt_cells = interrupt_controller.self_interrupt_cells()?;
    map_interrupt_at(&interrupt_controller, property, index * interrupt_cells)
}
