use crate::*;

pub fn cell_diff_4_vertical(prev_frame: &Frame, frame: &Frame, loss: usize) -> EncodedFrame {
	let loss = loss / 4;
	const CELLS_X: usize = WIDTH / 4;
	const CELLS_Y: usize = HEIGHT / 4;

	let mut modified_cells = [[0; CELLS_Y]; CELLS_X];

	for cellx in 0..CELLS_X {
		for celly in 0..CELLS_Y {
			let mut changed = 0;
			for dx in 0..4 {
				for dy in 0..4 {
					let x = cellx * 4 + dx;
					let y = celly * 4 + dy;
					let pixel = frame[x][y];

					if pixel != prev_frame[x][y] {
						changed += 1;
					}
				}
			}
			if changed > loss {
				modified_cells[cellx][celly] = 1;
			}
		}
	}
	let mut changed_pixels = Vec::new();
	for x in 0..WIDTH {
		for y in 0..HEIGHT {
			let cellx = x / 4;
			let celly = y / 4;

			if modified_cells[cellx][celly] != 0 {
				changed_pixels.push(frame[x][y]);
			}
		}
	}
	let mut modified_cells_flat = Vec::new();
	for x in 0..CELLS_X {
		for y in 0..CELLS_Y {
			modified_cells_flat.push(modified_cells[x][y]);
		}
	}
	let mut data = Vec::new();
	data.extend_from_slice(&pack_nybbles(rle_encode(&modified_cells_flat, 15)));
	data.extend_from_slice(&rle_255_encode(&changed_pixels));

	EncodedFrame {
		encoding: Encoding::CellDiff4VV,
		data,
	}
}

pub fn cell_diff_8_vertical_big(prev_frame: &Frame, frame: &Frame) -> EncodedFrame {
	// let loss = loss / 8;
	const CELLS_X: usize = WIDTH / 8;
	const CELLS_Y: usize = HEIGHT / 8;

	let mut modified_cells = [[0; CELLS_Y]; CELLS_X];

	for cellx in 0..CELLS_X {
		for celly in 0..CELLS_Y {
			let mut changed = 0;
			for dx in 0..8 {
				for dy in 0..8 {
					let x = cellx * 8 + dx;
					let y = celly * 8 + dy;
					let pixel = frame[x][y];

					if pixel != prev_frame[x][y] {
						changed += 1;
					}
				}
			}
			if changed > 0{
				modified_cells[cellx][celly] = 1;
			}
		}
	}
	let mut changed_pixels = Vec::new();
	for x in 0..WIDTH {
		for y in 0..HEIGHT {
			let cellx = x / 8;
			let celly = y / 8;

			if modified_cells[cellx][celly] != 0 {
				changed_pixels.push(frame[x][y]);
			}
		}
	}
	let mut modified_cells_flat = Vec::new();
	for x in 0..CELLS_X {
		for y in 0..CELLS_Y {
			modified_cells_flat.push(modified_cells[x][y]);
		}
	}
	let mut data = Vec::new();
	data.extend_from_slice(&rle_255_encode(&modified_cells_flat));
	data.extend_from_slice(&rle_255_encode(&changed_pixels));

	EncodedFrame {
		encoding: Encoding::CellDiff8VBig,
		data,
	}
}

pub fn cell_diff_8_vertical(prev_frame: &Frame, frame: &Frame, loss: usize) -> EncodedFrame {
	const CELLS_X: usize = WIDTH / 8;
	const CELLS_Y: usize = HEIGHT / 8;

	let mut bitmap: u32 = 0;

	for cellx in 0..CELLS_X {
		for celly in 0..CELLS_Y {
			let mut changed = 0;
			for dx in 0..8 {
				for dy in 0..8 {
					let x = cellx * 8 + dx;
					let y = celly * 8 + dy;
					let pixel = frame[x][y];

					if pixel != prev_frame[x][y] {
						changed += 1;
					}
				}
			}
			if changed > loss {
				bitmap |= 1 << (cellx + CELLS_X * celly);
			}
		}
	}
	let mut changed_pixels = Vec::new();
	for x in 0..WIDTH {
		for y in 0..HEIGHT {
			let cellx = x / 8;
			let celly = y / 8;
			let bit = 1 << (cellx + CELLS_X * celly);
			if (bitmap & bit) != 0 {
				changed_pixels.push(frame[x][y]);
			}
		}
	}
	let mut data = vec![(bitmap >> 16) as u8, (bitmap >> 8) as u8, bitmap as u8];
	data.extend_from_slice(&rle_255_encode(&changed_pixels));
	EncodedFrame {
		encoding: Encoding::CellDiff8V,
		data,
	}
}

