use std::io::Write;
use crate::error::Result;
use crate::index::lucene::codec::data_output::CodecOutput;
pub const PACKED_VERSION_CURRENT: i32 = 1;
pub const FORMAT_PACKED: i32 = 0;
pub const MIN_BLOCK_SIZE: usize = 64;
const MIN_VALUE_EQUALS_0: u32 = 1;
const BPV_SHIFT: u32 = 1;
#[must_use]
pub fn bits_required(max_value: u64) -> u32 {
(64 - max_value.leading_zeros()).max(1)
}
#[must_use]
pub const fn max_value(bits_per_value: u32) -> u64 {
if bits_per_value >= 64 {
i64::MAX as u64
} else {
!(!0u64 << bits_per_value)
}
}
#[must_use]
pub const fn packed_byte_count(value_count: usize, bits_per_value: u32) -> usize {
(value_count * bits_per_value as usize).div_ceil(8)
}
#[must_use]
pub const fn zig_zag_encode(value: i64) -> i64 {
(value >> 63) ^ (value << 1)
}
pub fn write_packed<Sink: Write>(
output: &mut CodecOutput<Sink>,
values: &[u64],
bits_per_value: u32,
) -> Result<()> {
if bits_per_value == 0 || bits_per_value > 64 {
return Err(crate::error::Error::InvalidFormat {
details: format!("{bits_per_value} bits per value is outside 1..=64"),
});
}
let mut buffer = vec![0u8; packed_byte_count(values.len(), bits_per_value)];
let mut bit = 0usize;
for value in values {
for offset in (0..bits_per_value).rev() {
if (value >> offset) & 1 == 1 {
buffer[bit / 8] |= 0x80 >> (bit % 8);
}
bit += 1;
}
}
output.write_bytes(&buffer)
}
pub fn write_block_packed<Sink: Write>(
output: &mut CodecOutput<Sink>,
values: &[i64],
) -> Result<()> {
if values.is_empty() {
return Err(crate::error::Error::InvalidFormat {
details: "a block-packed block holds at least one value".to_owned(),
});
}
let minimum = values.iter().copied().min().unwrap_or(0);
let maximum = values.iter().copied().max().unwrap_or(0);
let delta = maximum.wrapping_sub(minimum);
let bits = match delta {
negative if negative < 0 => 64,
0 => 0,
positive => bits_required(positive as u64),
};
let minimum = if bits == 64 {
0
} else if minimum > 0 {
0.max(maximum.wrapping_sub(max_value(bits) as i64))
} else {
minimum
};
let token = (bits << BPV_SHIFT) | if minimum == 0 { MIN_VALUE_EQUALS_0 } else { 0 };
output.write_byte(token as u8)?;
if minimum != 0 {
output.write_vlong_signed(zig_zag_encode(minimum).wrapping_sub(1))?;
}
if bits > 0 {
let shifted: Vec<u64> = values
.iter()
.map(|value| value.wrapping_sub(minimum) as u64)
.collect();
write_packed(output, &shifted, bits)?;
}
Ok(())
}
pub fn write_monotonic_block_packed<Sink: Write>(
output: &mut CodecOutput<Sink>,
values: &[i64],
) -> Result<()> {
if values.is_empty() {
return Err(crate::error::Error::InvalidFormat {
details: "a monotonic block holds at least one value".to_owned(),
});
}
let minimum = values[0];
let count = values.len();
#[expect(
clippy::cast_precision_loss,
reason = "docs/analysis/lucene-4-7-codec.md §2.3: the average is a float by design"
)]
let average: f32 = if count == 1 {
0.0
} else {
(values[count - 1].wrapping_sub(minimum)) as f32 / (count - 1) as f32
};
let deltas: Vec<i64> = values
.iter()
.enumerate()
.map(|(index, value)| {
#[expect(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
reason = "the reader replays exactly this: a float multiply, then a \
truncation toward zero (§2.3)"
)]
let expected = (average * index as f32) as i64;
zig_zag_encode(value.wrapping_sub(minimum).wrapping_sub(expected))
})
.collect();
let widest = deltas.iter().copied().max().unwrap_or(0);
output.write_vlong(minimum)?;
output.write_int(average.to_bits() as i32)?;
if widest == 0 {
output.write_vint(0)?;
} else {
let bits = bits_required(widest as u64);
output.write_vint(bits as i32)?;
let encoded: Vec<u64> = deltas.iter().map(|delta| *delta as u64).collect();
write_packed(output, &encoded, bits)?;
}
Ok(())
}