use std::fs::File;

use crate::drawable::Drawable;
use crate::pixel::Pixel;
use crate::pixel_buf::PixelBuf;

/// A precomputed frame: the offset pixels ready to draw and its duration.
struct GifFrame {
    /// Precomputed pixels at the fixed screen position.
    pixels: Vec<Pixel>,
    /// Duration of this frame in seconds.
    duration_secs: f32,
}

/// A fixed-position animated GIF drawable.
///
/// All frames are decoded and their opaque pixels precomputed at load time.
/// During playback the elapsed time selects which frame to display.
pub struct GifImage {
    frames: Vec<GifFrame>,
    /// Total loop duration (sum of all frame durations) in seconds.
    total_duration: f32,
}

impl GifImage {
    /// Loads a GIF file and precomputes the pixel data for every frame.
    ///
    /// `x`, `y` set the fixed screen position of the top-left corner.
    pub fn new(path: &str, x: i32, y: i32) -> Self {
        let file = File::open(path).expect("Failed to open GIF file");
        let mut decoder = gif::DecodeOptions::new();
        decoder.set_color_output(gif::ColorOutput::RGBA);
        let mut reader = decoder.read_info(file).expect("Failed to read GIF info");

        let width = reader.width() as u32;
        let height = reader.height() as u32;

        // Canvas holds the composited RGBA image, built up across frames
        // to handle disposal methods correctly.
        let mut canvas = vec![0u8; (width * height * 4) as usize];
        let mut frames = Vec::new();

        while let Some(frame) = reader.read_next_frame().expect("Failed to decode GIF frame") {
            let fw = frame.width as u32;
            let fh = frame.height as u32;
            let fx = frame.left as u32;
            let fy = frame.top as u32;

            // Composite this frame onto the canvas.
            for row in 0..fh {
                for col in 0..fw {
                    let src_idx = ((row * fw + col) * 4) as usize;
                    let dst_x = fx + col;
                    let dst_y = fy + row;
                    if dst_x >= width || dst_y >= height {
                        continue;
                    }
                    let dst_idx = ((dst_y * width + dst_x) * 4) as usize;
                    let a = frame.buffer[src_idx + 3];
                    if a > 0 {
                        canvas[dst_idx] = frame.buffer[src_idx];
                        canvas[dst_idx + 1] = frame.buffer[src_idx + 1];
                        canvas[dst_idx + 2] = frame.buffer[src_idx + 2];
                        canvas[dst_idx + 3] = a;
                    }
                }
            }

            // Extract opaque pixels from the composited canvas.
            let mut pixels = Vec::new();
            for sy in 0..height {
                for sx in 0..width {
                    let idx = ((sy * width + sx) * 4) as usize;
                    let a = canvas[idx + 3];
                    if a > 0 {
                        pixels.push(Pixel {
                            x: x as u16 + sx as u16,
                            y: y as u16 + sy as u16,
                            r: canvas[idx],
                            g: canvas[idx + 1],
                            b: canvas[idx + 2],
                        });
                    }
                }
            }

            // GIF frame delay is in centiseconds; 0 is conventionally ~100ms.
            let delay_cs = frame.delay;
            let duration_secs = if delay_cs == 0 {
                0.1
            } else {
                delay_cs as f32 / 10.0
            };

            frames.push(GifFrame {
                pixels,
                duration_secs,
            });

            // Handle disposal: if RestoreToBackground, clear the frame region.
            if frame.dispose == gif::DisposalMethod::Background {
                for row in 0..fh {
                    for col in 0..fw {
                        let dst_x = fx + col;
                        let dst_y = fy + row;
                        if dst_x < width && dst_y < height {
                            let idx = ((dst_y * width + dst_x) * 4) as usize;
                            canvas[idx] = 0;
                            canvas[idx + 1] = 0;
                            canvas[idx + 2] = 0;
                            canvas[idx + 3] = 0;
                        }
                    }
                }
            }
            // DisposalMethod::Previous is rare and complex; we ignore it
            // (treat as Keep, which is usually fine).
        }

        let total_duration: f32 = frames.iter().map(|f| f.duration_secs).sum();

        eprintln!(
            "[gif] {}: {}x{}, {} frames, {:.2}s total loop",
            path,
            width,
            height,
            frames.len(),
            total_duration,
        );

        GifImage {
            frames,
            total_duration,
        }
    }
}

impl Drawable for GifImage {
    fn draw(&self, buf: &mut PixelBuf, elapsed: f32) {
        if self.frames.is_empty() || self.total_duration <= 0.0 {
            return;
        }

        // Loop the animation.
        let t = elapsed % self.total_duration;

        // Find which frame corresponds to time t.
        let mut accumulated = 0.0;
        let mut frame_idx = 0;
        for (i, frame) in self.frames.iter().enumerate() {
            accumulated += frame.duration_secs;
            if t < accumulated {
                frame_idx = i;
                break;
            }
        }

        buf.set_pixels_ref(&self.frames[frame_idx].pixels);
    }

    fn update(&mut self, _dt: f32, _elapsed: f32) {
        // Fixed position, nothing to update.
    }
}