// meant for 42x32 mode
pub fn bg_strips_horizontal_16(_prev_frame: &Frame, frame: &Frame) -> EncodedFrame {
	let bg = most_common_pixel(frame);
	fn pack_strip(x: usize, y: usize, width: usize) -> [u8; 2] {
		// Y is 0..31 so will only need 5 bits
		// x is 0..42 so needs 6 bits
		// 5 bits remain for width
		// y: 1111100000000000
		let mut y_x_w: u16 = (y as u16) << 11;
		// x: 0000011111100000
		y_x_w |= (x as u16) << 5;
		// w: 0000000000011111
		y_x_w |= width as u16;
		[(y_x_w >> 8) as u8, (y_x_w as u8)]
	}

	let mut strips = Vec::new();
	'outer: for y in 0..HEIGHT {
		let mut strip_start = 0;
		let mut in_strip = false;
		for x in 0..WIDTH {
			let pixel = frame[x][y];
			if !in_strip && pixel != bg {
				in_strip = true;
				strip_start = x;
			}
			if in_strip {
				if pixel == bg {
					strips.push((strip_start, y, x - strip_start));
					in_strip = false;
				} else if x - strip_start == 31 {
					strips.push((strip_start, y, x - strip_start));
					strip_start = x;
				}
				if strips.len() == 127 {
					break 'outer;
				}
			}
		}
		if in_strip {
			strips.push((strip_start, y, WIDTH - strip_start));
		}
	}
	let mut frame_bytes = Vec::with_capacity(1 + strips.len() * 2);
	frame_bytes.push(bg << 7 | (strips.len() as u8));
	for (x, y, width) in strips {
		frame_bytes.extend_from_slice(&pack_strip(x, y, width));
	}
	EncodedFrame {
		encoding: Encoding::BGStripsH16,
		data: frame_bytes,
	}
}

// meant for 320x240 mode
pub fn bg_strips_horizontal_24(_prev_frame: &Frame, frame: &Frame) -> EncodedFrame {
	let bg = most_common_pixel(frame);
	fn pack_strip(x: usize, y: usize, width: usize) -> [u8; 3] {
		// Y is 0..240 so will need 8 bits
		// x is 0..320 so needs 9 bits
		// 7 bits remain for width
		let x_width = ((x >> 1) & 0x80) as u8 | (width as u8);
		[y as u8, (x & 0xff) as u8, x_width]
	}

	let mut strips = Vec::new();
	'outer: for y in 0..HEIGHT {
		let mut strip_start = 0;
		let mut in_strip = false;
		for x in 0..WIDTH {
			let pixel = frame[x][y];
			if !in_strip && pixel != bg {
				in_strip = true;
				strip_start = x;
			}
			if in_strip {
				if pixel == bg {
					strips.push((strip_start, y, x - strip_start));
					in_strip = false;
				} else if x - strip_start == 127 {
					strips.push((strip_start, y, x - strip_start));
					strip_start = x;
				}
				// if strips.len() == MAX_STRIPS {
				// 	break 'outer;
				// }
			}
		}
		if in_strip {
			strips.push((strip_start, y, WIDTH - strip_start));
		}
	}
	let mut frame_bytes = Vec::with_capacity(2 + strips.len() * 2);
	frame_bytes.push(bg << 7 | ((strips.len() >> 8) as u8));
	frame_bytes.push(strips.len() as u8);
	for (x, y, width) in strips {
		frame_bytes.extend_from_slice(&pack_strip(x, y, width));
	}
	EncodedFrame {
		encoding: Encoding::BGStripsH24,
		data: frame_bytes,
	}
}

pub fn rle_diff_horizontal(prev_frame: &Frame, frame: &Frame) -> EncodedFrame {
	let mut pixels = Vec::new();
	for y in 0..HEIGHT {
		for x in 0..WIDTH {
			pixels.push(frame[x][y] ^ prev_frame[x][y]);
		}
	}
	EncodedFrame {
		encoding: Encoding::RLEDiffHorizontal,
		data: rle_255_encode(&pixels),
	}
}

pub fn rle_diff_vertical(prev_frame: &Frame, frame: &Frame) -> EncodedFrame {
	let mut pixels = Vec::new();
	for x in 0..WIDTH {
		for y in 0..HEIGHT {
			pixels.push(frame[x][y] ^ prev_frame[x][y]);
		}
	}
	EncodedFrame {
		encoding: Encoding::RLEDiffVertical,
		data: rle_255_encode(&pixels),
	}
}

