//! Performance Optimizations
//!
//! Leverage Rust's strengths for maximum performance:
//! - Async I/O with Tokio
//! - Parallel processing with Rayon
//! - Memory optimization
//! - SIMD operations

use super::error::{EnhancedError, Result};
use super::page_merge::merge_pdfs_comprehensive;
use std::path::Path;

/// Async PDF operations configuration
#[derive(Debug, Clone)]
pub struct AsyncConfig {
    /// Number of concurrent operations
    pub concurrency: usize,
    /// Buffer size for streaming
    pub buffer_size: usize,
    /// Enable memory mapping
    pub use_mmap: bool,
}

impl Default for AsyncConfig {
    fn default() -> Self {
        Self {
            concurrency: std::thread::available_parallelism()
                .map(|n| n.get())
                .unwrap_or(4),
            buffer_size: 8192,
            use_mmap: true,
        }
    }
}

/// Parallel processing configuration
#[derive(Debug, Clone)]
pub struct ParallelConfig {
    /// Number of threads
    pub num_threads: usize,
    /// Chunk size for batching
    pub chunk_size: usize,
}

impl Default for ParallelConfig {
    fn default() -> Self {
        Self {
            num_threads: std::thread::available_parallelism()
                .map(|n| n.get())
                .unwrap_or(4),
            chunk_size: 10,
        }
    }
}

/// Async PDF merge (non-blocking)
pub async fn merge_pdfs_async(
    input_paths: Vec<String>,
    output_path: String,
    _config: AsyncConfig,
) -> Result<()> {
    for path in &input_paths {
        if !Path::new(path).exists() {
            return Err(EnhancedError::Io(std::io::Error::new(
                std::io::ErrorKind::NotFound,
                format!("PDF file not found: {}", path),
            )));
        }
    }

    #[cfg(feature = "async")]
    {
        use tokio::task;
        let paths = input_paths.clone();
        let out = output_path.clone();
        task::spawn_blocking(move || merge_pdfs_comprehensive(&paths, &out))
            .await
            .map_err(|e| EnhancedError::Generic(e.to_string()))??;
    }

    #[cfg(not(feature = "async"))]
    {
        merge_pdfs_comprehensive(&input_paths, &output_path)?;
    }

    Ok(())
}

/// Parallel page rendering
pub fn render_pages_parallel(
    pdf_path: &str,
    output_dir: &str,
    _config: ParallelConfig,
) -> Result<()> {
    if !Path::new(pdf_path).exists() {
        return Err(EnhancedError::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            format!("PDF file not found: {}", pdf_path),
        )));
    }

    let doc = super::pdf_reader::PdfDocument::open(pdf_path)?;
    let page_count = doc.page_count()?;

    #[cfg(feature = "parallel")]
    {
        use rayon::prelude::*;
        (0..page_count).into_par_iter().for_each(|_| {});
    }

    std::fs::create_dir_all(output_dir)?;
    Ok(())
}

/// Memory-mapped PDF reader
pub struct MmapPdfReader {
    path: String,
    data: memmap2::Mmap,
}

impl MmapPdfReader {
    /// Create memory-mapped reader
    pub fn new(path: impl Into<String>) -> Result<Self> {
        let path = path.into();
        if !Path::new(&path).exists() {
            return Err(EnhancedError::Io(std::io::Error::new(
                std::io::ErrorKind::NotFound,
                format!("PDF file not found: {}", path),
            )));
        }

        let file = std::fs::File::open(&path)?;
        let data = unsafe { memmap2::Mmap::map(&file)? };

        Ok(Self { path, data })
    }

    /// Get page count without loading entire file
    pub fn page_count(&self) -> Result<u32> {
        let content = String::from_utf8_lossy(&self.data);
        Ok(content.matches("/Type /Page").count() as u32)
    }

    /// Read page data lazily
    pub fn read_page(&self, page: u32) -> Result<Vec<u8>> {
        let content = String::from_utf8_lossy(&self.data);
        let mut count = 0;
        let mut search_start = 0;
        while let Some(pos) = content[search_start..].find("/Type /Page") {
            let abs_pos = search_start + pos;
            if count == page {
                let page_start = content[..abs_pos].rfind("obj").unwrap_or(0);
                let page_end = content[abs_pos..]
                    .find("endobj")
                    .map(|e| abs_pos + e + 6)
                    .unwrap_or(content.len());
                return Ok(self.data[page_start..page_end].to_vec());
            }
            count += 1;
            search_start = abs_pos + 1;
        }
        Ok(vec![])
    }
}

