use super::color::Color;
use super::style::LineStyle;
use crate::core::Result;
use crate::core::types::Point2f;
use crate::data::{PooledVec, SharedMemoryPool};
#[cfg(feature = "simd")]
use wide::f32x4;

// Re-export LineSegment from data module as canonical definition
pub use crate::data::elements::LineSegment;

/// Pooled rendering operations that reuse memory for performance
///
/// This module provides drop-in replacements for allocation-heavy rendering operations
/// using memory pools to reduce allocation overhead by 30-50%.
pub struct PooledRenderer {
    /// Memory pool for f32 coordinates (most common)
    f32_pool: SharedMemoryPool<f32>,
    /// Memory pool for Point2f coordinates
    point_pool: SharedMemoryPool<Point2f>,
    /// Pool for line segments and other rendering primitives
    segment_pool: SharedMemoryPool<LineSegment>,
}

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

impl PooledRenderer {
    /// Create a new pooled renderer with reasonable default pool sizes
    pub fn new() -> Self {
        Self {
            // Start with 1000 element pools, will grow as needed
            f32_pool: SharedMemoryPool::new(1000),
            point_pool: SharedMemoryPool::new(500),
            segment_pool: SharedMemoryPool::new(500),
        }
    }

    /// Create pooled renderer with custom pool sizes for specific workloads
    pub fn with_pool_sizes(f32_size: usize, point_size: usize, segment_size: usize) -> Self {
        Self {
            f32_pool: SharedMemoryPool::new(f32_size),
            point_pool: SharedMemoryPool::new(point_size),
            segment_pool: SharedMemoryPool::new(segment_size),
        }
    }

    /// Transform X coordinates using SIMD with memory pooling
    ///
    /// This replaces the allocation-heavy coordinate transformation with
    /// pooled memory reuse for significant performance improvements.
    pub fn transform_x_coordinates_pooled<T>(
        &self,
        x_data: &T,
        min: f64,
        max: f64,
        left: f32,
        right: f32,
    ) -> Result<PooledVec<f32>>
    where
        T: crate::data::Data1D<f64>,
    {
        let len = x_data.len();
        if len == 0 {
            return Ok(PooledVec::new(self.f32_pool.clone()));
        }

        // Get pooled buffer instead of Vec::with_capacity
        let mut result = PooledVec::with_capacity(len, self.f32_pool.clone());

        let range = max - min;
        if range == 0.0 {
            // All points have same x coordinate
            let mid_x = left + (right - left) / 2.0;
            result.resize(len, mid_x);
            return Ok(result);
        }

        let scale = (right - left) as f64 / range;
        let offset = left as f64 - min * scale;

        #[cfg(feature = "simd")]
        {
            // Use SIMD for batch processing with pooled memory
            if len >= 4 {
                let scale_vec = f32x4::splat(scale as f32);
                let offset_vec = f32x4::splat(offset as f32);

                // Process data in chunks of 4 using SIMD
                let mut i = 0;
                while i + 4 <= len {
                    let chunk = [
                        x_data.get(i).copied().unwrap_or(0.0) as f32,
                        x_data.get(i + 1).copied().unwrap_or(0.0) as f32,
                        x_data.get(i + 2).copied().unwrap_or(0.0) as f32,
                        x_data.get(i + 3).copied().unwrap_or(0.0) as f32,
                    ];

                    let x_vec = f32x4::new(chunk);
                    let transformed = x_vec * scale_vec + offset_vec;
                    let output = transformed.to_array();

                    result.push(output[0]);
                    result.push(output[1]);
                    result.push(output[2]);
                    result.push(output[3]);

                    i += 4;
                }

                // Handle remaining elements
                while i < len {
                    if let Some(x) = x_data.get(i) {
                        let transformed = (*x * scale + offset) as f32;
                        result.push(transformed);
                    }
                    i += 1;
                }
            } else {
                // Process small datasets without SIMD
                for i in 0..len {
                    if let Some(x) = x_data.get(i) {
                        let transformed = (*x * scale + offset) as f32;
                        result.push(transformed);
                    }
                }
            }
        }
        #[cfg(not(feature = "simd"))]
        {
            // Process all datasets without SIMD
            for i in 0..len {
                if let Some(x) = x_data.get(i) {
                    let transformed = (*x * scale + offset) as f32;
                    result.push(transformed);
                }
            }
        }

        Ok(result)
    }

