#![no_std]
#![doc = include_str!("../README.md")]

use embedded_hal::digital::{InputPin, OutputPin};

/// Monotonic time in milliseconds.
pub type Millis = u64;

/// Electrical input level that represents a pressed key while a column is
/// selected.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ActiveLevel {
    /// A key is pressed when the row input reads high.
    High,
    /// A key is pressed when the row input reads low.
    Low,
}

impl ActiveLevel {
    #[inline]
    const fn state_from_high(self, is_high: bool) -> ButtonState {
        match (self, is_high) {
            (Self::High, true) | (Self::Low, false) => ButtonState::Pressed,
            (Self::High, false) | (Self::Low, true) => ButtonState::Released,
        }
    }
}

/// Electrical level used to select one matrix column.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColumnDrive {
    /// A column is selected by driving it low and deselected by driving it high.
    ActiveLow,
    /// A column is selected by driving it high and deselected by driving it low.
    ActiveHigh,
}

impl ColumnDrive {
    #[inline]
    const fn level_for(self, active: bool) -> bool {
        match (self, active) {
            (Self::ActiveHigh, true) | (Self::ActiveLow, false) => true,
            (Self::ActiveHigh, false) | (Self::ActiveLow, true) => false,
        }
    }
}

/// Stable key state after debounce filtering.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ButtonState {
    /// The key is pressed.
    Pressed,
    /// The key is released.
    Released,
}

impl ButtonState {
    /// Returns `true` when the state is [`ButtonState::Pressed`].
    #[inline]
    pub const fn is_pressed(self) -> bool {
        matches!(self, Self::Pressed)
    }

    /// Returns `true` when the state is [`ButtonState::Released`].
    #[inline]
    pub const fn is_released(self) -> bool {
        matches!(self, Self::Released)
    }
}

/// Matrix timing and electrical configuration.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MatrixConfig {
    /// Time the raw key state must remain unchanged before it is accepted as the
    /// stable debounced state.
    pub debounce_ms: Millis,
    /// Row input level that means the scanned key is pressed.
    pub row_active_level: ActiveLevel,
    /// Column output polarity used during scanning.
    pub column_drive: ColumnDrive,
}

impl MatrixConfig {
    /// Default debounce time.
    pub const DEFAULT_DEBOUNCE_MS: Millis = 20;

    /// Creates the common pull-up configuration:
    /// selected columns are driven low and pressed rows read low.
    #[inline]
    pub const fn active_low() -> Self {
        Self {
            debounce_ms: Self::DEFAULT_DEBOUNCE_MS,
            row_active_level: ActiveLevel::Low,
            column_drive: ColumnDrive::ActiveLow,
        }
    }

    /// Creates a pull-down style configuration:
    /// selected columns are driven high and pressed rows read high.
    #[inline]
    pub const fn active_high() -> Self {
        Self {
            row_active_level: ActiveLevel::High,
            column_drive: ColumnDrive::ActiveHigh,
            ..Self::active_low()
        }
    }

    /// Returns the configuration with a different debounce time.
    #[inline]
    pub const fn with_debounce_ms(mut self, debounce_ms: Millis) -> Self {
        self.debounce_ms = debounce_ms;
        self
    }

    /// Returns the configuration with a different row input active level.
    #[inline]
    pub const fn with_row_active_level(mut self, row_active_level: ActiveLevel) -> Self {
        self.row_active_level = row_active_level;
        self
    }

    /// Returns the configuration with a different column drive polarity.
    #[inline]
    pub const fn with_column_drive(mut self, column_drive: ColumnDrive) -> Self {
        self.column_drive = column_drive;
        self
    }
}

impl Default for MatrixConfig {
    #[inline]
    fn default() -> Self {
        Self::active_low()
    }
}

/// Event kind emitted for one debounced key transition.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ButtonEventKind {
    /// The stable key state changed to pressed.
    Pressed,
    /// The stable key state changed to released.
    Released,
}

