//!  Decoding of GIF Images
//!
//!  GIF (Graphics Interchange Format) is an image format that supports lossless compression.
//!
//!  # Related Links
//!  * <http://www.w3.org/Graphics/GIF/spec-gif89a.txt> - The GIF Specification
//!
//! # Examples
//! ```rust,no_run
//! use image::codecs::gif::{GifDecoder, GifEncoder};
//! use image::{ImageDecoder, AnimationDecoder};
//! use std::fs::File;
//! # fn main() -> std::io::Result<()> {
//! // Decode a gif into frames
//! let file_in = File::open("foo.gif")?;
//! let mut decoder = GifDecoder::new(file_in).unwrap();
//! let frames = decoder.into_frames();
//! let frames = frames.collect_frames().expect("error decoding gif");
//!
//! // Encode frames into a gif and save to a file
//! let mut file_out = File::open("out.gif")?;
//! let mut encoder = GifEncoder::new(file_out);
//! encoder.encode_frames(frames.into_iter());
//! # Ok(())
//! # }
//! ```
#![allow(clippy::while_let_loop)]

use std::convert::TryFrom;
use std::convert::TryInto;
use std::io::{self, Cursor, Read, Write};
use std::marker::PhantomData;
use std::mem;

use gif::ColorOutput;
use gif::{DisposalMethod, Frame};
use num_rational::Ratio;

use crate::animation;
use crate::color::{ColorType, Rgba};
use crate::error::{
    DecodingError, EncodingError, ImageError, ImageResult, ParameterError, ParameterErrorKind,
    UnsupportedError, UnsupportedErrorKind,
};
use crate::image::{self, AnimationDecoder, ImageDecoder, ImageFormat};
use crate::io::Limits;
use crate::traits::Pixel;
use crate::ImageBuffer;

/// GIF decoder
pub struct GifDecoder<R: Read> {
    reader: gif::Decoder<R>,
    limits: Limits,
}

impl<R: Read> GifDecoder<R> {
    /// Creates a new decoder that decodes the input steam `r`
    pub fn new(r: R) -> ImageResult<GifDecoder<R>> {
        let mut decoder = gif::DecodeOptions::new();
        decoder.set_color_output(ColorOutput::RGBA);

        Ok(GifDecoder {
            reader: decoder.read_info(r).map_err(ImageError::from_decoding)?,
            limits: Limits::default(),
        })
    }

    /// Creates a new decoder that decodes the input steam `r`, using limits `limits`
    pub fn with_limits(r: R, limits: Limits) -> ImageResult<GifDecoder<R>> {
        let mut decoder = gif::DecodeOptions::new();
        decoder.set_color_output(ColorOutput::RGBA);

        Ok(GifDecoder {
            reader: decoder.read_info(r).map_err(ImageError::from_decoding)?,
            limits,
        })
    }
}

/// Wrapper struct around a `Cursor<Vec<u8>>`
pub struct GifReader<R>(Cursor<Vec<u8>>, PhantomData<R>);
impl<R> Read for GifReader<R> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.0.read(buf)
    }
    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
        if self.0.position() == 0 && buf.is_empty() {
            mem::swap(buf, self.0.get_mut());
            Ok(buf.len())
        } else {
            self.0.read_to_end(buf)
        }
    }
}