    /// Transform Y coordinates using SIMD with memory pooling
    pub fn transform_y_coordinates_pooled<T>(
        &self,
        y_data: &T,
        min: f64,
        max: f64,
        top: f32,
        bottom: f32,
    ) -> Result<PooledVec<f32>>
    where
        T: crate::data::Data1D<f64>,
    {
        let len = y_data.len();
        if len == 0 {
            return Ok(PooledVec::new(self.f32_pool.clone()));
        }

        // Get pooled buffer instead of Vec::with_capacity
        let mut result = PooledVec::with_capacity(len, self.f32_pool.clone());

        let range = max - min;
        if range == 0.0 {
            // All points have same y coordinate
            let mid_y = top + (bottom - top) / 2.0;
            result.resize(len, mid_y);
            return Ok(result);
        }

        // Note: Y-axis is flipped (higher values are lower on screen)
        let scale = (top - bottom) as f64 / range;
        let offset = bottom as f64 - min * scale;

        #[cfg(feature = "simd")]
        {
            // Use SIMD for batch processing with pooled memory
            if len >= 4 {
                let scale_vec = f32x4::splat(scale as f32);
                let offset_vec = f32x4::splat(offset as f32);

                // Process data in chunks of 4 using SIMD
                let mut i = 0;
                while i + 4 <= len {
                    let chunk = [
                        y_data.get(i).copied().unwrap_or(0.0) as f32,
                        y_data.get(i + 1).copied().unwrap_or(0.0) as f32,
                        y_data.get(i + 2).copied().unwrap_or(0.0) as f32,
                        y_data.get(i + 3).copied().unwrap_or(0.0) as f32,
                    ];

                    let y_vec = f32x4::new(chunk);
                    let transformed = y_vec * scale_vec + offset_vec;
                    let output = transformed.to_array();

                    result.push(output[0]);
                    result.push(output[1]);
                    result.push(output[2]);
                    result.push(output[3]);

                    i += 4;
                }

                // Handle remaining elements
                while i < len {
                    if let Some(y) = y_data.get(i) {
                        let transformed = (*y * scale + offset) as f32;
                        result.push(transformed);
                    }
                    i += 1;
                }
            } else {
                // Process small datasets without SIMD
                for i in 0..len {
                    if let Some(y) = y_data.get(i) {
                        let transformed = (*y * scale + offset) as f32;
                        result.push(transformed);
                    }
                }
            }
        }
        #[cfg(not(feature = "simd"))]
        {
            // Process all datasets without SIMD
            for i in 0..len {
                if let Some(y) = y_data.get(i) {
                    let transformed = (*y * scale + offset) as f32;
                    result.push(transformed);
                }
            }
        }

        Ok(result)
    }

    /// Transform both X and Y coordinates in one call using SIMD and memory pooling
    pub fn transform_coordinates_pooled<T>(
        &self,
        x_data: &T,
        y_data: &T,
        x_min: f64,
        x_max: f64,
        y_min: f64,
        y_max: f64,
        left: f32,
        top: f32,
        right: f32,
        bottom: f32,
    ) -> Result<(PooledVec<f32>, PooledVec<f32>)>
    where
        T: crate::data::Data1D<f64>,
    {
        let x_result = self.transform_x_coordinates_pooled(x_data, x_min, x_max, left, right)?;
        let y_result = self.transform_y_coordinates_pooled(y_data, y_min, y_max, top, bottom)?;
        Ok((x_result, y_result))
    }

    /// Generate tick marks using pooled memory
    ///
    /// Replaces the frequent small allocations in tick generation with
    /// pooled buffer reuse for better performance.
    pub fn generate_ticks_pooled(&self, min: f64, max: f64, target_count: usize) -> PooledVec<f64> {
        let mut ticks = PooledVec::new(SharedMemoryPool::new(target_count.max(10)));

        if min >= max {
            return ticks;
        }

        if target_count <= 1 {
            ticks.push(min);
            return ticks;
        }

        if target_count == 2 {
            ticks.push(min);
            ticks.push(max);
            return ticks;
        }

        let range = max - min;
        let raw_step = range / (target_count - 1) as f64;

        // Find nice step size
        let magnitude = 10.0_f64.powf(raw_step.log10().floor());
        let normalized_step = raw_step / magnitude;

        let nice_step = if normalized_step <= 1.0 {
            1.0
        } else if normalized_step <= 2.0 {
            2.0
        } else if normalized_step <= 5.0 {
            5.0
        } else {
            10.0
        } * magnitude;

        // Generate ticks
        let start = (min / nice_step).ceil() * nice_step;
        let mut current = start;

        while current <= max + nice_step * 0.001 {
            if current >= min - nice_step * 0.001 {
                ticks.push(current);
            }
            current += nice_step;
        }

        ticks
    }

    /// Create line segments with pooled memory for parallel rendering
    pub fn create_line_segments_pooled<T>(
        &self,
        points: &T,
        line_style: LineStyle,
        color: Color,
        line_width: f32,
    ) -> Result<PooledVec<LineSegment>>
    where
        T: crate::data::Data1D<Point2f>,
    {
        let len = points.len();
        if len < 2 {
            return Ok(PooledVec::new(self.segment_pool.clone()));
        }

        let mut segments = PooledVec::with_capacity(len - 1, self.segment_pool.clone());

        for i in 0..len - 1 {
            if let (Some(start), Some(end)) = (points.get(i), points.get(i + 1)) {
                segments.push(LineSegment {
                    start: *start,
                    end: *end,
                    style: line_style.clone(),
                    color,
                    width: line_width,
                });
            }
        }

        Ok(segments)
    }

