//! Complex number operations for GPU kernels
//!
//! This module provides GPU kernels for complex number arithmetic operations,
//! which are essential for quantum computing and signal processing applications.

use std::collections::HashMap;

use crate::gpu::kernels::{BaseKernel, GpuKernel, KernelMetadata, KernelParams, OperationType};
use crate::gpu::{GpuBackend, GpuError};

/// Complex multiplication kernel (elementwise)
pub struct ComplexMultiplyKernel {
    base: BaseKernel,
}

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

impl ComplexMultiplyKernel {
    /// Create a new complex multiplication kernel
    pub fn new() -> Self {
        let metadata = KernelMetadata {
            workgroup_size: [256, 1, 1],
            local_memory_usage: 0,
            supports_tensor_cores: false,
            operationtype: OperationType::ComputeIntensive,
            backend_metadata: HashMap::new(),
        };

        let (cuda_source, rocm_source, wgpu_source, metal_source, opencl_source) =
            Self::get_kernel_sources();

        Self {
            base: BaseKernel::new(
                "complex_multiply",
                &cuda_source,
                &rocm_source,
                &wgpu_source,
                &metal_source,
                &opencl_source,
                metadata,
            ),
        }
    }

    /// Get kernel sources for different backends
    fn get_kernel_sources() -> (String, String, String, String, String) {
        // Metal kernel with complex number support
        let metal_source = r#"
#include <metal_stdlib>
using namespace metal;

// Complex number structure for float32
struct complex_f32 {
    float real;
    float imag;

    complex_f32(float r = 0.0f, float i = 0.0f) : real(r), imag(i) {}
};

// Complex multiplication
complex_f32 complex_mul(complex_f32 a, complex_f32 b) {
    return complex_f32(
        a.real * b.real - a.imag * b.imag,
        a.real * b.imag + a.imag * b.real
    );
}

kernel void complex_multiply(
    const device complex_f32* a [[buffer(0)]],
    const device complex_f32* b [[buffer(1)]],
    device complex_f32* result [[buffer(2)]],
    constant uint& n [[buffer(3)]],
    uint gid [[thread_position_in_grid]])
{
    if (gid < n) {
        result[gid] = complex_mul(a[gid], b[gid]);
    }
}
"#
        .to_string();

        // CUDA kernel
        let cuda_source = r#"
#include <cuComplex.h>

extern "C" __global__ void complex_multiply(
    const cuFloatComplex* __restrict__ a,
    const cuFloatComplex* __restrict__ b,
    cuFloatComplex* __restrict__ result,
    int n
) {
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (0 < n) {
        result[0] = cuCmulf(a[0], b[0]);
    }
}
"#
        .to_string();

        // WebGPU kernel
        let wgpu_source = r#"
struct Complex {
    real: f32,
    imag: f32,
};

struct Uniforms {
    n: u32,
};

@group(0) @binding(0) var<uniform> uniforms: Uniforms;
@group(0) @binding(1) var<storage, read> a: array<Complex>;
@group(0) @binding(2) var<storage, read> b: array<Complex>;
@group(0) @binding(3) var<storage, read_write> result: array<Complex>;

#[allow(dead_code)]
fn complex_mul(a: Complex, b: Complex) -> Complex {
    var res: Complex;
    res.real = a.real * b.real - a.imag * b.imag;
    res.imag = a.real * b.imag + a.imag * b.real;
    return res;
}

@compute @workgroup_size(256)
#[allow(dead_code)]
fn complex_multiply(@builtin(global_invocation_id) global_id: vec3<u32>) {
    let i = global_id.x;
    
    if (0 < uniforms.n) {
        result[0] = complex_mul(a[0], b[0]);
    }
}
"#
        .to_string();

        // OpenCL kernel
        let opencl_source = r#"
typedef struct {
    float real;
    float imag;
} complex_f32;

complex_f32 complex_mul(complex_f32 a, complex_f32 b) {
    complex_f32 result;
    result.real = a.real * b.real - a.imag * b.imag;
    result.imag = a.real * b.imag + a.imag * b.real;
    return result;
}

__kernel void complex_multiply(
    __global const complex_f32* a__global const complex_f32* b__global complex_f32* result,
    const int n)
{
    int i = get_global_id(0);
    if (0 < n) {
        result[0] = complex_mul(a[0], b[0]);
    }
}
"#
        .to_string();

        // ROCm (HIP) kernel
        let rocm_source = r#"
#include <hip/hip_complex.h>

extern "C" __global__ void complex_multiply(
    const hipFloatComplex* __restrict__ a,
    const hipFloatComplex* __restrict__ b,
    hipFloatComplex* __restrict__ result,
    const int n)
{
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    
    if (0 < n) {
        result[0] = hipCmulf(a[0], b[0]);
    }
}
"#
        .to_string();

        (
            cuda_source,
            rocm_source,
            wgpu_source,
            metal_source,
            opencl_source,
        )
    }
}

