I'm working on a rust program that takes a directory full of images as input, and outputs a video file which chaotically flashes, strobes, and displays images at random times, following intricate and chaotic patterns. The transitions are fast and chaotic, creating an almost epileptic effect. It has a modular way of implementing interesting effects. Here is the source code:

////// src/config.rs //////

use serde::Deserialize;
use std::{fs, sync::LazyLock};

// TODO: Lock here might not be good, consider a RwLock kind of thing
pub static CONFIG: LazyLock<Config> = LazyLock::new(|| {
    let config_str = fs::read_to_string("config.toml")
        .expect(r"C:\Project\Workspace\Javascript\gpt4o-site\media-cycler-rs\config.toml");
    toml::from_str(&config_str)
        .expect("Failed to parse config.toml")
});

#[test]
fn test_deser()
{
    println!("{:#?}", *CONFIG)
}

#[derive(Debug, Deserialize)]
pub struct Config {
    pub output_file: String,
    pub input_dirs: Vec<String>,
    pub duration_s: u32,
    pub width: u32,
    pub height: u32,
    pub framerate: u32,
    pub bit_rate: usize,
    pub effects: Effects,
}

#[derive(Debug, Deserialize)]
pub struct Effects {
    pub invert_colors: InvertColorsEffect,
    pub color_flash: ColorFlashEffect,
    pub r#static: StaticEffect,
    pub spiral: SpiralEffect,
    pub transparency_strobe: TransparencyStrobeEffect,
    pub overlay_text: OverlayTextEffect,
    pub zoom_in: ZoomInEffect,
    pub kaleidoscope: KaleidoscopeEffect,
    pub bit_focus: BitFocusEffect,
    pub glow: GlowEffect,
    pub grow_shrink: GrowShrinkEffect,
    pub fade_out: FadeOutEffect,
    pub split_apart: SplitApartEffect,
    pub randomized_oval_fade: RandomizedOvalFadeEffect,
    pub glitch: GlitchEffect,
    pub feedback_loop: FeedbackLoopEffect,
}

#[derive(Debug, Deserialize)]
pub struct InvertColorsEffect {
    pub weight: f64,
}

#[derive(Debug, Deserialize)]
pub struct ColorFlashEffect {
    pub weight: f64,
}

#[derive(Debug, Deserialize)]
pub struct StaticEffect {
    pub weight: f64,
}

#[derive(Debug, Deserialize)]
pub struct SpiralEffect {
    pub weight: f64,
}


#[derive(Debug, Deserialize)]
pub struct TransparencyStrobeEffect {
    pub weight: f64,
}


#[derive(Debug, Deserialize)]
pub struct OverlayTextEffect {
    pub weight: f64,
    pub texts: Vec<String>
}


#[derive(Debug, Deserialize)]
pub struct ZoomInEffect {
    pub weight: f64,
}


#[derive(Debug, Deserialize)]
pub struct KaleidoscopeEffect {
    pub weight: f64,
}


#[derive(Debug, Deserialize)]
pub struct BitFocusEffect {
    pub weight: f64,
}


#[derive(Debug, Deserialize)]
pub struct GlowEffect {
    pub weight: f64,
}


#[derive(Debug, Deserialize)]
pub struct GrowShrinkEffect {
    pub weight: f64,
}


#[derive(Debug, Deserialize)]
pub struct FadeOutEffect {
    pub weight: f64,
}


#[derive(Debug, Deserialize)]
pub struct SplitApartEffect {
    pub weight: f64,
}

#[derive(Debug, Deserialize)]
pub struct RandomizedOvalFadeEffect {
    pub weight: f64,
}

#[derive(Debug, Deserialize)]
pub struct GlitchEffect {
    pub weight: f64,
}

#[derive(Debug, Deserialize)]
pub struct FeedbackLoopEffect {
    pub weight: f64,
}

impl Config
{
    pub fn total_frames(&self) -> usize { (self.framerate * self.duration_s) as usize }
}


////// src/effects.rs //////

use image::{ImageBuffer, Pixel, Rgba, RgbaImage};
use rand::prelude::*;
use rusttype::Font;
use noise::{Fbm, MultiFractal, NoiseFn, RidgedMulti};

use crate::{config::CONFIG, types};

// Define the Effect enum with all possible effects
#[derive(Clone)]
pub enum Effect {
    InvertColors,
    ColorFlash(image::Rgba<u8>),
    Static,
    Spiral(f32),
    TransparencyStrobe(f32), // The parameter represents strobe speed.
    OverlayText(String),
    ZoomIn(f32), // This parameter represents the zoom speed.
    Kaleidoscope,
    BitFocus,
    Glow { max_intensity: f32 },
    GrowShrink { speed: f32 },     // New effect for growing and shrinking
    FadeOut,                       // New effect for fading out
    SplitApart,                    // New effect for splitting apart
    LowOpacityFlash {
        flash_duration: usize,
        flash_interval: usize,
    },
    RandomizedOvalFade {
        randomness: f32, // Controls the amount of shape distortion
    },
    Glitch,
    FeedbackLoop {
        blend_factor: f32,
        delay_frames: usize,
    },
}


// EffectFactory to generate random effects
pub struct EffectFactory {
    font: rusttype::Font<'static>,
}

impl EffectFactory {
    pub fn new() -> Self {
        // Random number generator
        let font_data = include_bytes!(r"C:\Project\Workspace\Javascript\gpt4o-site\media-cycler-rs\Atop-R99O3.ttf") as &[u8];
        Self
        {
            font: Font::try_from_bytes(font_data).expect("Error loading font"),
        }
    }

    pub fn generate_random_effects(&mut self) -> Vec<Effect> {
        let mut effects = Vec::new();
        if rand::thread_rng().gen_bool(CONFIG.effects.invert_colors.weight) {
            effects.push(Effect::InvertColors);
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.color_flash.weight) {
            let color = image::Rgba([
                rand::thread_rng().gen_range(0..=255),
                rand::thread_rng().gen_range(0..=255),
                rand::thread_rng().gen_range(0..=255),
                128,
            ]);
            effects.push(Effect::ColorFlash(color));
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.r#static.weight) {
            effects.push(Effect::Static);
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.spiral.weight) {
            effects.push(Effect::Spiral(rand::thread_rng().gen_range(0.0..=1.0)));
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.transparency_strobe.weight) {
            effects.push(Effect::TransparencyStrobe(rand::thread_rng().gen_range(0.2..=10.0)));
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.overlay_text.weight) {
            let text = CONFIG.effects.overlay_text.texts.choose(&mut rand::thread_rng()).unwrap().to_string();
            effects.push(Effect::OverlayText(text));
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.zoom_in.weight) {
            let zoom_speed = rand::thread_rng().gen_range(0.001..0.01); // Adjust zoom speed as desired
            effects.push(Effect::ZoomIn(zoom_speed));
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.kaleidoscope.weight) {
            effects.push(Effect::Kaleidoscope);
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.bit_focus.weight) {
            effects.push(Effect::BitFocus);
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.glow.weight) {
            effects.push(Effect::Glow{max_intensity: rand::thread_rng().gen_range(1.5..3.0)});
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.feedback_loop.weight) {
            let blend_factor = rand::thread_rng().gen_range(0.1..0.9);
            let delay_frames = rand::thread_rng().gen_range(1..=5); // Adjust as needed
            effects.push(Effect::FeedbackLoop {
                blend_factor,
                delay_frames,
            });
        }
        effects
    }

    pub fn generate_overlay_effects(&mut self) -> Vec<Effect> {
        let mut effects = Vec::new();
        if rand::thread_rng().gen_bool(CONFIG.effects.grow_shrink.weight) {
            let speed = rand::thread_rng().gen_range(0.01..0.05);
            effects.push(Effect::GrowShrink { speed });
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.fade_out.weight) {
            effects.push(Effect::FadeOut);
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.split_apart.weight) {
            effects.push(Effect::SplitApart);
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.r#static.weight) {
            effects.push(Effect::Static);
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.spiral.weight) {
            effects.push(Effect::Spiral(rand::thread_rng().gen_range(0.0..=1.0)));
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.glow.weight) {
            effects.push(Effect::Glow{max_intensity: rand::thread_rng().gen_range(1.5..3.0)});
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.bit_focus.weight) {
            effects.push(Effect::BitFocus);
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.color_flash.weight) {
            let color = image::Rgba([
                rand::thread_rng().gen_range(0..=255),
                rand::thread_rng().gen_range(0..=255),
                rand::thread_rng().gen_range(0..=255),
                128,
            ]);
            effects.push(Effect::ColorFlash(color));
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.invert_colors.weight / 2.0) {
            effects.push(Effect::InvertColors);
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.randomized_oval_fade.weight) {
            let randomness = rand::thread_rng().gen_range(2.0..5.0);
            effects.push(Effect::RandomizedOvalFade { randomness });
        }
        if rand::thread_rng().gen_bool(CONFIG.effects.glitch.weight) {
            effects.push(Effect::Glitch);
        }

        effects
    }

    pub fn generate_random_global_effects(&mut self) -> Vec<Effect> {
        let mut effects = Vec::new();

        if rand::thread_rng().gen_bool(0.3) {
            effects.push(Effect::InvertColors);
        }
        if rand::thread_rng().gen_bool(0.2) {
            let color = image::Rgba([
                rand::thread_rng().gen_range(0..=255),
                rand::thread_rng().gen_range(0..=255),
                rand::thread_rng().gen_range(0..=255),
                128,
            ]);
            effects.push(Effect::ColorFlash(color));
        }
        if rand::thread_rng().gen_bool(0.2) {
            effects.push(Effect::Static);
        }
        if rand::thread_rng().gen_bool(0.2) {
            effects.push(Effect::Spiral(rand::thread_rng().gen_range(0.0..=1.0)));
        }
        if rand::thread_rng().gen_bool(0.2) {
            let text = CONFIG.effects.overlay_text.texts.choose(&mut rand::thread_rng()).unwrap().to_string();
            effects.push(Effect::OverlayText(text));
        }
        effects
    }

    // Implement effect functions
    /// Returns new image scale
    pub fn apply_effects(
        &self,
        image: &image::RgbaImage,
        frame_number: usize,
        state: &types::ImageState,
        previous_frames: &[RgbaImage],
    ) -> ImageBuffer<Rgba<u8>, Vec<u8>> {
        let mut image = image.clone();
        let mut current_scale = state.scale;
        let mut alpha = 1.0;
        for effect in &state.effects {
            match effect {
                Effect::InvertColors => Self::invert_colors(&mut image),
                Effect::ColorFlash(color) => Self::add_color_flash(&mut image, &color),
                Effect::Static => self.add_static(&mut image),
                Effect::Spiral(density) => self.add_spiral(&mut image, *density),
                Effect::TransparencyStrobe(speed) => {
                    Self::apply_transparency_strobe(&mut image, frame_number, state.start_frame, *speed)
                },
                Effect::OverlayText(text) => {
                    self.overlay_text(&mut image, &text);
                },
                Effect::ZoomIn(zoom_speed) => {
                    let zoom_factor = Self::calculate_zoom_scale(
                        image.width(),
                        image.height(),
                        CONFIG.width,
                        CONFIG.height,
                        frame_number,
                        state.start_frame,
                        *zoom_speed,
                    );
                    image = Self::apply_zoom(&image, CONFIG.width, CONFIG.height, zoom_factor);
                }
                Effect::Kaleidoscope => Self::apply_kaleidoscope(&mut image),
                Effect::BitFocus => Self::apply_bit_focus(
                    &mut image,
                    frame_number,
                    state.start_frame,
                    state.end_frame,
                ),
                Effect::Glow { max_intensity } => {
                    self.apply_glow(&mut image, frame_number, state, *max_intensity);
                },
                Effect::GrowShrink { speed } => {
                    let scale_factor = self.calculate_grow_shrink_scale(
                        frame_number,
                        state.start_frame,
                        state.end_frame,
                        *speed,
                    );
                    current_scale *= scale_factor;
                },
                Effect::FadeOut => {
                    alpha = self.calculate_fade_out_alpha(
                        frame_number,
                        state.start_frame,
                        state.end_frame,
                    );
                },
                Effect::SplitApart => {
                    image = self.apply_split_apart(&image, frame_number, state);
                },
                Effect::LowOpacityFlash {
                    flash_duration,
                    flash_interval,
                } => {
                    alpha = self.calculate_low_opacity_flash_alpha(
                        frame_number,
                        state.start_frame,
                        *flash_duration,
                        *flash_interval,
                    );
                },
                Effect::RandomizedOvalFade { randomness } => {
                    self.apply_randomized_oval_fade(&mut image, *randomness);
                },
                Effect::Glitch => {
                    self.apply_glitch_effect(&mut image);
                },
                Effect::FeedbackLoop { blend_factor, delay_frames } => {
                    self.apply_feedback_loop_effect(
                        &mut image,
                        previous_frames,
                        *blend_factor,
                        *delay_frames,
                    );
                }
            }
        }
        // Apply scaling
        let scaled_image = image::imageops::resize(
            &image,
            (image.width() as f32 * current_scale) as u32,
            (image.height() as f32 * current_scale) as u32,
            image::imageops::FilterType::Lanczos3,
        );

        // Apply alpha (transparency)
        if alpha != 1.0 {
            image = self.apply_alpha(&image, alpha);
        }
        image
    }

