//! Device tree nodes' property accessors
use core::marker::PhantomData;

use fdt_rs::{
    base::iters::StringPropIter,
    index::DevTreeIndexProp,
    prelude::{FallibleIterator, PropReader},
};

#[doc(hidden)]
pub trait CellTuple: Sized {
    type Sizes: Copy;

    fn read<'a, P: DeviceTreePropertyRead<'a> + ?Sized>(
        property: &P,
        offset: usize,
        sizes: Self::Sizes,
    ) -> Option<Self>;
    fn entry_size(sizes: Self::Sizes) -> usize;
}

/// Accessor trait for a device tree property
pub trait DeviceTreePropertyRead<'a> {
    /// Reads a cell value of `size` at a given `offset`.
    ///
    /// **NOTE** supported cell sizes are 1 and 2. If the size is 3 and larger,
    ///          perform multiple split reads instead.
    fn read_cell(&self, offset: usize, size: usize) -> Option<u64>;
    /// Reads the property as a single string
    fn as_str(&self) -> Option<&'a str>;
    /// Reads the property as an iterator over a `<stringlist>`
    fn as_str_list(&self) -> impl Iterator<Item = &'a str>;
    /// Returns the length of the property in bytes
    fn len(&self) -> usize;

    /// Reads a cell tuple from the property. The property's value is assumed to be
    /// an uniform array of such tuples, specified by the `sizes` parameter.
    fn read_cells<T: CellTuple>(&self, index: usize, sizes: T::Sizes) -> Option<T> {
        self.read_cells_at(index * T::entry_size(sizes), sizes)
    }

    /// Same as [DeviceTreePropertyRead::read_cells], but uses an unscaled `offset` instead.
    fn read_cells_at<T: CellTuple>(&self, offset: usize, sizes: T::Sizes) -> Option<T> {
        T::read(self, offset, sizes)
    }

    /// Reads the property as an iterator over uniformly-sized tuples, specified by the
    /// `sizes` parameter.
    fn iter_cells<T: CellTuple>(&self, sizes: T::Sizes) -> CellTupleIter<'a, '_, Self, T> {
        CellTupleIter {
            property: self,
            offset: 0,
            sizes,
            _pd: PhantomData,
        }
    }

    /// Returns `true` if `self.len() == 0`
    fn is_empty(&self) -> bool {
        self.len() == 0
    }
}

/// An iterator over the tuple cells of some device tree node's property
pub struct CellTupleIter<'a, 'p, P: DeviceTreePropertyRead<'a> + ?Sized, T: CellTuple> {
    property: &'p P,
    offset: usize,
    sizes: T::Sizes,
    _pd: PhantomData<&'a ()>,
}

/// An iterator over the string list propoerty
pub struct StringListIter<'a> {
    inner: StringPropIter<'a>,
}

impl<'a> DeviceTreePropertyRead<'a> for DevTreeIndexProp<'a, '_, '_> {
    fn read_cell(&self, offset: usize, size: usize) -> Option<u64> {
        match size {
            1 => self.u32(offset).ok().map(Into::into),
            2 => {
                let v0 = self.u32(offset).ok()? as u64;
                let v1 = self.u32(offset + 1).ok()? as u64;
                Some((v0 << 32) | v1)
            }
            // If cell size is larger than 2, split the read
            _ => None,
        }
    }

    fn as_str(&self) -> Option<&'a str> {
        self.str().ok()
    }

    fn as_str_list(&self) -> impl Iterator<Item = &'a str> {
        StringListIter {
            inner: self.iter_str(),
        }
    }

    fn len(&self) -> usize {
        self.length()
    }
}

impl<'a, P: DeviceTreePropertyRead<'a> + ?Sized, T: CellTuple> Iterator
    for CellTupleIter<'a, '_, P, T>
{
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        let size = T::entry_size(self.sizes);
        let entry = T::read(self.property, self.offset, self.sizes)?;
        self.offset += size;
        Some(entry)
    }
}

impl<'a> Iterator for StringListIter<'a> {
    type Item = &'a str;

    fn next(&mut self) -> Option<Self::Item> {
        self.inner.next().ok().flatten()
    }
}

macro impl_cell_tuple_types($($t:literal ($ty:ty) )+) {
    ($($ty),+)
}

macro impl_cell_tuple_sum {
    ($n:expr, $i:tt) => { $n.$i },
    ($n:expr, $i:tt $($t:tt)+) => { $n.$i + impl_cell_tuple_sum!($n, $($t)+) }
}

macro impl_cell_tuple_read {
    ( [ $($a:tt),* ] { $off:expr, $prop:ident, $sizes:ident }: $n:tt $($t:tt)*) => {
        impl_cell_tuple_read!(
            [
                $($a,)*
                ($prop.read_cell($off, $sizes.$n)?)
            ]
            { $off + $sizes.$n, $prop, $sizes }
            :
            $($t)*
        )
    },
    ( [ $($a:tt),* ] { $off:expr, $prop:ident, $sizes:ident }: ) => {
        Some(($($a),*))
    },
}

macro impl_cell_tuples(
    $(
        ( $($index:tt)+ )
    ),+
) {
    $(
        impl CellTuple for impl_cell_tuple_types!($($index (u64))+) {
            type Sizes = impl_cell_tuple_types!($($index (usize))+);

            fn read<'a, P: DeviceTreePropertyRead<'a> + ?Sized>(
                property: &P,
                offset: usize,
                sizes: Self::Sizes,
            ) -> Option<Self> {
                impl_cell_tuple_read!([] { offset, property, sizes }: $($index)+)
            }
            fn entry_size(sizes: Self::Sizes) -> usize {
                impl_cell_tuple_sum!(sizes, $($index)+)
            }
        }
    )+
}

impl_cell_tuples!(
    (0 1),
    (0 1 2),
    (0 1 2 3),
    (0 1 2 3 4),
    (0 1 2 3 4 5)
);
