use alloc::vec::Vec;
use core::cell::RefCell;
#[cfg(feature = "std")]
use log::{debug, log_enabled};
use crate::{
cache::NoCache, deflate::calculate_block_size_auto_type_with_scratch,
katajainen::HuffmanScratch, lz77::Lz77Store,
};
fn find_minimum<F: Fn(usize) -> f64>(f: F, start: usize, end: usize) -> (usize, f64) {
if end - start < 1024 {
let mut best = f64::INFINITY;
let mut result = start;
for i in start..end {
let v = f(i);
if v < best {
best = v;
result = i;
}
}
(result, best)
} else {
let mut start = start;
let mut end = end;
const NUM: usize = 9;
let mut p = [0; NUM];
let mut vp = [0.0; NUM];
let mut lastbest = f64::INFINITY;
let mut pos = start;
while end - start > NUM {
let mut besti = 0;
let mut best = f64::INFINITY;
let multiplier = (end - start) / (NUM + 1);
for i in 0..NUM {
p[i] = start + (i + 1) * multiplier;
vp[i] = f(p[i]);
if vp[i] < best {
best = vp[i];
besti = i;
}
}
if best > lastbest {
break;
}
start = if besti == 0 { start } else { p[besti - 1] };
end = if besti == NUM - 1 { end } else { p[besti + 1] };
pos = p[besti];
lastbest = best;
}
(pos, lastbest)
}
}
fn estimate_cost(
lz77: &Lz77Store,
lstart: usize,
lend: usize,
scratch: &RefCell<HuffmanScratch>,
) -> f64 {
calculate_block_size_auto_type_with_scratch(
lz77,
lstart,
lend,
false,
&mut scratch.borrow_mut(),
)
}
fn find_largest_splittable_block(
lz77size: usize,
done: &[u8],
splitpoints: &[usize],
) -> Option<(usize, usize)> {
let mut longest = 0;
let mut found = None;
let mut last = 0;
for &item in splitpoints {
if done[last] == 0 && item - last > longest {
found = Some((last, item));
longest = item - last;
}
last = item;
}
let end = lz77size - 1;
if done[last] == 0 && end - last > longest {
found = Some((last, end));
}
found
}
#[inline]
fn print_block_split_points(lz77: &Lz77Store, lz77splitpoints: &[usize]) {
if !log_enabled!(log::Level::Debug) {
return;
}
let nlz77points = lz77splitpoints.len();
let mut splitpoints = Vec::with_capacity(nlz77points);
let mut pos = 0;
if nlz77points > 0 {
for (i, item) in lz77.litlens.iter().enumerate() {
let length = item.size();
if lz77splitpoints[splitpoints.len()] == i {
splitpoints.push(pos);
if splitpoints.len() == nlz77points {
break;
}
}
pos += length;
}
}
debug_assert_eq!(splitpoints.len(), nlz77points);
debug!(
"block split points: {} (hex: {})",
splitpoints
.iter()
.map(|&sp| format!("{sp}"))
.collect::<Vec<_>>()
.join(" "),
splitpoints
.iter()
.map(|&sp| format!("{sp:x}"))
.collect::<Vec<_>>()
.join(" ")
);
}
pub fn blocksplit_lz77(lz77: &Lz77Store, maxblocks: u16, splitpoints: &mut Vec<usize>) {
if lz77.size() < 10 {
return;
}
let mut numblocks = 1u32;
let mut done = vec![0; lz77.size()];
let mut lstart = 0;
let mut lend = lz77.size();
let scratch = RefCell::new(HuffmanScratch::new());
while maxblocks != 0 && numblocks < u32::from(maxblocks) {
debug_assert!(lstart < lend);
let find_minimum_result = find_minimum(
|i| estimate_cost(lz77, lstart, i, &scratch) + estimate_cost(lz77, i, lend, &scratch),
lstart + 1,
lend,
);
let llpos = find_minimum_result.0;
let splitcost = find_minimum_result.1;
debug_assert!(llpos > lstart);
debug_assert!(llpos < lend);
let origcost = estimate_cost(lz77, lstart, lend, &scratch);
if splitcost > origcost || llpos == lstart + 1 || llpos == lend {
done[lstart] = 1;
} else {
splitpoints.push(llpos);
splitpoints.sort_unstable();
numblocks += 1;
}
let is_finished = find_largest_splittable_block(lz77.size(), &done, splitpoints)
.is_none_or(|(start, end)| {
lstart = start;
lend = end;
lend - lstart < 10
});
if is_finished {
break;
}
}
print_block_split_points(lz77, splitpoints);
}
pub fn blocksplit(
in_data: &[u8],
instart: usize,
inend: usize,
maxblocks: u16,
splitpoints: &mut Vec<usize>,
) {
splitpoints.clear();
let mut store = Lz77Store::with_capacity(inend - instart);
{
store.greedy(&mut NoCache, in_data, instart, inend);
}
let mut lz77splitpoints = Vec::with_capacity(maxblocks as usize);
blocksplit_lz77(&store, maxblocks, &mut lz77splitpoints);
let nlz77points = lz77splitpoints.len();
let mut pos = instart;
if nlz77points > 0 {
for (i, item) in store.litlens.iter().enumerate() {
let length = item.size();
if lz77splitpoints[splitpoints.len()] == i {
splitpoints.push(pos);
if splitpoints.len() == nlz77points {
break;
}
}
pos += length;
}
}
debug_assert_eq!(splitpoints.len(), nlz77points);
}