    fn apply_feedback_loop_effect(
        &self,
        current_image: &mut RgbaImage,
        previous_frames: &[RgbaImage],
        blend_factor: f32,
        delay_frames: usize,
    ) {
        if delay_frames == 0 {
            return;
        }
    
        if let Some(prev_frame) = previous_frames.get(previous_frames.len().saturating_sub(delay_frames)) {
            // Blending logic remains the same
            self.blend_images(current_image, prev_frame, blend_factor);
        }
    }

    fn blend_images(
        &self,
        current_image: &mut RgbaImage,
        overlay_image: &RgbaImage,
        blend_factor: f32,
    ) {
        // Clamp blend_factor between 0.0 and 1.0
        let blend_factor = blend_factor.clamp(0.0, 1.0);
        let inv_blend = 1.0 - blend_factor;

        for (x, y, pixel) in current_image.enumerate_pixels_mut() {
            let overlay_pixel = overlay_image.get_pixel(x, y);

            for i in 0..3 {
                let current_value = pixel[i] as f32;
                let overlay_value = overlay_pixel[i] as f32;

                pixel[i] = (current_value * inv_blend + overlay_value * blend_factor).clamp(0.0, 255.0) as u8;
            }
            // Handle alpha channel if desired
        }
    }

    // Function to calculate alpha for LowOpacityFlash effect
    fn calculate_low_opacity_flash_alpha(
        &self,
        frame_number: usize,
        start_frame: usize,
        flash_duration: usize,
        flash_interval: usize,
    ) -> f32 {
        let elapsed_frames = frame_number - start_frame;
        let cycle = flash_duration + flash_interval;

        // Determine where we are in the flash cycle
        let position_in_cycle = elapsed_frames % cycle;

        if position_in_cycle < flash_duration {
            0.8 // Full opacity during the flash
        } else {
            0.1 // Low opacity otherwise
        }
    }

    fn apply_randomized_oval_fade(&self, image: &mut RgbaImage, randomness: f32) {
        let fbm = Fbm::new()
            .set_octaves(6)
            .set_frequency(2.0)
            .set_lacunarity(2.0)
            .set_persistence(0.5);

        let ridged = RidgedMulti::new()
            .set_octaves(4)
            .set_frequency(1.0)
            .set_lacunarity(2.0)
            .set_persistence(0.5);

        let width = image.width() as f32;
        let height = image.height() as f32;
        let center_x = width / 2.0;
        let center_y = height / 2.0;
        let max_distance = ((center_x.powi(2) + center_y.powi(2)).sqrt()) as f32;
        let noise_scale = randomness * 5.0;
        let noise_threshold = 0.2;

        for y in 0..image.height() {
            for x in 0..image.width() {
                let dx = x as f32 - center_x;
                let dy = y as f32 - center_y;
                let distance = (dx.powi(2) + dy.powi(2)).sqrt();
                let distance_factor = (distance / max_distance).clamp(0.0, 1.0);
    
                // Get combined noise value
                let noise_value1 = fbm.get([
                    dx as f64 / width as f64 * noise_scale as f64,
                    dy as f64 / height as f64 * noise_scale as f64,
                ]) as f32;
    
                let noise_value2 = ridged.get([
                    dx as f64 / width as f64 * noise_scale as f64 * 0.5,
                    dy as f64 / height as f64 * noise_scale as f64 * 0.5,
                ]) as f32;
    
                // Combine noises
                let combined_noise = (noise_value1 + noise_value2) / 2.0;
    
                // Apply threshold
                let mask_value = if combined_noise > noise_threshold { 1.0 } else { 0.0 };
    
                let alpha = (1.0 - distance_factor.powf(0.5)) * mask_value;
                let edge_alpha = if distance_factor >= 1.0 { 0.0 } else { alpha };
    
                let pixel = image.get_pixel_mut(x, y);
                let color_shift = ((combined_noise - 0.5) * 2.0 * randomness).clamp(-0.5, 0.5);
                for i in 0..3 {
                    let color = pixel[i] as f32 / 255.0;
                    let shifted = (color + color_shift).clamp(0.0, 1.0);
                    pixel[i] = (shifted * 255.0) as u8;
                }
                pixel[3] = (pixel[3] as f32 * edge_alpha) as u8;
            }
        }
    }

    // Function to calculate scale for GrowShrink effect
    fn calculate_grow_shrink_scale(
        &self,
        frame_number: usize,
        start_frame: usize,
        end_frame: usize,
        speed: f32,
    ) -> f32 {
        let total_frames = (end_frame - start_frame) as f32;
        let elapsed_frames = (frame_number - start_frame) as f32;

        let progress = elapsed_frames / total_frames;
        let scale = (progress * speed * std::f32::consts::PI * 2.0).sin() * 0.5 + 1.0; // Oscillate between 0.5 and 1.5

        scale
    }

    // Function to calculate alpha for FadeOut effect
    fn calculate_fade_out_alpha(
        &self,
        frame_number: usize,
        start_frame: usize,
        end_frame: usize,
    ) -> f32 {
        let total_frames = (end_frame - start_frame) as f32;
        let elapsed_frames = (frame_number - start_frame) as f32;

        let progress = (elapsed_frames / total_frames).clamp(0.0, 1.0);
        let alpha = 1.0 - progress; // Fade out from 1.0 to 0.0

        alpha
    }

    // Function to apply alpha to an image
    fn apply_alpha(
        &self,
        image: &RgbaImage,
        alpha: f32,
    ) -> RgbaImage {
        let mut final_image = image.clone();

        for pixel in final_image.pixels_mut() {
            pixel[3] = (pixel[3] as f32 * alpha).clamp(0.0, 255.0) as u8;
        }

        final_image
    }

    fn apply_glitch_effect(&self, image: &mut RgbaImage) {
        let width = image.width();
        let height = image.height();
        let shift = (rand::thread_rng().gen_range(-10..10), rand::thread_rng().gen_range(-10..10));
        
        // Clone the image to avoid borrowing issues
        let original = image.clone();
    
        for y in 0..height {
            for x in 0..width {
                let src_x = x.wrapping_add(shift.0 as u32) % width;
                let src_y = y.wrapping_add(shift.1 as u32) % height;
                let src_pixel = original.get_pixel(src_x, src_y);
    
                let pixel = image.get_pixel_mut(x, y);
                // Shift the red channel
                pixel[0] = src_pixel[0];
            }
        }
    }

    // Function to apply the SplitApart effect
    fn apply_split_apart(
        &self,
        image: &RgbaImage,
        frame_number: usize,
        state: &types::ImageState,
    ) -> RgbaImage {
        // For simplicity, we'll split the image into two halves moving apart

        let total_frames = (state.end_frame - state.start_frame) as f32;
        let elapsed_frames = (frame_number - state.start_frame) as f32;
        let progress = (elapsed_frames / total_frames).clamp(0.0, 1.0);

        let width = image.width();
        let height = image.height();

        let mut new_image = RgbaImage::new(width, height);

        // Left half moves left
        let offset = (progress * width as f32 / 2.0) as i64;
        let left_half = image::imageops::crop_imm(image, 0, 0, width / 2, height).to_image();
        image::imageops::overlay(&mut new_image, &left_half, -offset, 0);

        // Right half moves right
        let right_half = image::imageops::crop_imm(image, width / 2, 0, width / 2, height).to_image();
        image::imageops::overlay(&mut new_image, &right_half, offset, 0);

        new_image
    }

    // Implement the apply_glow function
    fn apply_glow(
        &self,
        image: &mut RgbaImage,
        frame_number: usize,
        state: &types::ImageState,
        max_intensity: f32
    ) {
        // Calculate the progress of the effect between 0.0 and 1.0
        let total_frames = (state.end_frame - state.start_frame) as f32;
        let elapsed_frames = (frame_number - state.start_frame) as f32;
        let progress = (elapsed_frames / total_frames).clamp(0.0, 1.0);

        let brightness_factor = 1.0 + progress * (max_intensity - 1.0);

        for pixel in image.pixels_mut() {
            for i in 0..3 {
                let value = pixel[i] as f32 * brightness_factor;
                pixel[i] = value.clamp(0.0, 255.0) as u8;
            }
        }
    }

    // Function to invert colors in an image
    fn invert_colors(image: &mut RgbaImage) {
        for pixel in image.pixels_mut() {
            pixel[0] = 255 - pixel[0];
            pixel[1] = 255 - pixel[1];
            pixel[2] = 255 - pixel[2];
        }
    }

    // Function to add a color flash effect
    fn add_color_flash(image: &mut RgbaImage, color: &image::Rgba<u8>) {
        if rand::thread_rng().gen_bool(0.1)
        {
            for pixel in image.pixels_mut() {
                pixel.blend(&color);
            }
        }
    }

    fn apply_transparency_strobe(
        image: &mut RgbaImage,
        frame_number: usize,
        start_frame: usize,
        strobe_speed: f32,
    ) {
        // Calculate the alpha value based on a sine wave for smooth strobing
        let t = (frame_number - start_frame) as f32;
        let alpha = ((strobe_speed * t).sin() * 0.5 + 0.5) * 255.0;
        let alpha = alpha as u8;

        for pixel in image.pixels_mut() {
            pixel[3] = alpha;
        }
    }

    // Function to add static effects
    fn add_static(&self, image: &mut RgbaImage) {
        if image.width() == 0 || image.height() == 0 
        {
            return;
        }
        let num_sparks = rand::thread_rng().gen_range(100..500);
        for _ in 0..num_sparks {
            let x = rand::thread_rng().gen_range(0..image.width());
            let y = rand::thread_rng().gen_range(0..image.height());
            let pixel = image.get_pixel_mut(x, y);
            pixel[0] = 255;
            pixel[1] = 255;
            pixel[2] = 255;
            pixel[3] = 255;
        }
    }

