//! See the `Decompress` struct instead. You don't need to use this module directly.
use std::mem::MaybeUninit;
use bytemuck::Pod;
use crate::{colorspace::ColorSpace, PixelDensity};
use crate::colorspace::ColorSpaceExt;
use crate::component::CompInfo;
use crate::component::CompInfoExt;
use crate::errormgr::unwinding_error_mgr;
use crate::errormgr::ErrorMgr;
use crate::ffi;
use crate::ffi::jpeg_decompress_struct;
use crate::ffi::DCTSIZE;
use crate::ffi::JPEG_LIB_VERSION;
use crate::ffi::J_COLOR_SPACE as COLOR_SPACE;
use crate::marker::Marker;
use crate::readsrc::SourceMgr;
use libc::fdopen;
use std::cmp::min;
use std::fs::File;
use std::io;
use std::io::BufRead;
use std::io::BufReader;
use std::marker::PhantomData;
use std::mem;
use std::os::raw::{c_int, c_uchar, c_ulong, c_void};
use std::path::Path;
use std::ptr;
use std::ptr::addr_of_mut;
use std::slice;

const MAX_MCU_HEIGHT: usize = 16;
const MAX_COMPONENTS: usize = 4;

/// Empty list of markers
///
/// By default markers are not read from JPEG files.
pub const NO_MARKERS: &[Marker] = &[];

/// App 0-14 and comment markers
///
/// ```rust
/// # use mozjpeg::*;
/// Decompress::with_markers(ALL_MARKERS);
/// ```
pub const ALL_MARKERS: &[Marker] = &[
    Marker::APP(0), Marker::APP(1), Marker::APP(2), Marker::APP(3), Marker::APP(4),
    Marker::APP(5), Marker::APP(6), Marker::APP(7), Marker::APP(8), Marker::APP(9),
    Marker::APP(10), Marker::APP(11), Marker::APP(12), Marker::APP(13), Marker::APP(14),
    Marker::COM,
];

/// Algorithm for the DCT step.
#[derive(Clone, Copy, Debug)]
pub enum DctMethod {
    /// slow but accurate integer algorithm
    IntegerSlow,
    /// faster, less accurate integer method
    IntegerFast,
    /// floating-point method
    Float,
}

/// Use `Decompress` static methods instead of creating this directly
pub struct DecompressBuilder<'markers> {
    save_markers: &'markers [Marker],
    err_mgr: Option<Box<ErrorMgr>>,
}

#[deprecated(note = "Renamed to DecompressBuilder")]
#[doc(hidden)]
pub use DecompressBuilder as DecompressConfig;

impl<'markers> DecompressBuilder<'markers> {
    #[inline]
    #[must_use]
    pub const fn new() -> Self {
        DecompressBuilder {
            err_mgr: None,
            save_markers: NO_MARKERS,
        }
    }

    #[inline]
    #[must_use]
    pub fn with_err(mut self, err: ErrorMgr) -> Self {
        self.err_mgr = Some(Box::new(err));
        self
    }

    #[inline]
    #[must_use]
    pub const fn with_markers(mut self, save_markers: &'markers [Marker]) -> Self {
        self.save_markers = save_markers;
        self
    }

    #[inline]
    pub fn from_path<P: AsRef<Path>>(self, path: P) -> io::Result<Decompress<BufReader<File>>> {
        self.from_file(File::open(path.as_ref())?)
    }

    /// Reads from an already-open `File`.
    /// Use `from_reader` if you want to customize buffer size.
    #[inline]
    pub fn from_file(self, file: File) -> io::Result<Decompress<BufReader<File>>> {
        self.from_reader(BufReader::new(file))
    }

    /// Reads from a `Vec` or a slice.
    #[inline]
    pub fn from_mem(self, mem: &[u8]) -> io::Result<Decompress<&[u8]>> {
        self.from_reader(mem)
    }

    /// Takes `BufReader`. If you have `io::Read`, wrap it in `io::BufReader::new(read)`.
    #[inline]
    pub fn from_reader<R: BufRead>(self, reader: R) -> io::Result<Decompress<R>> {
        Decompress::from_builder_and_reader(self, reader)
    }
}

impl<'markers> Default for DecompressBuilder<'markers> {
    #[inline]
    fn default() -> Self {
        Self::new()
    }
}

