use std::cmp::max;
use crate::data::compression::bytes_for_match;
use crate::data::compression::match_length::match_length;
use crate::data::compression::prefix_search::hash_chain::HashChain;
use crate::data::compression::prefix_search::prefix;
use crate::data::control::{
COPY_LITERAL_MAX,
Command,
Control,
LITERAL_MAX,
LONG_LENGTH_MAX,
LONG_OFFSET_MAX,
SHORT_OFFSET_MIN,
};
const MAX_HASH_CHAIN_SEARCH_ITERATIONS: usize = 0x80;
pub(crate) fn encode(input: &[u8]) -> Vec<Control> {
let mut controls: Vec<Control> = vec![];
let mut prefix_table = HashChain::new(input.len());
let mut i = 0;
let end = max(3, input.len()) - 3;
let mut literal_block: Vec<u8> = Vec::with_capacity(LITERAL_MAX as usize);
while i < end {
let key = prefix(&input[i..]);
let matched = prefix_table.insert(key, i as u32);
let pair = matched
.take(MAX_HASH_CHAIN_SEARCH_ITERATIONS)
.filter_map(|matched| {
let matched = matched as usize;
let distance = i - matched;
if distance > LONG_OFFSET_MAX as usize || distance < SHORT_OFFSET_MIN as usize {
None
} else {
let max_copy_len = LONG_LENGTH_MAX as usize;
let match_length = match_length(input, i, matched, max_copy_len, 3);
let num_bytes = bytes_for_match(match_length, distance)?.0?;
Some((
matched,
match_length,
match_length as f64 / num_bytes as f64,
))
}
})
.max_by(|(_, _, r1), (_, _, r2)| r1.total_cmp(r2));
if let Some((found, match_length, _)) = pair {
let distance = i - found;
if literal_block.len() > COPY_LITERAL_MAX as usize {
let split_point: usize = literal_block.len() - (literal_block.len() % 4);
controls.push(Control::new_literal_block(&literal_block[..split_point]));
let second_block = &literal_block[split_point..];
controls.push(Control::new(
Command::new(
distance as u32,
match_length as u16,
second_block.len() as u8,
),
second_block.to_vec(),
));
} else {
controls.push(Control::new(
Command::new(
distance as u32,
match_length as u16,
literal_block.len() as u8,
),
literal_block.clone(),
));
}
literal_block.clear();
for k in (i..).take(match_length).skip(1) {
if k >= end {
break;
}
let _ = prefix_table.insert(prefix(&input[k..]), k as u32);
}
i += match_length;
} else {
literal_block.push(input[i]);
i += 1;
if literal_block.len() >= (LITERAL_MAX as usize) {
controls.push(Control::new_literal_block(&literal_block));
literal_block.clear();
}
}
}
if i < input.len() {
literal_block.extend_from_slice(&input[i..]);
}
if literal_block.len() > 3 {
let split_point: usize = literal_block.len() - (literal_block.len() % 4);
controls.push(Control::new_literal_block(&literal_block[..split_point]));
controls.push(Control::new_stop(&literal_block[split_point..]));
} else {
controls.push(Control::new_stop(&literal_block));
}
controls
}