    // Function to add a spiral effect
    fn add_spiral(&self, image: &mut RgbaImage, spiral_density: f32) {
        use imageproc::drawing::draw_line_segment_mut;
        use rand::Rng;
        use palette::{LinSrgb, Gradient};
    
        // Ensure spiral_density is clamped between 0.0 and 1.0
        let spiral_density = spiral_density.clamp(0.0, 1.0);
    
        let mut rng = rand::thread_rng();
        let center_x = image.width() as f32 / 2.0;
        let center_y = image.height() as f32 / 2.0;
        let max_radius = center_x.min(center_y);
        let num_turns = rng.gen_range(5.0..10.0);
    
        // Prepare gradient colors
        let gradient = Gradient::new(vec![
            LinSrgb::new(1.0, 0.0, 0.0), // Red
            LinSrgb::new(0.0, 1.0, 0.0), // Green
            LinSrgb::new(0.0, 0.0, 1.0), // Blue
        ]);
    
        // Random color for old implementation
        let random_color = image::Rgba([
            rng.gen(),
            rng.gen(),
            rng.gen(),
            255,
        ]);
    
        // Calculate the total number of steps (adjust as needed)
        let total_steps = 1000;
        let max_segment_length = 10.0; // Maximum length of segments
    
        // Calculate segment length and gap based on spiral_density
        let segment_length = 1.0 + spiral_density * (max_segment_length - 1.0);
        let gap_length = (1.0 - spiral_density) * max_segment_length;
    
        let mut t = 0.0;
        let dt = 1.0 / total_steps as f32;
    
        while t < 1.0 {
            let theta = num_turns * t * 2.0 * std::f32::consts::PI;
            let radius = max_radius * t;
    
            let x_start = center_x + radius * theta.cos();
            let y_start = center_y + radius * theta.sin();
    
            // Update t to get the end point of the segment
            let t_end = (t + segment_length * dt).min(1.0);
            let theta_end = num_turns * t_end * 2.0 * std::f32::consts::PI;
            let radius_end = max_radius * t_end;
    
            let x_end = center_x + radius_end * theta_end.cos();
            let y_end = center_y + radius_end * theta_end.sin();
    
            // Interpolate color based on t
            let lin_color = gradient.get(t);
            let gradient_rgba = image::Rgba([
                (lin_color.red * 255.0) as u8,
                (lin_color.green * 255.0) as u8,
                (lin_color.blue * 255.0) as u8,
                255,
            ]);
    
            // Blend colors based on spiral_density
            let color = image::Rgba([
                ((1.0 - spiral_density) * random_color[0] as f32 + spiral_density * gradient_rgba[0] as f32) as u8,
                ((1.0 - spiral_density) * random_color[1] as f32 + spiral_density * gradient_rgba[1] as f32) as u8,
                ((1.0 - spiral_density) * random_color[2] as f32 + spiral_density * gradient_rgba[2] as f32) as u8,
                255,
            ]);
    
            // Draw the segment
            if spiral_density == 0.0 {
                // At density zero, plot individual points
                if x_start >= 0.0 && x_start < image.width() as f32 && y_start >= 0.0 && y_start < image.height() as f32 {
                    image.put_pixel(x_start as u32, y_start as u32, color);
                }
            } else {
                // Draw line segments with length increasing as spiral_density increases
                draw_line_segment_mut(
                    image,
                    (x_start, y_start),
                    (x_end, y_end),
                    color,
                );
            }
    
            // Increment t by segment_length and gap_length
            t = t_end + gap_length * dt;
        }
    
        // Optionally, apply a slight blur when spiral_density is high
        if spiral_density > 0.8 {
            let blur_sigma = (spiral_density - 0.8) / 0.2 * 2.0; // Range from 0.0 to 2.0
            self.apply_blur(image, blur_sigma);
        }
    }
    
    // Add a method to apply a blur effect
    fn apply_blur(&self, image: &mut RgbaImage, sigma: f32) {
        use image::imageops::blur;
        let blurred = blur(image, sigma);
        *image = blurred;
    }

    fn add_spiral_old(&self, image: &mut RgbaImage) {
        let center_x = image.width() as i32 as f32 / 2.0;
        let center_y = image.height() as i32 as f32 / 2.0;
        let max_radius = center_x.min(center_y);
        let num_turns = rand::thread_rng().gen_range(5.0..10.0);
        let color = image::Rgba([rand::thread_rng().gen(), rand::thread_rng().gen(), rand::thread_rng().gen(), 255]);

        for t in 0..1000 {
            let theta = num_turns * t as f32 * 0.01 * std::f32::consts::PI;
            let radius = max_radius * t as f32 * 0.001;
            let x = center_x + radius * theta.cos();
            let y = center_y + radius * theta.sin();

            if x >= 0.0 && x < image.width() as f32 && y >= 0.0 && y < image.height() as f32 {
                let pixel = image.get_pixel_mut(x as u32, y as u32);
                *pixel = color;
            }
        }
    }

    fn overlay_text(&self,
        image: &mut RgbaImage,
        text: &str
    ) {
        use imageproc::drawing::draw_text_mut;
        use rusttype::Scale;

        if image.width() == 0 || image.height() == 0 { 
            return;
        }

        let scale = Scale {
            x: rand::thread_rng().gen_range(20.0..100.0), // Random font size
            y: rand::thread_rng().gen_range(20.0..100.0),
        };

        // Random position
        let x = rand::thread_rng().gen_range(0..(image.width() as i32));
        let y = rand::thread_rng().gen_range(0..(image.height() as i32));

        // Random color
        let color = image::Rgba([
            rand::thread_rng().gen(),
            rand::thread_rng().gen(),
            rand::thread_rng().gen(),
            255,
        ]);

        draw_text_mut(
            image,
            color,
            x,
            y,
            scale,
            &self.font,
            text,
        );
    }

    fn calculate_zoom_scale(
        image_width: u32,
        image_height: u32,
        frame_width: u32,
        frame_height: u32,
        frame_number: usize,
        start_frame: usize,
        zoom_speed: f32,
    ) -> f32 {
        let t = (frame_number - start_frame) as f32;
        let mut scale = 1.0 + zoom_speed * t;
    
        // Cap the scale so that the image does not exceed the frame size
        let max_scale_x = frame_width as f32 / image_width as f32;
        let max_scale_y = frame_height as f32 / image_height as f32;
        let max_scale = max_scale_x.min(max_scale_y);
    
        if scale > max_scale {
            scale = max_scale;
        }
        scale
    }

    fn apply_zoom(
        image: &RgbaImage,
        frame_width: u32,
        frame_height: u32,
        zoom_factor: f32,
    ) -> RgbaImage {
        // Compute the scaled image size
        let scaled_width = (image.width() as f32 * zoom_factor) as u32;
        let scaled_height = (image.height() as f32 * zoom_factor) as u32;
    
        // Compute the maximum allowed scale to not exceed frame size
        let max_scale_x = frame_width as f32 / image.width() as f32;
        let max_scale_y = frame_height as f32 / image.height() as f32;
        let max_scale = max_scale_x.min(max_scale_y);
    
        if zoom_factor <= max_scale {
            // Scale the image up to the zoom_factor
            let scaled_image = image::imageops::resize(
                image,
                scaled_width,
                scaled_height,
                image::imageops::FilterType::Lanczos3,
            );
    
            // Position the scaled image onto the frame
            let mut frame = RgbaImage::new(frame_width, frame_height);
            let x = ((frame_width as i32 - scaled_width as i32) / 2).max(0) as i64;
            let y = ((frame_height as i32 - scaled_height as i32) / 2).max(0) as i64;
    
            image::imageops::overlay(&mut frame, &scaled_image, x, y);
            frame
    
        } else {
            // If the scaled image would exceed the frame size, adjust accordingly
    
            // Scale the image up to the maximum allowed scale
            let scaled_image = image::imageops::resize(
                image,
                (image.width() as f32 * max_scale) as u32,
                (image.height() as f32 * max_scale) as u32,
                image::imageops::FilterType::Lanczos3,
            );
    
            // Simulate further zoom by cropping and resizing
            let additional_zoom = zoom_factor / max_scale;
    
            // Calculate the inverse of the additional zoom factor
            let inv_zoom = 1.0 / additional_zoom;
    
            // Compute the dimensions of the cropped area
            let crop_width = (scaled_image.width() as f32 * inv_zoom) as u32;
            let crop_height = (scaled_image.height() as f32 * inv_zoom) as u32;
    
            // Ensure crop dimensions are valid
            if crop_width == 0 || crop_height == 0 {
                return scaled_image;
            }
    
            // Calculate the top-left corner of the cropping area
            let x0 = (scaled_image.width() - crop_width) / 2;
            let y0 = (scaled_image.height() - crop_height) / 2;
    
            // Crop the image
            let cropped_image = image::imageops::crop_imm(&scaled_image, x0, y0, crop_width, crop_height).to_image();
    
            // Resize back to the frame size
            image::imageops::resize(
                &cropped_image,
                frame_width,
                frame_height,
                image::imageops::FilterType::Lanczos3,
            )
        }
    }

    fn apply_kaleidoscope(image: &mut RgbaImage) {
        let width = image.width();
        let height = image.height();
    
        // Create a new image buffer to store the kaleidoscope result
        let mut output = RgbaImage::new(width, height);
    
        // Number of segments in the kaleidoscope (adjust for different patterns)
        let num_segments = 8;
        let angle_per_segment = 360.0 / num_segments as f32;
        let center_x = width as f32 / 2.0;
        let center_y = height as f32 / 2.0;
    
        // Iterate over each pixel in the output image
        for y in 0..height {
            for x in 0..width {
                // Calculate coordinates relative to the center
                let dx = x as f32 - center_x;
                let dy = y as f32 - center_y;
    
                // Calculate the angle and radius from the center
                let mut angle = dy.atan2(dx).to_degrees();
                let radius = (dx * dx + dy * dy).sqrt();
    
                // Normalize the angle within [0, 360)
                if angle < 0.0 {
                    angle += 360.0;
                }
                // Map the angle into the first segment
                angle = angle % angle_per_segment;
    
                // Reflect the angle to create symmetry
                if angle > angle_per_segment / 2.0 {
                    angle = angle_per_segment - angle;
                }
    
                // Convert back to radians
                let angle_rad = angle.to_radians();
    
                // Calculate the source pixel coordinates
                let src_x = center_x + radius * angle_rad.cos();
                let src_y = center_y + radius * angle_rad.sin();
    
                // Check bounds and sample the pixel from the original image
                if src_x >= 0.0 && src_x < width as f32 && src_y >= 0.0 && src_y < height as f32 {
                    let src_pixel = image.get_pixel(src_x as u32, src_y as u32);
                    output.put_pixel(x, y, *src_pixel);
                } else {
                    // If out of bounds, set pixel to black or transparent
                    output.put_pixel(x, y, image::Rgba([0, 0, 0, 255]));
                }
            }
        }
    
        // Replace the original image data with the kaleidoscope result
        *image = output;
    }

    fn apply_bit_focus(
        image: &mut RgbaImage,
        frame_number: usize,
        start_frame: usize,
        end_frame: usize,
    ) {
        let total_duration = end_frame - start_frame;
        let elapsed = frame_number - start_frame;
    
        // Calculate the progression ratio [0.0, 1.0]
        let progress = elapsed as f32 / total_duration as f32;
    
        // Determine the pixelation level based on progress
        // Start with a small size (high pixelation) and increase to full size
        let min_pixels = 8; // Minimum number of pixels for pixelation (more pixelated)
        let max_pixels = 1; // Maximum number of pixels (full resolution)
    
        // Use an easing function for smoother animation (optional)
        let eased_progress = Self::ease_out_quad(progress);
    
        // Calculate current pixelation level
        let pixel_size = ((min_pixels as f32 * (1.0 - eased_progress)) + (max_pixels as f32 * eased_progress)).max(1.0);
    
        // Resize down to create pixelation, then upscale back to original size
        let (width, height) = image.dimensions();
        let new_width = (width as f32 / pixel_size).max(1.0) as u32;
        let new_height = (height as f32 / pixel_size).max(1.0) as u32;
    
        // Downscale the image to the new dimensions
        let downscaled = image::imageops::resize(
            image,
            new_width,
            new_height,
            image::imageops::FilterType::Nearest,
        );
    
        // Upscale back to original size
        let upscaled = image::imageops::resize(
            &downscaled,
            width,
            height,
            image::imageops::FilterType::Nearest,
        );
    
        // Replace the original image with the upscaled, pixelated image
        *image = upscaled;
    }
    
    // Optional easing function for smoother progression
    fn ease_out_quad(t: f32) -> f32 {
        1.0 - (1.0 - t).powi(2)
    }
}

////// src/frame_renderer.rs //////

use std::cell::RefCell;
use std::rc::Rc;

use crate::config::CONFIG;
use crate::transitions::{apply_reassembly_transition, TransitionEffect};
use crate::transitions::Transition;
use crate::effects::EffectFactory;
use crate::types::ImageState;
use image::{imageops, DynamicImage, RgbaImage};