/// Get pixels out of a JPEG file
///
/// High-level wrapper for `jpeg_decompress_struct`
///
/// ```rust
/// # use mozjpeg::*;
/// # fn t() -> std::io::Result<()> {
/// let d = Decompress::new_path("image.jpg")?;
/// # Ok(()) }
/// ```
pub struct Decompress<R> {
    cinfo: jpeg_decompress_struct,
    err_mgr: Box<ErrorMgr>,
    src_mgr: Option<Box<SourceMgr<R>>>,
}

/// Marker type and data slice returned by `MarkerIter`
pub struct MarkerData<'a> {
    pub marker: Marker,
    pub data: &'a [u8],
}

/// See `Decompress.markers()`
pub struct MarkerIter<'a> {
    marker_list: *mut ffi::jpeg_marker_struct,
    _references: ::std::marker::PhantomData<MarkerData<'a>>,
}

impl<'a> Iterator for MarkerIter<'a> {
    type Item = MarkerData<'a>;
    #[inline]
    fn next(&mut self) -> Option<MarkerData<'a>> {
        if self.marker_list.is_null() {
            return None;
        }
        unsafe {
            let last = &*self.marker_list;
            self.marker_list = last.next;
            Some(MarkerData {
                marker: last.marker.into(),
                data: ::std::slice::from_raw_parts(last.data, last.data_length as usize),
            })
        }
    }
}

impl Decompress<()> {
    /// Short for builder().with_err()
    #[inline]
    #[doc(hidden)]
    #[must_use]
    pub fn with_err(err_mgr: ErrorMgr) -> DecompressBuilder<'static> {
        DecompressBuilder::new().with_err(err_mgr)
    }

    /// Short for builder().with_markers()
    #[inline]
    #[doc(hidden)]
    #[must_use]
    pub fn with_markers(markers: &[Marker]) -> DecompressBuilder<'_> {
        DecompressBuilder::new().with_markers(markers)
    }

    /// Use builder()
    #[deprecated(note = "renamed to builder()")]
    #[doc(hidden)]
    #[must_use]
    pub fn config() -> DecompressBuilder<'static> {
        DecompressBuilder::new()
    }

    /// This is `DecompressBuilder::new()`
    #[inline]
    #[must_use]
    pub const fn builder() -> DecompressBuilder<'static> {
        DecompressBuilder::new()
    }
}

impl Decompress<BufReader<File>> {
    /// Decode file at path
    #[inline]
    pub fn new_path<P: AsRef<Path>>(path: P) -> io::Result<Self> {
        DecompressBuilder::new().from_path(path)
    }

    /// Decode an already-opened file
    #[inline]
    pub fn new_file(file: File) -> io::Result<Self> {
        DecompressBuilder::new().from_file(file)
    }
}

impl<'mem> Decompress<&'mem [u8]> {
    /// Decode from a JPEG file already in memory
    #[inline]
    pub fn new_mem(mem: &'mem [u8]) -> io::Result<Self> {
        DecompressBuilder::new().from_mem(mem)
    }
}

impl<R> Decompress<R> {
    /// Decode from an `io::BufRead`, which is `BufReader` wrapping any `io::Read`.
    #[inline]
    pub fn new_reader(reader: R) -> io::Result<Self> where R: BufRead {
        DecompressBuilder::new().from_reader(reader)
    }

    fn from_builder_and_reader(builder: DecompressBuilder<'_>, reader: R) -> io::Result<Self> where R: BufRead {
        let src_mgr = Box::new(SourceMgr::new(reader)?);
        let err_mgr = builder.err_mgr.unwrap_or_else(unwinding_error_mgr);
        unsafe {
            let mut newself = Decompress {
                cinfo: mem::zeroed(),
                src_mgr: Some(src_mgr),
                err_mgr,
            };
            let src_ptr = newself.src_mgr.as_mut().unwrap().iface_c_ptr();
            newself.cinfo.common.err = addr_of_mut!(*newself.err_mgr);
            ffi::jpeg_create_decompress(&mut newself.cinfo);
            newself.cinfo.src = src_ptr;
            for &marker in builder.save_markers {
                newself.save_marker(marker);
            }
            newself.read_header()?;
            Ok(newself)
        }
    }