/// One key event produced by a matrix scan.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ButtonMatrixEvent<NAME> {
    /// User-provided key name.
    pub name: NAME,
    /// Row index in the matrix.
    pub row: usize,
    /// Column index in the matrix.
    pub column: usize,
    /// Transition kind.
    pub kind: ButtonEventKind,
    /// Hold time for this event.
    ///
    /// For [`ButtonEventKind::Pressed`] this is `Some(0)`. For
    /// [`ButtonEventKind::Released`] this is the debounced time between the
    /// press event and the release event.
    pub held_ms: Option<Millis>,
}

/// Snapshot of one currently pressed key.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PressedKey<NAME> {
    /// User-provided key name.
    pub name: NAME,
    /// Row index in the matrix.
    pub row: usize,
    /// Column index in the matrix.
    pub column: usize,
    /// Current debounced hold time.
    pub held_ms: Millis,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct EventSlot {
    kind: ButtonEventKind,
    held_ms: Option<Millis>,
}

/// Events produced by one [`ButtonMatrix::scan`] call.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ButtonMatrixEvents<NAME, const ROWS: usize, const COLS: usize> {
    names: [[NAME; COLS]; ROWS],
    slots: [[Option<EventSlot>; COLS]; ROWS],
    count: usize,
}

impl<NAME, const ROWS: usize, const COLS: usize> ButtonMatrixEvents<NAME, ROWS, COLS>
where
    NAME: Copy,
{
    #[inline]
    const fn new(names: [[NAME; COLS]; ROWS]) -> Self {
        Self {
            names,
            slots: [[None; COLS]; ROWS],
            count: 0,
        }
    }

    #[inline]
    fn push(&mut self, row: usize, column: usize, kind: ButtonEventKind, held_ms: Option<Millis>) {
        if self.slots[row][column].is_none() {
            self.count += 1;
        }

        self.slots[row][column] = Some(EventSlot { kind, held_ms });
    }

    /// Returns the number of key events in this scan.
    #[inline]
    pub const fn count(&self) -> usize {
        self.count
    }

    /// Returns `true` if this scan produced no events.
    #[inline]
    pub const fn is_empty(&self) -> bool {
        self.count == 0
    }

    /// Returns the event at the given row and column, if that key changed in
    /// this scan.
    #[inline]
    pub fn event_at(&self, row: usize, column: usize) -> Option<ButtonMatrixEvent<NAME>> {
        if row >= ROWS || column >= COLS {
            return None;
        }

        self.slots[row][column].map(|slot| ButtonMatrixEvent {
            name: self.names[row][column],
            row,
            column,
            kind: slot.kind,
            held_ms: slot.held_ms,
        })
    }

    /// Iterates over all events in row-major order.
    #[inline]
    pub const fn iter(&self) -> ButtonMatrixEventIter<'_, NAME, ROWS, COLS> {
        ButtonMatrixEventIter {
            events: self,
            row: 0,
            column: 0,
        }
    }
}

/// Iterator over events produced by one matrix scan.
pub struct ButtonMatrixEventIter<'a, NAME, const ROWS: usize, const COLS: usize> {
    events: &'a ButtonMatrixEvents<NAME, ROWS, COLS>,
    row: usize,
    column: usize,
}

impl<NAME, const ROWS: usize, const COLS: usize> Iterator
    for ButtonMatrixEventIter<'_, NAME, ROWS, COLS>