pub fn render_frame(
    frame_number: usize,
    active_primary_images: impl Iterator<Item = Rc<RefCell<ImageState>>>, // impl Iterator<Item = Rc<RefCell<ImageState>>>
    active_overlay_images: impl Iterator<Item = Rc<RefCell<ImageState>>>,
    active_global_effects: impl Iterator<Item = Rc<RefCell<ImageState>>>,
    effect_factory: &EffectFactory,
    transitions: impl Iterator<Item = Rc<RefCell<Transition>>>,
    previous_frames: &[RgbaImage],
) -> RgbaImage {
    // Create a new blank image (frame) with the specified dimensions
    let mut frame = DynamicImage::new_rgba8(CONFIG.width as u32, CONFIG.height as u32).to_rgba8();

    // Overlay images
    for state in active_primary_images  {
        // Obtain the current frame
        let current_frame_image = (*state).borrow().frames.get_current_frame(frame_number, (*state).borrow().start_frame, (*state).borrow().end_frame).unwrap_or_else(|| RgbaImage::new(1, 1));

        // Apply scaling
        let mut img = image::imageops::resize(
            &current_frame_image,
            (current_frame_image.width() as f32 * (*state).borrow().scale) as u32,
            (current_frame_image.height() as f32 * (*state).borrow().scale) as u32,
            image::imageops::FilterType::Lanczos3,
        );

        // Apply effects
        let img = effect_factory.apply_effects(
            &mut img,
            frame_number,
            &(*state).borrow(),
            previous_frames
        );

        // Overlay the image onto the frame
        imageops::overlay(&mut frame, &img, (*state).borrow().position.0, (*state).borrow().position.1);
    }

    // Render overlay images (smaller, fast images)
    for state in active_overlay_images {
        // Similar handling as above
        let current_frame_image = (*state).borrow().frames.get_current_frame(frame_number, (*state).borrow().start_frame, (*state).borrow().end_frame).unwrap_or_else(|| RgbaImage::new(1, 1));

        // Apply scaling
        let mut img = image::imageops::resize(
            &current_frame_image,
            (current_frame_image.width() as f32 * (*state).borrow().scale) as u32,
            (current_frame_image.height() as f32 * (*state).borrow().scale) as u32,
            image::imageops::FilterType::Lanczos3,
        );

        // Apply effects
        let img = effect_factory.apply_effects(&mut img, frame_number, &(*state).borrow(), previous_frames);

        // Overlay the image onto the frame
        imageops::overlay(&mut frame, &img, (*state).borrow().position.0, (*state).borrow().position.1);
    }

    // Apply global effects to the frame
    for global_state in active_global_effects {
        effect_factory.apply_effects(&mut frame, 0, &(*global_state).borrow(), previous_frames);
    }

    // Render transitions
    for transition in transitions {
        if frame_number >= transition.borrow().start_frame && frame_number < transition.borrow().end_frame {
            let to_apply: TransitionEffect = transition.borrow().effect;
            match to_apply {
                TransitionEffect::Reassembly => {
                    let transition_frame = apply_reassembly_transition(
                        frame_number,
                        &mut transition.clone().borrow_mut(),
                    );
                    // Overlay the transition_frame onto the main frame
                    image::imageops::overlay(&mut frame, &transition_frame, 0, 0);
                },
                // Handle other transition effects...
            }
        }
    }

    frame
}

////// src/main.rs //////

use config::CONFIG;
use image::RgbaImage;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use video_encoder::EncoderMessage;
use std::sync::mpsc;

mod media_loader;
mod effects;
mod frame_renderer;
mod config;
mod scheduler;
mod types;
mod video_encoder;
mod transitions;

fn main() -> anyhow::Result<()> {
    // Initialize progress bars
    let progress_bars = MultiProgress::new();
    let frame_generation_pb = ProgressBar::new(CONFIG.total_frames() as u64);
    let encoder_pb = ProgressBar::new(CONFIG.total_frames() as u64);
    frame_generation_pb.set_style(
        ProgressStyle::default_bar()
            .template("{spinner:.blue} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} frames ({eta} remaining)")?
            .progress_chars("$>-")
    );
    encoder_pb.set_style(
        ProgressStyle::default_bar()
            .template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.green/green}] {pos}/{len} frames ({eta} remaining)")?
            .progress_chars("$>-")
    );
    progress_bars.add(frame_generation_pb.clone());
    progress_bars.add(encoder_pb.clone());

    // Read all images from the input directory
    println!("Loading media...");
    let images = media_loader::load_media(&CONFIG.input_dirs).unwrap();
    println!("Done.");

    // Initialize effect factory
    let mut effect_factory = effects::EffectFactory::new();

    // Initialize image scheduler
    println!("Preparing scheduler...");
    let scheduler = scheduler::ImageScheduler::new(
        &images,
        &mut effect_factory,
    );
    println!("Done.");

    // Initialize video encoder
    let (encoder_tx, encoder_rx) = mpsc::channel();
    let encoder_thread = std::thread::spawn(move || {
        let mut encoder = video_encoder::VideoEncoder::new(&CONFIG.output_file, CONFIG.width, CONFIG.height, CONFIG.framerate, CONFIG.bit_rate).unwrap();
        loop
        {
            match encoder_rx.recv()
            {
                Ok(EncoderMessage::Frame(frame)) => 
                {
                    encoder.encode_frame(&frame).unwrap();
                    encoder_pb.inc(1);
                }
                Ok(EncoderMessage::Complete) => 
                {
                    encoder.finish().unwrap();
                    encoder_pb.finish_with_message(format!("Video has been successfully encoded to {}", CONFIG.output_file));
                    break;
                }
                Err(err) => panic!("{err:?}"),
            }
        }
    });

    // Main frame generation loop
    let mut previous_frames: Vec<RgbaImage> = Vec::new();

    for frame_number in 0..CONFIG.total_frames() {

        // Render the frame
        let frame = scheduler.render_frame(
            frame_number,
            &effect_factory,
            &previous_frames,
        );

        // Encode the frame
        previous_frames.push(frame.clone());
        encoder_tx.send(EncoderMessage::Frame(frame)).unwrap();

        // Update progress bar
        frame_generation_pb.inc(1);
    }

    // Finish frame generation
    frame_generation_pb.finish_with_message(format!("Frame generation complete"));

    // Finalize encoding
    encoder_tx.send(EncoderMessage::Complete).unwrap();
    encoder_thread.join().unwrap();

    // Final message
    println!("Convert the IVF file to MP4 using FFmpeg for playback.");

    Ok(())
}

////// src/media_loader.rs //////

use anyhow::{Error, Result};
use image::{self, RgbaImage};
use image::AnimationDecoder;
use std::fs::{self, File};
use std::io::Read;
use std::path::PathBuf;
use std::rc::Rc;
use ffmpeg_next::{self as ffmpeg, codec, format, frame, media, Rational, Rescale};

pub enum Media {
    Static(RgbaImage),
    Animated {
        frames: Rc<Vec<RgbaImage>>,
        frame_count: usize,
    },
    Video {
        path: PathBuf,
        total_duration_ms: u64,
    },
}

struct VideoMetadata {
    duration_ms: u64,
}

pub struct VideoClip {
    pub path: PathBuf,
    pub ictx: format::context::Input,
    pub decoder: codec::decoder::Video,
    pub stream_index: usize,
    pub scaler: ffmpeg_next::software::scaling::Context,
    pub pending_frames: Vec<frame::Video>,
    pub frame_rate: f64,
    pub current_pts: i64,
    pub time_base: Rational,
}

impl VideoClip {
    pub fn open(path: &PathBuf) -> Result<Self> {
        // Initialize FFmpeg and open the input file
        ffmpeg_next::init()?;
        let ictx = format::input(path)?;
        let input_stream = ictx
            .streams()
            .best(media::Type::Video)
            .ok_or_else(|| anyhow::anyhow!("Could not find video stream"))?;
        let stream_index = input_stream.index();

        // Get codec parameters and open the decoder
        let context_decoder = codec::Context::from_parameters(input_stream.parameters())?;
        let decoder = context_decoder.decoder().video()?;

        // Create a scaler to convert frames to RGBA
        let scaler = ffmpeg_next::software::scaling::Context::get(
            decoder.format(),
            decoder.width(),
            decoder.height(),
            ffmpeg_next::format::Pixel::RGBA,
            decoder.width(),
            decoder.height(),
            ffmpeg_next::software::scaling::flag::Flags::BILINEAR,
        )?;

        // Get video metadata
        let frame_rate = input_stream.avg_frame_rate().0 as f64 / input_stream.avg_frame_rate().1 as f64;
        let time_base = input_stream.time_base();

        Ok(Self {
            path: path.clone(),
            ictx,
            decoder,
            stream_index,
            scaler,
            pending_frames: Vec::new(),
            frame_rate,
            current_pts: 0,
            time_base,
        })
    }

    pub fn seek_to_pts(&mut self, pts: i64) -> Result<()> {
        // Convert desired PTS from stream time base to AV_TIME_BASE units
        let desired_timestamp = pts.rescale(
            self.time_base,
            Rational(1, ffmpeg::ffi::AV_TIME_BASE as i32),
        );

        // Perform the seek operation
        let ictx_ptr = unsafe { self.ictx.as_mut_ptr() };
        
        let _ret = unsafe { 
            ffmpeg::ffi::av_seek_frame(
                ictx_ptr,
                self.stream_index as i32,
                desired_timestamp,
                ffmpeg::ffi::AVSEEK_FLAG_BACKWARD,
            ) 
        };

        // Flush the decoder
        self.decoder.flush();

        // Clear any pending frames
        self.pending_frames.clear();

        // Reset current_pts
        self.current_pts = -1;

        Ok(())
    }

    pub fn get_next_frame(&mut self) -> Result<Option<RgbaImage>> {
        // If we have frames pending from previous decode
        if let Some(frame) = self.pending_frames.pop() {
            self.current_pts += 1;
            return self.convert_frame_to_image(frame);
        }

        // Read packets and decode frames
        while let Some((stream, packet)) = self.ictx.packets().next() {
            if stream.index() == self.stream_index {
                self.decoder.send_packet(&packet)?;
                let mut frame = ffmpeg::util::frame::Video::empty();
                while let Ok(()) = self.decoder.receive_frame(&mut frame) {
                    self.current_pts += 1;
                    return self.convert_frame_to_image(frame);
                }
            }
        }

        // No more packets; flush the decoder
        self.decoder.send_eof()?;
        let mut frame = ffmpeg::util::frame::Video::empty();
        while let Ok(()) = self.decoder.receive_frame(&mut frame) {
            self.current_pts += 1;
            return self.convert_frame_to_image(frame);
        }

        Ok(None)
    }

    fn convert_frame_to_image(&mut self, frame: frame::Video) -> Result<Option<RgbaImage>> {
        let mut rgb_frame = frame::Video::empty();
        self.scaler.run(&frame, &mut rgb_frame)?;

        let data = rgb_frame.data(0);
        let width = rgb_frame.width() as u32;
        let height = rgb_frame.height() as u32;
        let stride = rgb_frame.stride(0);

        // Create RgbaImage
        let mut image = RgbaImage::new(width, height);
        let buf = image.as_mut();

        // Copy data from ffmpeg frame to image buffer
        for y in 0..height as usize {
            let src_start = y * stride as usize;
            let src_end = src_start + (width as usize * 4); // 4 bytes per pixel (RGBA)
            let src_row = &data[src_start..src_end];

            let dst_start = y * (width as usize * 4);
            let dst_end = dst_start + (width as usize * 4);

            buf[dst_start..dst_end].copy_from_slice(src_row);
        }

        Ok(Some(image))
    }
}