impl<'a, R: 'a + Read> ImageDecoder<'a> for GifDecoder<R> {
    type Reader = GifReader<R>;

    fn dimensions(&self) -> (u32, u32) {
        (
            u32::from(self.reader.width()),
            u32::from(self.reader.height()),
        )
    }

    fn color_type(&self) -> ColorType {
        ColorType::Rgba8
    }

    fn into_reader(self) -> ImageResult<Self::Reader> {
        Ok(GifReader(
            Cursor::new(image::decoder_to_vec(self)?),
            PhantomData,
        ))
    }

    fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
        assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));

        let frame = match self
            .reader
            .next_frame_info()
            .map_err(ImageError::from_decoding)?
        {
            Some(frame) => FrameInfo::new_from_frame(frame),
            None => {
                return Err(ImageError::Parameter(ParameterError::from_kind(
                    ParameterErrorKind::NoMoreData,
                )))
            }
        };

        let (width, height) = self.dimensions();

        if frame.left == 0
            && frame.width == width
            && (frame.top as u64 + frame.height as u64 <= height as u64)
        {
            // If the frame matches the logical screen, or, as a more general case,
            // fits into it and touches its left and right borders, then
            // we can directly write it into the buffer without causing line wraparound.
            let line_length = usize::try_from(width)
                .unwrap()
                .checked_mul(self.color_type().bytes_per_pixel() as usize)
                .unwrap();

            // isolate the portion of the buffer to read the frame data into.
            // the chunks above and below it are going to be zeroed.
            let (blank_top, rest) =
                buf.split_at_mut(line_length.checked_mul(frame.top as usize).unwrap());
            let (buf, blank_bottom) =
                rest.split_at_mut(line_length.checked_mul(frame.height as usize).unwrap());

            debug_assert_eq!(buf.len(), self.reader.buffer_size());

            // this is only necessary in case the buffer is not zeroed
            for b in blank_top {
                *b = 0;
            }
            // fill the middle section with the frame data
            self.reader
                .read_into_buffer(buf)
                .map_err(ImageError::from_decoding)?;
            // this is only necessary in case the buffer is not zeroed
            for b in blank_bottom {
                *b = 0;
            }
        } else {
            // If the frame does not match the logical screen, read into an extra buffer
            // and 'insert' the frame from left/top to logical screen width/height.
            let buffer_size = self.reader.buffer_size();

            self.limits.reserve_usize(buffer_size)?;

            let mut frame_buffer = vec![0; buffer_size];

            self.limits.free_usize(buffer_size);

            self.reader
                .read_into_buffer(&mut frame_buffer[..])
                .map_err(ImageError::from_decoding)?;

            let frame_buffer = ImageBuffer::from_raw(frame.width, frame.height, frame_buffer);
            let image_buffer = ImageBuffer::from_raw(width, height, buf);

            // `buffer_size` uses wrapping arithmetic, thus might not report the
            // correct storage requirement if the result does not fit in `usize`.
            // `ImageBuffer::from_raw` detects overflow and reports by returning `None`.
            if frame_buffer.is_none() || image_buffer.is_none() {
                return Err(ImageError::Unsupported(
                    UnsupportedError::from_format_and_kind(
                        ImageFormat::Gif.into(),
                        UnsupportedErrorKind::GenericFeature(format!(
                            "Image dimensions ({}, {}) are too large",
                            frame.width, frame.height
                        )),
                    ),
                ));
            }

            let frame_buffer = frame_buffer.unwrap();
            let mut image_buffer = image_buffer.unwrap();

            for (x, y, pixel) in image_buffer.enumerate_pixels_mut() {
                let frame_x = x.wrapping_sub(frame.left);
                let frame_y = y.wrapping_sub(frame.top);

                if frame_x < frame.width && frame_y < frame.height {
                    *pixel = *frame_buffer.get_pixel(frame_x, frame_y);
                } else {
                    // this is only necessary in case the buffer is not zeroed
                    *pixel = Rgba([0, 0, 0, 0]);
                }
            }
        }

        Ok(())
    }
}

struct GifFrameIterator<R: Read> {
    reader: gif::Decoder<R>,

    width: u32,
    height: u32,

    non_disposed_frame: ImageBuffer<Rgba<u8>, Vec<u8>>,
}

impl<R: Read> GifFrameIterator<R> {
    fn new(decoder: GifDecoder<R>) -> GifFrameIterator<R> {
        let (width, height) = decoder.dimensions();

        // TODO: Avoid this cast
        let (width, height) = (width as u32, height as u32);

        // intentionally ignore the background color for web compatibility

        // create the first non disposed frame
        let non_disposed_frame = ImageBuffer::from_pixel(width, height, Rgba([0, 0, 0, 0]));

        GifFrameIterator {
            reader: decoder.reader,
            width,
            height,
            non_disposed_frame,
        }
    }
}

impl<R: Read> Iterator for GifFrameIterator<R> {
    type Item = ImageResult<animation::Frame>;