where
    NAME: Copy,
{
    type Item = ButtonMatrixEvent<NAME>;

    fn next(&mut self) -> Option<Self::Item> {
        while self.row < ROWS {
            while self.column < COLS {
                let row = self.row;
                let column = self.column;
                self.column += 1;

                if let Some(event) = self.events.event_at(row, column) {
                    return Some(event);
                }
            }

            self.row += 1;
            self.column = 0;
        }

        None
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct KeySlot {
    raw_state: ButtonState,
    raw_changed_at: Millis,
    stable_state: ButtonState,
    pressed_at: Option<Millis>,
}

impl KeySlot {
    #[inline]
    const fn new() -> Self {
        Self {
            raw_state: ButtonState::Released,
            raw_changed_at: 0,
            stable_state: ButtonState::Released,
            pressed_at: None,
        }
    }
}

/// Error returned by [`ButtonMatrix::scan`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MatrixError<RowError, ColumnError> {
    /// Failed to drive a column output.
    Column {
        /// Column index.
        column: usize,
        /// Original GPIO error.
        source: ColumnError,
    },
    /// Failed to read a row input while a column was selected.
    Row {
        /// Row index.
        row: usize,
        /// Selected column index.
        column: usize,
        /// Original GPIO error.
        source: RowError,
    },
}

/// Debounced scanner for a rectangular button matrix.
///
/// `ROWS` input pins are read while each of the `COLS` output pins is selected
/// one at a time. Each key has an independent debouncer, so multiple keys can
/// be pressed and released during the same scan.
pub struct ButtonMatrix<ROW, COL, NAME, const ROWS: usize, const COLS: usize> {
    rows: [ROW; ROWS],
    columns: [COL; COLS],
    names: [[NAME; COLS]; ROWS],
    config: MatrixConfig,
    keys: [[KeySlot; COLS]; ROWS],
}

impl<ROW, COL, NAME, const ROWS: usize, const COLS: usize>
    ButtonMatrix<ROW, COL, NAME, ROWS, COLS>
{
    /// Creates a matrix using [`MatrixConfig::active_low`].
    #[inline]
    pub const fn new(rows: [ROW; ROWS], columns: [COL; COLS], names: [[NAME; COLS]; ROWS]) -> Self {
        Self::with_config(rows, columns, names, MatrixConfig::active_low())
    }

    /// Creates a matrix with a custom configuration.
    #[inline]
    pub const fn with_config(
        rows: [ROW; ROWS],
        columns: [COL; COLS],
        names: [[NAME; COLS]; ROWS],
        config: MatrixConfig,
    ) -> Self {
        Self {
            rows,
            columns,
            names,
            config,
            keys: [[KeySlot::new(); COLS]; ROWS],
        }
    }

    /// Returns the current configuration.
    #[inline]
    pub const fn config(&self) -> MatrixConfig {
        self.config
    }

    /// Replaces the whole configuration.
    #[inline]
    pub fn set_config(&mut self, config: MatrixConfig) {
        self.config = config;
    }

    /// Returns the current debounce time.
    #[inline]
    pub const fn debounce_ms(&self) -> Millis {
        self.config.debounce_ms
    }

    /// Sets the debounce time.
    #[inline]
    pub fn set_debounce_ms(&mut self, debounce_ms: Millis) {
        self.config.debounce_ms = debounce_ms;
    }

    /// Returns the current row active level.
    #[inline]
    pub const fn row_active_level(&self) -> ActiveLevel {
        self.config.row_active_level
    }

    /// Sets the row active level.
    #[inline]
    pub fn set_row_active_level(&mut self, row_active_level: ActiveLevel) {
        self.config.row_active_level = row_active_level;
    }

    /// Returns the current column drive polarity.
    #[inline]
    pub const fn column_drive(&self) -> ColumnDrive {
        self.config.column_drive
    }

    /// Sets the column drive polarity.
    #[inline]
    pub fn set_column_drive(&mut self, column_drive: ColumnDrive) {
        self.config.column_drive = column_drive;
    }

    /// Returns the name assigned to a key.
    #[inline]
    pub fn name(&self, row: usize, column: usize) -> Option<NAME>
    where
        NAME: Copy,
    {
        if row >= ROWS || column >= COLS {
            return None;
        }

        Some(self.names[row][column])
    }

    /// Returns the stable debounced state of a key.
    #[inline]
    pub fn state(&self, row: usize, column: usize) -> Option<ButtonState> {
        if row >= ROWS || column >= COLS {
            return None;
        }

        Some(self.keys[row][column].stable_state)
    }

    /// Returns `true` if the key is currently stably pressed.
    #[inline]
    pub fn is_pressed(&self, row: usize, column: usize) -> bool {
        self.state(row, column).is_some_and(ButtonState::is_pressed)
    }

    /// Returns the current debounced hold time for a key, or `None` if the key
    /// is released or the indexes are out of range.
    #[inline]
    pub fn held_ms(&self, row: usize, column: usize, now_ms: Millis) -> Option<Millis> {
        if row >= ROWS || column >= COLS {
            return None;
        }

        self.keys[row][column]
            .pressed_at
            .map(|pressed_at| elapsed(now_ms, pressed_at))
    }

    /// Iterates over all keys that are currently stably pressed.
    #[inline]
    pub const fn pressed_keys(
        &self,
        now_ms: Millis,
    ) -> PressedKeys<'_, ROW, COL, NAME, ROWS, COLS> {
        PressedKeys {
            matrix: self,
            now_ms,
            row: 0,
            column: 0,
        }
    }

    /// Returns mutable access to the row pins.
    #[inline]
    pub fn rows_mut(&mut self) -> &mut [ROW; ROWS] {
        &mut self.rows
    }

    /// Returns mutable access to the column pins.
    #[inline]
    pub fn columns_mut(&mut self) -> &mut [COL; COLS] {
        &mut self.columns
    }

    /// Returns the inner pins and the key-name matrix.
    #[inline]
    pub fn into_parts(self) -> ([ROW; ROWS], [COL; COLS], [[NAME; COLS]; ROWS]) {
        (self.rows, self.columns, self.names)
    }
}