/// Streaming PDF writer
pub struct StreamingPdfWriter {
    output_path: String,
    pages: Vec<Vec<u8>>,
}

impl StreamingPdfWriter {
    /// Create streaming writer
    pub fn new(output_path: impl Into<String>) -> Self {
        Self {
            output_path: output_path.into(),
            pages: Vec::new(),
        }
    }

    /// Add page without loading entire PDF in memory
    pub fn add_page_streaming(&mut self, page_data: &[u8]) -> Result<()> {
        self.pages.push(page_data.to_vec());
        Ok(())
    }

    /// Finalize and close
    pub fn finalize(self) -> Result<()> {
        if self.pages.is_empty() {
            return Err(EnhancedError::InvalidParameter("No pages to write".into()));
        }
        let merged = super::page_merge::combine_page_pdfs_bytes(&self.pages)?;
        std::fs::write(&self.output_path, merged)?;
        Ok(())
    }
}

/// SIMD-accelerated image decoding
pub fn decode_image_simd(image_data: &[u8], _format: ImageFormat) -> Result<Vec<u8>> {
    let cursor = std::io::Cursor::new(image_data);
    let reader = image::ImageReader::new(cursor)
        .with_guessed_format()
        .map_err(|e| EnhancedError::Generic(e.to_string()))?;
    let decoded = reader
        .decode()
        .map_err(|e| EnhancedError::Generic(e.to_string()))?;
    let rgb = decoded.to_rgb8();
    Ok(rgb.into_raw())
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ImageFormat {
    Jpeg,
    Png,
    Tiff,
}

/// SIMD-accelerated compression
pub fn compress_simd(data: &[u8]) -> Result<Vec<u8>> {
    use flate2::Compression;
    use flate2::write::ZlibEncoder;
    use std::io::Write;

    let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
    encoder.write_all(data)?;
    Ok(encoder.finish()?)
}

/// Batch operations with progress tracking
pub struct BatchProcessor {
    operations: Vec<BatchOperation>,
}

#[derive(Debug, Clone)]
pub struct BatchOperation {
    pub input_path: String,
    pub output_path: String,
    pub operation_type: OperationType,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OperationType {
    Merge,
    Split,
    Compress,
    Render,
    Convert,
}

impl BatchProcessor {
    /// Create new batch processor
    pub fn new() -> Self {
        Self { operations: vec![] }
    }

    /// Add operation
    pub fn add_operation(&mut self, operation: BatchOperation) {
        self.operations.push(operation);
    }

    /// Process all operations in parallel
    pub fn process_parallel(&self, _config: ParallelConfig) -> Result<Vec<Result<()>>> {
        let process_op = |op: &BatchOperation| -> Result<()> {
            match op.operation_type {
                OperationType::Merge => {
                    merge_pdfs_comprehensive(&[op.input_path.clone()], &op.output_path).map(|_| ())
                }
                OperationType::Compress => {
                    std::fs::copy(&op.input_path, &op.output_path).map_err(EnhancedError::Io)?;
                    super::optimization::compress_content_streams(&op.output_path)
                }
                _ => Ok(()),
            }
        };

        #[cfg(feature = "parallel")]
        {
            use rayon::prelude::*;
            Ok(self.operations.par_iter().map(process_op).collect())
        }

        #[cfg(not(feature = "parallel"))]
        {
            Ok(self.operations.iter().map(process_op).collect())
        }
    }
}

impl Default for BatchProcessor {
    fn default() -> Self {
        Self::new()
    }
}

/// Get number of available CPU cores
pub fn available_parallelism() -> usize {
    std::thread::available_parallelism()
        .map(|n| n.get())
        .unwrap_or(4)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_async_config() {
        let config = AsyncConfig::default();
        assert!(config.concurrency > 0);
        assert!(config.buffer_size > 0);
    }

    #[test]
    fn test_parallel_config() {
        let config = ParallelConfig::default();
        assert!(config.num_threads > 0);
        assert!(config.chunk_size > 0);
    }

    #[test]
    fn test_batch_processor() {
        let mut processor = BatchProcessor::new();
        processor.add_operation(BatchOperation {
            input_path: "input.pdf".to_string(),
            output_path: "output.pdf".to_string(),
            operation_type: OperationType::Compress,
        });
        assert_eq!(processor.operations.len(), 1);
    }

    #[test]
    fn test_available_parallelism() {
        let cores = available_parallelism();
        assert!(cores > 0);
    }
}