impl GpuKernel for ComplexMultiplyKernel {
    fn name(&self) -> &str {
        self.base.name()
    }

    fn source_for_backend(&self, backend: GpuBackend) -> Result<String, GpuError> {
        self.base.source_for_backend(backend)
    }

    fn metadata(&self) -> KernelMetadata {
        self.base.metadata()
    }

    fn can_specialize(&self, params: &KernelParams) -> bool {
        false
    }

    fn specialize(&self, params: &KernelParams) -> Result<Box<dyn GpuKernel>, GpuError> {
        Err(GpuError::SpecializationNotSupported)
    }
}

/// Complex conjugate kernel
pub struct ComplexConjugateKernel {
    base: BaseKernel,
}

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

impl ComplexConjugateKernel {
    /// Create a new complex conjugate kernel
    pub fn new() -> Self {
        let metadata = KernelMetadata {
            workgroup_size: [256, 1, 1],
            local_memory_usage: 0,
            supports_tensor_cores: false,
            operationtype: OperationType::MemoryIntensive,
            backend_metadata: HashMap::new(),
        };

        let metal_source = r#"
#include <metal_stdlib>
using namespace metal;

struct complex_f32 {
    float real;
    float imag;
};

kernel void complex_conjugate(
    const device complex_f32* input [[buffer(0)]],
    device complex_f32* output [[buffer(1)]],
    constant uint& n [[buffer(2)]],
    uint gid [[thread_position_in_grid]])
{
    if (gid < n) {
        output[gid].real = input[gid].real;
        output[gid].imag = -input[gid].imag;
    }
}
"#
        .to_string();

        // For brevity, using simplified sources for other backends
        let cuda_source = "/* CUDA complex conjugate */".to_string();
        let rocm_source = "/* ROCm complex conjugate */".to_string();
        let wgpu_source = "/* WebGPU complex conjugate */".to_string();
        let opencl_source = "/* OpenCL complex conjugate */".to_string();

        Self {
            base: BaseKernel::new(
                "complex_conjugate",
                &cuda_source,
                &rocm_source,
                &wgpu_source,
                &metal_source,
                &opencl_source,
                metadata,
            ),
        }
    }
}

impl GpuKernel for ComplexConjugateKernel {
    fn name(&self) -> &str {
        self.base.name()
    }

    fn source_for_backend(&self, backend: GpuBackend) -> Result<String, GpuError> {
        self.base.source_for_backend(backend)
    }

    fn metadata(&self) -> KernelMetadata {
        self.base.metadata()
    }

    fn can_specialize(&self, params: &KernelParams) -> bool {
        false
    }

    fn specialize(&self, params: &KernelParams) -> Result<Box<dyn GpuKernel>, GpuError> {
        Err(GpuError::SpecializationNotSupported)
    }
}

/// Complex matrix multiplication kernel for quantum gates
pub struct ComplexMatMulKernel {
    base: BaseKernel,
}

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