    /// Get memory pool statistics for monitoring and optimization
    pub fn get_pool_stats(&self) -> PooledRendererStats {
        PooledRendererStats {
            f32_pool_stats: self.f32_pool.statistics(),
            point_pool_stats: self.point_pool.statistics(),
            segment_pool_stats: self.segment_pool.statistics(),
        }
    }

    /// Shrink all pools to reclaim memory during idle periods
    pub fn shrink_pools(&self) {
        self.f32_pool.shrink();
        self.point_pool.shrink();
        self.segment_pool.shrink();
    }
}

/// Statistics about memory pool usage in the renderer
#[derive(Debug, Clone)]
pub struct PooledRendererStats {
    pub f32_pool_stats: crate::data::PoolStatistics,
    pub point_pool_stats: crate::data::PoolStatistics,
    pub segment_pool_stats: crate::data::PoolStatistics,
}

impl PooledRendererStats {
    /// Get total memory usage across all pools
    pub fn total_capacity(&self) -> usize {
        self.f32_pool_stats.total_capacity
            + self.point_pool_stats.total_capacity
            + self.segment_pool_stats.total_capacity
    }

    /// Get total number of active allocations
    pub fn total_in_use(&self) -> usize {
        self.f32_pool_stats.in_use_count
            + self.point_pool_stats.in_use_count
            + self.segment_pool_stats.in_use_count
    }

    /// Calculate memory efficiency (0.0 to 1.0)
    pub fn efficiency(&self) -> f32 {
        let total_capacity = self.total_capacity();
        if total_capacity == 0 {
            return 1.0;
        }
        self.total_in_use() as f32 / total_capacity as f32
    }
}

use std::sync::OnceLock;

/// Global pooled renderer instance for easy integration
static GLOBAL_POOLED_RENDERER: OnceLock<PooledRenderer> = OnceLock::new();

/// Get the global pooled renderer instance
///
/// This provides a convenient singleton for integrating pooled rendering
/// throughout the application without manual pool management.
pub fn get_pooled_renderer() -> &'static PooledRenderer {
    GLOBAL_POOLED_RENDERER.get_or_init(PooledRenderer::new)
}

// Manual implementations to avoid dependency chain issues
impl std::fmt::Debug for PooledRenderer {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("PooledRenderer")
            .field("f32_pool", &"SharedMemoryPool<f32>")
            .field("point_pool", &"SharedMemoryPool<Point2f>")
            .field("segment_pool", &"SharedMemoryPool<LineSegment>")
            .finish()
    }
}

impl Clone for PooledRenderer {
    fn clone(&self) -> Self {
        Self {
            f32_pool: self.f32_pool.clone(),
            point_pool: self.point_pool.clone(),
            segment_pool: self.segment_pool.clone(),
        }
    }
}

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

    #[test]
    fn test_pooled_coordinate_transform() {
        let renderer = PooledRenderer::new();
        let x_data = vec![1.0, 2.0, 3.0, 4.0, 5.0];

        let result = renderer
            .transform_x_coordinates_pooled(&x_data, 1.0, 5.0, 0.0, 100.0)
            .unwrap();

        assert_eq!(result.len(), 5);
        assert!((result[0] - 0.0).abs() < 0.001); // 1.0 -> 0.0
        assert!((result[4] - 100.0).abs() < 0.001); // 5.0 -> 100.0
    }

    #[test]
    fn test_pooled_tick_generation() {
        let renderer = PooledRenderer::new();
        let ticks = renderer.generate_ticks_pooled(0.0, 10.0, 6);

        assert!(ticks.len() >= 2); // At least min and max
        assert!(ticks[0] >= 0.0);
        assert!(ticks[ticks.len() - 1] <= 10.0);
    }

    #[test]
    fn test_memory_pool_reuse() {
        let renderer = PooledRenderer::new();
        let x_data = vec![1.0, 2.0, 3.0];

        // First allocation
        let result1 = renderer
            .transform_x_coordinates_pooled(&x_data, 0.0, 3.0, 0.0, 10.0)
            .unwrap();
        drop(result1); // Return to pool

        // Second allocation should reuse memory
        let result2 = renderer
            .transform_x_coordinates_pooled(&x_data, 0.0, 3.0, 0.0, 10.0)
            .unwrap();

        // Verify functionality is correct
        assert_eq!(result2.len(), 3);

        let stats = renderer.get_pool_stats();
        assert!(stats.f32_pool_stats.total_capacity > 0);
    }

    #[test]
    fn test_simd_vs_scalar_consistency() {
        let renderer = PooledRenderer::new();

        // Test with SIMD-sized data
        let large_data: Vec<f64> = (0..100).map(|i| i as f64).collect();
        let result_large = renderer
            .transform_x_coordinates_pooled(&large_data, 0.0, 99.0, 0.0, 100.0)
            .unwrap();

        // Test with small data (no SIMD)
        let small_data = vec![0.0, 50.0, 99.0];
        let result_small = renderer
            .transform_x_coordinates_pooled(&small_data, 0.0, 99.0, 0.0, 100.0)
            .unwrap();

        // Results should be consistent
        assert!((result_large[0] - result_small[0]).abs() < 0.001);
        assert!((result_large[99] - 100.0).abs() < 0.001);
    }
}