    #[inline]
    #[must_use]
    pub fn components(&self) -> &[CompInfo] {
        if self.cinfo.comp_info.is_null() {
            return &[];
        }
        unsafe {
            slice::from_raw_parts(self.cinfo.comp_info, self.cinfo.num_components as usize)
        }
    }

    #[inline]
    pub(crate) fn components_mut(&mut self) -> &mut [CompInfo] {
        if self.cinfo.comp_info.is_null() {
            return &mut [];
        }
        unsafe {
            slice::from_raw_parts_mut(self.cinfo.comp_info, self.cinfo.num_components as usize)
        }
    }

    /// Result here is mostly useless, because it will panic if the file is invalid
    #[inline]
    fn read_header(&mut self) -> io::Result<()> {
        // require_image = 0 allows handling this error without unwinding
        let res = unsafe { ffi::jpeg_read_header(&mut self.cinfo, 0) };
        if res == 1 {
            Ok(())
        } else {
            Err(io::Error::new(io::ErrorKind::Other, "no image in the JPEG file"))
        }
    }

    #[inline]
    #[must_use]
    pub fn color_space(&self) -> COLOR_SPACE {
        self.cinfo.jpeg_color_space
    }

    /// It's generally bogus in libjpeg
    #[inline]
    #[must_use]
    pub fn gamma(&self) -> f64 {
        self.cinfo.output_gamma
    }

    /// Get pixel density of an image from the JFIF APP0 segment.
    /// Returns None in case of an invalid density unit.
    #[inline]
    #[must_use]
    pub fn pixel_density(&mut self) -> Option<PixelDensity> {
        Some(PixelDensity {
            unit: match self.cinfo.density_unit {
                0 => crate::PixelDensityUnit::PixelAspectRatio,
                1 => crate::PixelDensityUnit::Inches,
                2 => crate::PixelDensityUnit::Centimeters,
                _ => return None,
            },
            x: self.cinfo.X_density,
            y: self.cinfo.Y_density,
        })
    }

    /// Markers are available only if you enable them via `with_markers()`
    #[inline]
    #[must_use]
    pub fn markers(&self) -> MarkerIter<'_> {
        MarkerIter {
            marker_list: self.cinfo.marker_list,
            _references: PhantomData,
        }
    }

    #[inline]
    fn save_marker(&mut self, marker: Marker) {
        unsafe {
            ffi::jpeg_save_markers(&mut self.cinfo, marker.into(), 0xFFFF);
        }
    }

    /// width,height
    #[inline]
    #[must_use]
    pub fn size(&self) -> (usize, usize) {
        (self.width(), self.height())
    }

    #[inline]
    #[must_use]
    pub fn width(&self) -> usize {
        self.cinfo.image_width as usize
    }

    #[inline]
    #[must_use]
    pub fn height(&self) -> usize {
        self.cinfo.image_height as usize
    }

    /// Start decompression with conversion to RGB
    #[inline(always)]
    pub fn rgb(self) -> io::Result<DecompressStarted<R>> {
        self.to_colorspace(ffi::J_COLOR_SPACE::JCS_RGB)
    }

    /// Start decompression with conversion to `colorspace`
    pub fn to_colorspace(mut self, colorspace: ColorSpace) -> io::Result<DecompressStarted<R>> {
        self.cinfo.out_color_space = colorspace;
        DecompressStarted::start_decompress(self)
    }

    /// Start decompression with conversion to RGBA
    #[inline(always)]
    pub fn rgba(self) -> io::Result<DecompressStarted<R>> {
        self.to_colorspace(ffi::J_COLOR_SPACE::JCS_EXT_RGBA)
    }

    /// Start decompression with conversion to grayscale.
    #[inline(always)]
    pub fn grayscale(self) -> io::Result<DecompressStarted<R>> {
        self.to_colorspace(ffi::J_COLOR_SPACE::JCS_GRAYSCALE)
    }

    /// Selects the algorithm used for the DCT step.
    pub fn dct_method(&mut self, method: DctMethod) {
        self.cinfo.dct_method = match method {
            DctMethod::IntegerSlow => ffi::J_DCT_METHOD::JDCT_ISLOW,
            DctMethod::IntegerFast => ffi::J_DCT_METHOD::JDCT_IFAST,
            DctMethod::Float => ffi::J_DCT_METHOD::JDCT_FLOAT,
        }
    }

    // If `true`, do careful upsampling of chroma components.  If `false`,
    // a faster but sloppier method is used.  Default is `true`.  The visual
    // impact of the sloppier method is often very small.
    pub fn do_fancy_upsampling(&mut self, value: bool) {
        self.cinfo.do_fancy_upsampling = ffi::boolean::from(value);
    }

    /// If `true`, interblock smoothing is applied in early stages of decoding
    /// progressive JPEG files; if `false`, not.  Default is `true`.  Early
    /// progression stages look "fuzzy" with smoothing, "blocky" without.
    /// In any case, block smoothing ceases to be applied after the first few
    /// AC coefficients are known to full accuracy, so it is relevant only
    /// when using buffered-image mode for progressive images.
    pub fn do_block_smoothing(&mut self, value: bool) {
        self.cinfo.do_block_smoothing = ffi::boolean::from(value);
    }

    #[inline(always)]
    pub fn raw(mut self) -> io::Result<DecompressStarted<R>> {
        self.cinfo.raw_data_out = ffi::boolean::from(true);
        DecompressStarted::start_decompress(self)
    }

    fn out_color_space(&self) -> ColorSpace {
        self.cinfo.out_color_space
    }

    /// Start decompression without colorspace conversion
    pub fn image(self) -> io::Result<Format<R>> {
        use crate::ffi::J_COLOR_SPACE::{JCS_CMYK, JCS_GRAYSCALE, JCS_RGB};
        match self.out_color_space() {
            JCS_RGB => Ok(Format::RGB(DecompressStarted::start_decompress(self)?)),
            JCS_CMYK => Ok(Format::CMYK(DecompressStarted::start_decompress(self)?)),
            JCS_GRAYSCALE => Ok(Format::Gray(DecompressStarted::start_decompress(self)?)),
            _ => Ok(Format::RGB(self.rgb()?)),
        }
    }

    /// Rescales the output image by `numerator / 8` during decompression.
    /// `numerator` must be between 1 and 16.
    /// Thus setting a value of `8` will result in an unscaled image.
    #[track_caller]
    #[inline]
    pub fn scale(&mut self, numerator: u8) {
        assert!(1 <= numerator && numerator <= 16, "numerator must be between 1 and 16");
        self.cinfo.scale_num = numerator.into();
        self.cinfo.scale_denom = 8;
    }
}

