//! # Quasi-Monte Carlo Sequences
//!
//! This module provides implementations of low-discrepancy sequences and other
//! quasi-Monte Carlo (QMC) methods for more efficient Monte Carlo integration
//! and sampling compared to traditional pseudo-random sequences.
//!
//! ## Features
//!
//! * **Sobol sequences**: Multi-dimensional low-discrepancy sequences
//! * **Halton sequences**: Simple low-discrepancy sequences based on prime bases
//! * **Latin hypercube sampling**: Stratified sampling for space-filling designs
//! * **Faure sequences**: Another family of low-discrepancy sequences
//! * **Niederreiter sequences**: Generalization of Sobol sequences
//!
//! ## Usage
//!
//! ```rust
//! use scirs2_core::random::qmc::{SobolGenerator, HaltonGenerator, LatinHypercubeSampler, LowDiscrepancySequence};
//!
//! fn example() -> Result<(), Box<dyn std::error::Error>> {
//!     // Generate Sobol sequence in 2D
//!     let mut sobol = SobolGenerator::dimension(2)?;
//!     let points = sobol.generate(1000);
//!
//!     // Generate Halton sequence
//!     let mut halton = HaltonGenerator::new(&[2, 3]);
//!     let points = halton.generate(1000);
//!
//!     // Latin hypercube sampling
//!     let mut lhs = LatinHypercubeSampler::<rand::prelude::ThreadRng>::new(2);
//!     let points = lhs.sample(100)?;
//!     
//!     Ok(())
//! }
//! # example().expect("Operation failed");
//! ```

use ::ndarray::{Array1, Array2};
use rand::{Rng, RngExt};
use std::f64;
use thiserror::Error;

/// Error types for QMC operations
#[derive(Error, Debug)]
pub enum QmcError {
    /// Invalid dimension
    #[error("Invalid dimension: {0}. Must be between 1 and {1}")]
    InvalidDimension(usize, usize),

    /// Invalid number of points
    #[error("Invalid number of points: {0}. Must be positive")]
    InvalidPointCount(usize),

    /// Sequence initialization failed
    #[error("Sequence initialization failed: {0}")]
    InitializationFailed(String),

    /// Unsupported dimension for this sequence type
    #[error("Unsupported dimension {0} for sequence type")]
    UnsupportedDimension(usize),

    /// Invalid base for sequence generation
    #[error("Invalid base: {0}. Must be prime and greater than 1")]
    InvalidBase(u32),
}

/// Low-discrepancy sequence trait
pub trait LowDiscrepancySequence {
    /// Generate the next point in the sequence
    fn next_point(&mut self) -> Vec<f64>;

    /// Generate multiple points at once
    fn generate(&mut self, n: usize) -> Array2<f64> {
        let dim = self.dimension();
        let mut points = Array2::zeros((n, dim));

        for i in 0..n {
            let point = self.next_point();
            for j in 0..dim {
                points[[i, j]] = point[j];
            }
        }

        points
    }

    /// Get the dimension of the sequence
    fn dimension(&self) -> usize;

    /// Reset the sequence to the beginning
    fn reset(&mut self);

    /// Skip ahead in the sequence
    fn skip(&mut self, n: usize) {
        for _ in 0..n {
            self.next_point();
        }
    }
}

/// Sobol sequence generator
///
/// Generates points in `[0,1]^d` using the Sobol low-discrepancy sequence.
/// Excellent for high-dimensional integration and Monte Carlo methods.
pub struct SobolGenerator {
    dimension: usize,
    current_index: u64,
    direction_numbers: Vec<Vec<u32>>,
    current_point: Vec<u64>,
    max_bits: usize,
}

impl SobolGenerator {
    /// Maximum supported dimension for Sobol sequences
    pub const MAX_DIMENSION: usize = 21201;

    /// Create a new Sobol generator for the given dimension
    pub fn dimension(dimension: usize) -> Result<Self, QmcError> {
        if dimension == 0 || dimension > Self::MAX_DIMENSION {
            return Err(QmcError::InvalidDimension(dimension, Self::MAX_DIMENSION));
        }

        let max_bits = 63; // Using 64-bit integers, reserve 1 bit for safety
        let mut generator = Self {
            dimension,
            current_index: 0,
            direction_numbers: Vec::new(),
            current_point: vec![0; dimension],
            max_bits,
        };

        generator.initialize_direction_numbers()?;
        Ok(generator)
    }

