use std::convert::TryFrom;
use std::io::{self, Read, Write};
use num_rational::Ratio;
use png::{BlendOp, DisposeOp};
use crate::{DynamicImage, GenericImage, ImageBuffer, Luma, LumaA, RgbaImage, Rgb, Rgba};
use crate::animation::{Delay, Frame, Frames};
use crate::color::{Blend, ColorType, ExtendedColorType};
use crate::error::{
    DecodingError, ImageError, ImageResult, LimitError, LimitErrorKind, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind
};
use crate::image::{AnimationDecoder, ImageDecoder, ImageEncoder, ImageFormat};
pub struct PngReader<R: Read> {
    reader: png::Reader<R>,
    buffer: Vec<u8>,
    index: usize,
}
#[allow(dead_code)]
#[deprecated(note = "Use `PngReader` instead")]
pub type PNGReader<R> = PngReader<R>;
impl<R: Read> PngReader<R> {
    fn new(mut reader: png::Reader<R>) -> ImageResult<PngReader<R>> {
        let len = reader.output_buffer_size();
        
        
        
        
        
        let buffer = if reader.info().interlaced {
            let mut buffer = vec![0; len];
            reader.next_frame(&mut buffer).map_err(ImageError::from_png)?;
            buffer
        } else {
            Vec::new()
        };
        Ok(PngReader {
            reader,
            buffer,
            index: 0,
        })
    }
}
impl<R: Read> Read for PngReader<R> {
    fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
        
        let readed = buf.write(&self.buffer[self.index..]).unwrap();
        let mut bytes = readed;
        self.index += readed;
        while self.index >= self.buffer.len() {
            match self.reader.next_row()? {
                Some(row) => {
                    
                    let readed  = buf.write(row).unwrap();
                    bytes += readed;
                    self.buffer = (&row[readed..]).to_owned();
                    self.index = 0;
                }
                None => return Ok(bytes)
            }
        }
        Ok(bytes)
    }
    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
        let mut bytes = self.buffer.len();
        if buf.is_empty() {
            std::mem::swap(&mut self.buffer, buf);
        } else {
            buf.extend_from_slice(&self.buffer);
            self.buffer.clear();
        }
        self.index = 0;
        while let Some(row) = self.reader.next_row()? {
            buf.extend_from_slice(row);
            bytes += row.len();
        }
        Ok(bytes)
    }
}
pub struct PngDecoder<R: Read> {
    color_type: ColorType,
    reader: png::Reader<R>,
}
impl<R: Read> PngDecoder<R> {
    