impl<ROW, COL, NAME, const ROWS: usize, const COLS: usize> ButtonMatrix<ROW, COL, NAME, ROWS, COLS>
where
    ROW: InputPin,
    COL: OutputPin,
    NAME: Copy,
{
    /// Scans the whole matrix once and returns all debounced transition events.
    ///
    /// The method drives one column active at a time, reads every row, updates
    /// independent debouncers for all keys, and then moves to the next column.
    /// It does not allocate and it does not wait internally.
    pub fn scan(
        &mut self,
        now_ms: Millis,
    ) -> Result<ButtonMatrixEvents<NAME, ROWS, COLS>, MatrixError<ROW::Error, COL::Error>> {
        let mut events = ButtonMatrixEvents::new(self.names);

        self.set_all_columns_inactive()?;

        for column in 0..COLS {
            self.set_column_active(column, true)?;

            for row in 0..ROWS {
                let is_high = self.rows[row]
                    .is_high()
                    .map_err(|source| MatrixError::Row {
                        row,
                        column,
                        source,
                    })?;
                let raw_state = self.config.row_active_level.state_from_high(is_high);
                self.update_key(row, column, raw_state, now_ms, &mut events);
            }

            self.set_column_active(column, false)?;
        }

        Ok(events)
    }

    fn set_all_columns_inactive(&mut self) -> Result<(), MatrixError<ROW::Error, COL::Error>> {
        for column in 0..COLS {
            self.set_column_active(column, false)?;
        }

        Ok(())
    }

    fn set_column_active(
        &mut self,
        column: usize,
        active: bool,
    ) -> Result<(), MatrixError<ROW::Error, COL::Error>> {
        let level_is_high = self.config.column_drive.level_for(active);

        if level_is_high {
            self.columns[column]
                .set_high()
                .map_err(|source| MatrixError::Column { column, source })
        } else {
            self.columns[column]
                .set_low()
                .map_err(|source| MatrixError::Column { column, source })
        }
    }

    fn update_key(
        &mut self,
        row: usize,
        column: usize,
        raw_state: ButtonState,
        now_ms: Millis,
        events: &mut ButtonMatrixEvents<NAME, ROWS, COLS>,
    ) {
        let key = &mut self.keys[row][column];

        if raw_state != key.raw_state {
            key.raw_state = raw_state;
            key.raw_changed_at = now_ms;
        }

        if key.stable_state == key.raw_state
            || elapsed(now_ms, key.raw_changed_at) < self.config.debounce_ms
        {
            return;
        }

        key.stable_state = key.raw_state;

        match key.stable_state {
            ButtonState::Pressed => {
                key.pressed_at = Some(now_ms);
                events.push(row, column, ButtonEventKind::Pressed, Some(0));
            }
            ButtonState::Released => {
                let held_ms = key.pressed_at.map(|pressed_at| elapsed(now_ms, pressed_at));
                key.pressed_at = None;
                events.push(row, column, ButtonEventKind::Released, held_ms);
            }
        }
    }
}

/// Iterator over currently pressed keys.
pub struct PressedKeys<'a, ROW, COL, NAME, const ROWS: usize, const COLS: usize> {
    matrix: &'a ButtonMatrix<ROW, COL, NAME, ROWS, COLS>,
    now_ms: Millis,
    row: usize,
    column: usize,
}