/// See `Decompress.image()`
pub enum Format<R> {
    RGB(DecompressStarted<R>),
    Gray(DecompressStarted<R>),
    CMYK(DecompressStarted<R>),
}

/// See methods on `Decompress`
pub struct DecompressStarted<R> {
    dec: Decompress<R>,
}

impl<R> DecompressStarted<R> {
    fn start_decompress(dec: Decompress<R>) -> io::Result<Self> {
        let mut dec = Self { dec };
        if 0 != unsafe { ffi::jpeg_start_decompress(&mut dec.dec.cinfo) } {
            Ok(dec)
        } else {
            io_suspend_err()
        }
    }

    #[must_use]
    pub fn color_space(&self) -> ColorSpace {
        self.dec.out_color_space()
    }

    /// Gets the minimal buffer size for using `DecompressStarted::read_scanlines_flat_into`
    #[inline(always)]
    #[must_use]
    pub fn min_flat_buffer_size(&self) -> usize {
        self.color_space().num_components() * self.width() * self.height()
    }

    fn can_read_more_scanlines(&self) -> bool {
        self.dec.cinfo.output_scanline < self.dec.cinfo.output_height
    }

    /// Append data
    #[track_caller]
    pub fn read_raw_data(&mut self, image_dest: &mut [&mut Vec<ffi::JSAMPLE>]) {
        while self.can_read_more_scanlines() {
            self.read_raw_data_chunk(image_dest);
        }
    }