    fn next(&mut self) -> Option<ImageResult<animation::Frame>> {
        // begin looping over each frame

        let frame = match self.reader.next_frame_info() {
            Ok(frame_info) => {
                if let Some(frame) = frame_info {
                    FrameInfo::new_from_frame(frame)
                } else {
                    // no more frames
                    return None;
                }
            }
            Err(err) => return Some(Err(ImageError::from_decoding(err))),
        };

        let mut vec = vec![0; self.reader.buffer_size()];
        if let Err(err) = self.reader.read_into_buffer(&mut vec) {
            return Some(Err(ImageError::from_decoding(err)));
        }

        // create the image buffer from the raw frame.
        // `buffer_size` uses wrapping arithmetic, thus might not report the
        // correct storage requirement if the result does not fit in `usize`.
        // on the other hand, `ImageBuffer::from_raw` detects overflow and
        // reports by returning `None`.
        let mut frame_buffer = match ImageBuffer::from_raw(frame.width, frame.height, vec) {
            Some(frame_buffer) => frame_buffer,
            None => {
                return Some(Err(ImageError::Unsupported(
                    UnsupportedError::from_format_and_kind(
                        ImageFormat::Gif.into(),
                        UnsupportedErrorKind::GenericFeature(format!(
                            "Image dimensions ({}, {}) are too large",
                            frame.width, frame.height
                        )),
                    ),
                )))
            }
        };

        // blend the current frame with the non-disposed frame, then update
        // the non-disposed frame according to the disposal method.
        fn blend_and_dispose_pixel(
            dispose: DisposalMethod,
            previous: &mut Rgba<u8>,
            current: &mut Rgba<u8>,
        ) {
            let pixel_alpha = current.channels()[3];
            if pixel_alpha == 0 {
                *current = *previous;
            }

            match dispose {
                DisposalMethod::Any | DisposalMethod::Keep => {
                    // do not dispose
                    // (keep pixels from this frame)
                    // note: the `Any` disposal method is underspecified in the GIF
                    // spec, but most viewers treat it identically to `Keep`
                    *previous = *current;
                }
                DisposalMethod::Background => {
                    // restore to background color
                    // (background shows through transparent pixels in the next frame)
                    *previous = Rgba([0, 0, 0, 0]);
                }
                DisposalMethod::Previous => {
                    // restore to previous
                    // (dispose frames leaving the last none disposal frame)
                }
            }
        }

        // if `frame_buffer`'s frame exactly matches the entire image, then
        // use it directly, else create a new buffer to hold the composited
        // image.
        let image_buffer = if (frame.left, frame.top) == (0, 0)
            && (self.width, self.height) == frame_buffer.dimensions()
        {
            for (x, y, pixel) in frame_buffer.enumerate_pixels_mut() {
                let previous_pixel = self.non_disposed_frame.get_pixel_mut(x, y);
                blend_and_dispose_pixel(frame.disposal_method, previous_pixel, pixel);
            }
            frame_buffer
        } else {
            ImageBuffer::from_fn(self.width, self.height, |x, y| {
                let frame_x = x.wrapping_sub(frame.left);
                let frame_y = y.wrapping_sub(frame.top);
                let previous_pixel = self.non_disposed_frame.get_pixel_mut(x, y);

                if frame_x < frame_buffer.width() && frame_y < frame_buffer.height() {
                    let mut pixel = *frame_buffer.get_pixel(frame_x, frame_y);
                    blend_and_dispose_pixel(frame.disposal_method, previous_pixel, &mut pixel);
                    pixel
                } else {
                    // out of bounds, return pixel from previous frame
                    *previous_pixel
                }
            })
        };

        Some(Ok(animation::Frame::from_parts(
            image_buffer,
            0,
            0,
            frame.delay,
        )))
    }
}

impl<'a, R: Read + 'a> AnimationDecoder<'a> for GifDecoder<R> {
    fn into_frames(self) -> animation::Frames<'a> {
        animation::Frames::new(Box::new(GifFrameIterator::new(self)))
    }
}

struct FrameInfo {
    left: u32,
    top: u32,
    width: u32,
    height: u32,
    disposal_method: DisposalMethod,
    delay: animation::Delay,
}

