use std::{fs::File, io::Write, os::unix::prelude::FileExt, path::Path};

use lri_rs::{proto::camera_module::CameraModule, Message};
use png::{BitDepth, ColorType};

// This code is going to be rough. Just trying to parse this using the technique
// I know: just play with the raw data
fn main() {
	let fname = std::env::args().nth(1).unwrap();
	let data = std::fs::read(fname).unwrap();

	println!("Read {:.2}MB", data.len() as f32 / (1024.0 * 1024.0));

	let magic_id = [76, 69, 76, 82];
	let magic_id_skip = 21;
	let reserved = [0, 0, 0, 0, 0, 0, 0];
	let look_length = magic_id.len() + magic_id_skip + reserved.len();

	let mut heads = vec![];
	let mut skeptical_heads = vec![];

	println!("\nLooking for LELR");
	for idx in 0..data.len() - look_length {
		if &data[idx..idx + magic_id.len()] == magic_id.as_slice() {
			print!("Found! Offset {idx} - ");

			let reserved_start = idx + magic_id.len() + magic_id_skip;
			if &data[reserved_start..reserved_start + reserved.len()] == reserved.as_slice() {
				println!("Reserved matched!");

				let header = LightHeader::new(&data[idx..]);
				let start = idx;
				let end = start + header.combined_length as usize;

				heads.push(HeaderAndOffset { header, start, end });
			} else {
				println!("No reserve match :(");

				let header = LightHeader::new(&data[idx..]);
				let start = idx;
				let end = start + header.combined_length as usize;

				skeptical_heads.push(HeaderAndOffset { header, start, end });
			}
		}
	}

	let ar835 = 3264 * 2448;
	let ar835_6mp = 3264 * 1836;
	let ar1335_crop = 4160 * 3120;
	let ar1335 = 4208 * 3120;
	let imx386 = 4032 * 3024;

	let known_res = vec![ar835, ar835_6mp, ar1335, imx386];

	println!("\nFound {} LightHeaders", heads.len());

	println!("\nLooking for known resolutions!");
	for (idx, head) in heads.iter().enumerate() {
		for res in &known_res {
			if head.header.header_length == *res {
				println!("KNOWN RES: {}", idx);
			}
		}
	}

	println!("\nChecking if there is outlying data...");
	for idx in 1..heads.len() {
		let this = &heads[idx];
		let before = &heads[idx - 1];

		if before.end != this.start {
			println!(
				"Headers {} and {} are gapped by {} bytes",
				idx - 1,
				idx,
				this.start - before.end
			);
		} else {
			println!("{} and {} are consecutive with no gap!", idx - 1, idx);
		}
	}

	let end_difference = heads.last().unwrap().end - data.len();
	if end_difference > 0 {
		println!("{} bytes at the end", end_difference);
	} else {
		println!("File has no extraneous data at the end!");
	}

	println!("\nDumping header info..");
	heads.iter().for_each(|h| h.header.nice_info());

	println!("\nDumping skeptical header info..");
	skeptical_heads.iter().for_each(|h| h.header.bin_info());

	println!("\nWriting large ones to disk and collecting the smalls!");
	let mut small: Vec<u8> = vec![];
	for (idx, head) in heads.iter().enumerate() {
		if head.header.header_length > 1024 * 1024 {
			// I guess we only care if it's at least a megabyte
			let name = format!("{idx}.lri_part");
			let mut file = File::create(&name).unwrap();
			file.write_all(&data[head.start..head.end]).unwrap();
			println!(
				"Wrote {:.2}MB to disk as {name}",
				head.header.combined_length as f32 / (1024.0 * 1024.0)
			);
			head.header.print_info();
		} else {
			small.extend(&data[head.start..head.end]);
		}
	}

	let mut file = File::create("small.lri_part").unwrap();
	file.write_all(&small).unwrap();
	println!(
		"Wrote {:.2}MB to disk as small.lri_part",
		small.len() as f32 / (1024.0 * 1024.0)
	);

	let stamp = [
		08, 0xe7, 0x0f, 0x10, 0x06, 0x18, 0x07, 0x20, 0x13, 0x28, 0x0e,
	];
	println!("\nLooking for timestamps!");
	for (idx, head) in find_pattern(&heads, &data, &stamp) {
		println!("Found stamp in {idx}");
	}

	println!("\nAttemtping to parse data after first image in 2");
	let head = &heads[2];
	let start = head.start + (ar1335_crop as f32 * 2.5).ceil() as usize;
	let after_image = &data[start..start + 4352];
	let proto = match lri_rs::proto::lightheader::LightHeader::parse_from_bytes(after_image) {
		Ok(_) => println!("Success?!?!?!"),
		Err(e) => println!("Failed {e}"),
	};

	println!("Eight before: {:?}", &data[start - 8..start]);
	println!("Eight in: {:?}", &data[start..start + 8]);

	println!("\nDumping the Message of idx 1");
	dump_body(&heads[4], &data, "msg4.lri_part");

	let mut modules = vec![];
	let mut sensor_data = vec![];

	for (idx, head) in heads.iter().enumerate() {
		print!("Head {idx} - ");
		let msg = body(head, &data);

		match (head.header.header_length == 32, head.header.kind) {
			(true, 1) => {
				match lri_rs::proto::view_preferences::ViewPreferences::parse_from_bytes(msg) {
					Ok(_) => println!("View Preferences: Parsed"),
					Err(e) => println!("View Preferences, failed: {e}"),
				}
			}
			(true, 0) => match lri_rs::proto::lightheader::LightHeader::parse_from_bytes(msg) {
				Ok(data) => {
					let mods = &data.modules;
					let datas = &data.sensor_data;

					print!(
						" [claimed: {} | actual: {}] - ",
						head.header.message_length,
						data.compute_size()
					);

					println!(
						"LightHeader! Modules: {} - Datas: {} \\ ModCal: {}",
						mods.len(),
						datas.len(),
						data.module_calibration.len()
					);
					modules.extend_from_slice(&mods);
					sensor_data.extend_from_slice(&datas);

					if false && data.module_calibration.len() > 0 {
						for modc in data.module_calibration {
							print!(" - {:?}", modc.get_camera_id());
						}
						println!("");
					}
				}
				Err(e) => println!("LightHeader, failed: {e}"),
			},
			(true, _) => {
				println!("Unknown header kind and header_length is 32, skipping...");
			}
			(false, _) => {
				println!("SensorData! Skipping for now...");
			}
		}
	}
}