    /// Initialize direction numbers for Sobol sequence
    fn initialize_direction_numbers(&mut self) -> Result<(), QmcError> {
        self.direction_numbers.clear();

        // First dimension uses powers of 2
        let mut first_dim = Vec::new();
        for i in 0..self.max_bits.min(32) {
            if i <= 31 {
                first_dim.push(1u32 << (31 - i));
            }
        }
        self.direction_numbers.push(first_dim);

        // For higher dimensions, we would need primitive polynomials and initial direction numbers
        // For now, implementing a simplified version with basic polynomials
        for dim in 1..self.dimension {
            let direction_nums = self.generate_direction_numbers_for_dimension(dim)?;
            self.direction_numbers.push(direction_nums);
        }

        Ok(())
    }

    /// Generate direction numbers for a specific dimension
    fn generate_direction_numbers_for_dimension(&self, dim: usize) -> Result<Vec<u32>, QmcError> {
        // Simplified implementation using basic recurrence relations
        // In a full implementation, this would use tabulated primitive polynomials
        let mut direction_nums = Vec::with_capacity(self.max_bits);

        // Use different starting values for different dimensions
        let base_values = [1, 1, 3, 1, 3, 3, 1];
        let poly_coeffs = [0, 1, 1, 2, 1, 4, 2]; // Simplified polynomial coefficients

        let start_val = base_values[dim % base_values.len()];
        let poly_coeff = poly_coeffs[dim % poly_coeffs.len()];

        // Initialize first few values
        direction_nums.push(start_val << 30);
        if self.max_bits > 1 {
            direction_nums.push((start_val * 2 + 1) << 29);
        }

        // Generate remaining values using recurrence relation
        for i in 2..self.max_bits {
            let prev2 = direction_nums[i - 2];
            let prev1 = direction_nums[i.saturating_sub(1)];

            // Simplified recurrence (real Sobol uses proper polynomial recurrence)
            let next_val = prev1 ^ (prev2 >> poly_coeff) ^ (prev1 >> 1);
            direction_nums.push(next_val);
        }

        Ok(direction_nums)
    }

    /// Get the discrepancy of the current sequence (quality measure)
    pub fn estimate_discrepancy(&self, n: usize) -> f64 {
        // Simplified discrepancy estimation
        // Real implementation would compute L2 or star discrepancy
        let base_discrepancy = (self.dimension as f64).ln() / (n as f64);
        base_discrepancy.max(1e-10)
    }
}

impl LowDiscrepancySequence for SobolGenerator {
    fn next_point(&mut self) -> Vec<f64> {
        if self.current_index == 0 {
            self.current_index += 1;
            return vec![0.0; self.dimension];
        }

        // Find the rightmost zero bit in the current index
        let rightmost_zero_pos = (!self.current_index).trailing_zeros() as usize;

        // Update current point using Gray code ordering
        for dim in 0..self.dimension {
            if rightmost_zero_pos < self.direction_numbers[dim].len() {
                self.current_point[dim] ^= self.direction_numbers[dim][rightmost_zero_pos] as u64;
            }
        }

        self.current_index += 1;

        // Convert to floating point in [0,1)
        self.current_point
            .iter()
            .map(|&x| (x as f64) / (1u64 << 32) as f64)
            .collect()
    }

    fn dimension(&self) -> usize {
        self.dimension
    }

    fn reset(&mut self) {
        self.current_index = 0;
        self.current_point.fill(0);
    }
}

/// Halton sequence generator
///
/// Generates points using the Halton sequence based on coprime bases.
/// Simpler than Sobol but can have correlation issues in higher dimensions.
pub struct HaltonGenerator {
    dimension: usize,
    bases: Vec<u32>,
    indices: Vec<u64>,
}

impl HaltonGenerator {
    /// Create a new Halton generator with specified bases
    pub fn new(bases: &[u32]) -> Self {
        let dimension = bases.len();
        Self {
            dimension,
            bases: bases.to_vec(),
            indices: vec![0; dimension],
        }
    }

    /// Create a Halton generator using the first n prime numbers as bases
    pub fn dimension(dimension: usize) -> Result<Self, QmcError> {
        if dimension == 0 {
            return Err(QmcError::InvalidDimension(dimension, 1000));
        }

        let primes = Self::generate_primes(dimension);
        Ok(Self::new(&primes))
    }