pub fn load_media(input_dirs: &[String]) -> Result<Vec<Media>> {
    let mut media = Vec::new();

    // Initialize ffmpeg
    ffmpeg::init().unwrap();

    for input_dir in input_dirs {
        for entry in fs::read_dir(input_dir)? {
            let entry = entry?;
            let path = entry.path();
            if path.is_file() {
                let extension = path.extension().and_then(|e| e.to_str()).unwrap_or("").to_lowercase();

                match extension.as_str() {
                    "jpg" | "jpeg" | "png" | "bmp" | "tiff" | "jfif" => {
                        // Load static image
                        if let Ok(img) = image::open(&path) {
                            media.push(Media::Static(img.to_rgba8()));
                        }
                    },
                    "gif" => {
                        if let Ok(file) = File::open(&path) {
                            if let Ok(decoder) = image::codecs::gif::GifDecoder::new(file) {
                                let frames_iter = decoder.into_frames();
                                let mut frames = Vec::new();

                                for frame_result in frames_iter {
                                    if let Ok(frame) = frame_result {
                                        frames.push(frame.into_buffer());
                                    } else {
                                        break;
                                    }
                                }
                    
                                let frame_count = frames.len();
                    
                                media.push(Media::Animated {
                                    frames: Rc::new(frames),
                                    frame_count,
                                });
                            }
                        }
                    },
                    "webp" => {
                        // Try to load as animated WebP
                        let mut file = File::open(&path)?;
                        let mut file_data = Vec::new();
                        file.read_to_end(&mut file_data)?;

                        if let Ok(decoder) = webp_animation::Decoder::new(&file_data) {
                            let mut frames = Vec::new();
                            let mut timestamps = Vec::new();

                            for frame in decoder.into_iter() {
                                let (width, height) = frame.dimensions();

                                // Convert raw RGBA data to RgbaImage
                                let img = RgbaImage::from_raw(width, height, frame.data().to_vec()).unwrap();
                                frames.push(img);
                                timestamps.push(frame.timestamp());
                            }

                            if !frames.is_empty() {
                                let frame_count = frames.len();

                                media.push(Media::Animated {
                                    frames: Rc::new(frames),
                                    frame_count,
                                });
                                continue;
                            }
                        }

                        // If not animated, try to load as static WebP
                        if let Ok(img) = image::open(&path) {
                            media.push(Media::Static(img.to_rgba8()));
                        } else {
                            println!("Failed to load WebP image: {:?}", path);
                        }
                    },
                    "mp4" | "mov" | "avi" | "mkv" | "webm" => {
                        // Load video files
                        let path_clone = path.clone();
                        if let Ok(metadata) = get_video_metadata(&path) {
                            media.push(Media::Video {
                                path: path_clone,
                                total_duration_ms: metadata.duration_ms,
                            });
                        }
                    },
                    _ => {
                        println!("Unsupported file type: {:?}", path.extension());
                    }
                }
            }
        }
    }

    if media.is_empty() {
        Err(anyhow::anyhow!("No media files found in the specified directory."))
    } else {
        Ok(media)
    }
}

fn get_video_metadata(path: &PathBuf) -> Result<VideoMetadata> {
    let ictx = ffmpeg::format::input(&path)?;
    let input: ffmpeg_next::Stream<'_> = ictx
        .streams()
        .best(ffmpeg::media::Type::Video)
        .ok_or_else(|| ffmpeg::Error::StreamNotFound)?;
    let codec_params = input.parameters();
    if codec_params.medium() != ffmpeg::media::Type::Video {
        return Err(Error::msg("Not a video"))
    }
    let time_base = input.time_base();
    let duration = input.duration();
    let duration_ms = ((duration as f64 * time_base.numerator() as f64 / time_base.denominator() as f64) * 1000.0) as u64;

    Ok(VideoMetadata {
        duration_ms,
    })
}

pub fn get_video_frame_at_timestamp(mp4_video_path: &PathBuf, timestamp_secs: u64) -> Result<RgbaImage> {
    // Initialize ffmpeg
    ffmpeg::init()?;

    // Open the input file
    let mut ictx = ffmpeg::format::input(mp4_video_path)?;

    // Find the best video stream
    let input_stream = ictx
        .streams()
        .best(ffmpeg::media::Type::Video)
        .ok_or_else(|| Error::msg("Could not find video stream"))?;

    let video_stream_index = input_stream.index();

    // Get codec parameters and open the decoder
    let codec_params = input_stream.parameters();
    let context_decoder = ffmpeg::codec::context::Context::from_parameters(codec_params)?;
    let mut decoder = context_decoder.decoder().video()?;

    // Get the stream's time base
    let tb = input_stream.time_base();

    // Convert the timestamp from seconds to the stream's time base units
    use ffmpeg_next::util::mathematics::rescale::Rescale;
    use ffmpeg::util::rational::Rational;

    // Check for timestamp overflow when casting to i64
    if timestamp_secs > i64::MAX as u64 {
        return Err(Error::msg("Timestamp is too large"));
    }
    let timestamp_secs_i64 = timestamp_secs as i64;

    let timestamp_in_stream_tb = timestamp_secs_i64.rescale(Rational(1, 1), tb);

    // Use av_seek_frame via FFI to seek to the desired timestamp
    unsafe {
        let ictx_ptr = ictx.as_mut_ptr();

        // Use AVSEEK_FLAG_BACKWARD to seek to the closest keyframe before the desired timestamp
        let ret = ffmpeg::ffi::av_seek_frame(
            ictx_ptr,
            video_stream_index as i32,
            timestamp_in_stream_tb,
            ffmpeg::ffi::AVSEEK_FLAG_BACKWARD,
        );
        if ret < 0 {
            return Err(Error::msg("Error seeking"));
        }
    }

    // Flush the decoder to clear any buffered frames
    decoder.flush();

    // Create a scaling context to convert frames to RGBA
    use ffmpeg::software::scaling::context::Context as ScalingContext;
    use ffmpeg::software::scaling::flag::Flags;

    let decoder_format = get_modern_pixel_format(decoder.format());
    let mut scaler = ScalingContext::get(
        decoder_format,
        decoder.width(),
        decoder.height(),
        ffmpeg::format::Pixel::RGBA,
        decoder.width(),
        decoder.height(),
        Flags::BILINEAR,
    )?;

    // Prepare frames
    let mut frame = ffmpeg::util::frame::Video::empty();
    let mut rgb_frame = ffmpeg::util::frame::Video::empty();

    // Read packets and decode frames
    for (stream, packet) in ictx.packets() {
        if stream.index() == video_stream_index {
            // Send the packet to the decoder
            decoder.send_packet(&packet)?;

            // Receive and process decoded frames
            while decoder.receive_frame(&mut frame).is_ok() {
                // Set color range for deprecated formats
                match decoder.format() {
                    ffmpeg::format::Pixel::YUVJ420P |
                    ffmpeg::format::Pixel::YUVJ422P |
                    ffmpeg::format::Pixel::YUVJ444P |
                    ffmpeg::format::Pixel::YUVJ440P => {
                        unsafe {
                            (*frame.as_mut_ptr()).color_range = ffmpeg::ffi::AVColorRange::AVCOL_RANGE_JPEG;
                        }
                    },
                    _ => {},
                }
                // Check if the frame PTS is greater than or equal to the desired timestamp
                if let Some(frame_pts) = frame.pts() {
                    if frame_pts >= timestamp_in_stream_tb {
                        // Convert the frame to RGBA format
                        scaler.run(&frame, &mut rgb_frame)?;

                        // Extract frame data
                        let data = rgb_frame.data(0);
                        let width = rgb_frame.width() as u32;
                        let height = rgb_frame.height() as u32;
                        let stride = rgb_frame.stride(0);

                        // Create RgbaImage
                        let mut image = RgbaImage::new(width, height);
                        let buf = image.as_mut();

                        // Copy data from ffmpeg frame to image buffer
                        for y in 0..height as usize {
                            let src_start = y * stride as usize;
                            let src_end = src_start + (width as usize * 4); // 4 bytes per pixel (RGBA)
                            let src_row = &data[src_start..src_end];

                            let dst_start = y * (width as usize * 4);
                            let dst_end = dst_start + (width as usize * 4);

                            buf[dst_start..dst_end].copy_from_slice(src_row);
                        }

                        return Ok(image);
                    }
                }
            }
        }
    }

    Err(Error::msg("Frame not found"))
}

fn get_modern_pixel_format(format: ffmpeg::format::Pixel) -> ffmpeg::format::Pixel {
    match format {
        ffmpeg::format::Pixel::YUVJ420P => ffmpeg::format::Pixel::YUV420P,
        ffmpeg::format::Pixel::YUVJ422P => ffmpeg::format::Pixel::YUV422P,
        ffmpeg::format::Pixel::YUVJ444P => ffmpeg::format::Pixel::YUV444P,
        ffmpeg::format::Pixel::YUVJ440P => ffmpeg::format::Pixel::YUV440P,
        _ => format,
    }
}
////// src/scheduler.rs //////

use std::cell::RefCell;
use std::rc::Rc;

use crate::config::CONFIG;
use crate::effects::{self, Effect, EffectFactory};
use crate::media_loader::{Media, VideoClip};
use crate::transitions::{Transition, TransitionEffect};
use crate::types::{ImageFrames, ImageState};
use crate::frame_renderer;
use image::RgbaImage;
use rand::prelude::*;
use rand::seq::SliceRandom;

pub struct ImageScheduler {
    primary_images: Vec<Rc<RefCell<ImageState>>>,
    overlay_images: Vec<Rc<RefCell<ImageState>>>,
    global_effects: Vec<Rc<RefCell<ImageState>>>,
    transitions: Vec<Rc<RefCell<Transition>>>,
}

impl ImageScheduler {
    pub fn new(
        images: &[Media],
        effect_factory: &mut effects::EffectFactory,
    ) -> Self {
        let mut primary_images = Vec::new();
        let mut overlay_images = Vec::new();
        let mut global_effects = Vec::new();

        // Schedule primary images
        Self::schedule_images(
            images,
            effect_factory,
            CONFIG.total_frames() / 5,
            Self::generate_weighted_duration,
            (0.5, 1.5),
            |ef| ef.generate_random_effects(),
            &mut primary_images,
        );

        // Generate a schedule for overlay images
        let overlay_schedule = Self::generate_overlay_schedule(CONFIG.total_frames());

        // Schedule overlay images
        Self::schedule_overlay_images(
            images,
            effect_factory,
            overlay_schedule.clone(),
            &mut overlay_images,
        );

        // Schedule larger, slower overlay images
        Self::schedule_images(
            images,
            effect_factory,
            CONFIG.total_frames() / 100,
            |max| {
                let min_duration = CONFIG.framerate;
                let max_duration = (CONFIG.framerate * 4).min(max as u32);
                if max_duration >= min_duration {
                    rand::thread_rng().gen_range(min_duration..=max_duration) as usize
                } else if max_duration > 0 {
                    rand::thread_rng().gen_range(1..=max_duration) as usize
                } else {
                    0
                }
            },
            (0.5, 1.5),
            |ef| {
                let mut effects = vec![Effect::LowOpacityFlash {
                    flash_duration: rand::thread_rng().gen_range(1..=3),
                    flash_interval: rand::thread_rng().gen_range(30..=100),
                }];
                effects.append(&mut ef.generate_random_effects());
                effects
            },
            &mut overlay_images,
        );

        // Schedule global effects
        Self::schedule_global_effects(effect_factory, &mut global_effects);

        let mut scheduler = Self {
            primary_images: primary_images.into_iter().map(RefCell::new).map(Rc::new).collect(),
            overlay_images: overlay_images.into_iter().map(RefCell::new).map(Rc::new).collect(),
            transitions: Vec::new(),
            global_effects: global_effects.into_iter().map(RefCell::new).map(Rc::new).collect(),
        };

        // Schedule transitions between images
        scheduler.schedule_transitions();

        scheduler
    }

    /// Passthrough to frame_renderer::render_frame
    pub fn render_frame(&self, frame_number: usize, effect_factory: &EffectFactory, previous_frames: &[RgbaImage]) -> RgbaImage
    {
        frame_renderer::render_frame(
            frame_number,
            self.get_active_primary_images(frame_number),
            self.get_active_overlay_images(frame_number),
            self.get_active_global_effects(frame_number),
            &effect_factory,
            self.get_active_transitions(frame_number),
            &previous_frames,
        )
    }

    fn schedule_images(
        images: &[Media],
        effect_factory: &mut effects::EffectFactory,
        num_images: usize,
        duration_generator: impl Fn(usize) -> usize,
        scale_range: (f32, f32),
        effect_generator: impl Fn(&mut effects::EffectFactory) -> Vec<Effect>,
        scheduled_images: &mut Vec<ImageState>,
    ) {
        for _ in 0..num_images {
            // Randomly select an image
            let media = images.choose(&mut rand::thread_rng()).unwrap();

            // Random start frame
            let start_frame = rand::thread_rng().gen_range(0..CONFIG.total_frames()) as usize;
            // Maximum duration in frames
            let max_possible_duration = CONFIG.total_frames() - start_frame;
            let duration = duration_generator(max_possible_duration);
            if duration == 0 {
                continue;
            }

            // Create image state
            if let Some(image_state) = Self::create_image_state(
                media,
                effect_factory,
                start_frame,
                duration,
                scale_range,
                &effect_generator,
            ) {
                scheduled_images.push(image_state);
            }
        }
    }