    pub fn new(r: R) -> ImageResult<PngDecoder<R>> {
        let limits = png::Limits {
            bytes: usize::max_value(),
        };
        let mut decoder = png::Decoder::new_with_limits(r, limits);
        
        
        
        decoder.set_transformations(png::Transformations::EXPAND);
        let (_, mut reader) = decoder.read_info().map_err(ImageError::from_png)?;
        let (color_type, bits) = reader.output_color_type();
        let color_type = match (color_type, bits) {
            (png::ColorType::Grayscale, png::BitDepth::Eight) => ColorType::L8,
            (png::ColorType::Grayscale, png::BitDepth::Sixteen) => ColorType::L16,
            (png::ColorType::GrayscaleAlpha, png::BitDepth::Eight) => ColorType::La8,
            (png::ColorType::GrayscaleAlpha, png::BitDepth::Sixteen) => ColorType::La16,
            (png::ColorType::RGB, png::BitDepth::Eight) => ColorType::Rgb8,
            (png::ColorType::RGB, png::BitDepth::Sixteen) => ColorType::Rgb16,
            (png::ColorType::RGBA, png::BitDepth::Eight) => ColorType::Rgba8,
            (png::ColorType::RGBA, png::BitDepth::Sixteen) => ColorType::Rgba16,
            (png::ColorType::Grayscale, png::BitDepth::One) =>
                return Err(unsupported_color(ExtendedColorType::L1)),
            (png::ColorType::GrayscaleAlpha, png::BitDepth::One) =>
                return Err(unsupported_color(ExtendedColorType::La1)),
            (png::ColorType::RGB, png::BitDepth::One) =>
                return Err(unsupported_color(ExtendedColorType::Rgb1)),
            (png::ColorType::RGBA, png::BitDepth::One) =>
                return Err(unsupported_color(ExtendedColorType::Rgba1)),
            (png::ColorType::Grayscale, png::BitDepth::Two) =>
                return Err(unsupported_color(ExtendedColorType::L2)),
            (png::ColorType::GrayscaleAlpha, png::BitDepth::Two) =>
                return Err(unsupported_color(ExtendedColorType::La2)),
            (png::ColorType::RGB, png::BitDepth::Two) =>
                return Err(unsupported_color(ExtendedColorType::Rgb2)),
            (png::ColorType::RGBA, png::BitDepth::Two) =>
                return Err(unsupported_color(ExtendedColorType::Rgba2)),
            (png::ColorType::Grayscale, png::BitDepth::Four) =>
                return Err(unsupported_color(ExtendedColorType::L4)),
            (png::ColorType::GrayscaleAlpha, png::BitDepth::Four) =>
                return Err(unsupported_color(ExtendedColorType::La4)),
            (png::ColorType::RGB, png::BitDepth::Four) =>
                return Err(unsupported_color(ExtendedColorType::Rgb4)),
            (png::ColorType::RGBA, png::BitDepth::Four) =>
                return Err(unsupported_color(ExtendedColorType::Rgba4)),
            (png::ColorType::Indexed, bits) =>
                return Err(unsupported_color(ExtendedColorType::Unknown(bits as u8))),
        };
        Ok(PngDecoder { color_type, reader })
    }
    
    
    
    
    
    
    
    
    
    
    pub fn apng(self) -> ApngDecoder<R> {
        ApngDecoder::new(self)
    }
    
    
    
    
    
    
    pub fn is_apng(&self) -> bool {
        self.reader.info().animation_control.is_some()
    }
}
fn unsupported_color(ect: ExtendedColorType) -> ImageError {
    ImageError::Unsupported(UnsupportedError::from_format_and_kind(
        ImageFormat::Png.into(),
        UnsupportedErrorKind::Color(ect),
    ))
}
impl<'a, R: 'a + Read> ImageDecoder<'a> for PngDecoder<R> {
    type Reader = PngReader<R>;
    fn dimensions(&self) -> (u32, u32) {
        self.reader.info().size()
    }
    fn color_type(&self) -> ColorType {
        self.color_type
    }
    fn into_reader(self) -> ImageResult<Self::Reader> {
        PngReader::new(self.reader)
    }
    fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
        use byteorder::{BigEndian, ByteOrder, NativeEndian};
        assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
        self.reader.next_frame(buf).map_err(ImageError::from_png)?;
        
        
        
        
        let bpc = self.color_type().bytes_per_pixel() / self.color_type().channel_count();
        match bpc {
            1 => (),  
            2 => buf.chunks_mut(2).for_each(|c| {
                let v = BigEndian::read_u16(c);
                NativeEndian::write_u16(c, v)
            }),
            _ => unreachable!(),
        }
        Ok(())
    }
    fn scanline_bytes(&self) -> u64 {
        let width = self.reader.info().width;
        self.reader.output_line_size(width) as u64
    }
}
pub struct ApngDecoder<R: Read> {
    inner: PngDecoder<R>,
    
    current: RgbaImage,
    
    previous: RgbaImage,
    
    dispose: DisposeOp,
    
    remaining: u32,
    
