extern crate tiff;

use std::io::{Cursor, Seek, Write};
use tiff::{
    decoder::{Decoder, DecodingResult},
    encoder::{
        colortype::{self, ColorType},
        compression::*,
        TiffEncoder, TiffValue,
    },
};

trait TestImage<const NUM_CHANNELS: usize>: From<Vec<<Self::Color as ColorType>::Inner>> {
    const WIDTH: u32;
    const HEIGHT: u32;
    type Color: ColorType;

    fn reference_data(&self) -> &[<Self::Color as ColorType>::Inner];
    fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; NUM_CHANNELS];

    fn compress<C: Compression, W: Write + Seek>(
        &self,
        encoder: &mut TiffEncoder<W>,
        compression: C,
    ) where
        [<Self::Color as ColorType>::Inner]: TiffValue,
    {
        let image = encoder
            .new_image_with_compression::<Self::Color, C>(Self::WIDTH, Self::HEIGHT, compression)
            .unwrap();
        image.write_data(self.reference_data()).unwrap();
    }

    fn generate() -> Self {
        assert_eq!(
            Self::Color::BITS_PER_SAMPLE.len(),
            NUM_CHANNELS,
            "Incompatible color type"
        );

        let mut data = Vec::with_capacity((Self::WIDTH * Self::HEIGHT) as usize * NUM_CHANNELS);
        for x in 0..Self::WIDTH {
            for y in 0..Self::HEIGHT {
                data.extend(IntoIterator::into_iter(Self::generate_pixel(x, y)));
            }
        }
        Self::from(data)
    }
}

struct TestImageColor(Vec<u16>);

impl From<Vec<u16>> for TestImageColor {
    fn from(value: Vec<u16>) -> Self {
        Self(value)
    }
}

impl TestImage<3> for TestImageColor {
    const WIDTH: u32 = 1;
    const HEIGHT: u32 = 7;
    type Color = colortype::RGB16;

    fn reference_data(&self) -> &[u16] {
        &self.0
    }

    fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; 3] {
        let val = (x + y) % <Self::Color as ColorType>::Inner::MAX as u32;
        [val as <Self::Color as ColorType>::Inner; 3]
    }
}

struct TestImageGrayscale(Vec<u8>);

impl From<Vec<u8>> for TestImageGrayscale {
    fn from(value: Vec<u8>) -> Self {
        Self(value)
    }
}

impl TestImage<1> for TestImageGrayscale {
    const WIDTH: u32 = 21;
    const HEIGHT: u32 = 10;
    type Color = colortype::Gray8;

    fn reference_data(&self) -> &[u8] {
        &self.0
    }

    fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; 1] {
        let val = (x + y) % <Self::Color as ColorType>::Inner::MAX as u32;
        [val as <Self::Color as ColorType>::Inner]
    }
}

fn encode_decode_with_compression<C: Compression + Clone>(compression: C) {
    let mut data = Cursor::new(Vec::new());

    let image_rgb = TestImageColor::generate();
    let image_grayscale = TestImageGrayscale::generate();

    // Encode tiff with compression
    {
        // Create a multipage image with 2 images
        let mut encoder = TiffEncoder::new(&mut data).unwrap();
        image_rgb.compress(&mut encoder, compression.clone());
        image_grayscale.compress(&mut encoder, compression);
    }

    // Decode tiff
    data.set_position(0);
    {
        let mut decoder = Decoder::new(data).unwrap();

        // Check the RGB image
        assert_eq!(
            match decoder.read_image() {
                Ok(DecodingResult::U16(image_data)) => image_data,
                unexpected => panic!("Descoding RGB failed: {:?}", unexpected),
            },
            image_rgb.reference_data()
        );

        // Check the grayscale image
        decoder.next_image().unwrap();
        assert_eq!(
            match decoder.read_image() {
                Ok(DecodingResult::U8(image_data)) => image_data,
                unexpected => panic!("Decoding grayscale failed: {:?}", unexpected),
            },
            image_grayscale.reference_data()
        );
    }
}

#[test]
fn encode_decode_without_compression() {
    encode_decode_with_compression(Uncompressed::default());
}

#[test]
fn encode_decode_with_lzw() {
    encode_decode_with_compression(Lzw::default());
}

#[test]
fn encode_decode_with_deflate() {
    encode_decode_with_compression(Deflate::with_level(DeflateLevel::Fast));
    encode_decode_with_compression(Deflate::with_level(DeflateLevel::Balanced));
    encode_decode_with_compression(Deflate::with_level(DeflateLevel::Best));
}

#[test]
fn encode_decode_with_packbits() {
    encode_decode_with_compression(Packbits::default());
}