    #[track_caller]
    fn read_raw_data_chunk(&mut self, image_dest: &mut [&mut Vec<ffi::JSAMPLE>]) {
        assert!(0 != self.dec.cinfo.raw_data_out, "Raw data not set");

        let mcu_height = self.dec.cinfo.max_v_samp_factor as usize * DCTSIZE;
        if mcu_height > MAX_MCU_HEIGHT {
            panic!("Subsampling factor too large");
        }

        let num_components = self.dec.components().len();
        if num_components > MAX_COMPONENTS || num_components > image_dest.len() {
            panic!("Too many components. Image has {}, destination vector has {} (max supported is {})", num_components, image_dest.len(), MAX_COMPONENTS);
        }

        unsafe {
            let mut row_ptrs = [[ptr::null_mut::<ffi::JSAMPLE>(); MAX_MCU_HEIGHT]; MAX_COMPONENTS];
            let mut comp_ptrs = [ptr::null_mut::<*mut ffi::JSAMPLE>(); MAX_COMPONENTS];
            for ((comp_info, comp_dest), (comp_ptrs, row_ptrs)) in self.dec.components().iter().zip(&mut *image_dest).zip(comp_ptrs.iter_mut().zip(row_ptrs.iter_mut())) {
                let row_stride = comp_info.row_stride();

                let comp_height = comp_info.v_samp_factor as usize * DCTSIZE;
                let required_len = comp_height * row_stride;
                comp_dest.try_reserve(required_len).expect("oom");
                let comp_dest = &mut comp_dest.spare_capacity_mut()[..required_len];
                for (row_ptr, comp_dest) in row_ptrs.iter_mut().zip(comp_dest.chunks_exact_mut(row_stride)) {
                    *row_ptr = comp_dest.as_mut_ptr().cast();
                }
                for row_ptr in &mut row_ptrs[comp_height..mcu_height] {
                    *row_ptr = ptr::null_mut();
                }
                *comp_ptrs = row_ptrs.as_mut_ptr();
            }

            let lines_read = ffi::jpeg_read_raw_data(&mut self.dec.cinfo, comp_ptrs.as_mut_ptr(), mcu_height as u32) as usize;

            assert_eq!(lines_read, mcu_height); // Partial reads would make subsampled height tricky to define

            for (comp_info, comp_dest) in self.dec.components().iter().zip(image_dest) {
                let row_stride = comp_info.row_stride();

                let comp_height = comp_info.v_samp_factor as usize * DCTSIZE;
                let original_len = comp_dest.len();
                let required_len = comp_height * row_stride;
                debug_assert!(original_len + required_len <= comp_dest.capacity());
                comp_dest.set_len(original_len + required_len);
            }
        }
    }

    #[must_use]
    pub fn width(&self) -> usize {
        self.dec.cinfo.output_width as usize
    }

    #[must_use]
    pub fn height(&self) -> usize {
        self.dec.cinfo.output_height as usize
    }

    /// Supports any pixel type that is marked as "plain old data", see bytemuck crate.
    ///
    /// Pixels can either have number of bytes matching number of channels, e.g. RGB as
    /// `[u8; 3]` or `rgb::RGB8`, or be an amorphous blob of `u8`s.
    pub fn read_scanlines<T: Pod>(&mut self) -> io::Result<Vec<T>> {
        let num_components = self.color_space().num_components();
        if num_components != mem::size_of::<T>() && mem::size_of::<T>() != 1 {
            return Err(io::Error::new(
                io::ErrorKind::Unsupported,
                format!("pixel size must have {num_components} bytes, but has {}", mem::size_of::<T>()),
            ));
        }
        let width = self.width();
        let height = self.height();
        let mut image_dst: Vec<T> = Vec::new();
        let required_len = height * width * (num_components / mem::size_of::<T>());
        image_dst.try_reserve_exact(required_len).map_err(|_| io::ErrorKind::OutOfMemory)?;
        let read_len = self.read_scanlines_into_uninit(&mut image_dst.spare_capacity_mut()[..required_len])?.len();
        if read_len <= required_len {
            unsafe { image_dst.set_len(read_len); }
        }
        Ok(image_dst)
    }

