use std::{fs::File, path::Path};

use criterion::{black_box, criterion_group, criterion_main, Criterion};

use ingris::{Image, FILE_VERSION, HEADER_SIZE};
use miniz_oxide::inflate::decompress_to_vec;


fn setup() -> Image
{
	let path = Path::new("Panorama2.png");
	let decoder = png::Decoder::new(File::open(path).expect( path.to_str().unwrap()));
	let mut reader = decoder.read_info().unwrap();
	let mut buf = vec![0; reader.output_buffer_size()];
	let png = reader.next_frame(&mut buf).unwrap();
	Image::new(FILE_VERSION, png.width as u16, png.height as u16, buf)
}

fn read_slow(buffer: &Vec<u8>, data_len: usize)
{
	// slow Mode
	let mut bytes: Vec<u8> = Vec::with_capacity(data_len);
	for b in &buffer[HEADER_SIZE..]
	{
		bytes.push(b >> 4);
		bytes.push(b & 0x0F);
	}
	black_box(bytes);
}

fn read_fast(buffer: &Vec<u8>, data_len: usize)
{
	// fast Mode
	let mut data: Vec<u32> = Vec::with_capacity(data_len / std::mem::size_of::<u32>());
	for b in buffer[HEADER_SIZE..].chunks_exact(2)
	{
		data.push((
			((b[0] as u32 >> 4) << 0) +
			((b[0] as u32 & 0x0F) << 8) +
			((b[1] as u32 >> 4) << 16) +
			((b[1] as u32 & 0x0F) << 24)).to_le());
	}
	let bytes = to_u8_vec(data);
	black_box(bytes);
}

fn write_slow(image: &Image)
{
	let mut buffer: Vec<u8> = Vec::with_capacity(image.size() + HEADER_SIZE);
	buffer.push(FILE_VERSION);
	buffer.extend(image.width.to_le_bytes());
	buffer.extend(image.height.to_le_bytes());
	buffer.push(image.bit_depth());

	// slow Mode
	for slice in image.bytes().chunks_exact(2)
	{
		let byte = (slice[0] << 4) + slice[1];
		buffer.push(byte);
	}
	black_box(buffer);
}

fn write_fast(image: &Image)
{
	let mut buffer: Vec<u8> = Vec::with_capacity(image.size() + HEADER_SIZE);
	buffer.push(FILE_VERSION);
	buffer.extend(image.width.to_le_bytes());
	buffer.extend(image.height.to_le_bytes());
	buffer.push(image.bit_depth());

	// fast Mode
	let mut dbuf: Vec<u32> = Vec::with_capacity(image.size() / std::mem::size_of::<u32>());
	for slice in image.bytes().chunks_exact(8)
	{
		let byte: u32 =
			((slice[0] as u32) << 4) +
			((slice[1] as u32) << 0) +
			((slice[2] as u32) << 12) +
			((slice[3] as u32) << 8) +
			((slice[4] as u32) << 20) +
			((slice[5] as u32) << 16) +
			((slice[6] as u32) << 28) +
			((slice[7] as u32) << 24);
		dbuf.push(byte.to_le());
	}
	buffer.extend_from_slice(&to_u8_vec(dbuf));
	black_box(buffer);
}

fn to_u8_vec<T>(mut vec: Vec<T>) -> Vec<u8>
{
	let length = vec.len() * std::mem::size_of::<T>();
	let ptr = vec.as_mut_ptr() as *mut u8;
	std::mem::forget(vec);

	unsafe { Vec::from_raw_parts(ptr, length, length) }
}

fn criterion_benchmark(c: &mut Criterion)
{
	let img = setup();
	c.bench_function("Schreiben slow", |b| b.iter(|| write_slow(&img)));
	c.bench_function("Schreiben fast", |b| b.iter(|| write_fast(&img)));

	let buffer = decompress_to_vec(&std::fs::read("Panorama2.ing").unwrap()).unwrap();
	let data_len = (img.width * img.height) as usize;

	c.bench_function("Lesen slow", |b| b.iter(|| read_slow(&buffer, data_len)));
	c.bench_function("Lesen fast", |b| b.iter(|| read_fast(&buffer, data_len)));
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);