impl<ROW, COL, NAME, const ROWS: usize, const COLS: usize> Iterator
    for PressedKeys<'_, ROW, COL, NAME, ROWS, COLS>
where
    NAME: Copy,
{
    type Item = PressedKey<NAME>;

    fn next(&mut self) -> Option<Self::Item> {
        while self.row < ROWS {
            while self.column < COLS {
                let row = self.row;
                let column = self.column;
                self.column += 1;

                let key = self.matrix.keys[row][column];
                if key.stable_state.is_pressed() {
                    return Some(PressedKey {
                        name: self.matrix.names[row][column],
                        row,
                        column,
                        held_ms: key
                            .pressed_at
                            .map(|pressed_at| elapsed(self.now_ms, pressed_at))
                            .unwrap_or(0),
                    });
                }
            }

            self.row += 1;
            self.column = 0;
        }

        None
    }
}

#[inline]
const fn elapsed(now_ms: Millis, earlier_ms: Millis) -> Millis {
    now_ms.saturating_sub(earlier_ms)
}

#[cfg(test)]
extern crate std;

#[cfg(test)]
mod tests {
    use std::vec::Vec;

    use embedded_hal_mock::eh1::digital::{
        Mock as PinMock, State as PinState, Transaction as PinTransaction,
    };

    use super::*;

    fn config() -> MatrixConfig {
        MatrixConfig::active_low().with_debounce_ms(10)
    }

    fn row_pin(states: &[PinState]) -> PinMock {
        let expectations: Vec<_> = states.iter().copied().map(PinTransaction::get).collect();
        PinMock::new(&expectations)
    }

    fn column_pin(scan_count: usize, drive: ColumnDrive) -> PinMock {
        let inactive = if drive.level_for(false) {
            PinState::High
        } else {
            PinState::Low
        };
        let active = if drive.level_for(true) {
            PinState::High
        } else {
            PinState::Low
        };
        let mut expectations = Vec::new();

        for _ in 0..scan_count {
            expectations.push(PinTransaction::set(inactive));
            expectations.push(PinTransaction::set(active));
            expectations.push(PinTransaction::set(inactive));
        }

        PinMock::new(&expectations)
    }

    fn assert_done<const ROWS: usize, const COLS: usize>(
        mut rows: [PinMock; ROWS],
        mut columns: [PinMock; COLS],
    ) {
        for row in &mut rows {
            row.done();
        }

        for column in &mut columns {
            column.done();
        }
    }

    #[test]
    fn reports_press_release_and_hold_time() {
        let rows = [
            row_pin(&[
                PinState::High,
                PinState::High,
                PinState::Low,
                PinState::High,
                PinState::Low,
                PinState::High,
                PinState::High,
                PinState::High,
                PinState::High,
                PinState::High,
            ]),
            row_pin(&[
                PinState::High,
                PinState::High,
                PinState::High,
                PinState::High,
                PinState::High,
                PinState::High,
                PinState::High,
                PinState::High,
                PinState::High,
                PinState::High,
            ]),
        ];
        let columns = [
            column_pin(5, ColumnDrive::ActiveLow),
            column_pin(5, ColumnDrive::ActiveLow),
        ];
        let names = [["A", "B"], ["C", "D"]];
        let mut matrix = ButtonMatrix::with_config(rows, columns, names, config());

        assert!(matrix.scan(0).unwrap().is_empty());
        assert!(matrix.scan(5).unwrap().is_empty());

        let events = matrix.scan(15).unwrap();
        let event = events.event_at(0, 0).unwrap();
        assert_eq!(event.name, "A");
        assert_eq!(event.kind, ButtonEventKind::Pressed);
        assert_eq!(event.held_ms, Some(0));
        assert!(matrix.is_pressed(0, 0));
        assert_eq!(matrix.held_ms(0, 0, 25), Some(10));

        assert!(matrix.scan(50).unwrap().is_empty());

        let events = matrix.scan(60).unwrap();
        let event = events.event_at(0, 0).unwrap();
        assert_eq!(event.name, "A");
        assert_eq!(event.kind, ButtonEventKind::Released);
        assert_eq!(event.held_ms, Some(45));
        assert_eq!(matrix.state(0, 0), Some(ButtonState::Released));

        let (rows, columns, _) = matrix.into_parts();
        assert_done(rows, columns);
    }