impl FrameInfo {
    fn new_from_frame(frame: &Frame) -> FrameInfo {
        FrameInfo {
            left: u32::from(frame.left),
            top: u32::from(frame.top),
            width: u32::from(frame.width),
            height: u32::from(frame.height),
            disposal_method: frame.dispose,
            // frame.delay is in units of 10ms so frame.delay*10 is in ms
            delay: animation::Delay::from_ratio(Ratio::new(u32::from(frame.delay) * 10, 1)),
        }
    }
}

/// Number of repetitions for a GIF animation
#[derive(Clone, Copy, Debug)]
pub enum Repeat {
    /// Finite number of repetitions
    Finite(u16),
    /// Looping GIF
    Infinite,
}

impl Repeat {
    pub(crate) fn to_gif_enum(&self) -> gif::Repeat {
        match self {
            Repeat::Finite(n) => gif::Repeat::Finite(*n),
            Repeat::Infinite => gif::Repeat::Infinite,
        }
    }
}

/// GIF encoder.
pub struct GifEncoder<W: Write> {
    w: Option<W>,
    gif_encoder: Option<gif::Encoder<W>>,
    speed: i32,
    repeat: Option<Repeat>,
}

impl<W: Write> GifEncoder<W> {
    /// Creates a new GIF encoder.
    pub fn new(w: W) -> GifEncoder<W> {
        Self::new_with_speed(w, 1)
    }

    /// Create a new GIF encoder, and has the speed parameter `speed`. See
    /// [`Frame::from_rgba_speed`](https://docs.rs/gif/latest/gif/struct.Frame.html#method.from_rgba_speed)
    /// for more information.
    pub fn new_with_speed(w: W, speed: i32) -> GifEncoder<W> {
        assert!(
            speed >= 1 && speed <= 30,
            "speed needs to be in the range [1, 30]"
        );
        GifEncoder {
            w: Some(w),
            gif_encoder: None,
            speed,
            repeat: None,
        }
    }

    /// Set the repeat behaviour of the encoded GIF
    pub fn set_repeat(&mut self, repeat: Repeat) -> ImageResult<()> {
        if let Some(ref mut encoder) = self.gif_encoder {
            encoder
                .set_repeat(repeat.to_gif_enum())
                .map_err(ImageError::from_encoding)?;
        }
        self.repeat = Some(repeat);
        Ok(())
    }

    /// Encode a single image.
    pub fn encode(
        &mut self,
        data: &[u8],
        width: u32,
        height: u32,
        color: ColorType,
    ) -> ImageResult<()> {
        let (width, height) = self.gif_dimensions(width, height)?;
        match color {
            ColorType::Rgb8 => self.encode_gif(Frame::from_rgb(width, height, data)),
            ColorType::Rgba8 => {
                self.encode_gif(Frame::from_rgba(width, height, &mut data.to_owned()))
            }
            _ => Err(ImageError::Unsupported(
                UnsupportedError::from_format_and_kind(
                    ImageFormat::Gif.into(),
                    UnsupportedErrorKind::Color(color.into()),
                ),
            )),
        }
    }

    /// Encode one frame of animation.
    pub fn encode_frame(&mut self, img_frame: animation::Frame) -> ImageResult<()> {
        let frame = self.convert_frame(img_frame)?;
        self.encode_gif(frame)
    }

    /// Encodes Frames.
    /// Consider using `try_encode_frames` instead to encode an `animation::Frames` like iterator.
    pub fn encode_frames<F>(&mut self, frames: F) -> ImageResult<()>
    where
        F: IntoIterator<Item = animation::Frame>,
    {
        for img_frame in frames {
            self.encode_frame(img_frame)?;
        }
        Ok(())
    }

    /// Try to encode a collection of `ImageResult<animation::Frame>` objects.
    /// Use this function to encode an `animation::Frames` like iterator.
    /// Whenever an `Err` item is encountered, that value is returned without further actions.
    pub fn try_encode_frames<F>(&mut self, frames: F) -> ImageResult<()>
    where
        F: IntoIterator<Item = ImageResult<animation::Frame>>,
    {
        for img_frame in frames {
            self.encode_frame(img_frame?)?;
        }
        Ok(())
    }

