use crate::*;

pub struct ProcessHandler<F: ChunkFactory, const COLS: usize, const ROWS: usize> {
    post_record_controller: PostRecordController,
    osc: OscController,
    persistence: PersistenceManagerController,
    ports: JackPorts,
    metronome: Metronome,
    track_matrix: TrackMatrix<F, COLS, ROWS>,
    selected_row: usize,
    selected_column: usize,
    last_volume_setting: f32,
    sample_rate: usize,
    tap_tempo: TapTempo,
}

impl<F: ChunkFactory, const COLS: usize, const ROWS: usize> ProcessHandler<F, COLS, ROWS> {
    pub fn new(
        client: &jack::Client,
        ports: JackPorts,
        chunk_factory: F,
        beep_samples: Arc<AudioChunk>,
        state: &State,
        post_record_controller: PostRecordController,
        osc: OscController,
        persistence: PersistenceManagerController,
    ) -> Result<Self> {
        let track_matrix = TrackMatrix::new(client, chunk_factory, state)?;
        Ok(Self {
            post_record_controller,
            osc,
            persistence,
            ports,
            metronome: Metronome::new(beep_samples, state),
            track_matrix,
            selected_row: 0,
            selected_column: 0,
            last_volume_setting: 1.0,
            sample_rate: client.sample_rate(),
            tap_tempo: TapTempo::new(client.sample_rate()),
        })
    }

    pub fn ports(&self) -> &JackPorts {
        &self.ports
    }

    pub fn handle_pedal_a(&mut self, value: u8) -> Result<()> {
        self.last_volume_setting = (value as f32) / 127.0;

        let controllers = TrackControllers::new(
            &self.post_record_controller,
            &self.osc,
            &self.persistence,
            self.sample_rate,
            self.selected_column,
            self.selected_row,
        );
        self.track_matrix
            .handle_volume_update(self.last_volume_setting, &controllers)
    }

    pub fn handle_pedal_b(&mut self, value: u8) -> Result<()> {
        self.metronome
            .set_click_volume(value as f32 / 127.0, &mut self.persistence)
    }

    pub fn handle_button_1(&mut self) -> Result<()> {
        let controllers = TrackControllers::new(
            &self.post_record_controller,
            &self.osc,
            &self.persistence,
            self.sample_rate,
            self.selected_column,
            self.selected_row,
        );
        self.track_matrix
            .handle_record_button(self.last_volume_setting, &controllers)
    }

    pub fn handle_button_2(&mut self) -> Result<()> {
        let controllers = TrackControllers::new(
            &self.post_record_controller,
            &self.osc,
            &self.persistence,
            self.sample_rate,
            self.selected_column,
            self.selected_row,
        );
        self.track_matrix.handle_play_button(&controllers)
    }

    pub fn handle_button_3(&mut self) -> Result<()> {
        Ok(())
    }

    pub fn handle_button_4(&mut self) -> Result<()> {
        let controllers = TrackControllers::new(
            &self.post_record_controller,
            &self.osc,
            &self.persistence,
            self.sample_rate,
            self.selected_column,
            self.selected_row,
        );
        self.track_matrix.handle_clear_button(&controllers)
    }

    pub fn handle_button_5(&mut self, ps: &jack::ProcessScope) -> Result<()> {
        if !self.track_matrix.is_all_tracks_cleared() {
            // Ignore tap if not all tracks are clear
            return Ok(());
        }

        let current_frame_time = ps.last_frame_time();

        let tempo_updated = self.tap_tempo.handle_tap(current_frame_time);

        if let Some(frames_per_beat) = tempo_updated {
            // If tempo was updated, we need to update all columns with new frames_per_beat
            self.metronome.set_frames_per_beat(frames_per_beat);
            self.persistence
                .update_metronome_frames_per_beat(frames_per_beat)?;
            self.track_matrix.set_frames_per_beat(frames_per_beat);
        }

        Ok(())
    }

    pub fn handle_button_6(&mut self) -> Result<()> {
        self.selected_column = 0;
        self.osc.selected_column_changed(self.selected_column)?;
        Ok(())
    }

    pub fn handle_button_7(&mut self) -> Result<()> {
        self.selected_column = 1;
        self.osc.selected_column_changed(self.selected_column)?;
        Ok(())
    }

    pub fn handle_button_8(&mut self) -> Result<()> {
        self.selected_column = 2;
        self.osc.selected_column_changed(self.selected_column)?;
        Ok(())
    }

    pub fn handle_button_9(&mut self) -> Result<()> {
        self.selected_column = 3;
        self.osc.selected_column_changed(self.selected_column)?;
        Ok(())
    }

    pub fn handle_button_10(&mut self) -> Result<()> {
        self.selected_column = 4;
        self.osc.selected_column_changed(self.selected_column)?;
        Ok(())
    }

    pub fn handle_button_up(&mut self) -> Result<()> {
        if self.selected_row == 0 {
            self.selected_row = ROWS - 1;
        } else {
            self.selected_row -= 1;
        }
        self.osc.selected_row_changed(self.selected_row)?;
        Ok(())
    }

    pub fn handle_button_down(&mut self) -> Result<()> {
        self.selected_row = (self.selected_row + 1) % ROWS;
        self.osc.selected_row_changed(self.selected_row)?;
        Ok(())
    }
}

impl<F: ChunkFactory, const COLS: usize, const ROWS: usize> jack::ProcessHandler
    for ProcessHandler<F, COLS, ROWS>
{
    fn process(&mut self, _client: &jack::Client, ps: &jack::ProcessScope) -> jack::Control {
        if let Err(e) = self.process_with_error_handling(ps) {
            log::error!("Error processing audio: {}", e);
            jack::Control::Quit
        } else {
            jack::Control::Continue
        }
    }
}

impl<F: ChunkFactory, const COLS: usize, const ROWS: usize> ProcessHandler<F, COLS, ROWS> {
    fn process_with_error_handling(&mut self, ps: &jack::ProcessScope) -> Result<()> {
        // Process metronome and get beat timing information
        let timing = self.metronome.process(ps, &mut self.ports, &self.osc)?;

        // Process MIDI
        midi::process_events(self, ps)?;

        // Process audio
        let controllers = MatrixControllers::new(
            &self.post_record_controller,
            &self.osc,
            &self.persistence,
            self.sample_rate,
        );
        self.track_matrix
            .process(ps, &mut self.ports, &timing, &controllers)?;

        Ok(())
    }
}
