use crate::*;

pub struct TrackMatrix<F: ChunkFactory, const COLS: usize, const ROWS: usize> {
    chunk_factory: F,
    columns: [Column<ROWS>; COLS],
    scratch_pad: Box<[f32]>,
}

impl<F: ChunkFactory, const COLS: usize, const ROWS: usize> TrackMatrix<F, COLS, ROWS> {
    pub fn new(client: &jack::Client, chunk_factory: F, state: &State) -> Result<Self> {
        let columns = std::array::from_fn(|_| Column::new(state.metronome.frames_per_beat));
        Ok(Self {
            chunk_factory,
            columns,
            scratch_pad: vec![0.0; client.buffer_size() as usize].into_boxed_slice(),
        })
    }

    pub fn handle_record_button(
        &mut self,
        last_volume_setting: f32,
        controllers: &TrackControllers,
    ) -> Result<()> {
        self.columns[controllers.column()].handle_record_button(last_volume_setting, controllers)
    }

    pub fn handle_play_button(&mut self, controllers: &TrackControllers) -> Result<()> {
        self.columns[controllers.column()].handle_play_button(controllers)
    }

    pub fn handle_clear_button(&mut self, controllers: &TrackControllers) -> Result<()> {
        self.columns[controllers.column()].handle_clear_button(controllers)
    }

    pub fn handle_volume_update(
        &mut self,
        new_volume: f32,
        controllers: &TrackControllers,
    ) -> Result<()> {
        self.columns[controllers.column()].handle_volume_update(new_volume, controllers)
    }

    pub fn is_all_tracks_cleared(&self) -> bool {
        self.columns
            .iter()
            .all(|column| column.is_all_tracks_cleared())
    }

    pub fn set_frames_per_beat(&mut self, new_frames_per_beat: usize) {
        for column in &mut self.columns {
            column.set_frames_per_beat(new_frames_per_beat);
        }
    }

    pub fn process(
        &mut self,
        ps: &jack::ProcessScope,
        ports: &mut JackPorts,
        timing: &BufferTiming,
        controllers: &MatrixControllers,
    ) -> Result<()> {
        // Check for consolidation response
        if let Some(response) = controllers.try_recv_post_record_response() {
            self.columns[response.column]
                .set_consolidated_buffer(response.row, response.consolidated_buffer)?;
        }

        // Handle xruns
        if timing.missed_frames > 0 {
            for (i, column) in &mut self.columns.iter_mut().enumerate() {
                let controllers = controllers.to_column_controllers(i);

                column.handle_xrun(&timing, &mut self.chunk_factory, &controllers)?;
            }
        }

        // Process audio
        let input_buffer = ports.audio_in.as_slice(ps);
        let output_buffer = ports.audio_out.as_mut_slice(ps);
        output_buffer.fill(0.0);

        for (i, column) in &mut self.columns.iter_mut().enumerate() {
            let controllers = controllers.to_column_controllers(i);

            column.process(
                &timing,
                input_buffer,
                output_buffer,
                &mut self.scratch_pad,
                &mut self.chunk_factory,
                &controllers,
            )?;
        }

        Ok(())
    }
}