    has_thumbnail: bool,
}
impl<R: Read> ApngDecoder<R> {
    fn new(inner: PngDecoder<R>) -> Self {
        let (width, height) = inner.dimensions();
        let info = inner.reader.info();
        let remaining = match info.animation_control() {
            
            Some(actl) => actl.num_frames,
            None => 0,
        };
        
        
        let has_thumbnail = info.frame_control.is_none();
        ApngDecoder {
            inner,
            
            current: RgbaImage::new(width, height),
            previous: RgbaImage::new(width, height),
            dispose: DisposeOp::Background,
            remaining,
            has_thumbnail,
        }
    }
    
    
    fn mix_next_frame(&mut self) -> Result<Option<&RgbaImage>, ImageError> {
        
        self.remaining = match self.remaining.checked_sub(1) {
            None => return Ok(None),
            Some(next) => next,
        };
        
        let remaining = self.remaining;
        self.remaining = 0;
        
        if self.has_thumbnail {
            self.has_thumbnail = false;
            let mut buffer = vec![0; self.inner.reader.output_buffer_size()];
            self.inner.reader.next_frame(&mut buffer).map_err(ImageError::from_png)?;
        }
        self.animatable_color_type()?;
        
        match self.dispose {
            DisposeOp::None => {
                self.previous.clone_from(&self.current);
            }
            DisposeOp::Background => {
                self.previous.clone_from(&self.current);
                self.current.pixels_mut().for_each(|pixel| *pixel = Rgba([0, 0, 0, 0]));
            }
            DisposeOp::Previous => {
                self.current.clone_from(&self.previous);
            }
        }
        
        let mut buffer = vec![0; self.inner.reader.output_buffer_size()];
        self.inner.reader.next_frame(&mut buffer).map_err(ImageError::from_png)?;
        let info = self.inner.reader.info();
        
        let (width, height, px, py, blend);
        match info.frame_control() {
            None => {
                width = info.width;
                height = info.height;
                px = 0;
                py = 0;
                blend = BlendOp::Source;
            }
            Some(fc) => {
                width = fc.width;
                height = fc.height;
                px = fc.x_offset;
                py = fc.y_offset;
                blend = fc.blend_op;
                self.dispose = fc.dispose_op;
            }
        };
        
        let source = match self.inner.color_type {
            ColorType::L8 => {
                let image = ImageBuffer::<Luma<_>, _>::from_raw(width, height, buffer).unwrap();
                DynamicImage::ImageLuma8(image).into_rgba8()
            }
            ColorType::La8 => {
                let image = ImageBuffer::<LumaA<_>, _>::from_raw(width, height, buffer).unwrap();
                DynamicImage::ImageLumaA8(image).into_rgba8()
            }
            ColorType::Rgb8 => {
                let image = ImageBuffer::<Rgb<_>, _>::from_raw(width, height, buffer).unwrap();
                DynamicImage::ImageRgb8(image).into_rgba8()
            }
            ColorType::Rgba8 => {
                ImageBuffer::<Rgba<_>, _>::from_raw(width, height, buffer).unwrap()
            }
            ColorType::L16 | ColorType::Rgb16 | ColorType::La16 | ColorType::Rgba16 => {
                
                unreachable!("16-bit apng not yet support")
            }
            _ => unreachable!("Invalid png color"),
        };
        match blend {
            BlendOp::Source => {
                self.current.copy_from(&source, px, py)
                    .expect("Invalid png image not detected in png");
            }
            BlendOp::Over => {
                
                for (x, y, p) in source.enumerate_pixels() {
                    self.current.get_pixel_mut(x + px, y + py).blend(p);
                }
            }
        }
        
        self.remaining = remaining;
        
        Ok(Some(&self.current))
    }
    fn animatable_color_type(&self) -> Result<(), ImageError> {
        match self.inner.color_type {
            ColorType::L8 | ColorType::Rgb8 | ColorType::La8 | ColorType::Rgba8 => Ok(()),
            
            ColorType::L16 | ColorType::Rgb16 | ColorType::La16 | ColorType::Rgba16  => {
                Err(unsupported_color(self.inner.color_type.into()))
            },
            _ => unreachable!("{:?} not a valid png color", self.inner.color_type),
        }
    }
}
impl<'a, R: Read + 'a> AnimationDecoder<'a> for ApngDecoder<R> {
    fn into_frames(self) -> Frames<'a> {
        struct FrameIterator<R: Read>(ApngDecoder<R>);
        impl<R: Read> Iterator for FrameIterator<R> {
            type Item = ImageResult<Frame>;
            fn next(&mut self) -> Option<Self::Item> {
                let image = match self.0.mix_next_frame() {
                    Ok(Some(image)) => image.clone(),
                    Ok(None) => return None,
                    Err(err) => return Some(Err(err)),
                };
                let info = self.0.inner.reader.info();
                let fc = info.frame_control().unwrap();
                
                let num = u32::from(fc.delay_num) * 1_000u32;
                let denom = match fc.delay_den {
                    
                    0 => 100,
                    d => u32::from(d),
                };
                let delay = Delay::from_ratio(Ratio::new(num, denom));
                Some(Ok(Frame::from_parts(image, 0, 0, delay)))
            }
        }
        Frames::new(Box::new(FrameIterator(self)))
    }
}
pub struct PngEncoder<W: Write> {
    w: W,
    compression: CompressionType,
    filter: FilterType,
}
#[allow(dead_code)]
#[deprecated(note = "Use `PngEncoder` instead")]
pub type PNGEncoder<W> = PngEncoder<W>;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CompressionType {
    
    Default,
    
    Fast,
    
    Best,
    
    Huffman,
    
    Rle,
    #[doc(hidden)]
    __NonExhaustive(crate::utils::NonExhaustiveMarker),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FilterType {
    
    
    NoFilter,
    
    Sub,
    
    Up,
    
    Avg,
    
    Paeth,
    #[doc(hidden)]
    __NonExhaustive(crate::utils::NonExhaustiveMarker),
}
impl<W: Write> PngEncoder<W> {
    
    pub fn new(w: W) -> PngEncoder<W> {
        PngEncoder {
            w,
            compression: CompressionType::Fast,
            filter: FilterType::Sub,
        }
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    pub fn new_with_quality(w: W, compression: CompressionType, filter: FilterType) -> PngEncoder<W> {
        PngEncoder {
            w,
            compression,
            filter,
        }
    }
    
    pub fn encode(self, data: &[u8], width: u32, height: u32, color: ColorType) -> ImageResult<()> {
        let (ct, bits) = match color {
            ColorType::L8 => (png::ColorType::Grayscale, png::BitDepth::Eight),
            ColorType::L16 => (png::ColorType::Grayscale,png::BitDepth::Sixteen),
            ColorType::La8 => (png::ColorType::GrayscaleAlpha, png::BitDepth::Eight),
            ColorType::La16 => (png::ColorType::GrayscaleAlpha,png::BitDepth::Sixteen),
            ColorType::Rgb8 => (png::ColorType::RGB, png::BitDepth::Eight),
            ColorType::Rgb16 => (png::ColorType::RGB,png::BitDepth::Sixteen),
            ColorType::Rgba8 => (png::ColorType::RGBA, png::BitDepth::Eight),
            ColorType::Rgba16 => (png::ColorType::RGBA,png::BitDepth::Sixteen),
            _ => return Err(ImageError::Unsupported(UnsupportedError::from_format_and_kind(
                ImageFormat::Png.into(),
                UnsupportedErrorKind::Color(color.into()),
            ))),
        };
        let comp = match self.compression {
            CompressionType::Default => png::Compression::Default,
            CompressionType::Fast => png::Compression::Fast,
            CompressionType::Best => png::Compression::Best,
            CompressionType::Huffman => png::Compression::Huffman,
            CompressionType::Rle => png::Compression::Rle,
            CompressionType::__NonExhaustive(marker) => match marker._private {},
        };
        let filt = match self.filter {
            FilterType::NoFilter => png::FilterType::NoFilter,
            FilterType::Sub => png::FilterType::Sub,
            FilterType::Up => png::FilterType::Up,
            FilterType::Avg => png::FilterType::Avg,
            FilterType::Paeth => png::FilterType::Paeth,
            FilterType::__NonExhaustive(marker) => match marker._private {},
        };
        let mut encoder = png::Encoder::new(self.w, width, height);
        encoder.set_color(ct);
        encoder.set_depth(bits);
        encoder.set_compression(comp);
        encoder.set_filter(filt);
        let mut writer = encoder.write_header().map_err(|e| ImageError::IoError(e.into()))?;
        writer.write_image_data(data).map_err(|e| ImageError::IoError(e.into()))
    }
}
impl<W: Write> ImageEncoder for PngEncoder<W> {
    fn write_image(
        self,
        buf: &[u8],
        width: u32,
        height: u32,
        color_type: ColorType,
    ) -> ImageResult<()> {
        use byteorder::{BigEndian, ByteOrder, NativeEndian};
        
        
        
        
        let bpc = color_type.bytes_per_pixel() / color_type.channel_count();
        match bpc {
            1 => self.encode(buf, width, height, color_type),  
            2 => {
                
                
                
                let mut reordered = vec![0; buf.len()];
                buf.chunks(2)
                    .zip(reordered.chunks_mut(2))
                    .for_each(|(b, r)| BigEndian::write_u16(r, NativeEndian::read_u16(b)));
                self.encode(&reordered, width, height, color_type)
            },
            _ => unreachable!(),
        }
    }
}
impl ImageError {
    fn from_png(err: png::DecodingError) -> ImageError {
        use png::DecodingError::*;
        match err {
            IoError(err) => ImageError::IoError(err),
            err @ Format(_) => ImageError::Decoding(DecodingError::new(
                ImageFormat::Png.into(),
                err,
            )),
            LimitsExceeded => ImageError::Limits(LimitError::from_kind(
                LimitErrorKind::InsufficientMemory,
            )),
            
            Other(message) => ImageError::Parameter(ParameterError::from_kind(
                ParameterErrorKind::Generic(message.into_owned())
            )),
            err @ InvalidSignature
            | err @ CrcMismatch { .. }
            | err @ CorruptFlateStream => {
                ImageError::Decoding(DecodingError::new(
                    ImageFormat::Png.into(),
                    err,
                ))
            }
        }
    }
}
impl Default for CompressionType {
    fn default() -> Self {
        CompressionType::Fast
    }
}
impl Default for FilterType {
    fn default() -> Self {
        FilterType::Sub
    }
}
#[cfg(test)]
mod tests {
    use crate::image::ImageDecoder;
    use std::io::Read;
    use super::*;
    #[test]
    fn ensure_no_decoder_off_by_one() {
        let dec = PngDecoder::new(std::fs::File::open("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png").unwrap())
            .expect("Unable to read PNG file (does it exist?)");
        assert_eq![(2000, 1000), dec.dimensions()];
        assert_eq![
            ColorType::Rgb8,
            dec.color_type(),
            "Image MUST have the Rgb8 format"
        ];
        let correct_bytes = dec
            .into_reader()
            .expect("Unable to read file")
            .bytes()
            .map(|x| x.expect("Unable to read byte"))
            .collect::<Vec<u8>>();
        assert_eq![6_000_000, correct_bytes.len()];
    }
    #[test]
    fn underlying_error() {
        use std::error::Error;
        let mut not_png = std::fs::read("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png").unwrap();
        not_png[0] = 0;
        let error = PngDecoder::new(¬_png[..]).err().unwrap();
        let _ = error
            .source()
            .unwrap()
            .downcast_ref::<png::DecodingError>()
            .expect("Caused by a png error");
    }
}