use std::io::Write;
use crate::base_decompressor::{BaseDecompressor, State, Step};
use crate::bit_reader::BitReader;
use crate::body_decompressor::{BodyDecompressor, Numbers};
use crate::data_types::NumberLike;
use crate::errors::{ErrorKind, QCompressError, QCompressResult};
use crate::{ChunkMetadata, DecompressorConfig, Flags};
#[derive(Clone, Debug, Default)]
pub struct Decompressor<T: NumberLike>(BaseDecompressor<T>);
#[derive(Clone, Debug)]
pub enum DecompressedItem<T: NumberLike> {
Flags(Flags),
ChunkMetadata(ChunkMetadata<T>),
Numbers(Vec<T>),
Footer,
}
impl<T: NumberLike> Decompressor<T> {
pub fn from_config(config: DecompressorConfig) -> Self {
Self(BaseDecompressor::<T>::from_config(config))
}
pub fn header(&mut self) -> QCompressResult<Flags> {
self.0.header(false)
}
pub fn chunk_metadata(&mut self) -> QCompressResult<Option<ChunkMetadata<T>>> {
self
.0
.state
.check_step(Step::StartOfChunk, "read chunk metadata")?;
self.0.with_reader(|reader, state, _| {
let maybe_meta = state.chunk_meta_option_dirty(reader)?;
if maybe_meta.is_none() {
state.terminated = true;
}
state.chunk_meta = maybe_meta.clone();
Ok(maybe_meta)
})
}
pub fn skip_chunk_body(&mut self) -> QCompressResult<()> {
self.0.state.check_step_among(
&[Step::StartOfDataPage, Step::MidDataPage],
"skip chunk body",
)?;
let bits_remaining = match &self.0.state.body_decompressor {
Some(bd) => bd.bits_remaining(),
None => {
let meta = self.0.state.chunk_meta.as_ref().unwrap();
meta.compressed_body_size * 8
}
};
let skipped_bit_idx = self.0.state.bit_idx + bits_remaining;
if skipped_bit_idx <= self.0.words.total_bits {
self.0.state.bit_idx = skipped_bit_idx;
self.0.state.chunk_meta = None;
self.0.state.body_decompressor = None;
Ok(())
} else {
Err(QCompressError::insufficient_data(format!(
"unable to skip chunk body to bit index {} when only {} bits available",
skipped_bit_idx, self.0.words.total_bits,
)))
}
}
pub fn chunk_body(&mut self) -> QCompressResult<Vec<T>> {
self
.0
.state
.check_step(Step::StartOfDataPage, "read chunk body")?;
let &ChunkMetadata {
n,
compressed_body_size,
..
} = self.0.state.chunk_meta.as_ref().unwrap();
let res = self.0.data_page_internal(n, compressed_body_size)?;
self.0.state.chunk_meta = None;
Ok(res)
}
pub fn simple_decompress(&mut self) -> QCompressResult<Vec<T>> {
let mut res: Option<Vec<T>> = None;
self.header()?;
while self.chunk_metadata()?.is_some() {
let nums = self.chunk_body()?;
res = match res {
Some(mut existing) => {
existing.extend(nums);
Some(existing)
}
None => Some(nums),
};
}
Ok(res.unwrap_or_default())
}
pub fn free_compressed_memory(&mut self) {
self.0.free_compressed_memory()
}
pub fn bit_idx(&self) -> usize {
self.0.bit_idx()
}
}
fn next_nums_dirty<T: NumberLike>(
reader: &mut BitReader,
bd: &mut BodyDecompressor<T>,
config: &DecompressorConfig,
) -> QCompressResult<Numbers<T>> {
bd.decompress_next_batch(reader, config.numbers_limit_per_item, false)
}
fn apply_nums<T: NumberLike>(
state: &mut State<T>,
numbers: Numbers<T>,
) -> Option<DecompressedItem<T>> {
if numbers.nums.is_empty() {
None
} else {
if numbers.finished_body {
state.chunk_meta = None;
state.body_decompressor = None;
}
Some(DecompressedItem::Numbers(numbers.nums))
}
}
impl<T: NumberLike> Iterator for &mut Decompressor<T> {
type Item = QCompressResult<DecompressedItem<T>>;
fn next(&mut self) -> Option<Self::Item> {
let res: QCompressResult<Option<DecompressedItem<T>>> = match self.0.state.step() {
Step::PreHeader => match self.header() {
Ok(flags) => Ok(Some(DecompressedItem::Flags(flags))),
Err(e) if matches!(e.kind, ErrorKind::InsufficientData) => Ok(None),
Err(e) => Err(e),
},
Step::StartOfChunk => match self.chunk_metadata() {
Ok(Some(meta)) => Ok(Some(DecompressedItem::ChunkMetadata(meta))),
Ok(None) => Ok(Some(DecompressedItem::Footer)),
Err(e) if matches!(e.kind, ErrorKind::InsufficientData) => Ok(None),
Err(e) => Err(e),
},
Step::StartOfDataPage => self.0.with_reader(|reader, state, config| {
let &ChunkMetadata {
n,
compressed_body_size,
..
} = state.chunk_meta.as_ref().unwrap();
let mut bd = state.new_body_decompressor(reader, n, compressed_body_size)?;
let numbers = next_nums_dirty(reader, &mut bd, config)?;
state.body_decompressor = Some(bd);
Ok(apply_nums(state, numbers))
}),
Step::MidDataPage => self.0.with_reader(|reader, state, config| {
let numbers = next_nums_dirty(
reader,
state.body_decompressor.as_mut().unwrap(),
config,
)?;
Ok(apply_nums(state, numbers))
}),
Step::Terminated => Ok(None),
};
match res {
Ok(Some(x)) => Some(Ok(x)),
Ok(None) => None,
Err(e) => Some(Err(e)),
}
}
}
impl<T: NumberLike> Write for Decompressor<T> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.0.flush()
}
}