//! Project: CMtec CMDR Keyboard 42
//! Date:    2025-03-09
//! Author:  Christoffer Martinsson
//! Email:   cm@cmtec.se
//! License: Please refer to LICENSE in root directory

use core::convert::Infallible;
use cortex_m::delay::Delay;
use embedded_hal::digital::{InputPin, OutputPin};

/// Button matrix driver
///
/// # Example
/// ```
/// let button_matrix: ButtonMatrix<4, 6, 48> = ButtonMatrix::new(row_pins, col_pins, 5);
/// ```
pub struct ButtonMatrix<'a, const R: usize, const C: usize, const N: usize> {
    rows: &'a mut [&'a mut dyn InputPin<Error = Infallible>; R],
    cols: &'a mut [&'a mut dyn OutputPin<Error = Infallible>; C],
    pressed: [bool; N],
    debounce: u8,
    debounce_counter: [u8; N],
}

impl<'a, const R: usize, const C: usize, const N: usize> ButtonMatrix<'a, R, C, N> {
    /// Creates a new button matrix.
    ///
    /// # Arguments
    ///
    /// * `rows` - An array of references to the row pins.
    /// * `cols` - An array of references to the column pins.
    /// * `debounce` - The debounce time in number of scans.
    pub fn new(
        rows: &'a mut [&'a mut dyn InputPin<Error = Infallible>; R],
        cols: &'a mut [&'a mut dyn OutputPin<Error = Infallible>; C],
        debounce: u8,
    ) -> Self {
        Self {
            rows,
            cols,
            pressed: [false; N],
            debounce,
            debounce_counter: [0; N],
        }
    }

    /// Initializes the button matrix.
    /// This should be called once before scanning the matrix.
    pub fn init_pins(&mut self) {
        for col in self.cols.iter_mut() {
            col.set_high().unwrap();
        }
    }

    /// Scans the button matrix and updates the pressed state of each button.
    /// This should be called at regular intervals.
    /// Allow at least 5 times the delay compared to the needed button latency.
    ///
    /// # Arguments
    ///
    /// * `delay` - A mutable reference to a delay object.
    pub fn scan_matrix(&mut self, delay: &mut Delay) {
        for col_index in 0..self.cols.len() {
            self.cols[col_index].set_low().unwrap();
            delay.delay_us(1);
            self.process_column(col_index);
            self.cols[col_index].set_high().unwrap();
            delay.delay_us(1);
        }
    }

    /// Processes a column of the button matrix.
    ///
    /// # Arguments
    ///
    /// * `col_index` - The index of the column to process.
    fn process_column(&mut self, col_index: usize) {
        for row_index in 0..self.rows.len() {
            let button_index: usize = col_index + (row_index * C);
            let current_state = self.rows[row_index].is_low().unwrap();

            if current_state == self.pressed[button_index] {
                self.debounce_counter[button_index] = 0;
                continue;
            }

            self.debounce_counter[button_index] += 1;
            if self.debounce_counter[button_index] >= self.debounce {
                self.pressed[button_index] = current_state;
            }
        }
    }

    /// Returns an array of booleans indicating whether each button is pressed.
    pub fn buttons_pressed(&mut self) -> [bool; N] {
        self.pressed
    }
}