    fn schedule_overlay_images(
        images: &[Media],
        effect_factory: &mut effects::EffectFactory,
        overlay_schedule: Vec<usize>,
        overlay_images: &mut Vec<ImageState>,
    ) {
        for &frame_number in &overlay_schedule {
            // Randomly select an image
            let media = images.choose(&mut rand::thread_rng()).unwrap();

            // Short durations for overlay images
            let max_possible_duration = CONFIG.total_frames() - frame_number;
            let max_duration = 10.min(max_possible_duration);
            let duration = Self::generate_short_duration(max_duration);
            if duration == 0 {
                continue;
            }

            // Create image state
            if let Some(image_state) = Self::create_image_state(
                media,
                effect_factory,
                frame_number,
                duration,
                (0.05, 0.2),
                &|ef| ef.generate_overlay_effects(),
            ) {
                overlay_images.push(image_state);
            }
        }
    }

    fn schedule_global_effects(
        effect_factory: &mut effects::EffectFactory,
        global_effects: &mut Vec<ImageState>,
    ) {
        for _ in 0..(CONFIG.total_frames() / 50) {
            // Random start frame
            let start_frame = rand::thread_rng().gen_range(0..CONFIG.total_frames());

            // Maximum duration in frames
            let max_duration = ((CONFIG.framerate as f32 * 2.5)
                .min(CONFIG.total_frames() as f32 - start_frame as f32)) as usize;
            let duration = Self::generate_weighted_duration(max_duration);
            if duration == 0 {
                continue;
            }
            let end_frame = start_frame + duration;

            // Generate global effects
            let effects = effect_factory.generate_random_global_effects();

            // Create a dummy ImageState for global effects
            let image_state = ImageState {
                frames: ImageFrames::Static(image::RgbaImage::new(CONFIG.width as u32, CONFIG.height as u32)),
                position: (0, 0),
                scale: 1.0,
                start_frame,
                end_frame,
                effects,
            };

            global_effects.push(image_state);
        }
    }

    fn create_image_state(
        media: &Media,
        effect_factory: &mut effects::EffectFactory,
        start_frame: usize,
        duration: usize,
        scale_range: (f32, f32),
        effect_generator: &impl Fn(&mut effects::EffectFactory) -> Vec<Effect>,
    ) -> Option<ImageState> {
        // Process media into frames and durations
        let (frames, any_frame, _) =
            Self::process_media_to_frames(media, duration)?;

        // Calculate scale
        let scale = Self::calculate_scale(&any_frame, scale_range);

        // Calculate position
        let position = Self::calculate_position(&any_frame);

        // Generate effects
        let effects = effect_generator(effect_factory);

        // Create image state
        Some(ImageState {
            frames,
            position,
            scale,
            start_frame,
            end_frame: start_frame + duration,
            effects,
        })
    }

    fn process_media_to_frames(
        media: &Media,
        duration: usize,
    ) -> Option<(ImageFrames, RgbaImage, Option<u64>)> {
        let mut video_start_time = None;
        let frames: ImageFrames = match media {
            Media::Static(img) => {
                ImageFrames::Static(img.clone())
            },
            Media::Animated { frames, frame_count } => {
                ImageFrames::Animated {
                    frames: frames.clone(),
                    frame_count: *frame_count,
                }
            },
            Media::Video { path, total_duration_ms } => {
                let max_start_time_ms = *total_duration_ms as i64
                    - (duration as i64 * 1000 / CONFIG.framerate as i64);
                let video_start_time_ms = if max_start_time_ms > 0 {
                    rand::thread_rng().gen_range(0..=max_start_time_ms as u64)
                } else {
                    0
                };
                video_start_time = Some(video_start_time_ms);
                ImageFrames::Video {
                    clip: Rc::new(RefCell::new(VideoClip::open(&path).ok()?)),
                }
            }
        };

        let any_frame = frames.get_first_frame().unwrap_or_else(|| {
            // In case of error, use a placeholder image
            RgbaImage::new(1, 1)
        });

        Some((frames, any_frame, video_start_time))
    }

    fn calculate_scale(
        any_frame: &RgbaImage,
        scale_range: (f32, f32),
    ) -> f32 {
        // You can customize this function further if needed
        let max_scale_x = CONFIG.width as f32 / any_frame.width() as f32;
        let max_scale_y = CONFIG.height as f32 / any_frame.height() as f32;
        let max_scale = max_scale_x.min(max_scale_y).min(scale_range.1); // Allow up to 1.5x scaling
        let min_scale = scale_range.0.min(max_scale);
        if max_scale > min_scale {
            rand::thread_rng().gen_range(min_scale..=max_scale)
        } else {
            min_scale
        }
    }
    

    fn calculate_position(
        any_frame: &RgbaImage,
    ) -> (i64, i64) {
        let max_x = CONFIG.width.saturating_sub(any_frame.width() as u32) as i64;
        let max_y = CONFIG.height.saturating_sub(any_frame.height() as u32) as i64;
        let x = if max_x > 0 {
            rand::thread_rng().gen_range(0..=max_x)
        } else {
            0
        };
        let y = if max_y > 0 {
            rand::thread_rng().gen_range(0..=max_y)
        } else {
            0
        };
        (x, y)
    }

    // New function to generate overlay image schedule
    fn generate_overlay_schedule(total_frames: usize) -> Vec<usize> {
        let mut overlay_schedule = Vec::new();
        let mut frame_number = 0;
    
        while frame_number < total_frames {
            // Randomly decide whether to schedule an overlay image
            if rand::thread_rng().gen_bool(0.5) { // 50% chance to schedule an image
                overlay_schedule.push(frame_number);
            }

            // Calculate the maximum possible gap without exceeding total_frames
            let max_gap = (total_frames - frame_number).min(30);
            if max_gap == 0 {
                break; // No more frames left
            }
    
            // Random gap before next potential overlay image
            let gap = rand::thread_rng().gen_range(1..=max_gap); // Adjust range as needed
            frame_number += gap;
        }
    
        overlay_schedule
    }

    // Helper function to generate short durations for overlay images
    fn generate_short_duration(max_duration: usize) -> usize {
        if max_duration == 0 {
            1 // Default to a duration of 1 frame if max_duration is zero
        } else {
            let duration = rand::thread_rng().gen_range(1..=5).min(max_duration);
            duration
        }
    }

    pub fn get_active_primary_images(&self, frame_number: usize) -> impl Iterator<Item = Rc<RefCell<ImageState>>> + '_ {
        self.primary_images
            .iter()
            .filter(move |state| (**state).borrow().start_frame <= frame_number && (**state).borrow().end_frame > frame_number)
            .map(|rc| rc.clone())
    }

    pub fn get_active_overlay_images(&self, frame_number: usize) -> impl Iterator<Item = Rc<RefCell<ImageState>>> + '_ {
        self.overlay_images
            .iter()
            .filter(move |state| (**state).borrow().start_frame <= frame_number && (**state).borrow().end_frame > frame_number)
            .cloned()
    }

    pub fn get_active_global_effects(&self, frame_number: usize) -> impl Iterator<Item = Rc<RefCell<ImageState>>> + '_ {
        self.global_effects
            .iter()
            .filter(move |state| state.borrow().start_frame <= frame_number && state.borrow().end_frame > frame_number)
            .cloned()
    }

    pub fn get_active_transitions(&self, frame_number: usize) -> impl Iterator<Item = Rc<RefCell<Transition>>> + '_ {
        self.transitions
            .iter()
            .filter(move |transition| transition.borrow().start_frame <= frame_number && transition.borrow().end_frame > frame_number)
            .cloned()
    }

    fn generate_weighted_duration(max_duration: usize) -> usize {
        let mut duration_options = Vec::new();

        // Always include duration of 1 frame if max_duration >= 1
        if max_duration >= 1 {
            duration_options.push((1..=1, 70)); // 70% chance of duration being 1 frame
        }

        // Include duration of 2 frames if max_duration >= 2
        if max_duration >= 2 {
            duration_options.push((2..=2, 15)); // 15% chance of duration being 2 frames
        }

        // Include durations between 3 and up to max_duration (min of 5)
        if max_duration >= 3 {
            let upper = max_duration.min(5);
            if upper >= 3 {
                duration_options.push((3..=upper, 10)); // 10% chance
            }
        }

        // Include longer durations if max_duration >= 6
        if max_duration >= 6 {
            duration_options.push((6..=max_duration, 5)); // 5% chance
        }

        // Recalculate total weight
        let total_weight: u32 = duration_options.iter().map(|(_, weight)| *weight).sum();

        // If no duration options are available (should not happen), default to 1 frame
        if total_weight == 0 {
            return 1;
        }
        
        // Generate a random number within the total weight
        let mut choice = rand::thread_rng().gen_range(0..total_weight);
        
        // Select a duration based on weighted probabilities
        for (duration_range, weight) in duration_options {
            if choice < weight {
                return rand::thread_rng().gen_range(duration_range);
            } else {
                choice -= weight;
            }
        }
        
        // Fallback in case of error
        1
    }

    // fn generate_weighted_duration_continuous(rng: &mut ThreadRng, max_duration: usize) -> usize {
    //     // Use exponential distribution to favor shorter durations
    //     let lambda = 0.1; // Adjust lambda to control decay rate (higher lambda = more short durations)
    //     let duration = (-rng.gen::<f64>().ln() / lambda).ceil() as usize;
        
    //     // Ensure duration is at least 1 and does not exceed max_duration
    //     duration.clamp(1, max_duration)
    // }

    pub fn schedule_transitions(&mut self) {
        // We need at least two images to create a transition
        if self.primary_images.len() < 2 {
            return;
        }

        // Iterate over pairs of images to create transitions
        for i in 0..(self.primary_images.len() - 1) {
            let (current_state, next_state)= self.primary_images.split_at_mut(i+1);
            let current_state = &mut current_state[current_state.len() - 1];
            let next_state = &mut next_state[0];

            // Decide whether to create a transition between these images
            if rand::thread_rng().gen_bool(0.3) { // 30% chance to create a transition
                // Determine the duration of the transition (e.g., between 1 and 3 seconds)
                let min_transition_duration = CONFIG.framerate; // 1 second
                let max_transition_duration = CONFIG.framerate * 3; // 3 seconds
                let transition_duration = rand::thread_rng().gen_range(min_transition_duration..=max_transition_duration);

                // Adjust start and end frames to accommodate the transition
                let transition_midpoint = ((**current_state).borrow().end_frame + (**next_state).borrow().start_frame) / 2;
                let transition_start_frame = transition_midpoint.saturating_sub(transition_duration as usize / 2);
                let transition_end_frame = transition_midpoint.saturating_add(transition_duration as usize / 2);

                // Ensure the frames are within bounds
                let transition_start_frame = transition_start_frame.max((**current_state).borrow().start_frame);
                let transition_end_frame = transition_end_frame.min((**current_state).borrow().end_frame);

                // Adjust current and next image durations if necessary
                if (**current_state).borrow().end_frame < transition_end_frame {
                    (**current_state).borrow_mut().end_frame = transition_end_frame;
                }
                if (**next_state).borrow().start_frame > transition_start_frame {
                    (**next_state).borrow_mut().start_frame = transition_start_frame;
                }

                // Resize images to frame dimensions
                // let frame_width = CONFIG.width;
                // let frame_height = CONFIG.height;

                // Resize current image
                // let from_image_resized: Vec<_> = current_state.frames.iter().map(|img| {
                //     image::imageops::resize(
                //         img,
                //         frame_width as u32,
                //         frame_height as u32,
                //         image::imageops::FilterType::Lanczos3,
                //     )
                // }).collect();

                // // Resize next image
                // let to_image_resized: Vec<_> = next_state.frames.iter().map(|img| {
                //     image::imageops::resize(
                //         img,
                //         frame_width as u32,
                //         frame_height as u32,
                //         image::imageops::FilterType::Lanczos3,
                //     )
                // }).collect();

                // Create the transition
                let duration = transition_end_frame.saturating_sub(transition_start_frame);
                if duration != 0 {
                    // TODO: These could possibly be done without cloning
                    self.transitions.push(Rc::new(RefCell::new(Transition {
                        from_frames: (**current_state).borrow().frames.clone(),//: from_image_resized,
                        to_frames: (**next_state).borrow().frames.clone(),//: to_image_resized,
                        start_frame: transition_start_frame,
                        end_frame: transition_end_frame,
                        duration,
                        effect: TransitionEffect::Reassembly,
                        particles: Vec::new(), // Particles will be initialized during rendering
                        initialized: false,
                    })));
                }
            }
        }
    }
}

////// src/test_effects.rs //////