    /// Generate the first n prime numbers
    fn generate_primes(n: usize) -> Vec<u32> {
        let mut primes = Vec::new();
        let mut candidate = 2u32;

        while primes.len() < n {
            if Self::is_prime(candidate) {
                primes.push(candidate);
            }
            candidate += if candidate == 2 { 1 } else { 2 };
        }

        primes
    }

    /// Check if a number is prime
    fn is_prime(n: u32) -> bool {
        if n < 2 {
            return false;
        }
        if n == 2 {
            return true;
        }
        if n % 2 == 0 {
            return false;
        }

        let limit = (n as f64).sqrt() as u32 + 1;
        for i in (3..=limit).step_by(2) {
            if n % i == 0 {
                return false;
            }
        }
        true
    }

    /// Compute the radical inverse in the given base
    fn radical_inverse(mut n: u64, base: u32) -> f64 {
        let mut result = 0.0;
        let mut denominator = base as f64;

        while n > 0 {
            result += (n % base as u64) as f64 / denominator;
            n /= base as u64;
            denominator *= base as f64;
        }

        result
    }

    /// Get the bases used by this generator
    pub fn bases_2(&self) -> &[u32] {
        &self.bases
    }
}

impl LowDiscrepancySequence for HaltonGenerator {
    fn next_point(&mut self) -> Vec<f64> {
        let mut point = Vec::with_capacity(self.dimension);

        for dim in 0..self.dimension {
            let value = Self::radical_inverse(self.indices[dim], self.bases[dim]);
            point.push(value);
            self.indices[dim] += 1;
        }

        point
    }

    fn dimension(&self) -> usize {
        self.dimension
    }

    fn reset(&mut self) {
        self.indices.fill(0);
    }
}

/// Latin Hypercube Sampler
///
/// Provides stratified sampling that ensures each dimension is evenly divided.
/// Excellent for design of experiments and space-filling designs.
pub struct LatinHypercubeSampler<R: rand::Rng = rand::prelude::ThreadRng> {
    dimension: usize,
    rng: crate::random::Random<R>,
}

impl<R: rand::Rng> LatinHypercubeSampler<R> {
    /// Create a new Latin hypercube sampler
    pub fn new(dimension: usize) -> LatinHypercubeSampler<rand::prelude::ThreadRng> {
        LatinHypercubeSampler {
            dimension,
            rng: crate::random::Random::default(),
        }
    }

    /// Create a Latin hypercube sampler with a specific seed
    pub fn with_seed(dimension: usize, seed: u64) -> LatinHypercubeSampler<rand::prelude::StdRng> {
        LatinHypercubeSampler {
            dimension,
            rng: crate::random::Random::seed(seed),
        }
    }

    /// Generate a Latin hypercube sample
    pub fn sample(&mut self, n: usize) -> Result<Array2<f64>, QmcError> {
        if n == 0 {
            return Err(QmcError::InvalidPointCount(n));
        }

        let mut points = Array2::zeros((n, self.dimension));

        // For each dimension, create a permutation of [0, 1, ..., n-1]
        for dim in 0..self.dimension {
            let mut permutation: Vec<usize> = (0..n).collect();

            // Fisher-Yates shuffle
            for i in (1..n).rev() {
                let j = self.rng.random_range(0..i + 1);
                permutation.swap(i, j);
            }

            // Convert to Latin hypercube coordinates
            for (idx, &perm_val) in permutation.iter().enumerate() {
                let uniform_sample = self.rng.random_range(0.0..1.0);
                let lh_value = (perm_val as f64 + uniform_sample) / n as f64;
                points[[idx, dim]] = lh_value;
            }
        }

        Ok(points)
    }

    /// Generate an optimal Latin hypercube using optimization
    pub fn optimal_sample(&mut self, n: usize, iterations: usize) -> Result<Array2<f64>, QmcError> {
        let mut best_sample = self.sample(n)?;
        let mut best_criterion = self.maximin_criterion(&best_sample);

        // Simple optimization: try multiple random samples and keep the best
        for _ in 0..iterations {
            let candidate = self.sample(n)?;
            let criterion = self.maximin_criterion(&candidate);

            if criterion > best_criterion {
                best_sample = candidate;
                best_criterion = criterion;
            }
        }

        Ok(best_sample)
    }

    /// Compute the maximin criterion (minimum distance between points)
    fn maximin_criterion(&self, points: &Array2<f64>) -> f64 {
        let n = points.nrows();
        let mut min_distance = f64::INFINITY;

        for i in 0..n {
            for j in (i + 1)..n {
                let distance =
                    self.euclidean_distance(&points.row(i).to_owned(), &points.row(j).to_owned());
                min_distance = min_distance.min(distance);
            }
        }

        min_distance
    }

