use alloc::{
alloc::{alloc, handle_alloc_error, Layout},
boxed::Box,
};
use core::ptr::{addr_of, addr_of_mut, NonNull};
#[cfg(feature = "std")]
use lockfree_object_pool::LinearObjectPool;
#[cfg(feature = "std")]
use once_cell::sync::Lazy;
use crate::util::{ZOPFLI_MIN_MATCH, ZOPFLI_WINDOW_MASK, ZOPFLI_WINDOW_SIZE};
const HASH_SHIFT: i32 = 5;
const HASH_MASK: u16 = 32767;
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Which {
Hash1,
Hash2,
}
#[derive(Clone)]
pub struct SmallerHashThing {
prev: u16, hashval: Option<u16>, }
#[derive(Clone)]
pub struct HashThing {
head: [i16; 65536], prev_and_hashval: [SmallerHashThing; ZOPFLI_WINDOW_SIZE],
val: u16, }
impl HashThing {
fn update(&mut self, hpos: usize) {
let hashval = self.val;
let index = self.val as usize;
let head_index = self.head[index];
let prev = if head_index >= 0
&& self.prev_and_hashval[head_index as usize]
.hashval
.map_or(false, |hv| hv == self.val)
{
head_index as u16
} else {
hpos as u16
};
self.prev_and_hashval[hpos] = SmallerHashThing {
prev,
hashval: Some(hashval),
};
self.head[index] = hpos as i16;
}
}
#[derive(Clone)]
pub struct ZopfliHash {
hash1: HashThing,
hash2: HashThing,
pub same: [u16; ZOPFLI_WINDOW_SIZE], }
impl ZopfliHash {
pub fn new() -> Box<ZopfliHash> {
const LAYOUT: Layout = Layout::new::<ZopfliHash>();
let ptr = NonNull::new(unsafe { alloc(LAYOUT) } as *mut ZopfliHash)
.unwrap_or_else(|| handle_alloc_error(LAYOUT));
unsafe {
Self::init(ptr);
Box::from_raw(ptr.as_ptr())
}
}
unsafe fn init(hash: NonNull<Self>) {
let hash = hash.as_ptr();
for i in 0..ZOPFLI_WINDOW_SIZE {
let prev_and_hashval =
(addr_of_mut!((*hash).hash1.prev_and_hashval) as *mut SmallerHashThing).add(i);
addr_of_mut!((*prev_and_hashval).prev).write(i as u16);
addr_of_mut!((*prev_and_hashval).hashval).write(None);
}
addr_of_mut!((*hash).hash1.head).write_bytes(0xFF, 1);
addr_of_mut!((*hash).hash1.val).write(0);
addr_of_mut!((*hash).hash2).copy_from_nonoverlapping(addr_of!((*hash).hash1), 1);
addr_of_mut!((*hash).same).write_bytes(0, 1);
}
pub fn reset(&mut self) {
unsafe { Self::init(NonNull::new(self).unwrap()) }
}
pub fn warmup(&mut self, arr: &[u8], pos: usize, end: usize) {
let c = arr[pos];
self.update_val(c);
if pos + 1 < end {
let c = arr[pos + 1];
self.update_val(c);
}
}
fn update_val(&mut self, c: u8) {
self.hash1.val = ((self.hash1.val << HASH_SHIFT) ^ c as u16) & HASH_MASK;
}
pub fn update(&mut self, array: &[u8], pos: usize) {
let hash_value = array.get(pos + ZOPFLI_MIN_MATCH - 1).cloned().unwrap_or(0);
self.update_val(hash_value);
let hpos = pos & ZOPFLI_WINDOW_MASK;
self.hash1.update(hpos);
let mut amount = 0;
let same_index = pos.wrapping_sub(1) & ZOPFLI_WINDOW_MASK;
let same = self.same[same_index];
if same > 1 {
amount = same - 1;
}
self.same[hpos] = amount;
self.hash2.val = (amount.wrapping_sub(ZOPFLI_MIN_MATCH as u16) & 255) ^ self.hash1.val;
self.hash2.update(hpos);
}
pub fn prev_at(&self, index: usize, which: Which) -> usize {
(match which {
Which::Hash1 => self.hash1.prev_and_hashval[index].prev,
Which::Hash2 => self.hash2.prev_and_hashval[index].prev,
}) as usize
}
pub fn hash_val_at(&self, index: usize, which: Which) -> i32 {
let hashval = match which {
Which::Hash1 => self.hash1.prev_and_hashval[index].hashval,
Which::Hash2 => self.hash2.prev_and_hashval[index].hashval,
};
hashval.map_or(-1, |hv| hv as i32)
}
pub fn val(&self, which: Which) -> u16 {
match which {
Which::Hash1 => self.hash1.val,
Which::Hash2 => self.hash2.val,
}
}
}
#[cfg(feature = "std")]
pub static HASH_POOL: Lazy<LinearObjectPool<Box<ZopfliHash>>> =
Lazy::new(|| LinearObjectPool::new(ZopfliHash::new, |boxed| boxed.reset()));
#[cfg(not(feature = "std"))]
#[derive(Copy, Clone)]
pub struct ZopfliHashFactory;
#[cfg(not(feature = "std"))]
impl ZopfliHashFactory {
pub fn pull(self) -> Box<ZopfliHash> {
ZopfliHash::new()
}
}
#[cfg(not(feature = "std"))]
pub static HASH_POOL: ZopfliHashFactory = ZopfliHashFactory;