    /// Supports any pixel type that is marked as "plain old data", see bytemuck crate.
    /// `[u8; 3]` and `rgb::RGB8` are fine, for example. `[u8]` is allowed for any pixel type.
    ///
    /// Allocation-less version of `read_scanlines`
    pub fn read_scanlines_into<'dest, T: Pod>(&mut self, dest: &'dest mut [T]) -> io::Result<&'dest mut [T]> {
        let dest_uninit = unsafe {
            std::mem::transmute::<&'dest mut [T], &'dest mut [MaybeUninit<T>]>(dest)
        };
        self.read_scanlines_into_uninit(dest_uninit)
    }

    /// Returns written-to slice
    pub fn read_scanlines_into_uninit<'dest, T: Pod>(&mut self, dest: &'dest mut [MaybeUninit<T>]) -> io::Result<&'dest mut [T]> {
        let num_components = self.color_space().num_components();
        let item_size = if mem::size_of::<T>() == 1 {
            num_components
        } else if num_components == mem::size_of::<T>() {
            1
        } else {
            return Err(io::Error::new(
                io::ErrorKind::Unsupported,
                format!("pixel size must have {num_components} bytes, but has {}", mem::size_of::<T>()),
            ));
        };
        let width = self.width();
        let height = self.height();
        let line_width = width * item_size;
        if dest.len() % line_width != 0 {
            return Err(io::Error::new(
                io::ErrorKind::Unsupported,
                format!("destination slice length must be multiple of {width}x{num_components} bytes long, got {}B", std::mem::size_of_val(dest)),
            ));
        }
        for row in dest.chunks_exact_mut(line_width) {
            if !self.can_read_more_scanlines() {
                return Err(io::ErrorKind::UnexpectedEof.into());
            }
            let start_line = self.dec.cinfo.output_scanline as usize;
            let mut row_ptr = row.as_mut_ptr().cast::<ffi::JSAMPLE>();
            let rows = std::ptr::addr_of_mut!(row_ptr);
            unsafe {
                let rows_read = ffi::jpeg_read_scanlines(&mut self.dec.cinfo, rows, 1) as usize;
                debug_assert_eq!(start_line + rows_read, self.dec.cinfo.output_scanline as usize, "{start_line}+{rows_read} != {} of {height}", self.dec.cinfo.output_scanline);
                if 0 == rows_read {
                    return Err(io::ErrorKind::UnexpectedEof.into());
                }
            }
        }
        let dest_init = unsafe {
            std::mem::transmute::<&'dest mut [MaybeUninit<T>], &'dest mut [T]>(dest)
        };
        Ok(dest_init)
    }

    #[deprecated(note = "use read_scanlines::<u8>")]
    #[doc(hidden)]
    pub fn read_scanlines_flat(&mut self) -> io::Result<Vec<u8>> {
        self.read_scanlines()
    }

    #[deprecated(note = "use read_scanlines_into::<u8>")]
    #[doc(hidden)]
    pub fn read_scanlines_flat_into<'dest>(&mut self, dest: &'dest mut [u8]) -> io::Result<&'dest mut [u8]> {
        self.read_scanlines_into(dest)
    }

    #[must_use]
    pub fn components(&self) -> &[CompInfo] {
        self.dec.components()
    }

    #[deprecated(note = "too late to mutate, use components()")]
    #[doc(hidden)]
    pub fn components_mut(&mut self) -> &[CompInfo] {
        self.dec.components_mut()
    }

    #[deprecated(note = "use finish()")]
    #[doc(hidden)]
    #[must_use]
    pub fn finish_decompress(mut self) -> bool {
        self.finish_internal().is_ok()
    }

    /// Finish decompress and return the reader
    pub fn finish_into_inner(mut self) -> io::Result<R> where R: BufRead {
        self.finish_internal()?;
        self.dec.cinfo.src = ptr::null_mut();
        let mgr = self.dec.src_mgr.take().ok_or(io::ErrorKind::Other)?;
        Ok(mgr.into_inner())
    }

    #[inline]
    pub fn finish(mut self) -> io::Result<()> {
        self.finish_internal()
    }

    #[inline]
    fn finish_internal(&mut self) -> io::Result<()> {
        if 0 != unsafe { ffi::jpeg_finish_decompress(&mut self.dec.cinfo) } {
            Ok(())
        } else {
            io_suspend_err()
        }
    }
}

#[cold]
fn io_suspend_err<T>() -> io::Result<T> {
    Err(io::ErrorKind::WouldBlock.into())
}

impl<R> Drop for Decompress<R> {
    fn drop(&mut self) {
        unsafe {
            ffi::jpeg_destroy_decompress(&mut self.cinfo);
        }
    }
}