    #[test]
    fn returns_multiple_events_from_one_scan() {
        let rows = [
            row_pin(&[
                PinState::High,
                PinState::High,
                PinState::Low,
                PinState::High,
                PinState::Low,
                PinState::High,
            ]),
            row_pin(&[
                PinState::High,
                PinState::High,
                PinState::High,
                PinState::Low,
                PinState::High,
                PinState::Low,
            ]),
        ];
        let columns = [
            column_pin(3, ColumnDrive::ActiveLow),
            column_pin(3, ColumnDrive::ActiveLow),
        ];
        let names = [["A", "B"], ["C", "D"]];
        let mut matrix = ButtonMatrix::with_config(rows, columns, names, config());

        assert!(matrix.scan(0).unwrap().is_empty());
        assert!(matrix.scan(10).unwrap().is_empty());

        let events = matrix.scan(20).unwrap();
        assert_eq!(events.count(), 2);

        let events: Vec<_> = events.iter().collect();
        assert_eq!(
            events,
            [
                ButtonMatrixEvent {
                    name: "A",
                    row: 0,
                    column: 0,
                    kind: ButtonEventKind::Pressed,
                    held_ms: Some(0),
                },
                ButtonMatrixEvent {
                    name: "D",
                    row: 1,
                    column: 1,
                    kind: ButtonEventKind::Pressed,
                    held_ms: Some(0),
                },
            ]
        );

        let pressed: Vec<_> = matrix.pressed_keys(25).collect();
        assert_eq!(
            pressed,
            [
                PressedKey {
                    name: "A",
                    row: 0,
                    column: 0,
                    held_ms: 5,
                },
                PressedKey {
                    name: "D",
                    row: 1,
                    column: 1,
                    held_ms: 5,
                },
            ]
        );

        let (rows, columns, _) = matrix.into_parts();
        assert_done(rows, columns);
    }

    #[test]
    fn supports_enum_key_names() {
        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
        enum Key {
            Up,
            Down,
            Left,
            Right,
        }

        let rows = [
            row_pin(&[
                PinState::High,
                PinState::High,
                PinState::High,
                PinState::Low,
                PinState::High,
                PinState::Low,
            ]),
            row_pin(&[
                PinState::High,
                PinState::High,
                PinState::High,
                PinState::High,
                PinState::High,
                PinState::High,
            ]),
        ];
        let columns = [
            column_pin(3, ColumnDrive::ActiveLow),
            column_pin(3, ColumnDrive::ActiveLow),
        ];
        let names = [[Key::Up, Key::Down], [Key::Left, Key::Right]];
        let mut matrix = ButtonMatrix::with_config(rows, columns, names, config());

        assert_eq!(matrix.name(0, 1), Some(Key::Down));
        assert!(matrix.scan(0).unwrap().is_empty());
        assert!(matrix.scan(10).unwrap().is_empty());

        let events = matrix.scan(20).unwrap();
        let event = events.event_at(0, 1).unwrap();
        assert_eq!(event.name, Key::Down);
        assert_eq!(event.kind, ButtonEventKind::Pressed);

        let (rows, columns, _) = matrix.into_parts();
        assert_done(rows, columns);
    }

    #[test]
    fn supports_active_high_matrices() {
        let rows = [row_pin(&[
            PinState::Low,
            PinState::Low,
            PinState::High,
            PinState::Low,
            PinState::High,
            PinState::Low,
        ])];
        let columns = [
            column_pin(3, ColumnDrive::ActiveHigh),
            column_pin(3, ColumnDrive::ActiveHigh),
        ];
        let names = [["A", "B"]];
        let config = MatrixConfig::active_high().with_debounce_ms(5);
        let mut matrix = ButtonMatrix::with_config(rows, columns, names, config);

        assert!(matrix.scan(0).unwrap().is_empty());
        assert!(matrix.scan(5).unwrap().is_empty());

        let events = matrix.scan(10).unwrap();
        let event = events.event_at(0, 0).unwrap();
        assert_eq!(event.name, "A");
        assert_eq!(event.kind, ButtonEventKind::Pressed);

        let (rows, columns, _) = matrix.into_parts();
        assert_done(rows, columns);
    }
}
