//! Bevy-compatible GPU coordinator.
//!
//! This module provides a thread-safe (`Send + Sync`) GPU coordinator
//! for use with Bevy ECS. It excludes the frame arenas (which are `!Send`)
//! since Bevy has its own frame management via `memkit-bevy`.

use memkit_gpu::{MkGpu, MkGpuBackend, MkBufferUsage, MkDeviceBuffer, MkStagingBuffer};
use parking_lot::Mutex;
use std::sync::atomic::{AtomicU64, Ordering};

use crate::scheduler::TransferScheduler;
use crate::staging::DynamicStagingPool;
use crate::sync::TransferFence;

/// Thread-safe GPU coordinator for Bevy integration.
///
/// Unlike [`MkCoordinator`](crate::MkCoordinator), this does not include
/// frame arenas (which are `!Send + !Sync`). Use `memkit-bevy`'s
/// `MkAllocatorRes` for CPU frame allocations instead.
///
/// # Example
///
/// ```rust,ignore
/// use memkit_co::bevy::BevyGpuCoordinator;
/// use memkit_gpu::DummyBackend;
///
/// let coordinator = BevyGpuCoordinator::new(DummyBackend::new());
/// ```
pub struct BevyGpuCoordinator<B: MkGpuBackend> {
    gpu: MkGpu<B>,
    scheduler: TransferScheduler<B>,
    staging_pool: DynamicStagingPool<B>,
    frame_counter: AtomicU64,
    stats: BevyCoordinatorStats,
}

/// Statistics for the Bevy GPU coordinator.
pub struct BevyCoordinatorStats {
    uploads_completed: AtomicU64,
    bytes_uploaded: AtomicU64,
}

impl Default for BevyCoordinatorStats {
    fn default() -> Self {
        Self {
            uploads_completed: AtomicU64::new(0),
            bytes_uploaded: AtomicU64::new(0),
        }
    }
}

impl BevyCoordinatorStats {
    /// Get total uploads completed.
    pub fn uploads_completed(&self) -> u64 {
        self.uploads_completed.load(Ordering::Relaxed)
    }
    
    /// Get total bytes uploaded.
    pub fn bytes_uploaded(&self) -> u64 {
        self.bytes_uploaded.load(Ordering::Relaxed)
    }
}

// Safety: BevyGpuCoordinator only contains thread-safe types:
// - MkGpu<B> where B: MkGpuBackend (backend handles its own thread safety)
// - TransferScheduler uses parking_lot::Mutex
// - DynamicStagingPool uses parking_lot::Mutex
// - AtomicU64 is inherently thread-safe
unsafe impl<B: MkGpuBackend + Send> Send for BevyGpuCoordinator<B> {}
unsafe impl<B: MkGpuBackend + Sync> Sync for BevyGpuCoordinator<B> {}

impl<B: MkGpuBackend> BevyGpuCoordinator<B> {
    /// Create a new Bevy GPU coordinator.
    pub fn new(backend: B) -> Self {
        Self {
            gpu: MkGpu::new(backend),
            scheduler: TransferScheduler::new(64),
            staging_pool: DynamicStagingPool::new(),
            frame_counter: AtomicU64::new(0),
            stats: BevyCoordinatorStats::default(),
        }
    }
    
    /// Get the underlying GPU allocator.
    pub fn gpu(&self) -> &MkGpu<B> {
        &self.gpu
    }
    
    /// Get the backend.
    pub fn backend(&self) -> &B {
        self.gpu.backend()
    }
    
    /// Get coordinator statistics.
    pub fn stats(&self) -> &BevyCoordinatorStats {
        &self.stats
    }
    
    /// Get current frame number.
    pub fn frame(&self) -> u64 {
        self.frame_counter.load(Ordering::Acquire)
    }
    
    /// Notify the coordinator that a new frame has begun.
    ///
    /// This is called automatically by `memkit-bevy` when using the GPU feature.
    pub fn begin_frame(&self) {
        self.frame_counter.fetch_add(1, Ordering::AcqRel);
    }
    
    /// Notify the coordinator that the current frame has ended.
    ///
    /// Flushes any pending transfers.
    pub fn end_frame(&self) -> Result<(), B::Error> {
        if self.scheduler.pending_count() > 0 {
            self.scheduler.flush(self.gpu.backend())?;
        }
        Ok(())
    }
    
    // =========================================================================
    // Direct Uploads
    // =========================================================================
    
