use crate::*;

pub fn cell_diff_4_vertical(prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame {
	const CELLS_X: usize = WIDTH / 4;
	const CELLS_Y: usize = HEIGHT / 4;

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

	let (mut cell_runs, modified_cells_flat) =
		rle_255_decode(&unpack_nybbles(encoded), CELLS_X * CELLS_Y);

	if cell_runs % 2 == 1 {
		cell_runs += 1;
	}
	*reader += cell_runs / 2;
	let encoded = &encoded[(cell_runs / 2)..];

	let mut changed_cell_count = 0;
	let mut i = 0;
	for cellx in 0..CELLS_X {
		for celly in 0..CELLS_Y {
			let cell = modified_cells_flat[i] == 1;
			modified_cells[cellx][celly] = cell;
			if cell {
				changed_cell_count += 1;
			}
			i += 1;
		}
	}

	let mut frame = *prev_frame;

	let changed_pixel_count = changed_cell_count * 4 * 4;
	let (runs, new_pixels) = rle_255_decode(encoded, changed_pixel_count);
	*reader += runs;

	let mut index = 0;

	for x in 0..WIDTH {
		for y in 0..HEIGHT {
			let cellx = x / 4;
			let celly = y / 4;
			let is_changed = modified_cells[cellx][celly];
			if is_changed {
				frame[x][y] = new_pixels[index];
				index += 1;
			}
		}
	}

	frame
}

pub fn cell_diff_8_vertical_big(prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame {
	const CELLS_X: usize = WIDTH / 8;
	const CELLS_Y: usize = HEIGHT / 8;

	let (cell_runs, modified_cells_flat) = rle_255_decode(&encoded, CELLS_X * CELLS_Y);

	*reader += cell_runs;
	let encoded = &encoded[cell_runs..];
	let mut frame = *prev_frame;

	let mut x = 0;
	let mut y = 0;
	let mut color = 1;
	let mut run = 0;
	let mut i = 0;
	while x < WIDTH {
		let cx = x / 8;
		let cy = y / 8;
		if modified_cells_flat[cx * CELLS_Y + cy] != 0 {
			while run == 0 {
				run = encoded[i];
				i += 1;
				color = 1 - color;
			}
			run -= 1;
			frame[x][y] = color;
		}
		y += 1;
		if y == HEIGHT {
			x += 1;
			y = 0;
		}
	}
	*reader += i;

	frame
}

pub fn cell_diff_8_vertical(prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame {
	let bitmap = {
		*reader += 3;
		let mut bitmap = (encoded[0] as u32) << 16;
		bitmap |= (encoded[1] as u32) << 8;
		bitmap | encoded[2] as u32
	};
	let encoded = &encoded[3..];

	let mut frame = *prev_frame;
	let changed_pixel_count = bitmap.count_ones() as usize * 8 * 8;
	let (runs, new_pixels) = rle_255_decode(encoded, changed_pixel_count);
	*reader += runs;

	let cells_x = WIDTH / 8;
	let mut index = 0;

	for x in 0..WIDTH {
		for y in 0..HEIGHT {
			let cellx = x / 8;
			let celly = y / 8;
			let is_changed = (bitmap >> (cellx + cells_x * celly)) & 1;
			if is_changed == 1 {
				frame[x][y] = new_pixels[index];
				index += 1;
			}
		}
	}

	frame
}

pub fn bg_strips_horizontal_16(_prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame {
	*reader += 1;
	let bg = encoded[0] >> 7;
	let fg = 1 - bg;
	let mut frame = [[bg; HEIGHT]; WIDTH];
	let count = (encoded[0] & 0x7f) as usize;
	*reader += count * 2;
	for i in 0..count {
		let upper = (encoded[i * 2 + 1] as u16) << 8;
		let lower = encoded[i * 2 + 2] as u16;
		let y_x_w: u16 = upper | lower;
		let y = (y_x_w >> 11) as usize;
		let x_start = ((y_x_w >> 5) & 0x3f) as usize;
		let w = (y_x_w & 0x1f) as usize;
		for x in x_start..(x_start + w) {
			frame[x][y] = fg;
		}
	}
	frame
}

pub fn bg_strips_horizontal_24(_prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame {
	*reader += 2;
	let bg = encoded[0] >> 7;
	let fg = 1 - bg;
	let mut frame = [[bg; HEIGHT]; WIDTH];
	let count_upper = (encoded[0] & 0x7f) as usize;
	let count = (count_upper << 8) | encoded[1] as usize;
	*reader += count * 3;
	for i in 0..count {
		let y = encoded[i * 3 + 2] as usize;
		let x_lower = encoded[i * 3 + 3];
		let x_width = encoded[i * 3 + 4];
		let x_upper = ((x_width & 0x80) as usize) << 1;
		let x_start = x_lower as usize | x_upper;
		let width = (x_width & 0x7f) as usize;
		for x in x_start..(x_start + width) {
			frame[x][y] = fg;
		}
	}
	frame
}

pub fn rle_diff_horizontal(prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame {
	let (runs, decoded) = rle_255_decode(encoded, FRAME_SIZE);
	*reader += runs;
	let mut frame = *prev_frame;
	let mut i = 0;
	for y in 0..HEIGHT {
		for x in 0..WIDTH {
			frame[x][y] ^= decoded[i];
			i += 1;
		}
	}
	frame
}

pub fn rle_diff_vertical(prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame {
	let (runs, decoded) = rle_255_decode(encoded, FRAME_SIZE);
	*reader += runs;
	let mut frame = *prev_frame;
	let mut dbg_frame = FRAME_0;
	let mut i = 0;
	for x in 0..WIDTH {
		for y in 0..HEIGHT {
			frame[x][y] ^= decoded[i];
			frame[x][y] &= 1;
			dbg_frame[x][y] ^= decoded[i];
			dbg_frame[x][y] &= 1;
			i += 1;
		}
	}
	frame
}

pub fn rle_horizontal(_prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame {
	let (runs, pixels) = rle_255_decode(encoded, FRAME_SIZE);
	*reader += runs;
	let mut frame = FRAME_0;
	let mut i = 0;
	for y in 0..HEIGHT {
		for x in 0..WIDTH {
			frame[x][y] = pixels[i];
			i += 1;
		}
	}
	frame
}

pub fn rle_vertical(_prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame {
	let (runs, pixels) = rle_255_decode(encoded, FRAME_SIZE);
	*reader += runs;
	let mut frame = FRAME_0;
	let mut i = 0;
	for x in 0..WIDTH {
		for y in 0..HEIGHT {
			frame[x][y] = pixels[i];
			i += 1;
		}
	}
	frame
}

pub fn rle_horizontal_ext(_prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame {
	let mut x = 0;
	let mut y = 0;
	let mut color = 0;
	let mut i = 0;
	let mut frame = FRAME_0;

	while y < HEIGHT {
		let byte = encoded[i];
		i += 1;

		let mut run = byte as u16;
		if byte == 0 && encoded[i] == 0 {
			// 16 bit run length
			let upper = (encoded[i + 1] as u16) << 8;
			if upper > 0 {
				let lower = encoded[i + 2] as u16;
				i += 3;
				run = upper | lower;
			}
		}
		for _ in 0..run {
			frame[x][y] = color;
			x += 1;
			if x == WIDTH {
				x = 0;
				y += 1;
			}
		}
		color = 1 - color;
	}
	*reader += i;
	frame
}

pub fn rle_vertical_ext(_prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame {
	let mut x = 0;
	let mut y = 0;
	let mut color = 0;
	let mut i = 0;
	let mut frame = FRAME_0;

	while x < WIDTH {
		let byte = encoded[i];
		i += 1;

		let mut run = byte as u16;
		if byte == 0 && encoded[i] == 0 {
			// 16 bit run length
			let upper = (encoded[i + 1] as u16) << 8;
			if upper > 0 {
				let lower = encoded[i + 2] as u16;
				i += 3;
				run = upper | lower;
			}
		}
		for _ in 0..run {
			frame[x][y] = color;
			y += 1;
			if y == HEIGHT {
				y = 0;
				x += 1;
			}
		}
		color = 1 - color;
	}
	*reader += i;
	frame
}

pub fn rle_vertical_ext_2(_prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame {
	let mut x = 0;
	let mut y = 0;
	let mut color = 0;
	let mut i = 0;
	let mut frame = FRAME_0;

	while x < WIDTH {
		let byte = encoded[i];
		i += 1;

		let mut run = byte as u16;
		if byte == 255 {
			// 16 bit run length
			let upper = (encoded[i] as u16) << 8;
			let lower = encoded[i + 1] as u16;
			i += 2;
			run = upper | lower;
		}
		for _ in 0..run {
			frame[x][y] = color;
			y += 1;
			if y == HEIGHT {
				y = 0;
				x += 1;
			}
		}
		color = 1 - color;
	}
	*reader += i;
	frame
}

pub fn rle_horizontal_ext_2(_prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame {
	let mut x = 0;
	let mut y = 0;
	let mut color = 0;
	let mut i = 0;
	let mut frame = FRAME_0;

	while y < HEIGHT {
		let byte = encoded[i];
		i += 1;

		let mut run = byte as u16;
		if byte == 255 {
			// 16 bit run length
			let upper = (encoded[i] as u16) << 8;
			let lower = encoded[i + 1] as u16;
			i += 2;
			run = upper | lower;
		}
		for _ in 0..run {
			frame[x][y] = color;
			x += 1;
			if x == WIDTH {
				x = 0;
				y += 1;
			}
		}
		color = 1 - color;
	}
	*reader += i;
	frame
}

pub fn rle_vertical_var(_prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame {
	let mut x = 0;
	let mut y = 0;
	let mut color = 0;
	let mut i = 0;
	let mut frame = FRAME_0;

	while x < WIDTH {
		let byte = encoded[i];
		i += 1;

		let run;
		if byte & 0xC0 == 0x80 {
			// two-byte mode
			let upper = (byte & 0x3f) as u32;
			let lower = encoded[i] as u32;
			i += 1;
			run = (upper << 8) | lower;
		} else if byte & 0xE0 == 0xC0 {
			// 3-byte mode
			let upper = (byte & 0x1f) as u32;
			let mid = encoded[i] as u32;
			let lower = encoded[i + 1] as u32;
			i += 2;
			run = (upper << 16) | (mid << 8) | lower;
		} else {
			run = byte as u32;
		}
		for _ in 0..run {
			frame[x][y] = color;
			y += 1;
			if y == HEIGHT {
				y = 0;
				x += 1;
			}
		}
		color = 1 - color;
	}
	*reader += i;
	frame
}

pub fn rle_vertical_16(_prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame {
	let mut x = 0;
	let mut y = 0;
	let mut color = 0;
	let mut i = 0;
	let mut frame = FRAME_0;

	while x < WIDTH {
		let upper = encoded[i] as u16;
		let lower = encoded[i + 1] as u16;
		i += 2;

		let run = (upper << 8) | lower;
		for _ in 0..run {
			frame[x][y] = color;
			y += 1;
			if y == HEIGHT {
				y = 0;
				x += 1;
			}
		}
		color = 1 - color;
	}
	*reader += i;
	frame
}

pub fn tree_16(_prev_frame: &Frame, encoded: &[u8], reader: &mut usize) -> Frame {
	let fg = 1;
	let mut frame = [[1 - fg; HEIGHT]; WIDTH];
	let top_node = ((encoded[0] as u16) << 8) | encoded[1] as u16;
	let mut i = 2;
	let mut nodes = [0u16; 16];
	for cx in 0..4 {
		for cy in 0..4 {
			let index = cy * 4 + cx;
			let has_node = (top_node & (1 << index)) != 0;
			if has_node {
				let node = ((encoded[i] as u16) << 8) | encoded[i + 1] as u16;
				i += 2;
				nodes[index] = node;
			}
		}
	}
	let mut color = fg;
	let mut run = 0;
	for cx in 0..4 {
		for cy in 0..4 {
			let index = cy * 4 + cx;
			let node = nodes[index];
			if node != 0 {
				for ccx in 0..4 {
					for ccy in 0..4 {
						let x = cx * (WIDTH / 4) + ccx * (WIDTH / 16);
						let y = cy * (HEIGHT / 4) + ccy * (HEIGHT / 16);

						let lower_index = ccy * 4 + ccx;
						if node & (1 << lower_index) != 0 {
							// leaf node exists, fill from RLE
							let mut px = 0;
							let mut py = 0;
							while px < WIDTH / 16 {
								while run == 0 {
									run = encoded[i];
									i += 1;
									color = 1 - color;
								}
								run -= 1;
								frame[x + px][y + py] = color;
								py += 1;
								if py == HEIGHT / 16 {
									py = 0;
									px += 1;
								}
							}
						}
					}
				}
			}
		}
	}
	*reader += i;

	frame
}

pub fn fill_white(_prev_frame: &Frame, _encoded: &[u8], _reader: &mut usize) -> Frame {
	FRAME_1
}

pub fn fill_black(_prev_frame: &Frame, _encoded: &[u8], _reader: &mut usize) -> Frame {
	FRAME_0
}