    pub(crate) fn convert_frame(
        &mut self,
        img_frame: animation::Frame,
    ) -> ImageResult<Frame<'static>> {
        // get the delay before converting img_frame
        let frame_delay = img_frame.delay().into_ratio().to_integer();
        // convert img_frame into RgbaImage
        let mut rbga_frame = img_frame.into_buffer();
        let (width, height) = self.gif_dimensions(rbga_frame.width(), rbga_frame.height())?;

        // Create the gif::Frame from the animation::Frame
        let mut frame = Frame::from_rgba_speed(width, height, &mut *rbga_frame, self.speed);
        // Saturate the conversion to u16::MAX instead of returning an error as that
        // would require a new special cased variant in ParameterErrorKind which most
        // likely couldn't be reused for other cases. This isn't a bad trade-off given
        // that the current algorithm is already lossy.
        frame.delay = (frame_delay / 10).try_into().unwrap_or(std::u16::MAX);

        Ok(frame)
    }

    fn gif_dimensions(&self, width: u32, height: u32) -> ImageResult<(u16, u16)> {
        fn inner_dimensions(width: u32, height: u32) -> Option<(u16, u16)> {
            let width = u16::try_from(width).ok()?;
            let height = u16::try_from(height).ok()?;
            Some((width, height))
        }

        // TODO: this is not very idiomatic yet. Should return an EncodingError.
        inner_dimensions(width, height).ok_or_else(|| {
            ImageError::Parameter(ParameterError::from_kind(
                ParameterErrorKind::DimensionMismatch,
            ))
        })
    }

    pub(crate) fn encode_gif(&mut self, mut frame: Frame) -> ImageResult<()> {
        let gif_encoder;
        if let Some(ref mut encoder) = self.gif_encoder {
            gif_encoder = encoder;
        } else {
            let writer = self.w.take().unwrap();
            let mut encoder = gif::Encoder::new(writer, frame.width, frame.height, &[])
                .map_err(ImageError::from_encoding)?;
            if let Some(ref repeat) = self.repeat {
                encoder
                    .set_repeat(repeat.to_gif_enum())
                    .map_err(ImageError::from_encoding)?;
            }
            self.gif_encoder = Some(encoder);
            gif_encoder = self.gif_encoder.as_mut().unwrap()
        }

        frame.dispose = gif::DisposalMethod::Background;

        gif_encoder
            .write_frame(&frame)
            .map_err(ImageError::from_encoding)
    }
}

impl ImageError {
    fn from_decoding(err: gif::DecodingError) -> ImageError {
        use gif::DecodingError::*;
        match err {
            err @ Format(_) => {
                ImageError::Decoding(DecodingError::new(ImageFormat::Gif.into(), err))
            }
            Io(io_err) => ImageError::IoError(io_err),
        }
    }

    fn from_encoding(err: gif::EncodingError) -> ImageError {
        use gif::EncodingError::*;
        match err {
            err @ Format(_) => {
                ImageError::Encoding(EncodingError::new(ImageFormat::Gif.into(), err))
            }
            Io(io_err) => ImageError::IoError(io_err),
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn frames_exceeding_logical_screen_size() {
        // This is a gif with 10x10 logical screen, but a 16x16 frame + 6px offset inside.
        let data = vec![
            0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x0A, 0x00, 0x0A, 0x00, 0xF0, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x0E, 0xFF, 0x1F, 0x21, 0xF9, 0x04, 0x09, 0x64, 0x00, 0x00, 0x00, 0x2C,
            0x06, 0x00, 0x06, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x23, 0x84, 0x8F, 0xA9,
            0xBB, 0xE1, 0xE8, 0x42, 0x8A, 0x0F, 0x50, 0x79, 0xAE, 0xD1, 0xF9, 0x7A, 0xE8, 0x71,
            0x5B, 0x48, 0x81, 0x64, 0xD5, 0x91, 0xCA, 0x89, 0x4D, 0x21, 0x63, 0x89, 0x4C, 0x09,
            0x77, 0xF5, 0x6D, 0x14, 0x00, 0x3B,
        ];

        let decoder = GifDecoder::new(Cursor::new(data)).unwrap();
        let mut buf = vec![0u8; decoder.total_bytes() as usize];

        assert!(decoder.read_image(&mut buf).is_ok());
    }
}