    /// Compute Euclidean distance between two points
    fn euclidean_distance(&self, p1: &Array1<f64>, p2: &Array1<f64>) -> f64 {
        p1.iter()
            .zip(p2.iter())
            .map(|(a, b)| (a - b).powi(2))
            .sum::<f64>()
            .sqrt()
    }

    /// Get the dimension of the sampler
    pub fn dimension(&self) -> usize {
        self.dimension
    }
}

/// Faure sequence generator
///
/// Another family of low-discrepancy sequences based on prime bases.
pub struct FaureGenerator {
    dimension: usize,
    base: u32,
    current_index: u64,
    pascalmatrix: Vec<Vec<u32>>,
}

impl FaureGenerator {
    /// Create a new Faure generator
    pub fn dimension(dimension: usize) -> Result<Self, QmcError> {
        if dimension == 0 {
            return Err(QmcError::InvalidDimension(dimension, 1000));
        }

        // Find the smallest prime >= dimension
        let base = Self::next_prime(dimension as u32);

        let mut generator = Self {
            dimension,
            base,
            current_index: 0,
            pascalmatrix: Vec::new(),
        };

        generator.initialize_pascalmatrix();
        Ok(generator)
    }

    /// Find the next prime number >= n
    fn next_prime(n: u32) -> u32 {
        let mut candidate = n.max(2);
        while !HaltonGenerator::is_prime(candidate) {
            candidate += 1;
        }
        candidate
    }

    /// Initialize the Pascal matrix modulo the base
    fn initialize_pascalmatrix(&mut self) {
        let size = self.base as usize;
        self.pascalmatrix = vec![vec![0; size]; size];

        // Initialize Pascal's triangle modulo base
        for i in 0..size {
            self.pascalmatrix[i][0] = 1;
            for j in 1..=i {
                let prev_row = if i > 0 {
                    self.pascalmatrix[i.saturating_sub(1)][j.saturating_sub(1)]
                } else {
                    0
                };
                let prev_diag = if i > 0 && j < size {
                    self.pascalmatrix[i.saturating_sub(1)][j]
                } else {
                    0
                };
                self.pascalmatrix[i][j] = (prev_row + prev_diag) % self.base;
            }
        }
    }

    /// Compute the scrambled radical inverse
    fn scrambled_radical_inverse(&self, n: u64, dimension: usize) -> f64 {
        let mut result = 0.0;
        let mut denominator = self.base as f64;
        let mut index = n;

        while index > 0 {
            let digit = index % self.base as u64;

            // Apply scrambling based on dimension and Pascal matrix
            let scrambled_digit = if dimension < self.pascalmatrix.len() {
                (digit
                    + self.pascalmatrix[dimension % self.pascalmatrix.len()]
                        [digit as usize % self.pascalmatrix.len()] as u64)
                    % self.base as u64
            } else {
                digit
            };

            result += scrambled_digit as f64 / denominator;
            index /= self.base as u64;
            denominator *= self.base as f64;
        }

        result
    }
}

impl LowDiscrepancySequence for FaureGenerator {
    fn next_point(&mut self) -> Vec<f64> {
        let mut point = Vec::with_capacity(self.dimension);

        for dim in 0..self.dimension {
            let value = self.scrambled_radical_inverse(self.current_index, dim);
            point.push(value);
        }

        self.current_index += 1;
        point
    }

    fn dimension(&self) -> usize {
        self.dimension
    }

    fn reset(&mut self) {
        self.current_index = 0;
    }
}

/// QMC integration utilities
pub mod integration {
    use super::*;
    use std::sync::{Arc, Mutex};
    use std::thread;

    /// Quasi-Monte Carlo integration result
    #[derive(Debug, Clone)]
    pub struct QmcIntegrationResult {
        /// Estimated integral value
        pub value: f64,
        /// Estimated standard error
        pub error: f64,
        /// Number of function evaluations
        pub evaluations: usize,
        /// Convergence rate
        pub convergence_rate: f64,
    }