pub fn rle_horizontal_ext(_prev_frame: &Frame, frame: &Frame) -> EncodedFrame {
	let mut data = Vec::new();
	let mut last_pixel = 0;
	let mut run: u16 = 0;

	let push_run = |data: &mut Vec<u8>, run| {
		if run < 256 {
			data.push(run as u8);
		} else {
			// double zero to mark 16 bit run length
			data.push(0);
			data.push(0);
			data.push((run >> 8) as u8);
			data.push((run & 0xff) as u8);
		}
	};

	for y in 0..HEIGHT {
		for x in 0..WIDTH {
			let pixel = frame[x][y];
			if pixel != last_pixel || run == 0xffff {
				push_run(&mut data, run);
				if run == 0xffff && pixel == last_pixel {
					// inserting dummy run because we ran out of max len
					data.push(0);
				}
				run = 1;
			} else {
				run += 1;
			}
			last_pixel = pixel;
		}
	}
	push_run(&mut data, run);
	EncodedFrame {
		encoding: Encoding::RLEHorizontalExt,
		data,
	}
}

pub fn rle_horizontal(_prev_frame: &Frame, frame: &Frame) -> EncodedFrame {
	let mut pixels = Vec::new();
	for y in 0..HEIGHT {
		for x in 0..WIDTH {
			pixels.push(frame[x][y]);
		}
	}
	EncodedFrame {
		encoding: Encoding::RLEHorizontal,
		data: rle_255_encode(&pixels),
	}
}

/// requires WIDTH and HEIGHT to be multiples of 16
/// two-level tree with 4x4 branches per level
pub fn tree_16(_prev_frame: &Frame, frame: &Frame) -> EncodedFrame {
	let mut top_node: u16 = 0;
	let mut second_nodes = Vec::new();
	let mut included_pixels = Vec::new();
	// todo allow using white as bg
	let fg = 1;
	for cx in 0..4 {
		for cy in 0..4 {
			let mut any_filled_here = false;
			'a: for px in 0..(WIDTH / 4) {
				let x = cx * (WIDTH / 4) + px;
				for py in 0..(HEIGHT / 4) {
					let y = cy * (HEIGHT / 4) + py;
					if frame[x][y] == fg {
						any_filled_here = true;
						break 'a;
					}
				}
			}
			if any_filled_here {
				top_node |= 1 << (cy * 4 + cx);
				let mut second_node: u16 = 0;
				for ccx in 0..4 {
					for ccy in 0..4 {
						let mut any_filled_here = false;
						let mut node_pixels = Vec::new();
						for px in 0..(WIDTH / 16) {
							for py in 0..(HEIGHT / 16) {
								let x = cx * (WIDTH / 4) + ccx * (WIDTH / 16) + px;
								let y = cy * (HEIGHT / 4) + ccy * (HEIGHT / 16) + py;
								if frame[x][y] == fg {
									node_pixels.push(fg);
									any_filled_here = true;
								} else {
									node_pixels.push(1 - fg);
								}
							}
						}
						if any_filled_here {
							second_node |= 1 << (ccy * 4 + ccx);
							included_pixels.extend_from_slice(&node_pixels);
						}
					}
				}
				second_nodes.push(second_node);
			}
		}
	}
	let mut data = Vec::new();
	data.push((top_node >> 8) as u8);
	data.push(top_node as u8);
	for n in second_nodes {
		data.push((n >> 8) as u8);
		data.push(n as u8);
	}
	data.extend_from_slice(&rle_255_encode(&included_pixels));

	EncodedFrame {
		encoding: Encoding::Tree16,
		data,
	}
}

pub fn rle_vertical(_prev_frame: &Frame, frame: &Frame) -> EncodedFrame {
	let mut pixels = Vec::new();
	for x in 0..WIDTH {
		for y in 0..HEIGHT {
			pixels.push(frame[x][y]);
		}
	}
	EncodedFrame {
		encoding: Encoding::RLEVertical,
		data: rle_255_encode(&pixels),
	}
}

pub fn rle_vertical_ext(_prev_frame: &Frame, frame: &Frame) -> EncodedFrame {
	let mut data = Vec::new();
	let mut last_pixel = 0;
	let mut run: u16 = 0;

	let push_run = |data: &mut Vec<u8>, run| {
		if run < 256 {
			data.push(run as u8);
		} else {
			// double zero to mark 16 bit run length
			data.push(0);
			data.push(0);
			data.push((run >> 8) as u8);
			data.push((run & 0xff) as u8);
		}
	};

	for x in 0..WIDTH {
		for y in 0..HEIGHT {
			let pixel = frame[x][y];
			if pixel != last_pixel || run == 0xffff {
				push_run(&mut data, run);
				if run == 0xffff && pixel == last_pixel {
					// inserting dummy run because we ran out of max len
					data.push(0);
				}
				run = 1;
			} else {
				run += 1;
			}
			last_pixel = pixel;
		}
	}
	push_run(&mut data, run);
	EncodedFrame {
		encoding: Encoding::RLEVerticalExt,
		data,
	}
}

