Skip to main content

scirs2_io/compression/
mod.rs

1//! Compression utilities for scientific data
2//!
3//! This module provides functionality for compressing and decompressing data using
4//! various algorithms suitable for scientific computing. It focuses on lossless
5//! compression to ensure data integrity while reducing storage requirements.
6//!
7//! ## Features
8//!
9//! - Multiple compression algorithms (GZIP, ZSTD, LZ4, BZIP2)
10//! - Configurable compression levels
11//! - Memory-efficient compression of large datasets
12//! - Metadata preservation during compression
13//! - Array-specific compression optimizations
14//!
15//! ## Sub-modules
16//!
17//! - `ndarray`: Specialized compression utilities for ndarray types
18//!
19//! ## Examples
20//!
21//! ```rust,no_run
22//! use scirs2_io::compression::{compress_data, decompress_data, CompressionAlgorithm};
23//! use std::fs::File;
24//! use std::io::prelude::*;
25//!
26//! // Compress some data using ZSTD with default compression level
27//! let data = b"Large scientific dataset with repetitive patterns";
28//! let compressed = compress_data(data, CompressionAlgorithm::Zstd, None).unwrap();
29//!
30//! // Save the compressed data to a file
31//! let mut file = File::create("data.zst").unwrap();
32//! file.write_all(&compressed).unwrap();
33//!
34//! // Later, read and decompress the data
35//! let mut compressed_data = Vec::new();
36//! File::open("data.zst").unwrap().read_to_end(&mut compressed_data).unwrap();
37//! let original = decompress_data(&compressed_data, CompressionAlgorithm::Zstd).unwrap();
38//! assert_eq!(original, data);
39//! ```
40
41use std::fs::File;
42use std::io::{Read, Write};
43use std::path::Path;
44
45// Pure Rust compression libraries (COOLJAPAN Policy)
46use oxiarc_archive::{bzip2, gzip, lz4, zstd};
47
48// Re-export ndarray submodule
49pub mod advanced;
50pub mod ndarray;
51
52use crate::error::{IoError, Result};
53
54/// Compression algorithms supported by the library
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum CompressionAlgorithm {
57    /// GZIP compression (good balance of speed and compression ratio)
58    Gzip,
59    /// Zstandard compression (excellent compression ratio, fast decompression)
60    Zstd,
61    /// LZ4 compression (extremely fast, moderate compression ratio)
62    Lz4,
63    /// BZIP2 compression (high compression ratio, slower speed)
64    Bzip2,
65    /// Brotli compression (excellent compression ratio, web-optimized)
66    Brotli,
67    /// Snappy compression (very fast, Google-developed)
68    Snappy,
69    /// Floating-point specific compression optimized for scientific data
70    FpZip,
71    /// Delta + LZ4 compression for time series data
72    DeltaLz4,
73}
74
75impl CompressionAlgorithm {
76    /// Get the file extension associated with this compression algorithm
77    pub fn extension(&self) -> &'static str {
78        match self {
79            CompressionAlgorithm::Gzip => "gz",
80            CompressionAlgorithm::Zstd => "zst",
81            CompressionAlgorithm::Lz4 => "lz4",
82            CompressionAlgorithm::Bzip2 => "bz2",
83            CompressionAlgorithm::Brotli => "br",
84            CompressionAlgorithm::Snappy => "snappy",
85            CompressionAlgorithm::FpZip => "fpz",
86            CompressionAlgorithm::DeltaLz4 => "dlz4",
87        }
88    }
89
90    /// Try to determine the compression algorithm from a file extension
91    pub fn from_extension(ext: &str) -> Option<Self> {
92        match ext.to_lowercase().as_str() {
93            "gz" | "gzip" => Some(CompressionAlgorithm::Gzip),
94            "zst" | "zstd" => Some(CompressionAlgorithm::Zstd),
95            "lz4" => Some(CompressionAlgorithm::Lz4),
96            "bz2" | "bzip2" => Some(CompressionAlgorithm::Bzip2),
97            "br" | "brotli" => Some(CompressionAlgorithm::Brotli),
98            "snappy" | "snp" => Some(CompressionAlgorithm::Snappy),
99            "fpz" | "fpzip" => Some(CompressionAlgorithm::FpZip),
100            "dlz4" | "delta-lz4" => Some(CompressionAlgorithm::DeltaLz4),
101            _ => None,
102        }
103    }
104}
105
106/// Convert a compression level (0-9) to the appropriate internal level for each algorithm
107#[allow(dead_code)]
108fn normalize_level(level: Option<u32>, algorithm: CompressionAlgorithm) -> Result<u32> {
109    let _level = level.unwrap_or(6); // Default compression level
110
111    if _level > 9 {
112        return Err(IoError::CompressionError(format!(
113            "Compression level must be between 0 and 9, got {_level}"
114        )));
115    }
116
117    // Each compression library has different ranges for compression levels
118    match algorithm {
119        CompressionAlgorithm::Gzip => Ok(_level),
120        CompressionAlgorithm::Zstd => {
121            // ZSTD supports levels 1-22, map our 0-9 to 1-22
122            Ok(1 + (_level * 21) / 9)
123        }
124        CompressionAlgorithm::Lz4 => Ok(_level),
125        CompressionAlgorithm::Bzip2 => Ok(_level),
126        CompressionAlgorithm::Brotli => {
127            // Brotli supports levels 0-11, map our 0-9 to 0-11
128            Ok((_level * 11) / 9)
129        }
130        CompressionAlgorithm::Snappy => Ok(0), // Snappy has no compression levels
131        CompressionAlgorithm::FpZip => Ok(_level), // Will implement custom handling
132        CompressionAlgorithm::DeltaLz4 => Ok(_level), // Uses LZ4 internally
133    }
134}
135
136/// Compress data using the specified algorithm and compression level
137///
138/// # Arguments
139///
140/// * `data` - The data to compress
141/// * `algorithm` - The compression algorithm to use
142/// * `level` - The compression level (0-9, where 0 is no compression, 9 is maximum compression)
143///
144/// # Returns
145///
146/// The compressed data as a `Vec<u8>`
147#[allow(dead_code)]
148pub fn compress_data(
149    mut data: &[u8],
150    algorithm: CompressionAlgorithm,
151    level: Option<u32>,
152) -> Result<Vec<u8>> {
153    let normalized_level = normalize_level(level, algorithm)?;
154
155    match algorithm {
156        CompressionAlgorithm::Gzip => {
157            // Use Pure Rust oxiarc-archive implementation
158            gzip::compress(data, normalized_level as u8)
159                .map_err(|e| IoError::CompressionError(e.to_string()))
160        }
161        CompressionAlgorithm::Zstd => {
162            // Use Pure Rust oxiarc-archive implementation
163            let writer = zstd::ZstdWriter::new();
164            writer
165                .compress(data)
166                .map_err(|e| IoError::CompressionError(e.to_string()))
167        }
168        CompressionAlgorithm::Lz4 => {
169            // Use Pure Rust oxiarc-archive implementation
170            use std::io::Write;
171            let mut writer = lz4::Lz4Writer::new(Vec::new());
172            writer
173                .write_compressed(data)
174                .map_err(|e| IoError::CompressionError(e.to_string()))?;
175            Ok(writer.into_inner())
176        }
177        CompressionAlgorithm::Bzip2 => {
178            // Use Pure Rust oxiarc-archive implementation
179            let writer = bzip2::Bzip2Writer::with_level(normalized_level as u8);
180            writer
181                .compress(data)
182                .map_err(|e| IoError::CompressionError(e.to_string()))
183        }
184        CompressionAlgorithm::Brotli => oxiarc_brotli::compress(data, normalized_level)
185            .map_err(|e| IoError::CompressionError(format!("Brotli compression failed: {e}"))),
186        CompressionAlgorithm::Snappy => Ok(oxiarc_snappy::compress(data)),
187        CompressionAlgorithm::FpZip => {
188            // Implement floating-point specific compression
189            compress_fpzip(data, normalized_level)
190        }
191        CompressionAlgorithm::DeltaLz4 => {
192            // Implement delta + LZ4 compression for time series
193            compress_delta_lz4(data, normalized_level)
194        }
195    }
196}
197
198/// Decompress data using the specified algorithm
199///
200/// # Arguments
201///
202/// * `data` - The compressed data
203/// * `algorithm` - The compression algorithm used
204///
205/// # Returns
206///
207/// The decompressed data as a `Vec<u8>`
208#[allow(dead_code)]
209pub fn decompress_data(data: &[u8], algorithm: CompressionAlgorithm) -> Result<Vec<u8>> {
210    match algorithm {
211        CompressionAlgorithm::Gzip => {
212            // Use Pure Rust oxiarc-archive implementation
213            use std::io::Cursor;
214            let mut cursor = Cursor::new(data);
215            gzip::decompress(&mut cursor).map_err(|e| IoError::DecompressionError(e.to_string()))
216        }
217        CompressionAlgorithm::Zstd => {
218            // Use Pure Rust oxiarc-archive implementation
219            use std::io::Cursor;
220            let cursor = Cursor::new(data);
221            let mut reader = zstd::ZstdReader::new(cursor)
222                .map_err(|e| IoError::DecompressionError(e.to_string()))?;
223            reader
224                .decompress()
225                .map_err(|e| IoError::DecompressionError(e.to_string()))
226        }
227        CompressionAlgorithm::Lz4 => {
228            // Use Pure Rust oxiarc-archive implementation
229            use std::io::Cursor;
230            let cursor = Cursor::new(data);
231            let mut reader = lz4::Lz4Reader::new(cursor)
232                .map_err(|e| IoError::DecompressionError(e.to_string()))?;
233            reader
234                .decompress()
235                .map_err(|e| IoError::DecompressionError(e.to_string()))
236        }
237        CompressionAlgorithm::Bzip2 => {
238            // Use Pure Rust oxiarc-archive implementation
239            bzip2::decompress(data).map_err(|e| IoError::DecompressionError(e.to_string()))
240        }
241        CompressionAlgorithm::Brotli => oxiarc_brotli::decompress(data)
242            .map_err(|e| IoError::DecompressionError(format!("Brotli decompression failed: {e}"))),
243        CompressionAlgorithm::Snappy => oxiarc_snappy::decompress(data)
244            .map_err(|e| IoError::DecompressionError(format!("Snappy decompression failed: {e}"))),
245        CompressionAlgorithm::FpZip => {
246            // Implement floating-point specific decompression
247            decompress_fpzip(data)
248        }
249        CompressionAlgorithm::DeltaLz4 => {
250            // Implement delta + LZ4 decompression for time series
251            decompress_delta_lz4(data)
252        }
253    }
254}
255
256/// Compress a file using the specified algorithm and save it to a new file
257///
258/// # Arguments
259///
260/// * `input_path` - Path to the file to compress
261/// * `output_path` - Path to save the compressed file (if None, appends algorithm extension to input_path)
262/// * `algorithm` - The compression algorithm to use
263/// * `level` - The compression level (0-9, where 0 is no compression, 9 is maximum compression)
264///
265/// # Returns
266///
267/// The path to the compressed file
268#[allow(dead_code)]
269pub fn compress_file<P: AsRef<Path>>(
270    input_path: P,
271    output_path: Option<P>,
272    algorithm: CompressionAlgorithm,
273    level: Option<u32>,
274) -> Result<String> {
275    // Read input file
276    let mut input_data = Vec::new();
277    File::open(input_path.as_ref())
278        .map_err(|e| IoError::FileError(format!("Failed to open input file: {e}")))?
279        .read_to_end(&mut input_data)
280        .map_err(|e| IoError::FileError(format!("Failed to read input file: {e}")))?;
281
282    // Compress the data
283    let compressed_data = compress_data(&input_data, algorithm, level)?;
284
285    // Determine output _path
286    let output_path_string = match output_path {
287        Some(path) => path.as_ref().to_string_lossy().to_string(),
288        None => {
289            // Generate output _path by appending algorithm extension
290            let mut path_buf = input_path.as_ref().to_path_buf();
291            let ext = algorithm.extension();
292
293            // Get the file name as a string
294            let file_name = path_buf
295                .file_name()
296                .ok_or_else(|| IoError::FileError("Invalid input file _path".to_string()))?
297                .to_string_lossy()
298                .to_string();
299
300            // Append the extension and update the file name
301            let new_file_name = format!("{file_name}.{ext}");
302            path_buf.set_file_name(new_file_name);
303
304            path_buf.to_string_lossy().to_string()
305        }
306    };
307
308    // Write the compressed data to the output file
309    File::create(&output_path_string)
310        .map_err(|e| IoError::FileError(format!("Failed to create output file: {e}")))?
311        .write_all(&compressed_data)
312        .map_err(|e| IoError::FileError(format!("Failed to write to output file: {e}")))?;
313
314    Ok(output_path_string)
315}
316
317/// Decompress a file using the specified algorithm and save it to a new file
318///
319/// # Arguments
320///
321/// * `input_path` - Path to the compressed file
322/// * `output_path` - Path to save the decompressed file (if None, removes algorithm extension from input_path)
323/// * `algorithm` - The compression algorithm to use (if None, tries to determine from file extension)
324///
325/// # Returns
326///
327/// The path to the decompressed file
328#[allow(dead_code)]
329pub fn decompress_file<P: AsRef<Path>>(
330    input_path: P,
331    output_path: Option<P>,
332    algorithm: Option<CompressionAlgorithm>,
333) -> Result<String> {
334    // Determine the compression algorithm
335    let algorithm = match algorithm {
336        Some(algo) => algo,
337        None => {
338            // Try to determine from the file extension
339            let ext = input_path
340                .as_ref()
341                .extension()
342                .ok_or_else(|| {
343                    IoError::DecompressionError("Unable to determine file extension".to_string())
344                })?
345                .to_string_lossy()
346                .to_string();
347
348            CompressionAlgorithm::from_extension(&ext)
349                .ok_or(IoError::UnsupportedCompressionAlgorithm(ext))?
350        }
351    };
352
353    // Read input file
354    let mut input_data = Vec::new();
355    File::open(input_path.as_ref())
356        .map_err(|e| IoError::FileError(format!("Failed to open input file: {e}")))?
357        .read_to_end(&mut input_data)
358        .map_err(|e| IoError::FileError(format!("Failed to read input file: {e}")))?;
359
360    // Decompress the data
361    let decompressed_data = decompress_data(&input_data, algorithm)?;
362
363    // Determine output _path
364    let output_path_string = match output_path {
365        Some(path) => path.as_ref().to_string_lossy().to_string(),
366        None => {
367            // Generate output _path by removing algorithm extension
368            let path_str = input_path.as_ref().to_string_lossy().to_string();
369            let ext = algorithm.extension();
370
371            if path_str.ends_with(&format!(".{ext}")) {
372                // Remove the extension
373                path_str[0..path_str.len() - ext.len() - 1].to_string()
374            } else {
375                // If the extension doesn't match, add a ".decompressed" suffix
376                format!("{path_str}.decompressed")
377            }
378        }
379    };
380
381    // Write the decompressed data to the output file
382    File::create(&output_path_string)
383        .map_err(|e| IoError::FileError(format!("Failed to create output file: {e}")))?
384        .write_all(&decompressed_data)
385        .map_err(|e| IoError::FileError(format!("Failed to write to output file: {e}")))?;
386
387    Ok(output_path_string)
388}
389
390/// Calculate the compression ratio for the given data and algorithm
391///
392/// # Arguments
393///
394/// * `data` - The original data
395/// * `algorithm` - The compression algorithm to use
396/// * `level` - The compression level (0-9, optional)
397///
398/// # Returns
399///
400/// The compression ratio (original size / compressed size)
401#[allow(dead_code)]
402pub fn compression_ratio(
403    data: &[u8],
404    algorithm: CompressionAlgorithm,
405    level: Option<u32>,
406) -> Result<f64> {
407    let compressed = compress_data(data, algorithm, level)?;
408    let original_size = data.len() as f64;
409    let compressed_size = compressed.len() as f64;
410
411    // Avoid division by zero
412    if compressed_size == 0.0 {
413        return Err(IoError::CompressionError(
414            "Compressed data has zero size".to_string(),
415        ));
416    }
417
418    Ok(original_size / compressed_size)
419}
420
421/// Information about a compression algorithm
422pub struct CompressionInfo {
423    /// Name of the compression algorithm
424    pub name: String,
425    /// Brief description of the algorithm
426    pub description: String,
427    /// Typical compression ratio for scientific data (higher is better)
428    pub typical_compression_ratio: f64,
429    /// Relative compression speed (1-10, higher is faster)
430    pub compression_speed: u8,
431    /// Relative decompression speed (1-10, higher is faster)
432    pub decompression_speed: u8,
433    /// File extension associated with this compression
434    pub file_extension: String,
435}
436
437/// Get information about a specific compression algorithm
438#[allow(dead_code)]
439pub fn algorithm_info(algorithm: CompressionAlgorithm) -> CompressionInfo {
440    match algorithm {
441        CompressionAlgorithm::Gzip => CompressionInfo {
442            name: "GZIP".to_string(),
443            description: "General-purpose compression _algorithm with good balance of speed and compression ratio".to_string(),
444            typical_compression_ratio: 2.5,
445            compression_speed: 6,
446            decompression_speed: 7,
447            file_extension: "gz".to_string(),
448        },
449        CompressionAlgorithm::Zstd => CompressionInfo {
450            name: "Zstandard".to_string(),
451            description: "Modern compression _algorithm with excellent compression ratio and fast decompression".to_string(),
452            typical_compression_ratio: 3.2,
453            compression_speed: 7,
454            decompression_speed: 9,
455            file_extension: "zst".to_string(),
456        },
457        CompressionAlgorithm::Lz4 => CompressionInfo {
458            name: "LZ4".to_string(),
459            description: "Extremely fast compression _algorithm with moderate compression ratio".to_string(),
460            typical_compression_ratio: 1.8,
461            compression_speed: 10,
462            decompression_speed: 10,
463            file_extension: "lz4".to_string(),
464        },
465        CompressionAlgorithm::Bzip2 => CompressionInfo {
466            name: "BZIP2".to_string(),
467            description: "High compression ratio but slower speed, good for archival storage".to_string(),
468            typical_compression_ratio: 3.5,
469            compression_speed: 3,
470            decompression_speed: 4,
471            file_extension: "bz2".to_string(),
472        },
473        CompressionAlgorithm::Brotli => CompressionInfo {
474            name: "Brotli".to_string(),
475            description: "Web-optimized compression _algorithm with excellent compression ratio".to_string(),
476            typical_compression_ratio: 3.8,
477            compression_speed: 5,
478            decompression_speed: 8,
479            file_extension: "br".to_string(),
480        },
481        CompressionAlgorithm::Snappy => CompressionInfo {
482            name: "Snappy".to_string(),
483            description: "Very fast compression _algorithm developed by Google".to_string(),
484            typical_compression_ratio: 1.5,
485            compression_speed: 10,
486            decompression_speed: 10,
487            file_extension: "snappy".to_string(),
488        },
489        CompressionAlgorithm::FpZip => CompressionInfo {
490            name: "FPZip".to_string(),
491            description: "Floating-point specific compression optimized for scientific data".to_string(),
492            typical_compression_ratio: 4.0,
493            compression_speed: 7,
494            decompression_speed: 8,
495            file_extension: "fpz".to_string(),
496        },
497        CompressionAlgorithm::DeltaLz4 => CompressionInfo {
498            name: "Delta+LZ4".to_string(),
499            description: "Delta encoding followed by LZ4 compression, optimized for time series data".to_string(),
500            typical_compression_ratio: 5.0,
501            compression_speed: 8,
502            decompression_speed: 9,
503            file_extension: "dlz4".to_string(),
504        },
505    }
506}
507
508/// Magic bytes for different compression formats
509const GZIP_MAGIC: &[u8] = &[0x1f, 0x8b];
510const ZSTD_MAGIC: &[u8] = &[0x28, 0xb5, 0x2f, 0xfd];
511const LZ4_MAGIC: &[u8] = &[0x04, 0x22, 0x4d, 0x18];
512const BZIP2_MAGIC: &[u8] = &[0x42, 0x5a, 0x68];
513const BROTLI_MAGIC: &[u8] = &[0xce, 0xb2, 0xcf, 0x81]; // Custom magic for our Brotli implementation
514const SNAPPY_MAGIC: &[u8] = &[0x73, 0x4e, 0x61, 0x50]; // "sNaP"
515const FPZIP_MAGIC: &[u8] = &[0x46, 0x50, 0x5a, 0x49]; // "FPZI"
516const DELTA_LZ4_MAGIC: &[u8] = &[0x44, 0x4c, 0x5a, 0x34]; // "DLZ4"
517
518/// Detect compression algorithm from magic bytes
519#[allow(dead_code)]
520pub fn detect_compression_from_bytes(data: &[u8]) -> Option<CompressionAlgorithm> {
521    if data.starts_with(GZIP_MAGIC) {
522        Some(CompressionAlgorithm::Gzip)
523    } else if data.starts_with(ZSTD_MAGIC) {
524        Some(CompressionAlgorithm::Zstd)
525    } else if data.starts_with(LZ4_MAGIC) {
526        Some(CompressionAlgorithm::Lz4)
527    } else if data.starts_with(BZIP2_MAGIC) {
528        Some(CompressionAlgorithm::Bzip2)
529    } else if data.starts_with(BROTLI_MAGIC) {
530        Some(CompressionAlgorithm::Brotli)
531    } else if data.starts_with(SNAPPY_MAGIC) {
532        Some(CompressionAlgorithm::Snappy)
533    } else if data.starts_with(FPZIP_MAGIC) {
534        Some(CompressionAlgorithm::FpZip)
535    } else if data.starts_with(DELTA_LZ4_MAGIC) {
536        Some(CompressionAlgorithm::DeltaLz4)
537    } else {
538        None
539    }
540}
541
542/// Transparent file handler that automatically handles compression/decompression
543pub struct TransparentFileHandler {
544    /// Automatically detect compression from file extension
545    pub auto_detect_extension: bool,
546    /// Automatically detect compression from file content
547    pub auto_detect_content: bool,
548    /// Default compression algorithm when creating new files
549    pub default_algorithm: CompressionAlgorithm,
550    /// Default compression level
551    pub default_level: Option<u32>,
552}
553
554impl Default for TransparentFileHandler {
555    fn default() -> Self {
556        Self {
557            auto_detect_extension: true,
558            auto_detect_content: true,
559            default_algorithm: CompressionAlgorithm::Zstd,
560            default_level: Some(6),
561        }
562    }
563}
564
565impl TransparentFileHandler {
566    /// Create a new transparent file handler with custom settings
567    pub fn new(
568        auto_detect_extension: bool,
569        auto_detect_content: bool,
570        default_algorithm: CompressionAlgorithm,
571        default_level: Option<u32>,
572    ) -> Self {
573        Self {
574            auto_detect_extension,
575            auto_detect_content,
576            default_algorithm,
577            default_level,
578        }
579    }
580
581    /// Read a file with automatic decompression
582    pub fn read_file<P: AsRef<Path>>(&self, path: P) -> Result<Vec<u8>> {
583        let mut file_data = Vec::new();
584        File::open(path.as_ref())
585            .map_err(|e| IoError::FileError(format!("Failed to open file: {e}")))?
586            .read_to_end(&mut file_data)
587            .map_err(|e| IoError::FileError(format!("Failed to read file: {e}")))?;
588
589        // Try to detect compression
590        let mut algorithm = None;
591
592        // Check file extension first if enabled
593        if self.auto_detect_extension {
594            if let Some(ext) = path.as_ref().extension() {
595                algorithm = CompressionAlgorithm::from_extension(&ext.to_string_lossy());
596            }
597        }
598
599        // Check content magic bytes if enabled and extension detection failed
600        if algorithm.is_none() && self.auto_detect_content {
601            algorithm = detect_compression_from_bytes(&file_data);
602        }
603
604        // Decompress if compression was detected
605        match algorithm {
606            Some(algo) => decompress_data(&file_data, algo),
607            None => Ok(file_data), // Return as-is if no compression detected
608        }
609    }
610
611    /// Write a file with automatic compression
612    pub fn write_file<P: AsRef<Path>>(&self, path: P, data: &[u8]) -> Result<()> {
613        let mut algorithm = None;
614        let level = self.default_level;
615
616        // Check if we should compress based on file extension
617        if self.auto_detect_extension {
618            if let Some(ext) = path.as_ref().extension() {
619                algorithm = CompressionAlgorithm::from_extension(&ext.to_string_lossy());
620            }
621        }
622
623        // Use default algorithm if no compression detected but we want to compress
624        if algorithm.is_none() && self.should_compress_by_default(&path) {
625            algorithm = Some(self.default_algorithm);
626        }
627
628        // Compress data if needed
629        let output_data = match algorithm {
630            Some(algo) => compress_data(data, algo, level)?,
631            None => data.to_vec(),
632        };
633
634        // Write to file
635        File::create(path.as_ref())
636            .map_err(|e| IoError::FileError(format!("Failed to create file: {e}")))?
637            .write_all(&output_data)
638            .map_err(|e| IoError::FileError(format!("Failed to write file: {e}")))?;
639
640        Ok(())
641    }
642
643    /// Determine if file should be compressed by default based on path
644    fn should_compress_by_default<P: AsRef<Path>>(&self, path: P) -> bool {
645        // Don't compress if file already has a compression extension
646        if let Some(ext) = path.as_ref().extension() {
647            let ext_str = ext.to_string_lossy().to_lowercase();
648            matches!(
649                ext_str.as_str(),
650                "gz" | "gzip" | "zst" | "zstd" | "lz4" | "bz2" | "bzip2"
651            )
652        } else {
653            false
654        }
655    }
656
657    /// Copy a file with transparent compression/decompression
658    pub fn copy_file<P: AsRef<Path>, Q: AsRef<Path>>(
659        &self,
660        source: P,
661        destination: Q,
662    ) -> Result<()> {
663        let data = self.read_file(source)?;
664        self.write_file(destination, &data)?;
665        Ok(())
666    }
667
668    /// Get file info including compression details
669    pub fn file_info<P: AsRef<Path>>(&self, path: P) -> Result<FileCompressionInfo> {
670        let mut file_data = Vec::new();
671        File::open(path.as_ref())
672            .map_err(|e| IoError::FileError(format!("Failed to open file: {e}")))?
673            .read_to_end(&mut file_data)
674            .map_err(|e| IoError::FileError(format!("Failed to read file: {e}")))?;
675
676        let original_size = file_data.len();
677
678        // Detect compression
679        let detected_algorithm = if self.auto_detect_content {
680            detect_compression_from_bytes(&file_data)
681        } else {
682            None
683        };
684
685        let extension_algorithm = if self.auto_detect_extension {
686            path.as_ref()
687                .extension()
688                .and_then(|ext| CompressionAlgorithm::from_extension(&ext.to_string_lossy()))
689        } else {
690            None
691        };
692
693        let is_compressed = detected_algorithm.is_some() || extension_algorithm.is_some();
694        let algorithm = detected_algorithm.or(extension_algorithm);
695
696        let uncompressed_size = if let Some(algo) = algorithm {
697            match decompress_data(&file_data, algo) {
698                Ok(decompressed) => Some(decompressed.len()),
699                Err(_) => None,
700            }
701        } else {
702            Some(original_size)
703        };
704
705        Ok(FileCompressionInfo {
706            path: path.as_ref().to_path_buf(),
707            is_compressed,
708            algorithm,
709            compressed_size: if is_compressed {
710                Some(original_size)
711            } else {
712                None
713            },
714            uncompressed_size,
715            compression_ratio: if let (Some(compressed), Some(uncompressed)) = (
716                if is_compressed {
717                    Some(original_size)
718                } else {
719                    None
720                },
721                uncompressed_size,
722            ) {
723                Some(uncompressed as f64 / compressed as f64)
724            } else {
725                None
726            },
727        })
728    }
729}
730
731/// Information about a file's compression status
732#[derive(Debug, Clone)]
733pub struct FileCompressionInfo {
734    /// Path to the file
735    pub path: std::path::PathBuf,
736    /// Whether the file is compressed
737    pub is_compressed: bool,
738    /// Detected compression algorithm
739    pub algorithm: Option<CompressionAlgorithm>,
740    /// Size of compressed data (if compressed)
741    pub compressed_size: Option<usize>,
742    /// Size of uncompressed data
743    pub uncompressed_size: Option<usize>,
744    /// Compression ratio (uncompressed / compressed)
745    pub compression_ratio: Option<f64>,
746}
747
748/// Global transparent file handler instance
749static GLOBAL_HANDLER: std::sync::OnceLock<TransparentFileHandler> = std::sync::OnceLock::new();
750
751/// Initialize the global transparent file handler
752#[allow(dead_code)]
753pub fn init_global_handler(handler: TransparentFileHandler) {
754    let _ = GLOBAL_HANDLER.set(handler);
755}
756
757/// Get a reference to the global transparent file handler
758#[allow(dead_code)]
759pub fn global_handler() -> &'static TransparentFileHandler {
760    GLOBAL_HANDLER.get_or_init(TransparentFileHandler::default)
761}
762
763/// Convenient function to read a file with automatic decompression using global handler
764#[allow(dead_code)]
765pub fn read_file_transparent<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
766    global_handler().read_file(path)
767}
768
769/// Convenient function to write a file with automatic compression using global handler
770#[allow(dead_code)]
771pub fn write_file_transparent<P: AsRef<Path>>(path: P, data: &[u8]) -> Result<()> {
772    global_handler().write_file(path, data)
773}
774
775/// Convenient function to copy a file with transparent compression/decompression using global handler
776#[allow(dead_code)]
777pub fn copy_file_transparent<P: AsRef<Path>, Q: AsRef<Path>>(
778    source: P,
779    destination: Q,
780) -> Result<()> {
781    global_handler().copy_file(source, destination)
782}
783
784/// Convenient function to get file compression info using global handler
785#[allow(dead_code)]
786pub fn file_info_transparent<P: AsRef<Path>>(path: P) -> Result<FileCompressionInfo> {
787    global_handler().file_info(path)
788}
789
790//
791// Specialized Scientific Data Compression Implementations
792//
793
794/// Compress floating-point data using specialized techniques
795#[allow(dead_code)]
796fn compress_fpzip(data: &[u8], level: u32) -> Result<Vec<u8>> {
797    // Simple implementation: magic header + floating-point optimized compression
798    let mut result = Vec::with_capacity(FPZIP_MAGIC.len() + data.len());
799
800    // Add magic header
801    result.extend_from_slice(FPZIP_MAGIC);
802
803    // For now, use a simple approach: if data length is divisible by 4 or 8,
804    // assume it's floating-point data and apply bit manipulation optimizations
805    if data.len().is_multiple_of(8) {
806        // Assume f64 data - apply bit manipulation to reduce entropy
807        let float_data =
808            unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f64, data.len() / 8) };
809
810        // XOR consecutive values to reduce entropy (delta-like encoding for floats)
811        let mut encoded_data = Vec::with_capacity(data.len());
812        if !float_data.is_empty() {
813            // Store first value as-is
814            encoded_data.extend_from_slice(&float_data[0].to_le_bytes());
815
816            // XOR subsequent values with previous
817            for i in 1..float_data.len() {
818                let current_bits = float_data[i].to_bits();
819                let prev_bits = float_data[i - 1].to_bits();
820                let xor_result = current_bits ^ prev_bits;
821                encoded_data.extend_from_slice(&xor_result.to_le_bytes());
822            }
823        }
824
825        // Compress the XOR'd data with LZ4 (Pure Rust implementation)
826        let compressed = compress_data(&encoded_data, CompressionAlgorithm::Lz4, Some(level))?;
827        result.extend_from_slice(&compressed);
828    } else if data.len().is_multiple_of(4) {
829        // Assume f32 data - apply similar technique
830        let float_data =
831            unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, data.len() / 4) };
832
833        let mut encoded_data = Vec::with_capacity(data.len());
834        if !float_data.is_empty() {
835            encoded_data.extend_from_slice(&float_data[0].to_le_bytes());
836
837            for i in 1..float_data.len() {
838                let current_bits = float_data[i].to_bits();
839                let prev_bits = float_data[i - 1].to_bits();
840                let xor_result = current_bits ^ prev_bits;
841                encoded_data.extend_from_slice(&xor_result.to_le_bytes());
842            }
843        }
844
845        let compressed = compress_data(&encoded_data, CompressionAlgorithm::Lz4, Some(level))?;
846        result.extend_from_slice(&compressed);
847    } else {
848        // Not aligned to float boundaries, use regular compression
849        let compressed = compress_data(data, CompressionAlgorithm::Zstd, Some(level))?;
850        result.extend_from_slice(&compressed);
851    }
852
853    Ok(result)
854}
855
856/// Decompress floating-point data
857#[allow(dead_code)]
858fn decompress_fpzip(data: &[u8]) -> Result<Vec<u8>> {
859    if !data.starts_with(FPZIP_MAGIC) {
860        return Err(IoError::DecompressionError(
861            "Invalid FPZip magic bytes".to_string(),
862        ));
863    }
864
865    let compressed_data = &data[FPZIP_MAGIC.len()..];
866
867    // Decompress the inner data
868    let decompressed = decompress_data(compressed_data, CompressionAlgorithm::Lz4)?;
869
870    // Check if we need to reverse the XOR encoding
871    if decompressed.len() % 8 == 0 {
872        // f64 data
873        let mut float_data = unsafe {
874            std::slice::from_raw_parts(decompressed.as_ptr() as *const u64, decompressed.len() / 8)
875        }
876        .to_vec();
877
878        // Reverse XOR encoding
879        for i in 1..float_data.len() {
880            float_data[i] ^= float_data[i - 1];
881        }
882
883        // Convert back to bytes
884        let result = unsafe {
885            std::slice::from_raw_parts(float_data.as_ptr() as *const u8, float_data.len() * 8)
886        }
887        .to_vec();
888        Ok(result)
889    } else if decompressed.len() % 4 == 0 {
890        // f32 data
891        let mut float_data = unsafe {
892            std::slice::from_raw_parts(decompressed.as_ptr() as *const u32, decompressed.len() / 4)
893        }
894        .to_vec();
895
896        for i in 1..float_data.len() {
897            float_data[i] ^= float_data[i - 1];
898        }
899
900        let result = unsafe {
901            std::slice::from_raw_parts(float_data.as_ptr() as *const u8, float_data.len() * 4)
902        }
903        .to_vec();
904        Ok(result)
905    } else {
906        // Regular decompression
907        decompress_data(compressed_data, CompressionAlgorithm::Zstd)
908    }
909}
910
911/// Compress time series data using delta encoding + LZ4
912#[allow(dead_code)]
913fn compress_delta_lz4(data: &[u8], level: u32) -> Result<Vec<u8>> {
914    let mut result = Vec::with_capacity(DELTA_LZ4_MAGIC.len() + data.len());
915
916    // Add magic header
917    result.extend_from_slice(DELTA_LZ4_MAGIC);
918
919    if data.len() < 8 {
920        // Too small for delta encoding, use regular LZ4
921        let compressed = compress_data(data, CompressionAlgorithm::Lz4, Some(level))?;
922        result.extend_from_slice(&compressed);
923        return Ok(result);
924    }
925
926    // Try to detect _data type and apply appropriate delta encoding
927    let mut delta_encoded = Vec::with_capacity(data.len());
928
929    if data.len().is_multiple_of(8) {
930        // Assume i64 or f64 time series
931        let values =
932            unsafe { std::slice::from_raw_parts(data.as_ptr() as *const i64, data.len() / 8) };
933
934        // Store first value
935        delta_encoded.extend_from_slice(&values[0].to_le_bytes());
936
937        // Store differences
938        for i in 1..values.len() {
939            let delta = values[i].wrapping_sub(values[i - 1]);
940            delta_encoded.extend_from_slice(&delta.to_le_bytes());
941        }
942    } else if data.len().is_multiple_of(4) {
943        // Assume i32 or f32 time series
944        let values =
945            unsafe { std::slice::from_raw_parts(data.as_ptr() as *const i32, data.len() / 4) };
946
947        delta_encoded.extend_from_slice(&values[0].to_le_bytes());
948
949        for i in 1..values.len() {
950            let delta = values[i].wrapping_sub(values[i - 1]);
951            delta_encoded.extend_from_slice(&delta.to_le_bytes());
952        }
953    } else {
954        // Byte-level delta encoding
955        delta_encoded.push(data[0]);
956        for i in 1..data.len() {
957            let delta = data[i].wrapping_sub(data[i - 1]);
958            delta_encoded.push(delta);
959        }
960    }
961
962    // Compress the delta-encoded data
963    let compressed = compress_data(&delta_encoded, CompressionAlgorithm::Lz4, Some(level))?;
964    result.extend_from_slice(&compressed);
965
966    Ok(result)
967}
968
969/// Decompress delta-encoded time series data
970#[allow(dead_code)]
971fn decompress_delta_lz4(data: &[u8]) -> Result<Vec<u8>> {
972    if !data.starts_with(DELTA_LZ4_MAGIC) {
973        return Err(IoError::DecompressionError(
974            "Invalid Delta-LZ4 magic bytes".to_string(),
975        ));
976    }
977
978    let compressed_data = &data[DELTA_LZ4_MAGIC.len()..];
979
980    // Decompress the delta-encoded data
981    let delta_data = decompress_data(compressed_data, CompressionAlgorithm::Lz4)?;
982
983    if delta_data.len() < 8 {
984        return Ok(delta_data);
985    }
986
987    // Reverse delta encoding
988    if delta_data.len() % 8 == 0 {
989        // i64/f64 data
990        let mut values = unsafe {
991            std::slice::from_raw_parts(delta_data.as_ptr() as *const i64, delta_data.len() / 8)
992        }
993        .to_vec();
994
995        // Reconstruct original values
996        for i in 1..values.len() {
997            values[i] = values[i - 1].wrapping_add(values[i]);
998        }
999
1000        let result =
1001            unsafe { std::slice::from_raw_parts(values.as_ptr() as *const u8, values.len() * 8) }
1002                .to_vec();
1003        Ok(result)
1004    } else if delta_data.len() % 4 == 0 {
1005        // i32/f32 data
1006        let mut values = unsafe {
1007            std::slice::from_raw_parts(delta_data.as_ptr() as *const i32, delta_data.len() / 4)
1008        }
1009        .to_vec();
1010
1011        for i in 1..values.len() {
1012            values[i] = values[i - 1].wrapping_add(values[i]);
1013        }
1014
1015        let result =
1016            unsafe { std::slice::from_raw_parts(values.as_ptr() as *const u8, values.len() * 4) }
1017                .to_vec();
1018        Ok(result)
1019    } else {
1020        // Byte-level reconstruction
1021        let mut result = delta_data.clone();
1022        for i in 1..result.len() {
1023            result[i] = result[i - 1].wrapping_add(result[i]);
1024        }
1025        Ok(result)
1026    }
1027}
1028
1029//
1030// Parallel Compression/Decompression
1031//
1032
1033use std::sync::atomic::{AtomicUsize, Ordering};
1034use std::sync::Arc;
1035use std::time::Instant;
1036
1037// Parallel iterator support via scirs2-core (re-exports rayon::prelude::*)
1038#[allow(unused_imports)]
1039use scirs2_core::parallel_ops::{IntoParallelIterator, ParallelIterator};
1040
1041/// Configuration for parallel compression/decompression operations
1042#[derive(Debug, Clone)]
1043pub struct ParallelCompressionConfig {
1044    /// Number of threads to use (0 means use all available cores)
1045    pub num_threads: usize,
1046    /// Size of each chunk in bytes for parallel processing
1047    pub chunk_size: usize,
1048    /// Buffer size for I/O operations
1049    pub buffer_size: usize,
1050    /// Whether to enable memory mapping for large files
1051    pub enable_memory_mapping: bool,
1052}
1053
1054impl Default for ParallelCompressionConfig {
1055    fn default() -> Self {
1056        Self {
1057            num_threads: 0,          // Use all available cores
1058            chunk_size: 1024 * 1024, // 1 MB chunks
1059            buffer_size: 64 * 1024,  // 64 KB buffer
1060            enable_memory_mapping: true,
1061        }
1062    }
1063}
1064
1065/// Statistics for parallel compression/decompression operations
1066#[derive(Debug, Clone)]
1067pub struct ParallelCompressionStats {
1068    /// Total number of chunks processed
1069    pub chunks_processed: usize,
1070    /// Total bytes processed (uncompressed)
1071    pub bytes_processed: usize,
1072    /// Total bytes output (compressed/decompressed)
1073    pub bytes_output: usize,
1074    /// Time taken for the operation in milliseconds
1075    pub operation_time_ms: f64,
1076    /// Throughput in bytes per second
1077    pub throughput_bps: f64,
1078    /// Compression ratio (input_size / output_size)
1079    pub compression_ratio: f64,
1080    /// Number of threads used
1081    pub threads_used: usize,
1082}
1083
1084/// Compress data in parallel using multiple threads
1085#[allow(dead_code)]
1086pub fn compress_data_parallel(
1087    data: &[u8],
1088    algorithm: CompressionAlgorithm,
1089    level: Option<u32>,
1090    config: ParallelCompressionConfig,
1091) -> Result<(Vec<u8>, ParallelCompressionStats)> {
1092    let start_time = Instant::now();
1093    let input_size = data.len();
1094
1095    // Configure thread pool
1096    let num_threads = if config.num_threads == 0 {
1097        std::thread::available_parallelism()
1098            .map(|p| p.get())
1099            .unwrap_or(1)
1100    } else {
1101        config.num_threads
1102    };
1103
1104    // For very small data, use sequential compression
1105    if input_size <= config.chunk_size {
1106        let compressed = compress_data(data, algorithm, level)?;
1107        let operation_time = start_time.elapsed().as_secs_f64() * 1000.0;
1108
1109        let stats = ParallelCompressionStats {
1110            chunks_processed: 1,
1111            bytes_processed: input_size,
1112            bytes_output: compressed.len(),
1113            operation_time_ms: operation_time,
1114            throughput_bps: input_size as f64 / (operation_time / 1000.0),
1115            compression_ratio: input_size as f64 / compressed.len() as f64,
1116            threads_used: 1,
1117        };
1118
1119        return Ok((compressed, stats));
1120    }
1121
1122    // Split data into chunks
1123    let chunk_size = config.chunk_size;
1124    let chunks: Vec<&[u8]> = data.chunks(chunk_size).collect();
1125    let chunk_count = chunks.len();
1126
1127    // Process chunks in parallel using rayon (via scirs2-core parallel_ops)
1128    let processed_count = Arc::new(AtomicUsize::new(0));
1129    let compressed_chunks: Result<Vec<Vec<u8>>> = chunks
1130        .into_par_iter()
1131        .map(|chunk| {
1132            let result = compress_data(chunk, algorithm, level);
1133            processed_count.fetch_add(1, Ordering::Relaxed);
1134            result
1135        })
1136        .collect();
1137
1138    let compressed_chunks = compressed_chunks?;
1139
1140    // Calculate total size and concatenate results
1141    let total_compressed_size: usize = compressed_chunks.iter().map(|chunk| chunk.len()).sum();
1142    let mut result = Vec::with_capacity(total_compressed_size + (chunk_count * 8)); // Extra space for chunk headers
1143
1144    // Write chunk headers (size information for decompression)
1145    result.extend_from_slice(&(chunk_count as u64).to_le_bytes());
1146    for chunk in &compressed_chunks {
1147        result.extend_from_slice(&(chunk.len() as u64).to_le_bytes());
1148    }
1149
1150    // Write compressed chunks
1151    for chunk in compressed_chunks {
1152        result.extend_from_slice(&chunk);
1153    }
1154
1155    let operation_time = start_time.elapsed().as_secs_f64() * 1000.0;
1156
1157    let stats = ParallelCompressionStats {
1158        chunks_processed: chunk_count,
1159        bytes_processed: input_size,
1160        bytes_output: result.len(),
1161        operation_time_ms: operation_time,
1162        throughput_bps: input_size as f64 / (operation_time / 1000.0),
1163        compression_ratio: input_size as f64 / result.len() as f64,
1164        threads_used: num_threads,
1165    };
1166
1167    Ok((result, stats))
1168}
1169
1170/// Decompress data in parallel using multiple threads
1171#[allow(dead_code)]
1172pub fn decompress_data_parallel(
1173    data: &[u8],
1174    algorithm: CompressionAlgorithm,
1175    config: ParallelCompressionConfig,
1176) -> Result<(Vec<u8>, ParallelCompressionStats)> {
1177    let start_time = Instant::now();
1178    let input_size = data.len();
1179
1180    // Configure thread pool
1181    let num_threads = if config.num_threads == 0 {
1182        std::thread::available_parallelism()
1183            .map(|p| p.get())
1184            .unwrap_or(1)
1185    } else {
1186        config.num_threads
1187    };
1188
1189    // Check if this is parallel-compressed data by looking for chunk headers
1190    if data.len() < 8 {
1191        // Too small to be parallel-compressed data, use sequential decompression
1192        let decompressed = decompress_data(data, algorithm)?;
1193        let operation_time = start_time.elapsed().as_secs_f64() * 1000.0;
1194
1195        let stats = ParallelCompressionStats {
1196            chunks_processed: 1,
1197            bytes_processed: input_size,
1198            bytes_output: decompressed.len(),
1199            operation_time_ms: operation_time,
1200            throughput_bps: decompressed.len() as f64 / (operation_time / 1000.0),
1201            compression_ratio: decompressed.len() as f64 / input_size as f64,
1202            threads_used: 1,
1203        };
1204
1205        return Ok((decompressed, stats));
1206    }
1207
1208    // Read chunk count
1209    let chunk_count = u64::from_le_bytes(
1210        data[0..8]
1211            .try_into()
1212            .map_err(|_| IoError::DecompressionError("Invalid chunk header".to_string()))?,
1213    ) as usize;
1214
1215    if chunk_count == 0 || chunk_count > data.len() / 8 {
1216        // Not parallel-compressed data or invalid, use sequential decompression
1217        let decompressed = decompress_data(data, algorithm)?;
1218        let operation_time = start_time.elapsed().as_secs_f64() * 1000.0;
1219
1220        let stats = ParallelCompressionStats {
1221            chunks_processed: 1,
1222            bytes_processed: input_size,
1223            bytes_output: decompressed.len(),
1224            operation_time_ms: operation_time,
1225            throughput_bps: decompressed.len() as f64 / (operation_time / 1000.0),
1226            compression_ratio: decompressed.len() as f64 / input_size as f64,
1227            threads_used: 1,
1228        };
1229
1230        return Ok((decompressed, stats));
1231    }
1232
1233    // Read chunk sizes
1234    let header_size = 8 + (chunk_count * 8);
1235    if data.len() < header_size {
1236        return Err(IoError::DecompressionError(
1237            "Truncated chunk headers".to_string(),
1238        ));
1239    }
1240
1241    let mut chunk_sizes = Vec::with_capacity(chunk_count);
1242    for i in 0..chunk_count {
1243        let start_idx = 8 + (i * 8);
1244        let size = u64::from_le_bytes(
1245            data[start_idx..start_idx + 8]
1246                .try_into()
1247                .map_err(|_| IoError::DecompressionError("Invalid chunk size".to_string()))?,
1248        ) as usize;
1249        chunk_sizes.push(size);
1250    }
1251
1252    // Extract compressed chunks
1253    let mut chunks = Vec::with_capacity(chunk_count);
1254    let mut offset = header_size;
1255
1256    for &size in &chunk_sizes {
1257        if offset + size > data.len() {
1258            return Err(IoError::DecompressionError(
1259                "Truncated chunk data".to_string(),
1260            ));
1261        }
1262        chunks.push(&data[offset..offset + size]);
1263        offset += size;
1264    }
1265
1266    // Decompress chunks in parallel using rayon (via scirs2-core parallel_ops)
1267    let processed_count = Arc::new(AtomicUsize::new(0));
1268    let decompressed_chunks: Result<Vec<Vec<u8>>> = chunks
1269        .into_par_iter()
1270        .map(|chunk| {
1271            let result = decompress_data(chunk, algorithm);
1272            processed_count.fetch_add(1, Ordering::Relaxed);
1273            result
1274        })
1275        .collect();
1276
1277    let decompressed_chunks = decompressed_chunks?;
1278
1279    // Concatenate results
1280    let total_size: usize = decompressed_chunks.iter().map(|chunk| chunk.len()).sum();
1281    let mut result = Vec::with_capacity(total_size);
1282
1283    for chunk in decompressed_chunks {
1284        result.extend_from_slice(&chunk);
1285    }
1286
1287    let operation_time = start_time.elapsed().as_secs_f64() * 1000.0;
1288
1289    let stats = ParallelCompressionStats {
1290        chunks_processed: chunk_count,
1291        bytes_processed: input_size,
1292        bytes_output: result.len(),
1293        operation_time_ms: operation_time,
1294        throughput_bps: result.len() as f64 / (operation_time / 1000.0),
1295        compression_ratio: result.len() as f64 / input_size as f64,
1296        threads_used: num_threads,
1297    };
1298
1299    Ok((result, stats))
1300}
1301
1302/// Compress a file in parallel and save it to a new file
1303#[allow(dead_code)]
1304pub fn compress_file_parallel<P: AsRef<Path>>(
1305    input_path: P,
1306    output_path: Option<P>,
1307    algorithm: CompressionAlgorithm,
1308    level: Option<u32>,
1309    config: ParallelCompressionConfig,
1310) -> Result<(String, ParallelCompressionStats)> {
1311    // Read input file
1312    let mut input_data = Vec::new();
1313    File::open(input_path.as_ref())
1314        .map_err(|e| IoError::FileError(format!("Failed to open input file: {e}")))?
1315        .read_to_end(&mut input_data)
1316        .map_err(|e| IoError::FileError(format!("Failed to read input file: {e}")))?;
1317
1318    // Compress the data in parallel
1319    let (compressed_data, stats) = compress_data_parallel(&input_data, algorithm, level, config)?;
1320
1321    // Determine output _path
1322    let output_path_string = match output_path {
1323        Some(path) => path.as_ref().to_string_lossy().to_string(),
1324        None => {
1325            let mut path_buf = input_path.as_ref().to_path_buf();
1326            let ext = algorithm.extension();
1327            let file_name = path_buf
1328                .file_name()
1329                .ok_or_else(|| IoError::FileError("Invalid input file _path".to_string()))?
1330                .to_string_lossy()
1331                .to_string();
1332            let new_file_name = format!("{file_name}.{ext}");
1333            path_buf.set_file_name(new_file_name);
1334            path_buf.to_string_lossy().to_string()
1335        }
1336    };
1337
1338    // Write the compressed data to the output file
1339    File::create(&output_path_string)
1340        .map_err(|e| IoError::FileError(format!("Failed to create output file: {e}")))?
1341        .write_all(&compressed_data)
1342        .map_err(|e| IoError::FileError(format!("Failed to write to output file: {e}")))?;
1343
1344    Ok((output_path_string, stats))
1345}
1346
1347/// Decompress a file in parallel and save it to a new file
1348#[allow(dead_code)]
1349pub fn decompress_file_parallel<P: AsRef<Path>>(
1350    input_path: P,
1351    output_path: Option<P>,
1352    algorithm: Option<CompressionAlgorithm>,
1353    config: ParallelCompressionConfig,
1354) -> Result<(String, ParallelCompressionStats)> {
1355    // Determine the compression algorithm
1356    let algorithm = match algorithm {
1357        Some(algo) => algo,
1358        None => {
1359            let ext = input_path
1360                .as_ref()
1361                .extension()
1362                .ok_or_else(|| {
1363                    IoError::DecompressionError("Unable to determine file extension".to_string())
1364                })?
1365                .to_string_lossy()
1366                .to_string();
1367
1368            CompressionAlgorithm::from_extension(&ext)
1369                .ok_or(IoError::UnsupportedCompressionAlgorithm(ext))?
1370        }
1371    };
1372
1373    // Read input file
1374    let mut input_data = Vec::new();
1375    File::open(input_path.as_ref())
1376        .map_err(|e| IoError::FileError(format!("Failed to open input file: {e}")))?
1377        .read_to_end(&mut input_data)
1378        .map_err(|e| IoError::FileError(format!("Failed to read input file: {e}")))?;
1379
1380    // Decompress the data in parallel
1381    let (decompressed_data, stats) = decompress_data_parallel(&input_data, algorithm, config)?;
1382
1383    // Determine output _path
1384    let output_path_string = match output_path {
1385        Some(path) => path.as_ref().to_string_lossy().to_string(),
1386        None => {
1387            let path_str = input_path.as_ref().to_string_lossy().to_string();
1388            let ext = algorithm.extension();
1389
1390            if path_str.ends_with(&format!(".{ext}")) {
1391                path_str[0..path_str.len() - ext.len() - 1].to_string()
1392            } else {
1393                format!("{path_str}.decompressed")
1394            }
1395        }
1396    };
1397
1398    // Write the decompressed data to the output file
1399    File::create(&output_path_string)
1400        .map_err(|e| IoError::FileError(format!("Failed to create output file: {e}")))?
1401        .write_all(&decompressed_data)
1402        .map_err(|e| IoError::FileError(format!("Failed to write to output file: {e}")))?;
1403
1404    Ok((output_path_string, stats))
1405}
1406
1407/// Benchmark compression performance for different algorithms and configurations
1408#[allow(dead_code)]
1409pub fn benchmark_compression_algorithms(
1410    data: &[u8],
1411    algorithms: &[CompressionAlgorithm],
1412    levels: &[u32],
1413    parallel_configs: &[ParallelCompressionConfig],
1414) -> Result<Vec<CompressionBenchmarkResult>> {
1415    let mut results = Vec::new();
1416
1417    for &algorithm in algorithms {
1418        for &level in levels {
1419            // Sequential compression
1420            let start_time = Instant::now();
1421            let compressed = compress_data(data, algorithm, Some(level))?;
1422            let sequential_time = start_time.elapsed().as_secs_f64() * 1000.0;
1423
1424            let decompressed = decompress_data(&compressed, algorithm)?;
1425            let sequential_decomp_time =
1426                start_time.elapsed().as_secs_f64() * 1000.0 - sequential_time;
1427
1428            assert_eq!(data, &decompressed, "Round-trip failed for {algorithm:?}");
1429
1430            // Parallel compression for each config
1431            for config in parallel_configs {
1432                let (par_compressed, par_comp_stats) =
1433                    compress_data_parallel(data, algorithm, Some(level), config.clone())?;
1434                let (par_decompressed, par_decomp_stats) =
1435                    decompress_data_parallel(&par_compressed, algorithm, config.clone())?;
1436
1437                assert_eq!(
1438                    data, &par_decompressed,
1439                    "Parallel round-trip failed for {algorithm:?}"
1440                );
1441
1442                results.push(CompressionBenchmarkResult {
1443                    algorithm,
1444                    level,
1445                    config: config.clone(),
1446                    input_size: data.len(),
1447                    compressed_size: compressed.len(),
1448                    parallel_compressed_size: par_compressed.len(),
1449                    sequential_compression_time_ms: sequential_time,
1450                    sequential_decompression_time_ms: sequential_decomp_time,
1451                    parallel_compression_stats: par_comp_stats,
1452                    parallel_decompression_stats: par_decomp_stats,
1453                    compression_ratio: data.len() as f64 / compressed.len() as f64,
1454                    parallel_compression_ratio: data.len() as f64 / par_compressed.len() as f64,
1455                });
1456            }
1457        }
1458    }
1459
1460    Ok(results)
1461}
1462
1463/// Results from compression benchmarking
1464#[derive(Debug, Clone)]
1465pub struct CompressionBenchmarkResult {
1466    /// The compression algorithm tested
1467    pub algorithm: CompressionAlgorithm,
1468    /// The compression level used
1469    pub level: u32,
1470    /// The parallel configuration used
1471    pub config: ParallelCompressionConfig,
1472    /// Size of input data
1473    pub input_size: usize,
1474    /// Size of sequentially compressed data
1475    pub compressed_size: usize,
1476    /// Size of parallel compressed data
1477    pub parallel_compressed_size: usize,
1478    /// Time for sequential compression
1479    pub sequential_compression_time_ms: f64,
1480    /// Time for sequential decompression  
1481    pub sequential_decompression_time_ms: f64,
1482    /// Statistics from parallel compression
1483    pub parallel_compression_stats: ParallelCompressionStats,
1484    /// Statistics from parallel decompression
1485    pub parallel_decompression_stats: ParallelCompressionStats,
1486    /// Compression ratio for sequential compression
1487    pub compression_ratio: f64,
1488    /// Compression ratio for parallel compression
1489    pub parallel_compression_ratio: f64,
1490}
1491
1492impl CompressionBenchmarkResult {
1493    /// Calculate the speedup factor for parallel compression vs sequential
1494    pub fn compression_speedup(&self) -> f64 {
1495        self.sequential_compression_time_ms / self.parallel_compression_stats.operation_time_ms
1496    }
1497
1498    /// Calculate the speedup factor for parallel decompression vs sequential
1499    pub fn decompression_speedup(&self) -> f64 {
1500        self.sequential_decompression_time_ms / self.parallel_decompression_stats.operation_time_ms
1501    }
1502
1503    /// Calculate the overhead factor for parallel compression (how much larger the parallel-compressed data is)
1504    pub fn compression_overhead(&self) -> f64 {
1505        self.parallel_compressed_size as f64 / self.compressed_size as f64
1506    }
1507}