use crate::*;

pub struct Column<const ROWS: usize> {
    frames_per_beat: usize,
    tracks: [Track; ROWS],
    playback_position: usize,
}

impl<const ROWS: usize> Column<ROWS> {
    pub fn new(frames_per_beat: usize) -> Self {
        Self {
            frames_per_beat,
            tracks: core::array::from_fn(|_| Track::new()),
            playback_position: 0,
        }
    }

    pub fn len(&self) -> usize {
        for track in &self.tracks {
            if track.is_recording() {
                continue;
            }
            let len = track.len();
            if len > 0 {
                return len;
            }
        }
        return 0;
    }

    pub fn idle(&self) -> bool {
        for track in &self.tracks {
            if !track.is_idle() {
                return false;
            }
        }
        return true;
    }

    pub fn handle_record_button(
        &mut self,
        last_volume_setting: f32,
        controllers: &TrackControllers,
    ) -> Result<()> {
        let len = self.len();
        let track = &mut self.tracks[controllers.row()];

        if track.is_recording() {
            if len > 0 {
                track.clear();
            } else {
                track.play();
            }
        } else {
            if len > 0 {
                let sync_offset = len - self.playback_position;
                track.record_auto_stop(len, sync_offset, last_volume_setting);
            } else {
                track.record(last_volume_setting);
            }
        }
        Ok(())
    }

    pub fn handle_play_button(&mut self, controllers: &TrackControllers) -> Result<()> {
        let track = &mut self.tracks[controllers.row()];
        if track.len() > 0 && track.is_idle() {
            track.play();
        } else if !track.is_idle() {
            track.stop();
        }
        Ok(())
    }

    pub fn handle_clear_button(&mut self, controllers: &TrackControllers) -> Result<()> {
        let track = &mut self.tracks[controllers.row()];
        track.clear();
        Ok(())
    }

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

    pub fn set_consolidated_buffer(&mut self, row: usize, buffer: Box<[f32]>) -> Result<()> {
        self.tracks[row].set_consolidated_buffer(buffer)
    }

    pub fn is_all_tracks_cleared(&self) -> bool {
        self.tracks.iter().all(|track| track.is_empty())
    }

    pub fn set_frames_per_beat(&mut self, new_frames_per_beat: usize) {
        self.frames_per_beat = new_frames_per_beat;
    }

    pub fn handle_xrun(
        &mut self,
        timing: &BufferTiming,
        chunk_factory: &mut impl ChunkFactory,
        controllers: &ColumnControllers,
    ) -> Result<()> {
        for (row, track) in self.tracks.iter_mut().enumerate() {
            let track_controllers = controllers.to_track_controllers(row);

            track.handle_xrun(
                timing.beat_in_missed,
                timing.missed_frames,
                chunk_factory,
                &track_controllers,
            )?;
        }
        Ok(())
    }

    pub fn process(
        &mut self,
        timing: &BufferTiming,
        input_buffer: &[f32],
        output_buffer: &mut [f32],
        scratch_pad: &mut [f32],
        chunk_factory: &mut impl ChunkFactory,
        controllers: &ColumnControllers,
    ) -> Result<()> {
        let old_len = self.len();

        if self.idle() {
            if let Some(beat_index) = timing.beat_in_buffer {
                // When starting from idle, always start at beat 0 (beginning of first beat)
                // regardless of where in the buffer the beat occurs
                if old_len == 0 {
                    self.playback_position = 0; // Start at beat 0 for first recording
                } else {
                    let idle_time = input_buffer.len() - beat_index as usize;
                    self.playback_position = old_len - idle_time;
                }
            }
        }

        for (row, track) in self.tracks.iter_mut().enumerate() {
            let track_controllers = controllers.to_track_controllers(row);

            track.process(
                self.playback_position,
                timing.beat_in_buffer,
                input_buffer,
                scratch_pad,
                chunk_factory,
                &track_controllers,
            )?;

            for (output_val, scratch_pad_val) in output_buffer.iter_mut().zip(scratch_pad.iter()) {
                *output_val += *scratch_pad_val;
            }
        }

        let new_len = self.len();

        if old_len != new_len {
            let beats = new_len / self.frames_per_beat;
            controllers.send_column_beats_changed(beats)?;
        }

        // Update playback position
        if new_len > 0 {
            self.playback_position = (self.playback_position + input_buffer.len()) % new_len;
        } else {
            // During recording of first track, don't use modulo - let position grow
            // so that beat calculation works correctly
            self.playback_position += input_buffer.len();
        }

        // Send beat change message if a beat occurred in this buffer
        if timing.beat_in_buffer.is_some() {
            // Calculate which beat we're entering (after the beat that just occurred)
            let current_beat = if new_len > 0 {
                // When column has defined length, calculate beat within that loop
                let column_beats = new_len / self.frames_per_beat;
                (self.playback_position / self.frames_per_beat) % column_beats
            } else {
                // During recording of first track, calculate beat based on absolute position
                // The beat that just occurred is the beat we're now in
                self.playback_position / self.frames_per_beat
            };

            controllers.send_column_beat_changed(current_beat)?;
        }

        Ok(())
    }
}