use clap::Parser;
use config::CONFIG;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use video_encoder::EncoderMessage;
use std::{path::PathBuf, sync::mpsc};
use anyhow::Result;
use std::collections::HashMap;

mod media_loader;
mod effects;
mod frame_renderer;
mod config;
mod scheduler;
mod types;
mod video_encoder;
mod transitions;

// Import necessary modules
use crate::{
    effects::{Effect, EffectFactory},
    types::{ImageState, ImageFrames},
};
use image::RgbaImage;

fn main() -> Result<()> {
    let args = Args::parse();

    // Initialize progress bars
    let progress_bars = MultiProgress::new();
    let frame_generation_pb = ProgressBar::new(CONFIG.total_frames() as u64);
    let encoder_pb = ProgressBar::new(CONFIG.total_frames() as u64);
    frame_generation_pb.set_style(
        ProgressStyle::default_bar()
            .template("{spinner:.blue} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} frames ({eta} remaining)")?
            .progress_chars("$>-")
    );
    encoder_pb.set_style(
        ProgressStyle::default_bar()
            .template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.green/green}] {pos}/{len} frames ({eta} remaining)")?
            .progress_chars("$>-")
    );
    progress_bars.add(frame_generation_pb.clone());
    progress_bars.add(encoder_pb.clone());

    // Load the image
    let img = image::open(&args.input)?.to_rgba8();

    // Calculate total frames
    let total_frames = CONFIG.duration_s * CONFIG.framerate;

    // Parse effects
    let effects = parse_effects(&args.effect)?;

    // Prepare ImageState
    let image_state = ImageState {
        frames: ImageFrames::Static(img.clone()),
        position: (0, 0),
        scale: 1.0,
        start_frame: 0,
        end_frame: total_frames as usize,
        effects,
    };

    // Initialize effect factory
    let effect_factory = EffectFactory::new();

    // Initialize video encoder
    let (encoder_tx, encoder_rx) = mpsc::channel();
    let encoder_thread = std::thread::spawn(move || {
        let mut encoder = video_encoder::VideoEncoder::new(
            &CONFIG.output_file, 
            CONFIG.width, 
            CONFIG.height, 
            CONFIG.framerate, 
            CONFIG.bit_rate
        ).unwrap();
        loop
        {
            match encoder_rx.recv()
            {
                Ok(EncoderMessage::Frame(frame)) => 
                {
                    encoder.encode_frame(&frame).unwrap();
                    encoder_pb.inc(1);
                }
                Ok(EncoderMessage::Complete) => 
                {
                    encoder.finish().unwrap();
                    encoder_pb.finish_with_message(format!("Video has been successfully encoded to {}", CONFIG.output_file));
                    break;
                }
                Err(err) => panic!("{err:?}"),
            }
        }
    });

    // Frame generation loop
    let mut previous_frames: Vec<RgbaImage> = Vec::new();

    for frame_number in 0..total_frames {

        // Apply effects
        let processed_frame = effect_factory.apply_effects(
            &img,
            frame_number as usize,
            &image_state,
            &previous_frames,
        );

        // Add to previous frames
        previous_frames.push(processed_frame.clone());

        // Encode the frame
        encoder_tx.send(EncoderMessage::Frame(processed_frame)).unwrap();

        // Update progress bar
        frame_generation_pb.inc(1);
    }

    // Finalize encoding
    encoder_tx.send(EncoderMessage::Complete).unwrap();
    encoder_thread.join().unwrap();

    Ok(())
}

#[derive(Parser)]
#[command(name = "test_effects")]
#[command(about = "Test Effects")]
struct Args {
    /// Input image path
    #[arg(short, long, value_name = "IMAGE_PATH")]
    input: PathBuf,

    /// Effects to apply, can be specified multiple times
    /// Example: --effect invert_colors
    /// Example: --effect color_flash:r=255,g=0,b=0,a=128
    #[arg(long)]
    effect: Vec<String>,
}

fn parse_effects(effect_strings: &[String]) -> Result<Vec<Effect>> {
    let mut effects = Vec::new();
    for effect_str in effect_strings {
        let effect = parse_effect(effect_str)?;
        effects.push(effect);
    }
    Ok(effects)
}

fn parse_effect(effect_str: &str) -> Result<Effect> {
    // Split effect_str into effect name and params
    let parts: Vec<&str> = effect_str.splitn(2, ':').collect();
    let effect_name = parts[0];
    let params_str = if parts.len() > 1 { Some(parts[1]) } else { None };

    match effect_name.to_lowercase().as_str() {
        "invert_colors" => Ok(Effect::InvertColors),
        "color_flash" => {
            let params = parse_params(params_str.unwrap_or(""))?;
            let r = parse_u8_param(&params, "r")?;
            let g = parse_u8_param(&params, "g")?;
            let b = parse_u8_param(&params, "b")?;
            let a = parse_u8_param(&params, "a")?;
            Ok(Effect::ColorFlash(image::Rgba([r, g, b, a])))
        },
        "static" => Ok(Effect::Static),
        "spiral" => {
            let params = parse_params(params_str.unwrap_or(""))?;
            let density = parse_f32_param(&params, "density")?;
            Ok(Effect::Spiral(density))
        },
        "transparency_strobe" => {
            let params = parse_params(params_str.unwrap_or(""))?;
            let speed = parse_f32_param(&params, "speed")?;
            Ok(Effect::TransparencyStrobe(speed))
        },
        "overlay_text" => {
            let params = parse_params(params_str.unwrap_or(""))?;
            let text = params.get("text").cloned().unwrap_or_default();
            Ok(Effect::OverlayText(text))
        },
        "zoom_in" => {
            let params = parse_params(params_str.unwrap_or(""))?;
            let zoom_speed = parse_f32_param(&params, "speed")?;
            Ok(Effect::ZoomIn(zoom_speed))
        },
        "kaleidoscope" => Ok(Effect::Kaleidoscope),
        "bit_focus" => Ok(Effect::BitFocus),
        "glow" => {
            let params = parse_params(params_str.unwrap_or(""))?;
            let max_intensity = parse_f32_param(&params, "max_intensity")?;
            Ok(Effect::Glow { max_intensity })
        },
        "grow_shrink" => {
            let params = parse_params(params_str.unwrap_or(""))?;
            let speed = parse_f32_param(&params, "speed")?;
            Ok(Effect::GrowShrink { speed })
        },
        "fade_out" => Ok(Effect::FadeOut),
        "split_apart" => Ok(Effect::SplitApart),
        "low_opacity_flash" => {
            let params = parse_params(params_str.unwrap_or(""))?;
            let flash_duration = parse_usize_param(&params, "flash_duration")?;
            let flash_interval = parse_usize_param(&params, "flash_interval")?;
            Ok(Effect::LowOpacityFlash {
                flash_duration,
                flash_interval,
            })
        },
        "randomized_oval_fade" => {
            let params = parse_params(params_str.unwrap_or(""))?;
            let randomness = parse_f32_param(&params, "randomness")?;
            Ok(Effect::RandomizedOvalFade { randomness })
        },
        "glitch" => Ok(Effect::Glitch),
        "feedback_loop" => {
            let params = parse_params(params_str.unwrap_or(""))?;
            let blend_factor = parse_f32_param(&params, "blend_factor")?;
            let delay_frames = parse_usize_param(&params, "delay_frames")?;
            Ok(Effect::FeedbackLoop {
                blend_factor,
                delay_frames,
            })
        },
        _ => Err(anyhow::anyhow!("Unknown effect: {}", effect_name)),
    }
}

fn parse_params(param_str: &str) -> Result<HashMap<String, String>> {
    let mut params = HashMap::new();
    if param_str.is_empty() {
        return Ok(params);
    }

    for param in param_str.split(',') {
        let kv: Vec<&str> = param.splitn(2, '=').collect();
        if kv.len() != 2 {
            return Err(anyhow::anyhow!("Invalid parameter: {}", param));
        }
        params.insert(kv[0].to_string(), kv[1].to_string());
    }

    Ok(params)
}

fn parse_u8_param(params: &HashMap<String, String>, key: &str) -> Result<u8> {
    if let Some(value) = params.get(key) {
        Ok(value.parse::<u8>()?)
    } else {
        Err(anyhow::anyhow!("Missing parameter: {}", key))
    }
}

fn parse_f32_param(params: &HashMap<String, String>, key: &str) -> Result<f32> {
    if let Some(value) = params.get(key) {
        Ok(value.parse::<f32>()?)
    } else {
        Err(anyhow::anyhow!("Missing parameter: {}", key))
    }
}

fn parse_usize_param(params: &HashMap<String, String>, key: &str) -> Result<usize> {
    if let Some(value) = params.get(key) {
        Ok(value.parse::<usize>()?)
    } else {
        Err(anyhow::anyhow!("Missing parameter: {}", key))
    }
}
////// src/transitions.rs //////

use image::{imageops, Rgba, RgbaImage};
use imageproc::geometric_transformations::{rotate_about_center, Interpolation};
use rand::prelude::*;
use crate::{config::CONFIG, types::ImageFrames};

#[derive(Clone)]
pub struct Transition {
    pub from_frames: ImageFrames,
    pub to_frames: ImageFrames,
    pub start_frame: usize,
    pub end_frame: usize,
    pub duration: usize,
    pub effect: TransitionEffect,
    pub particles: Vec<Particle>,
    pub initialized: bool, 
}

#[derive(Clone, Copy)]
pub enum TransitionEffect {
    Reassembly,
    // You can add other transition effects in the future
}

// Define the Particle struct
#[derive(Clone)]
pub struct Particle {
    pub src_x: u32,
    pub src_y: u32,
    pub dst_x: u32,
    pub dst_y: u32,
    pub current_x: f32,
    pub current_y: f32,
    pub block_size: u32,
    // Transformations
    pub rotation: f32,
    pub rotation_speed: f32,
    pub scale: f32,
    pub alpha: f32,
}

impl Transition
{
    fn initialize_particles(&mut self, block_size: u32) {
        let first_frame = if let Some(first_frame) = self.from_frames.get_first_frame()
        {
            first_frame
        }
        else
        {
            return
        };
        let width = first_frame.width();
        let height = first_frame.height();

        let mut particles = Vec::new();
        let mut rng = rand::thread_rng();

        for y in (0..height).step_by(block_size as usize) {
            for x in (0..width).step_by(block_size as usize) {
                // Calculate block dimensions, ensuring they are at least 1
                let block_width = block_size.min(width - x).max(1);
                let block_height = block_size.min(height - y).max(1);

                // Skip blocks with zero dimensions
                if block_width == 0 || block_height == 0 {
                    continue;
                }

                // Randomize rotation speed
                let rotation_speed = rng.gen_range(-5.0..5.0);

                // Create the particle
                particles.push(Particle {
                    src_x: x,
                    src_y: y,
                    dst_x: x,
                    dst_y: y,
                    current_x: x as f32,
                    current_y: y as f32,
                    block_size,
                    rotation: 0.0,
                    rotation_speed,
                    scale: 1.0,
                    alpha: 1.0,
                });
            }
        }

        self.particles = particles;
        self.initialized = true;
    }
}

pub fn apply_reassembly_transition(
    frame_number: usize,
    transition: &mut Transition,
) -> RgbaImage {
    let progress = (frame_number - transition.start_frame) as f32 / transition.duration as f32;

    // Get the current frames
    let from_image = transition.from_frames.get_current_frame(frame_number, transition.start_frame, transition.end_frame)
        .unwrap_or_else(|| RgbaImage::new(CONFIG.width, CONFIG.height));
    let to_image = transition.to_frames.get_current_frame(frame_number, transition.start_frame, transition.end_frame)
        .unwrap_or_else(|| RgbaImage::new(CONFIG.width, CONFIG.height));

    let width = CONFIG.width;
    let height = CONFIG.height;

    // Initialize particles
    if !transition.initialized {
        let block_size = 16; // Adjust as needed
        transition.initialize_particles(block_size);
    }

    let mut frame = RgbaImage::new(width as u32, height as u32);

    for particle in &mut transition.particles {
        // Update transformations
        particle.rotation += particle.rotation_speed;
        particle.scale = (1.0 + 0.5 * (1.0 - progress)).max(0.1); // Ensure scale isn't too small

        // Interpolate position
        particle.current_x = particle.src_x as f32 + ((particle.dst_x as f32 - particle.src_x as f32) * progress);
        particle.current_y = particle.src_y as f32 + ((particle.dst_y as f32 - particle.src_y as f32) * progress);

        // Interpolate alpha for blending
        particle.alpha = 1.0 - ((progress * 2.0).min(1.0));

        // Extract blocks from current frames
        let src_block = extract_block(&from_image, particle.src_x, particle.src_y, particle.block_size);
        let dst_block = extract_block(&to_image, particle.dst_x, particle.dst_y, particle.block_size);

        // Blend and transform blocks
        let blended_block = blend_blocks(
            &src_block,
            &dst_block,
            progress,
            particle.rotation,
            particle.scale,
            particle.alpha,
        );

        // Overlay the transformed block onto the frame
        imageops::overlay(
            &mut frame,
            &blended_block,
            particle.current_x as i64,
            particle.current_y as i64,
        );
    }

    frame
}