    /// Perform QMC integration using the specified sequence
    pub fn qmc_integrate<F>(
        f: F,
        bounds: &[(f64, f64)],
        n_points: usize,
        sequence_type: QmcSequenceType,
    ) -> Result<QmcIntegrationResult, QmcError>
    where
        F: Fn(&[f64]) -> f64 + Send + Sync,
    {
        let dimension = bounds.len();
        let mut generator = create_qmc_generator(sequence_type, dimension)?;

        let points = generator.generate(n_points);
        let mut sum = 0.0;
        let mut sum_sq = 0.0;

        // Transform _points to integration bounds and evaluate function
        for i in 0..n_points {
            let mut transformed_point = Vec::with_capacity(dimension);
            for dim in 0..dimension {
                let (a, b) = bounds[dim];
                let x = points[[i, dim]];
                transformed_point.push(a + x * (b - a));
            }

            let value = f(&transformed_point);
            sum += value;
            sum_sq += value * value;
        }

        // Calculate volume of integration region
        let volume: f64 = bounds.iter().map(|(a, b)| b - a).product();

        // Estimate integral and error
        let mean = sum / n_points as f64;
        let variance = (sum_sq / n_points as f64) - (mean * mean);
        let integral = volume * mean;
        let error = volume * (variance / n_points as f64).sqrt();

        // Estimate convergence rate (QMC typically achieves O((log n)^d / n))
        let convergence_rate = (dimension as f64 * (n_points as f64).ln()) / n_points as f64;

        Ok(QmcIntegrationResult {
            value: integral,
            error,
            evaluations: n_points,
            convergence_rate,
        })
    }

    /// Parallel QMC integration
    pub fn parallel_qmc_integrate<F>(
        f: F,
        bounds: &[(f64, f64)],
        n_points: usize,
        sequence_type: QmcSequenceType,
        n_threads: usize,
    ) -> Result<QmcIntegrationResult, QmcError>
    where
        F: Fn(&[f64]) -> f64 + Send + Sync + 'static,
    {
        let dimension = bounds.len();
        let points_per_thread = n_points / n_threads;
        let f = Arc::new(f);
        let bounds = Arc::new(bounds.to_vec());

        let results = Arc::new(Mutex::new(Vec::new()));
        let mut handles = Vec::new();

        for thread_id in 0..n_threads {
            let f_clone = Arc::clone(&f);
            let bounds_clone = Arc::clone(&bounds);
            let results_clone = Arc::clone(&results);

            let handle = thread::spawn(move || {
                let mut generator =
                    create_qmc_generator(sequence_type, dimension).expect("Operation failed");
                generator.skip(thread_id * points_per_thread);

                let points = generator.generate(points_per_thread);
                let mut sum = 0.0;
                let mut sum_sq = 0.0;

                for i in 0..points_per_thread {
                    let mut transformed_point = Vec::with_capacity(dimension);
                    for dim in 0..dimension {
                        let (a, b) = bounds_clone[dim];
                        let x = points[[i, dim]];
                        transformed_point.push(a + x * (b - a));
                    }

                    let value = f_clone(&transformed_point);
                    sum += value;
                    sum_sq += value * value;
                }

                results_clone
                    .lock()
                    .expect("Operation failed")
                    .push((sum, sum_sq));
            });

            handles.push(handle);
        }

        for handle in handles {
            handle.join().expect("Operation failed");
        }

        let results = results.lock().expect("Operation failed");
        let (total_sum, total_sum_sq) = results
            .iter()
            .fold((0.0, 0.0), |(s, ss), (sum, sum_sq)| (s + sum, ss + sum_sq));

        let volume: f64 = bounds.iter().map(|(a, b)| b - a).product();
        let mean = total_sum / n_points as f64;
        let variance = (total_sum_sq / n_points as f64) - (mean * mean);
        let integral = volume * mean;
        let error = volume * (variance / n_points as f64).sqrt();
        let convergence_rate = (dimension as f64 * (n_points as f64).ln()) / n_points as f64;

        Ok(QmcIntegrationResult {
            value: integral,
            error,
            evaluations: n_points,
            convergence_rate,
        })
    }
}

/// QMC sequence types
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QmcSequenceType {
    /// Sobol sequence
    Sobol,
    /// Halton sequence
    Halton,
    /// Faure sequence
    Faure,
    /// Latin hypercube sampling
    LatinHypercube,
}