#[test]
fn read_incomplete_file() {
    use crate::colorspace::{ColorSpace, ColorSpaceExt};
    use std::fs::File;
    use std::io::Read;

    let data = std::fs::read("tests/test.jpg").unwrap();
    assert_eq!(2169, data.len());

    // the reader fakes EOI marker, so it always succeeds!
    for l in [data.len()/2, data.len()/3, data.len()/4, data.len()-4, data.len()-3, data.len()-2, data.len()-1] {
        let dinfo = Decompress::new_mem(&data[..l]).unwrap();
        let mut dinfo = dinfo.rgb().unwrap();
        let _bitmap: Vec<[u8; 3]> = dinfo.read_scanlines().unwrap();
        let remaining = dinfo.finish_into_inner().unwrap();
        assert_eq!(0, remaining.len());
    }
}

#[test]
fn mem_no_trailer() {
    let data = std::fs::read("tests/test.jpg").unwrap();
    let dinfo = Decompress::new_mem(&data).unwrap();
    let mut dinfo = dinfo.rgb().unwrap();

    let _: Vec<[u8; 3]> = dinfo.read_scanlines().unwrap();

    assert_eq!(dinfo.finish_into_inner().unwrap().len(), 0);
}

#[test]
fn file_no_trailer() {
    let dinfo = Decompress::new_file(File::open("tests/test.jpg").unwrap()).unwrap();
    let mut dinfo = dinfo.rgb().unwrap();

    let _: Vec<[u8; 3]> = dinfo.read_scanlines().unwrap();

    assert_eq!(dinfo.finish_into_inner().unwrap().buffer().len(), 0);
}

#[test]
fn mem_trailer() {
    let data = std::fs::read("tests/trailer.jpg").unwrap();
    let dinfo = Decompress::new_mem(&data).unwrap();
    let mut dinfo = dinfo.rgb().unwrap();

    let _: Vec<[u8; 3]> = dinfo.read_scanlines().unwrap();

    assert_ne!(dinfo.finish_into_inner().unwrap().len(), 0);
}

#[test]
fn file_trailer() {
    let dinfo = Decompress::new_file(File::open("tests/trailer.jpg").unwrap()).unwrap();
    let mut dinfo = dinfo.rgb().unwrap();

    let _: Vec<[u8; 3]> = dinfo.read_scanlines().unwrap();

    assert_ne!(dinfo.finish_into_inner().unwrap().buffer().len(), 0);
}

#[test]
fn file_trailer_bytes_left() {
    let dinfo = Decompress::new_file(File::open("tests/test.jpg").unwrap()).unwrap();
    let mut dinfo = dinfo.rgb().unwrap();

    let _: Vec<[u8; 3]> = dinfo.read_scanlines().unwrap();

    assert_eq!(dinfo.finish_into_inner().unwrap().buffer().len(), 0);
}

#[test]
fn read_file() {
    use crate::colorspace::{ColorSpace, ColorSpaceExt};
    use std::fs::File;
    use std::io::Read;

    let data = std::fs::read("tests/test.jpg").unwrap();
    assert_eq!(2169, data.len());

    let dinfo = Decompress::new_mem(&data[..]).unwrap();

    assert_eq!(1.0, dinfo.gamma());
    assert_eq!(ColorSpace::JCS_YCbCr, dinfo.color_space());
    assert_eq!(dinfo.components().len(), dinfo.color_space().num_components() as usize);

    assert_eq!((45, 30), dinfo.size());
    {
        let comps = dinfo.components();
        assert_eq!(2, comps[0].h_samp_factor);
        assert_eq!(2, comps[0].v_samp_factor);

        assert_eq!(48, comps[0].row_stride());
        assert_eq!(32, comps[0].col_stride());

        assert_eq!(1, comps[1].h_samp_factor);
        assert_eq!(1, comps[1].v_samp_factor);
        assert_eq!(1, comps[2].h_samp_factor);
        assert_eq!(1, comps[2].v_samp_factor);

        assert_eq!(24, comps[1].row_stride());
        assert_eq!(16, comps[1].col_stride());
        assert_eq!(24, comps[2].row_stride());
        assert_eq!(16, comps[2].col_stride());
    }

    let mut dinfo = dinfo.raw().unwrap();

    let mut has_chunks = false;
    let mut bitmaps = [&mut Vec::new(), &mut Vec::new(), &mut Vec::new()];
    while dinfo.can_read_more_scanlines() {
        has_chunks = true;
        dinfo.read_raw_data_chunk(&mut bitmaps);
        assert_eq!(bitmaps[0].len(), 4 * bitmaps[1].len());
    }
    assert!(has_chunks);

    for (bitmap, comp) in bitmaps.iter().zip(dinfo.components()) {
        assert_eq!(comp.row_stride() * comp.col_stride(), bitmap.len());
    }

    assert!(dinfo.finish().is_ok());
}

