use std::io::{Read, Seek, Write};
use byteorder::{ReadBytesExt, WriteBytesExt};
use crate::data::control::mode::reference::Reference;
use crate::data::control::mode::{Mode, Sizes};
use crate::data::control::Command;
use crate::RefPackError;
pub struct Simcity4;
impl Mode for Simcity4 {
const SIZES: Sizes = Sizes {
literal: Reference::SIZES.literal,
copy_literal: Reference::SIZES.copy_literal,
short_offset: Reference::SIZES.short_offset,
short_length: Reference::SIZES.short_length,
medium_offset: Reference::SIZES.medium_offset,
medium_length: Reference::SIZES.medium_length,
long_offset: (0, 65535),
long_length: Reference::SIZES.long_length,
};
fn read<R: Read + Seek>(reader: &mut R) -> Result<Command, RefPackError> {
let first = reader.read_u8()?;
match first {
0x00..=0x7F => Reference::read_short(first, reader),
0x80..=0xBF => Reference::read_medium(first, reader),
0xC0..=0xDF => {
let second = reader.read_u8()?;
let third = reader.read_u8()?;
let fourth = reader.read_u8()?;
let offset = (second as u32) << 8 | third as u32;
let length = (((first & 0b0001_1100) as u16) << 6 | fourth as u16) + 5;
let literal = first & 0b0000_0011;
Ok(Command::Long {
offset,
length,
literal,
})
}
0xE0..=0xFB => Ok(Reference::read_literal(first)),
0xFC..=0xFF => Ok(Reference::read_stop(first)),
}
}
fn write<W: Write + Seek>(command: Command, writer: &mut W) -> Result<(), RefPackError> {
match command {
Command::Short {
offset,
length,
literal,
} => Reference::write_short(offset, length, literal, writer),
Command::Medium {
offset,
length,
literal,
} => Reference::write_medium(offset, length, literal, writer),
Command::Long {
offset,
length,
literal,
} => {
let length_adjusted = length - 5;
let first = 0b1100_0000u8
| ((length_adjusted >> 6) & 0b0001_1100) as u8
| literal & 0b0000_0011;
let second = ((offset >> 8) & 0b1111_1111) as u8;
let third = (offset & 0b1111_1111) as u8;
let fourth = (length_adjusted & 0b1111_1111) as u8;
writer.write_u8(first)?;
writer.write_u8(second)?;
writer.write_u8(third)?;
writer.write_u8(fourth)?;
Ok(())
}
Command::Literal(literal) => Reference::write_literal(literal, writer),
Command::Stop(literal) => Reference::write_stop(literal, writer),
}
}
}
#[cfg(test)]
mod test {
use proptest::prelude::*;
use test_strategy::proptest;
use super::super::test::{
generate_decoder_input, generate_decoder_input_with_ceiling, read_write_mode,
};
use super::*;
#[proptest]
fn symmetrical_read_write(
#[strategy(generate_decoder_input(0b0000_0000, 0b1000_0000, 2))] short_in: Vec<u8>,
#[strategy(generate_decoder_input(0b1000_0000, 0b1100_0000, 3))] medium_in: Vec<u8>,
#[strategy(generate_decoder_input(0b1100_0000, 0b1110_0000, 4))] long_in: Vec<u8>,
#[strategy(generate_decoder_input_with_ceiling(0b1110_0000, 0b1110_0000, 1, 27))]
literal_in: Vec<u8>,
#[strategy(generate_decoder_input(0b1111_1100, 0b1111_1100, 1))] stop_in: Vec<u8>,
) {
let result = read_write_mode::<Simcity4>(short_in, medium_in, long_in, literal_in, stop_in);
prop_assert!(result.is_ok(), "Inner: {}", result.unwrap_err());
}
}