use crate::{effects::Channel, Timestamp};

pub trait EventProcessorEvent {
    fn get_time(&self) -> Timestamp;
    fn should_clear_queue(&self) -> bool;
    fn sequence_number(&self) -> usize;
}

pub struct EventProcessor<Event>
where
    Event: EventProcessorEvent,
{
    pending_events: Vec<Event>,
    receive_channel: Channel::Receiver<Event>,
    sample_rate: usize,
}

impl<Event> EventProcessor<Event>
where
    Event: EventProcessorEvent,
{
    pub fn with_capacity(
        capacity: usize,
        receive_channel: Channel::Receiver<Event>,
        sample_rate: usize,
    ) -> Self {
        Self {
            pending_events: Vec::with_capacity(capacity),
            receive_channel,
            sample_rate,
        }
    }

    pub fn receive_events(&mut self) {
        let mut sort_required = false;

        while let Ok(event) = self.receive_channel.try_recv() {
            if event.should_clear_queue() {
                self.pending_events.clear();
            }

            self.pending_events.push(event);
            sort_required = true;
        }

        if sort_required {
            self.pending_events
                .sort_by(|a, b| match a.get_time().cmp(&b.get_time()) {
                    std::cmp::Ordering::Equal => a.sequence_number().cmp(&b.sequence_number()),
                    comparison => comparison,
                });
        }
    }

    fn next_event_before(&mut self, end_time: &Timestamp) -> Option<Event> {
        if let Some(next_event) = self.pending_events.first() {
            if next_event.get_time() < *end_time {
                return Some(self.pending_events.remove(0));
            }
        }

        None
    }

    pub fn next_event(
        &mut self,
        frame_start_time: &Timestamp,
        current_frame_position: &Timestamp,
        frame_count: usize,
    ) -> (usize, Option<Event>) {
        let frame_end_time = frame_start_time.incremented_by_samples(frame_count, self.sample_rate);

        if let Some(next_event) = self.next_event_before(&frame_end_time) {
            let event_time = std::cmp::max(next_event.get_time(), *current_frame_position);
            let position_in_frame = event_time - *frame_start_time;

            (
                position_in_frame.as_samples(self.sample_rate).floor() as usize,
                Some(next_event),
            )
        } else {
            (frame_count, None)
        }
    }
}
