use crate::engine::animation::base::{Animation, AudioEvent};
use std::time::Duration;

const INFINITE_DURATION_CAP: Duration = Duration::from_secs(3600); // 1 hour cap for infinite loops

/// An animation that repeats another animation multiple times (or infinitely).
pub struct LoopAnim {
    pub(crate) factory: Box<dyn Fn() -> Box<dyn Animation> + Send + Sync>,
    pub(crate) current: Box<dyn Animation>,
    pub(crate) repeat_count: Option<usize>,
    pub(crate) finished_count: usize,
}

impl LoopAnim {
    pub fn new(
        factory: Box<dyn Fn() -> Box<dyn Animation> + Send + Sync>,
        count: Option<usize>,
    ) -> Self {
        let current = factory();
        Self {
            factory,
            current,
            repeat_count: count,
            finished_count: 0,
        }
    }
}

impl Animation for LoopAnim {
    fn update(&mut self, mut dt: Duration) -> (bool, Duration) {
        loop {
            let (finished, leftover) = self.current.update(dt);
            if !finished {
                return (false, Duration::ZERO);
            }

            self.finished_count += 1;

            if let Some(max) = self.repeat_count {
                if self.finished_count >= max {
                    return (true, leftover);
                }
            }

            self.current = (self.factory)();
            dt = leftover;
            if dt == Duration::ZERO {
                return (false, Duration::ZERO);
            }
        }
    }

    fn duration(&self) -> Duration {
        match self.repeat_count {
            Some(count) => self.current.duration() * count as u32,
            None => INFINITE_DURATION_CAP,
        }
    }

    fn collect_audio_events(&mut self, current_time: Duration, events: &mut Vec<AudioEvent>) {
        self.current.collect_audio_events(current_time, events);
    }

    fn reset(&mut self) {
        self.current = (self.factory)();
        self.finished_count = 0;
    }
}

/// Creates an animation that repeats an animation generated by a factory.
///
/// Generally used via the `loop_anim!` macro.
///
/// ### Example
/// ```rust
/// # use motion_canvas_rs::prelude::*;
/// # use std::time::Duration;
/// # let node = Rect::default().with_size(Vec2::new(100.0, 100.0)).with_fill(Color::RED);
/// # let target = Vec2::new(100.0, 100.0);
/// # let dur = Duration::from_secs(1);
/// // Repeat 5 times
/// loop_anim!(node.position.to(target.clone(), dur), Some(5));
///
/// # let node_2 = Rect::default().with_size(Vec2::new(100.0, 100.0)).with_fill(Color::RED);
/// // Repeat infinitely
/// loop_anim!(node_2.position.to(target, dur), None);
/// ```
pub fn loop_anim<F>(factory: F, count: Option<usize>) -> Box<dyn Animation>
where
    F: Fn() -> Box<dyn Animation> + Send + Sync + 'static,
{
    Box::new(LoopAnim::new(Box::new(factory), count))
}