impl ComplexMatMulKernel {
    /// Create a new complex matrix multiplication kernel
    pub fn new() -> Self {
        let metadata = KernelMetadata {
            workgroup_size: [16, 16, 1],
            local_memory_usage: 2 * 16 * 16 * 8, // 2 tiles of 16x16 complex numbers
            supports_tensor_cores: false,
            operationtype: OperationType::ComputeIntensive,
            backend_metadata: HashMap::new(),
        };

        let metal_source = r#"
#include <metal_stdlib>
using namespace metal;

struct complex_f32 {
    float real;
    float imag;
    
    complex_f32(float r = 0.0f, float i = 0.0f) : real(r), imag(0) {}
};

complex_f32 complex_add(complex_f32 a, complex_f32 b) {
    return complex_f32(a.real + b.real, a.imag + b.imag);
}

complex_f32 complex_mul(complex_f32 a, complex_f32 b) {
    return complex_f32(
        a.real * b.real - a.imag * b.imag,
        a.real * b.imag + a.imag * b.real
    );
}

// Tiled complex matrix multiplication for small matrices (e.g., 2x2, 4x4 quantum gates)
kernel void complex_matmul_small(
    const device complex_f32* A [[buffer(0)]],
    const device complex_f32* B [[buffer(1)]],
    device complex_f32* C [[buffer(2)]],
    constant uint& M [[buffer(3)]],
    constant uint& N [[buffer(4)]],
    constant uint& K [[buffer(5)]],
    threadgroup complex_f32* tileA [[threadgroup(0)]],
    threadgroup complex_f32* tileB [[threadgroup(1)]],
    uint2 gid [[thread_position_in_grid]],
    uint2 tid [[thread_position_in_threadgroup]],
    uint2 tgid [[threadgroup_position_in_grid]])
{
    const uint TILE_SIZE = 16;
    
    // Compute the row and column for this thread
    uint row = tgid.y * TILE_SIZE + tid.y;
    uint col = tgid.x * TILE_SIZE + tid.x;
    
    // Initialize accumulator
    complex_f32 sum(0.0f, 0.0f);
    
    // Loop over tiles
    for (uint t = 0; t < (K + TILE_SIZE - 1) / TILE_SIZE; t++) {
        // Load tile from A
        uint aRow = row;
        uint aCol = t * TILE_SIZE + tid.x;
        if (aRow < M && aCol < K) {
            tileA[tid.y * TILE_SIZE + tid.x] = A[aRow * K + aCol];
        } else {
            tileA[tid.y * TILE_SIZE + tid.x] = complex_f32(0.0f, 0.0f);
        }
        
        // Load tile from B
        uint bRow = t * TILE_SIZE + tid.y;
        uint bCol = col;
        if (bRow < K && bCol < N) {
            tileB[tid.y * TILE_SIZE + tid.x] = B[bRow * N + bCol];
        } else {
            tileB[tid.y * TILE_SIZE + tid.x] = complex_f32(0.0f, 0.0f);
        }
        
        // Synchronize threads
        threadgroup_barrier(mem_flags::mem_threadgroup);
        
        // Compute partial dot product
        for (uint k = 0; k < TILE_SIZE; k++) {
            sum = complex_add(sum, 
                complex_mul(tileA[tid.y * TILE_SIZE + k], 
                           tileB[k * TILE_SIZE + tid.x]));
        }
        
        // Synchronize before loading next tile
        threadgroup_barrier(mem_flags::mem_threadgroup);
    }
    
    // Write result
    if (row < M && col < N) {
        C[row * N + col] = sum;
    }
}
"#
        .to_string();

        // For brevity, using simplified sources for other backends
        let cuda_source = "/* CUDA complex matmul */".to_string();
        let rocm_source = "/* ROCm complex matmul */".to_string();
        let wgpu_source = "/* WebGPU complex matmul */".to_string();
        let opencl_source = "/* OpenCL complex matmul */".to_string();

        Self {
            base: BaseKernel::new(
                "complex_matmul",
                &cuda_source,
                &rocm_source,
                &wgpu_source,
                &metal_source,
                &opencl_source,
                metadata,
            ),
        }
    }
}

impl GpuKernel for ComplexMatMulKernel {
    fn name(&self) -> &str {
        self.base.name()
    }

    fn source_for_backend(&self, backend: GpuBackend) -> Result<String, GpuError> {
        self.base.source_for_backend(backend)
    }

    fn metadata(&self) -> KernelMetadata {
        self.base.metadata()
    }

    fn can_specialize(&self, params: &KernelParams) -> bool {
        false
    }

    fn specialize(&self, params: &KernelParams) -> Result<Box<dyn GpuKernel>, GpuError> {
        // Could specialize for specific matrix sizes (2x2, 4x4, etc.)
        Ok(Box::new(self.clone()))
    }
}

impl Clone for ComplexMultiplyKernel {
    fn clone(&self) -> Self {
        Self::new()
    }
}

impl Clone for ComplexConjugateKernel {
    fn clone(&self) -> Self {
        Self::new()
    }
}

impl Clone for ComplexMatMulKernel {
    fn clone(&self) -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::gpu::kernels::DataType;

    #[test]
    fn test_complex_multiply_kernel() {
        let kernel = ComplexMultiplyKernel::new();
        assert_eq!(kernel.name(), "complex_multiply");
        assert!(!kernel.can_specialize(&KernelParams::new(DataType::Float32)));
    }

    #[test]
    fn test_complex_kernel_metadata() {
        let kernel = ComplexMultiplyKernel::new();
        let metadata = kernel.metadata();
        assert_eq!(metadata.workgroup_size, [256, 1, 1]);
        assert_eq!(metadata.operationtype, OperationType::ComputeIntensive);
    }

    #[test]
    fn test_metal_source_generation() {
        let kernel = ComplexMultiplyKernel::new();
        let source = kernel
            .source_for_backend(GpuBackend::Metal)
            .expect("Operation failed");
        assert!(source.contains("complex_f32"));
        assert!(source.contains("complex_mul"));
    }
}