#[test]
fn no_markers() {
    use crate::colorspace::{ColorSpace, ColorSpaceExt};
    use std::fs::File;
    use std::io::Read;

    // btw tests src manager with 1-byte len, which requires libjpeg to refill the buffer a lot
    let tricky_buf = io::BufReader::with_capacity(1, File::open("tests/test.jpg").unwrap());
    let dinfo = Decompress::builder().from_reader(tricky_buf).unwrap();
    assert_eq!(0, dinfo.markers().count());

    let res = dinfo.rgb().unwrap().read_scanlines::<[u8; 3]>().unwrap();
    assert_eq!(res.len(), 45 * 30);

    let dinfo = Decompress::builder().with_markers(&[]).from_path("tests/test.jpg").unwrap();
    assert_eq!(0, dinfo.markers().count());
}

#[test]
fn buffer_into_inner() {
    use std::io::Read;

    let data = std::fs::read("tests/test.jpg").unwrap();
    let orig_data_len = data.len();

    let dec = Decompress::builder()
        .from_reader(BufReader::with_capacity(data.len()/17, std::io::Cursor::new(data)))
        .unwrap();
    let mut dec = dec.rgb().unwrap();
    let _: Vec<[u8; 3]> = dec.read_scanlines().unwrap();
    let mut buf = dec.finish_into_inner().unwrap();
    assert_eq!(0, buf.fill_buf().unwrap().len());

    let mut data = buf.into_inner().into_inner();
    assert_eq!(orig_data_len, data.len());

    // put two images in one vec
    data.extend_from_slice(b"unexpected data after first image eoi");
    data.append(&mut data.clone());
    let appended_len = data.len() - orig_data_len;

    // read one image
    let dec = Decompress::builder()
        .from_reader(BufReader::with_capacity(data.len()/21, std::io::Cursor::new(data)))
        .unwrap();
    let mut dec = dec.rgb().unwrap();
    let _: Vec<[u8; 3]> = dec.read_scanlines().unwrap();

    // expect buf to have the other one
    let mut buf = dec.finish_into_inner().unwrap();
    let mut tmp = Vec::new();
    buf.read_to_end(&mut tmp).unwrap();
    let data = buf.into_inner().into_inner();
    assert_eq!(appended_len, tmp.len());
    assert_eq!(data[orig_data_len..], tmp);
}

#[test]
fn read_file_rgb() {
    use crate::colorspace::{ColorSpace, ColorSpaceExt};
    use std::fs::File;
    use std::io::Read;

    let data = std::fs::read("tests/test.jpg").unwrap();
    let dinfo = Decompress::builder().with_markers(ALL_MARKERS).from_mem(&data[..]).unwrap();

    assert_eq!(ColorSpace::JCS_YCbCr, dinfo.color_space());

    assert_eq!(1, dinfo.markers().count());

    let mut dinfo = dinfo.rgb().unwrap();
    assert_eq!(ColorSpace::JCS_RGB, dinfo.color_space());
    assert_eq!(dinfo.components().len(), dinfo.color_space().num_components() as usize);

    let bitmap: Vec<[u8; 3]> = dinfo.read_scanlines().unwrap();
    assert_eq!(bitmap.len(), 45 * 30);

    assert!(!bitmap.contains(&[0; 3]));

    dinfo.finish().unwrap();
}

#[test]
fn drops_reader() {
    #[repr(align(1024))]
    struct CountsDrops<'a, R> {drop_count: &'a mut u8, reader: R}

    impl<R> Drop for CountsDrops<'_, R> {
        fn drop(&mut self) {
            assert!(self as *mut _ as usize % 1024 == 0); // alignment
            *self.drop_count += 1;
        }
    }
    impl<R: io::Read> io::Read for CountsDrops<'_, R> {
        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
            self.reader.read(buf)
        }
    }
    let mut drop_count = 0;
    let r = Decompress::builder().from_reader(BufReader::new(CountsDrops {
        drop_count: &mut drop_count,
        reader: File::open("tests/test.jpg").unwrap(),
    })).unwrap();
    drop(r);
    assert_eq!(1, drop_count);
}