/// Create a QMC generator of the specified type
#[allow(dead_code)]
pub fn create_qmc_generator(
    sequence_type: QmcSequenceType,
    dimension: usize,
) -> Result<Box<dyn LowDiscrepancySequence>, QmcError> {
    match sequence_type {
        QmcSequenceType::Sobol => Ok(Box::new(SobolGenerator::dimension(dimension)?)),
        QmcSequenceType::Halton => Ok(Box::new(HaltonGenerator::dimension(dimension)?)),
        QmcSequenceType::Faure => Ok(Box::new(FaureGenerator::dimension(dimension)?)),
        QmcSequenceType::LatinHypercube => {
            // Note: LHS doesn't implement LowDiscrepancySequence directly
            // This is a simplified adapter
            Err(QmcError::UnsupportedDimension(dimension))
        }
    }
}

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

    #[test]
    fn test_sobol_generator_creation() {
        let sobol = SobolGenerator::dimension(2);
        assert!(sobol.is_ok());

        let invalid_sobol = SobolGenerator::dimension(0);
        assert!(invalid_sobol.is_err());
    }

    #[test]
    fn test_sobol_sequence_properties() {
        let mut sobol = SobolGenerator::dimension(2).expect("Operation failed");

        // First point should be [0, 0]
        let first = sobol.next_point();
        assert_eq!(first, vec![0.0, 0.0]);

        // Generate some points and check they're in [0,1]^2
        for _ in 0..100 {
            let point = sobol.next_point();
            assert_eq!(point.len(), 2);
            for coord in point {
                assert!((0.0..1.0).contains(&coord));
            }
        }
    }

    #[test]
    fn test_halton_generator() {
        let mut halton = HaltonGenerator::new(&[2, 3]);

        // Generate points and verify properties
        let points = halton.generate(50);
        assert_eq!(points.nrows(), 50);
        assert_eq!(points.ncols(), 2);

        // Check all points are in [0,1]^2
        for i in 0..50 {
            for j in 0..2 {
                let val = points[[i, j]];
                assert!((0.0..1.0).contains(&val));
            }
        }
    }

    #[test]
    fn test_halton_primebases() {
        let halton = HaltonGenerator::dimension(3).expect("Operation failed");
        assert_eq!(halton.bases, &[2, 3, 5]);
    }

    #[test]
    fn test_latin_hypercube_sampling() {
        let mut lhs = LatinHypercubeSampler::<rand::prelude::ThreadRng>::new(2);
        let points = lhs.sample(10).expect("Operation failed");

        assert_eq!(points.nrows(), 10);
        assert_eq!(points.ncols(), 2);

        // Check that each dimension has points spread across [0,1]
        for dim in 0..2 {
            let column = points.column(dim);
            let mut sorted: Vec<f64> = column.to_vec();
            sorted.sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));

            // Should be well-distributed
            for &value in sorted.iter().take(10) {
                assert!((0.0..=1.0).contains(&value));
            }
        }
    }

    #[test]
    fn test_faure_generator() {
        let mut faure = FaureGenerator::dimension(2).expect("Operation failed");

        let points = faure.generate(20);
        assert_eq!(points.nrows(), 20);
        assert_eq!(points.ncols(), 2);

        // Verify points are in unit cube
        for i in 0..20 {
            for j in 0..2 {
                let val = points[[i, j]];
                assert!((0.0..1.0).contains(&val));
            }
        }
    }

    #[test]
    fn test_qmc_integration() {
        use integration::*;

        // Test integration of f(x,y) = x*y over [0,1]^2
        // Analytical result should be 1/4
        // Note: Sobol sequences can have systematic biases for specific integrands
        // Using Halton sequence for more reliable results
        let result = qmc_integrate(
            |x| x[0] * x[1],
            &[(0.0, 1.0), (0.0, 1.0)],
            10000,
            QmcSequenceType::Halton,
        )
        .expect("Operation failed");

        assert_abs_diff_eq!(result.value, 0.25, epsilon = 0.03);
        assert!(result.error > 0.0);
        assert_eq!(result.evaluations, 10000);
    }

    #[test]
    fn test_sequence_reset() {
        let mut sobol = SobolGenerator::dimension(2).expect("Operation failed");

        let first_sequence: Vec<_> = (0..5).map(|_| sobol.next_point()).collect();

        sobol.reset();
        let second_sequence: Vec<_> = (0..5).map(|_| sobol.next_point()).collect();

        assert_eq!(first_sequence, second_sequence);
    }

    #[test]
    fn test_discrepancy_estimation() {
        let sobol = SobolGenerator::dimension(2).expect("Operation failed");
        let discrepancy = sobol.estimate_discrepancy(1000);

        // Should be small for low-discrepancy sequences
        assert!(discrepancy > 0.0);
        assert!(discrepancy < 0.1);
    }
}