fn extract_block(image: &RgbaImage, x: u32, y: u32, block_size: u32) -> RgbaImage {
    let width = image.width();
    let height = image.height();
    let block_width = block_size.min(width.saturating_sub(x));
    let block_height = block_size.min(height.saturating_sub(y));

    image::imageops::crop_imm(
        image,
        x,
        y,
        block_width,
        block_height,
    ).to_image()
}

fn blend_blocks(
    src_block: &RgbaImage,
    dst_block: &RgbaImage,
    progress: f32,
    rotation: f32,
    scale: f32,
    alpha: f32,
) -> RgbaImage {
    let (width, height) = src_block.dimensions();

    // Blend the pixels
    let mut blended_block = RgbaImage::new(width, height);
    for (x, y, pixel) in blended_block.enumerate_pixels_mut() {
        let src_pixel = get_pixel_safe(src_block, x, y);
        let dst_pixel = get_pixel_safe(dst_block, x, y);

        // Interpolate between src and dst pixels based on progress
        let blended_pixel = Rgba([
            (src_pixel[0] as f32 * (1.0 - progress) + dst_pixel[0] as f32 * progress) as u8,
            (src_pixel[1] as f32 * (1.0 - progress) + dst_pixel[1] as f32 * progress) as u8,
            (src_pixel[2] as f32 * (1.0 - progress) + dst_pixel[2] as f32 * progress) as u8,
            (src_pixel[3] as f32 * (1.0 - progress) + dst_pixel[3] as f32 * progress) as u8,
        ]);

        *pixel = blended_pixel;
    }

    // Apply transformations
    let transformed_block = transform_block(&blended_block, rotation, scale, alpha);

    transformed_block
}

fn transform_block(
    block: &RgbaImage,
    rotation: f32,
    scale: f32,
    alpha: f32,
) -> RgbaImage {
    let (width, height) = block.dimensions();

    // Early exit if width or height is zero
    if width == 0 || height == 0 {
        // Can't rotate or scale zero-dimension image
        return RgbaImage::new(1, 1); // Return a minimal image
    }

    // Apply rotation
    let rotated = rotate_about_center(
        block,
        rotation.to_radians(),
        Interpolation::Nearest,
        Rgba([0, 0, 0, 0]),
    );

    // Apply scaling
    let new_width = (width as f32 * scale).max(1.0) as u32;
    let new_height = (height as f32 * scale).max(1.0) as u32;

    // Skip if new dimensions are zero
    if new_width == 0 || new_height == 0 {
        return RgbaImage::new(1, 1); // Return a minimal image
    }

    let scaled = imageops::resize(
        &rotated,
        new_width,
        new_height,
        imageops::FilterType::Nearest, // TODO: Try nearest too
    );

    // Apply alpha
    let mut final_block = scaled.clone();
    for pixel in final_block.pixels_mut() {
        let new_alpha = (pixel[3] as f32 * alpha).clamp(0.0, 255.0) as u8;
        pixel[3] = new_alpha;
    }

    final_block
}

// fn blend_pixels(
//     base: &image::Rgba<u8>,
//     overlay: &image::Rgba<u8>,
//     alpha: f32,
// ) -> image::Rgba<u8> {
//     let inv_alpha = 1.0 - alpha;
//     image::Rgba([
//         (base[0] as f32 * inv_alpha + overlay[0] as f32 * alpha) as u8,
//         (base[1] as f32 * inv_alpha + overlay[1] as f32 * alpha) as u8,
//         (base[2] as f32 * inv_alpha + overlay[2] as f32 * alpha) as u8,
//         255,
//     ])
// }

// Helper function to get pixel safely, returns black if out of bounds
pub fn get_pixel_safe(image: &RgbaImage, x: u32, y: u32) -> image::Rgba<u8> {
    if x < image.width() && y < image.height() {
        *image.get_pixel(x, y)
    } else {
        image::Rgba([0, 0, 0, 255])
    }
}

////// src/types.rs //////

use image::RgbaImage;
use std::{cell::RefCell, rc::Rc};

use crate::{config::CONFIG, media_loader::VideoClip};

#[derive(Clone)]
pub struct ImageState {
    pub frames: ImageFrames,
    pub position: (i64, i64),
    pub scale: f32,
    pub start_frame: usize,
    pub end_frame: usize,
    pub effects: Vec<crate::effects::Effect>,
}

#[derive(Clone)]
pub enum ImageFrames {
    Static(RgbaImage),
    Animated {
        frames: Rc<Vec<RgbaImage>>, // Store all frames
        frame_count: usize,
    },
    Video {
        clip: Rc<RefCell<VideoClip>>,
    },
}

impl ImageFrames
{
    pub fn get_first_frame(&self) -> Option<RgbaImage> {
        match self
        {
            ImageFrames::Static(img) => Some(img.clone()),
            ImageFrames::Animated { frames, frame_count:_ } => 
            {
                frames.get(0).cloned()
            },
            ImageFrames::Video { clip } => {
                if let Ok(frame) = crate::media_loader::get_video_frame_at_timestamp(&clip.borrow().path, 0 as u64) {
                    return Some(frame);
                }
                None
            },
        }
    }

    pub fn get_current_frame(&self, frame_number: usize, start_frame: usize, end_frame: usize) -> Option<RgbaImage> 
    {
        match self {
            ImageFrames::Static(img) => Some(img.clone()),
            ImageFrames::Animated { frames, frame_count, .. } => {
                let relative_frame = frame_number - start_frame;
                let total_duration_frames = end_frame - start_frame;
                let progress = relative_frame as f64 / total_duration_frames as f64;
                let frame_index = (progress * *frame_count as f64).floor() as usize % *frame_count;
                frames.get(frame_index).cloned()
            },
            ImageFrames::Video { clip } => {
                let mut clip = clip.borrow_mut();
                // Calculate the desired PTS based on the frame number
                let relative_frame = frame_number - start_frame;
                let desired_pts = (relative_frame as f64 / CONFIG.framerate as f64) * clip.frame_rate;

                // Seek efficiently to the desired PTS
                if let Err(e) = clip.seek_to_pts(desired_pts as i64) {
                    eprintln!("Error seeking video: {:?}", e);
                    return None;
                }

                clip.get_next_frame().ok().flatten()
            },
        }
    }
}

////// src/video_encoder.rs //////

use std::path::Path;
use std::error::Error;
use ffmpeg_next::codec::Context;
use ffmpeg_next::encoder::Video;
use ffmpeg_next as ffmpeg;
use image::RgbaImage;

pub struct VideoEncoder {
    oc: ffmpeg::format::context::Output,
    stream_index: usize,
    encoder: Video,
    scaler: ffmpeg::software::scaling::Context,
    width: u32,
    height: u32,
    frame_index: usize,
}

impl VideoEncoder {
    pub fn new(
        output_file: impl AsRef<Path>,
        width: u32,
        height: u32,
        framerate: u32,
        bitrate: usize
    ) -> Result<Self, Box<dyn Error>> {
        // Initialize FFmpeg
        ffmpeg::init()?;

        // Create output format context
        let output_file = output_file.as_ref().to_str().unwrap();
        let mut oc = ffmpeg::format::output(&output_file)?;

        // Get the video codec (H.264)
        let codec = ffmpeg::encoder::find(ffmpeg::codec::Id::H264)
            .ok_or(ffmpeg_next::Error::InvalidData).unwrap();

        // Create a new stream
        let mut stream = oc.add_stream(codec)?;

        // Set up the encoder context
        let mut encoder_options = ffmpeg::Dictionary::new();
        encoder_options.set("crf", "1");
        encoder_options.set("preset", "1");
        encoder_options.set("threads", "0");
        let mut context_encoder =
            Context::new_with_codec(codec)
                .encoder()
                .video()?;
        context_encoder.set_width(width);
        context_encoder.set_height(height);
        context_encoder.set_format(ffmpeg::format::Pixel::YUV420P);
        context_encoder.set_time_base(ffmpeg::Rational(1, framerate as i32));
        context_encoder.set_frame_rate(Some(ffmpeg::Rational(framerate as i32, 1)));
        context_encoder.set_bit_rate(bitrate);

        // Set stream time base
        stream.set_time_base(ffmpeg::Rational(1, framerate as i32));
        stream.set_parameters(&context_encoder);

        // Open the encoder
        let encoder = context_encoder.open_with(encoder_options)?;
        stream.set_parameters(&encoder);

        // Create a scaling context
        let scaler = ffmpeg::software::scaling::Context::get(
            ffmpeg::format::Pixel::RGBA,
            width,
            height,
            ffmpeg::format::Pixel::YUV420P,
            width,
            height,
            ffmpeg::software::scaling::flag::Flags::BITEXACT,
        )?;

        let stream_index = stream.index();

        // Write header
        oc.write_header().unwrap();

        Ok(Self {
            oc,
            stream_index,
            encoder,
            scaler,
            width,
            height,
            frame_index: 0,
        })
    }

    pub fn encode_frame(&mut self, img: &RgbaImage) -> Result<(), Box<dyn Error>> {
        use std::ptr;

        // Create input frame (RGBA)
        let mut input_frame = ffmpeg::frame::Video::new(
            ffmpeg::format::Pixel::RGBA,
            self.width,
            self.height,
        );

        // Copy image data into input_frame
        let image_data = img.as_raw();

        let input_data_ptr = input_frame.data_mut(0).as_mut_ptr();
        let input_linesize = input_frame.stride(0) as usize;

        for y in 0..(self.height as usize) {
            let src_offset = y * self.width as usize * 4; // RGBA has 4 bytes per pixel
            let dst_offset = y * input_linesize;
            unsafe {
                ptr::copy_nonoverlapping(
                    image_data.as_ptr().add(src_offset),
                    input_data_ptr.add(dst_offset),
                    self.width as usize * 4,
                );
            }
        }

        // Create output frame (YUV420P)
        let mut output_frame = ffmpeg::frame::Video::new(
            ffmpeg::format::Pixel::YUV420P,
            self.width,
            self.height,
        );

        // Convert input_frame to output_frame using scaler
        self.scaler.run(&input_frame, &mut output_frame)?;

        // Set the presentation timestamp (PTS)
        output_frame.set_pts(Some(self.frame_index as i64));

        self.frame_index += 1;

        // Encode the frame
        self.encoder.send_frame(&output_frame)?;

        // Receive and write packets
        let mut packet = ffmpeg::Packet::empty();
        while let Ok(_) = self.encoder.receive_packet(&mut packet) {
            packet.rescale_ts(
                self.encoder.time_base(),
                self.oc.stream(self.stream_index).unwrap().time_base(),
            );
            packet.write(&mut self.oc)?;
        }

        Ok(())
    }

    pub fn finish(&mut self) -> Result<(), Box<dyn Error>> {
        // Flush the encoder
        self.encoder.send_eof()?;
        let mut packet = ffmpeg::Packet::empty();
        while let Ok(_) = self.encoder.receive_packet(&mut packet) {
            packet.rescale_ts(
                self.encoder.time_base(),
                self.oc.stream(self.stream_index).unwrap().time_base(),
            );
            packet.write(&mut self.oc)?;
        }

        // Write the trailer and close the file
        self.oc.write_trailer()?;

        Ok(())
    }
}

#[derive(Debug)]
pub enum EncoderMessage
{
    Frame(RgbaImage),
    Complete
}
////// end of source code //////

When running test_effects, the zoom_in effect does nothing but display a static image. Can you explain why, and provide a solution?