pub fn rle_vertical_ext_2(_prev_frame: &Frame, frame: &Frame) -> EncodedFrame {
	let mut data = Vec::new();
	let mut last_pixel = 0;
	let mut run: u16 = 0;

	let push_run = |data: &mut Vec<u8>, run| {
		if run < 255 {
			data.push(run as u8);
		} else {
			data.push(255);
			data.push((run >> 8) as u8);
			data.push((run & 0xff) as u8);
		}
	};

	for x in 0..WIDTH {
		for y in 0..HEIGHT {
			let pixel = frame[x][y];
			if pixel != last_pixel || run == 0xffff {
				push_run(&mut data, run);
				if run == 0xffff && pixel == last_pixel {
					// inserting dummy run because we ran out of max len
					data.push(0);
				}
				run = 1;
			} else {
				run += 1;
			}
			last_pixel = pixel;
		}
	}
	push_run(&mut data, run);
	EncodedFrame {
		encoding: Encoding::RLEVerticalExt2,
		data,
	}
}

pub fn rle_horizontal_ext_2(_prev_frame: &Frame, frame: &Frame) -> EncodedFrame {
	let mut data = Vec::new();
	let mut last_pixel = 0;
	let mut run: u16 = 0;

	let push_run = |data: &mut Vec<u8>, run| {
		if run < 255 {
			data.push(run as u8);
		} else {
			data.push(255);
			data.push((run >> 8) as u8);
			data.push((run & 0xff) as u8);
		}
	};

	for y in 0..HEIGHT {
		for x in 0..WIDTH {
			let pixel = frame[x][y];
			if pixel != last_pixel || run == 0xffff {
				push_run(&mut data, run);
				if run == 0xffff && pixel == last_pixel {
					// inserting dummy run because we ran out of max len
					data.push(0);
				}
				run = 1;
			} else {
				run += 1;
			}
			last_pixel = pixel;
		}
	}
	push_run(&mut data, run);
	EncodedFrame {
		encoding: Encoding::RLEHorizontalExt2,
		data,
	}
}

pub fn rle_vertical_var(_prev_frame: &Frame, frame: &Frame) -> EncodedFrame {
	// variable bit count
	// 0xxx xxxx 7 bits <=127
	// 10xx xxxx xxxx xxxx 14 bits <= 16_383
	// 110x xxxx xxxx xxxx xxxx xxxx 21 bits <= 2_097_151
	// no dummy zero-length runs needed, as the max length will not be reachable, even at 1080p resolution
	let mut data = Vec::new();
	let mut last_pixel = 0;
	let mut run: u32 = 0;

	let push_run = |data: &mut Vec<u8>, run| {
		if run < 0x7f {
			data.push(run as u8);
		} else if run < 0x3fff {
			data.push((run >> 8) as u8 | 0x80);
			data.push(run as u8);
		} else {
			data.push((run >> 16) as u8 | 0xC0);
			data.push((run >> 8) as u8);
			data.push(run as u8);
		}
	};

	for x in 0..WIDTH {
		for y in 0..HEIGHT {
			let pixel = frame[x][y];
			if pixel != last_pixel {
				push_run(&mut data, run);
				run = 1;
			} else {
				run += 1;
			}
			last_pixel = pixel;
		}
	}
	push_run(&mut data, run);
	EncodedFrame {
		encoding: Encoding::RLEVerticalVar,
		data,
	}
}

pub fn rle_vertical_16(_prev_frame: &Frame, frame: &Frame) -> EncodedFrame {
	let mut data = Vec::new();
	let mut last_pixel = 0;
	let mut run: u16 = 0;
	for x in 0..WIDTH {
		for y in 0..HEIGHT {
			let pixel = frame[x][y];
			if pixel != last_pixel || run == 0xffff {
				data.push((run >> 8) as u8);
				data.push((run & 0xff) as u8);
				if run == 0xffff && pixel == last_pixel {
					// inserting dummy run because we ran out of max len
					data.push(0);
					data.push(0);
				}
				run = 1;
			} else {
				run += 1;
			}
			last_pixel = pixel;
		}
	}
	data.push((run >> 8) as u8);
	data.push((run & 0xff) as u8);
	EncodedFrame {
		encoding: Encoding::RLEVertical16,
		data,
	}
}

pub fn fill_white(_prev_frame: &Frame, _frame: &Frame, _loss: usize) -> EncodedFrame {
	EncodedFrame {
		encoding: Encoding::FillWhite,
		data: Vec::new(),
	}
}

pub fn fill_black(_prev_frame: &Frame, _frame: &Frame, _loss: usize) -> EncodedFrame {
	EncodedFrame {
		encoding: Encoding::FillBlack,
		data: Vec::new(),
	}
}