    /// Upload a slice to a new GPU buffer.
    pub fn upload_slice<T: Copy>(&self, data: &[T]) -> Result<MkDeviceBuffer<B>, B::Error> {
        let staging = self.gpu.staging_buffer_with_data(data)?;
        let size = std::mem::size_of_val(data);
        
        let device = self.gpu.create_device_buffer(
            size,
            MkBufferUsage::STORAGE | MkBufferUsage::TRANSFER_DST,
        )?;
        
        self.gpu.transfer(&staging, &device)?;
        self.gpu.wait_idle()?;
        
        self.stats.uploads_completed.fetch_add(1, Ordering::Relaxed);
        self.stats.bytes_uploaded.fetch_add(size as u64, Ordering::Relaxed);
        
        Ok(device)
    }
    
    /// Upload a slice to an existing device buffer.
    pub fn upload_to_buffer<T: Copy>(
        &self,
        data: &[T],
        device: &MkDeviceBuffer<B>,
    ) -> Result<(), B::Error> {
        let staging = self.gpu.staging_buffer_with_data(data)?;
        let size = std::mem::size_of_val(data);
        
        self.gpu.transfer(&staging, device)?;
        
        self.stats.uploads_completed.fetch_add(1, Ordering::Relaxed);
        self.stats.bytes_uploaded.fetch_add(size as u64, Ordering::Relaxed);
        
        Ok(())
    }
    
    // =========================================================================
    // Staged Transfers
    // =========================================================================
    
    /// Queue a transfer for later execution.
    pub fn queue_transfer<T: Copy>(
        &self,
        data: &[T],
        device: &MkDeviceBuffer<B>,
    ) -> Result<TransferFence, B::Error> {
        let staging = self.gpu.staging_buffer_with_data(data)?;
        let size = std::mem::size_of_val(data);
        
        let fence = self.scheduler.queue(
            staging.handle().clone(),
            device.handle().clone(),
            size,
        );
        
        Ok(fence)
    }
    
    /// Flush all pending transfers.
    pub fn flush_transfers(&self) -> Result<Vec<TransferFence>, B::Error> {
        self.scheduler.flush(self.gpu.backend())
    }
    
    /// Get number of pending transfers.
    pub fn pending_transfer_count(&self) -> usize {
        self.scheduler.pending_count()
    }
    
    // =========================================================================
    // Buffer Creation
    // =========================================================================
    
    /// Create a device buffer.
    pub fn create_device_buffer(
        &self,
        size: usize,
        usage: MkBufferUsage,
    ) -> Result<MkDeviceBuffer<B>, B::Error> {
        self.gpu.create_device_buffer(size, usage)
    }
    
    /// Create a staging buffer.
    pub fn create_staging_buffer(&self, size: usize) -> Result<MkStagingBuffer<B>, B::Error> {
        self.gpu.create_staging_buffer(size)
    }
    
    /// Create a staging buffer with data.
    pub fn staging_buffer_with_data<T: Copy>(
        &self,
        data: &[T],
    ) -> Result<MkStagingBuffer<B>, B::Error> {
        self.gpu.staging_buffer_with_data(data)
    }
    
    // =========================================================================
    // Synchronization
    // =========================================================================
    
    /// Wait for GPU to become idle.
    pub fn wait_idle(&self) -> Result<(), B::Error> {
        self.gpu.wait_idle()
    }
}

impl<B: MkGpuBackend> Drop for BevyGpuCoordinator<B> {
    fn drop(&mut self) {
        self.staging_pool.clear(self.gpu.backend());
        self.scheduler.clear();
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use memkit_gpu::{DummyBackend, DummyBackendConfig};
    
    fn create_backend() -> DummyBackend {
        DummyBackend::with_config(DummyBackendConfig {
            max_buffer_size: 64 * 1024 * 1024,
            simulate_device_local: true,
            transfer_delay_us: 0,
        })
    }
    
    #[test]
    fn test_bevy_coordinator_is_send_sync() {
        fn assert_send_sync<T: Send + Sync>() {}
        assert_send_sync::<BevyGpuCoordinator<DummyBackend>>();
    }
    
    #[test]
    fn test_bevy_coordinator_upload() {
        let coordinator = BevyGpuCoordinator::new(create_backend());
        
        let data: Vec<f32> = vec![1.0, 2.0, 3.0, 4.0];
        let buffer = coordinator.upload_slice(&data).unwrap();
        
        assert_eq!(buffer.size(), data.len() * std::mem::size_of::<f32>());
        assert_eq!(coordinator.stats().uploads_completed(), 1);
    }
    
    #[test]
    fn test_bevy_coordinator_frame_lifecycle() {
        let coordinator = BevyGpuCoordinator::new(create_backend());
        
        coordinator.begin_frame();
        assert_eq!(coordinator.frame(), 1);
        
        coordinator.end_frame().unwrap();
    }
}