fn dump_body(head: &HeaderAndOffset, data: &[u8], path: &str) {
	let msg = body(head, data);
	let mut file = File::create(&path).unwrap();
	file.write_all(msg).unwrap();
	println!("Wrote {:.2}KB to disk as {path}", msg.len() as f32 / 1024.0);
}

fn body<'a>(head: &HeaderAndOffset, data: &'a [u8]) -> &'a [u8] {
	&data[head.start + head.header.header_length as usize
		..head.start + head.header.header_length as usize + head.header.message_length as usize]
}

fn find_pattern<'a>(
	heads: &'a [HeaderAndOffset],
	data: &[u8],
	pattern: &[u8],
) -> Vec<(usize, &'a HeaderAndOffset)> {
	let mut finds = vec![];

	for (head_idx, head) in heads.iter().enumerate() {
		for idx in head.start..head.end - pattern.len() {
			if &data[idx..idx + pattern.len()] == pattern {
				finds.push((head_idx, head));
			}
		}
	}

	finds
}

fn make_png<P: AsRef<Path>>(
	path: P,
	width: usize,
	height: usize,
	color: ColorType,
	depth: BitDepth,
	data: &[u8],
) {
	let bpp = match (color, depth) {
		(ColorType::Grayscale, BitDepth::Eight) => 1,
		(ColorType::Grayscale, BitDepth::Sixteen) => 2,
		(ColorType::Rgb, BitDepth::Eight) => 3,
		(ColorType::Rgb, BitDepth::Sixteen) => 6,
		_ => panic!("unsupported color or depth"),
	};

	let pix = width * height;

	let file = File::create("ahh.png").unwrap();
	let mut enc = png::Encoder::new(file, width as u32, height as u32);
	enc.set_color(color);
	enc.set_depth(depth);
	let mut writer = enc.write_header().unwrap();
	writer.write_image_data(&data[..pix * bpp]).unwrap();
}

#[derive(Clone, Debug)]
struct HeaderAndOffset {
	header: LightHeader,
	// Inclusive
	start: usize,
	// Exclusive
	end: usize,
}

#[derive(Clone, Debug)]
struct LightHeader {
	magic_number: String,
	combined_length: u64,
	//FIXME: This appears to be the content length and not the header length? I thought
	//it was weird that they were putting the header length here. Is the java decomp
	//wrong?
	header_length: u64,
	message_length: u32,
	// type
	kind: u8,
	reserved: [u8; 7],
}

impl LightHeader {
	pub fn new(data: &[u8]) -> Self {
		let magic_number = String::from_utf8(data[0..4].to_vec()).unwrap();
		let combined_length = u64::from_le_bytes(data[4..12].try_into().unwrap());
		//println!("Combined Length: {:?}", &data[4..12]);

		let header_length = u64::from_le_bytes(data[12..20].try_into().unwrap());
		//println!("Header Length: {:?}", &data[12..20]);

		let message_length = u32::from_le_bytes(data[20..24].try_into().unwrap());
		//println!("Message Length: {:?}", &data[20..24]);

		let kind = data[24];
		let reserved = data[25..32].try_into().unwrap();

		LightHeader {
			magic_number,
			combined_length,
			header_length,
			message_length,
			kind,
			reserved,
		}
	}

	pub fn print_info(&self) {
		let LightHeader {
			magic_number,
			combined_length,
			header_length,
			message_length,
			kind,
			reserved,
		} = self;

		println!("\nMagic: {magic_number}\nCombined Length: {combined_length}\nHeader Length: {header_length}\nMessage Length: {message_length}\nKind: {kind}\nReserved: {reserved:?}");
	}

	pub fn nice_info(&self) {
		let LightHeader {
			magic_number,
			combined_length,
			header_length,
			message_length,
			kind,
			reserved,
		} = self;

		println!(
			"Content length: {:.2}KB | Kind {kind}",
			*header_length as f32 / 1024.0
		);
	}

	pub fn bin_info(&self) {
		let LightHeader {
			magic_number,
			combined_length,
			header_length,
			message_length,
			kind,
			reserved,
		} = self;

		println!("{magic_number} {:b}", combined_length);
	}
}