use crate::data::Buffer;
use crate::runtime::Env;
use obwio::*;
use std::ffi::c_void;

/// setarg() sets an argument. For scalar values, use setarg_scalar().
/// The third parameter, arg, is the 0-based index of the kernel argument.
///
/// # Examples
///
/// ```rust
/// use obrah::kernel;
/// use obrah::runtime;
/// use obrah::data;
/// use obrah::runtime::{ClError, Env};
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
///
///     let mut env = Env::new(0, 0)?;
///     env.use_kernel("examples/vecadd_kernel.cl")?.program()?.make_kernel("vec_add")?;
///
///     let mut a = vec![7.0f32, 8.0, 2.0, 6.0];
///     let mut b = vec![134.0f32, 134.11, 34.8, 112.9];
///     let mut result = vec![0.0f32; a.len()];
///
///     // Buffers
///     let mut buf_a = data::Buffer::new(&mut env, &mut a);
///     let mut buf_b = data::Buffer::new(&mut env, &mut b);
///     let mut buf_result = data::Buffer::new(&mut env, &mut result);
///
///      // Send data to GPU
///     buf_a.to(&mut env);
///     buf_b.to(&mut env);
///
///     // Set kernel arguments
///     kernel::setarg(&env, &mut buf_a, 0);
///     kernel::setarg(&env, &mut buf_b, 1);
///     kernel::setarg(&env, &mut buf_result, 2);
/// Ok(())
/// }
/// ```
///
pub fn setarg(env: &Env, buffer: &Buffer, arg: usize) {
    unsafe {
        let size = std::mem::size_of::<cl_mem>();
        let buf_ptr: *const std::ffi::c_void = &buffer.buffer as *const _ as *const _;
        clSetKernelArg(env.kernel, arg as u32, size, buf_ptr as *const _);
        if env.err != 0 {
            panic!("OpenCL error: {}", env.err);
        }
    }
}

pub fn get_devices() {
    unsafe {
        let mut num_platforms = 0;
        clGetPlatformIDs(0, std::ptr::null_mut(), &mut num_platforms);
        let mut platforms = vec![std::ptr::null_mut(); num_platforms as usize];
        clGetPlatformIDs(num_platforms, platforms.as_mut_ptr(), std::ptr::null_mut());

        for (p_idx, &platform) in platforms.iter().enumerate() {
            println!("Platform {}:", p_idx);

            let mut num_devices = 0;
            clGetDeviceIDs(
                platform,
                CL_DEVICE_TYPE_ALL.into(),
                0,
                std::ptr::null_mut(),
                &mut num_devices,
            );
            let mut devices = vec![std::ptr::null_mut(); num_devices as usize];
            clGetDeviceIDs(
                platform,
                CL_DEVICE_TYPE_ALL.into(),
                num_devices,
                devices.as_mut_ptr(),
                std::ptr::null_mut(),
            );

            for (d_idx, &device) in devices.iter().enumerate() {
                let mut size = 0;
                clGetDeviceInfo(device, CL_DEVICE_NAME, 0, std::ptr::null_mut(), &mut size);
                let mut name_buf = vec![0u8; size];
                clGetDeviceInfo(
                    device,
                    CL_DEVICE_NAME,
                    size,
                    name_buf.as_mut_ptr() as *mut _,
                    std::ptr::null_mut(),
                );

                let name = std::ffi::CStr::from_bytes_with_nul(&name_buf).unwrap();
                println!("  Device {}: {}", d_idx, name.to_str().unwrap());
            }
        }
    }
}

/// Set a scalar argument.
/// Scalar arguments are single-data types, such as
/// floats and integers.
/// They cannot be read from.
pub fn setarg_scalar<T>(env: &Env, val: &T, arg: usize) {
    unsafe {
        let size = std::mem::size_of::<T>();
        clSetKernelArg(
            env.kernel,
            arg as u32,
            size,
            val as *const T as *const c_void,
        );
        if env.err != 0 {
            panic!("OpenCL error: {}", env.err);
        }
    }
}

/// Run the kernel! Simply input the number of threads in the x, and threads in the y.
pub fn run_kernel(env: &mut Env, threadsx: usize, threadsy: usize) {
    unsafe {
        let global_work_size: [usize; 2] = [threadsx, threadsy];
        clEnqueueNDRangeKernel(
            env.queue,
            env.kernel,
            2,
            std::ptr::null(),
            global_work_size.as_ptr(),
            std::ptr::null(),
            0,
            std::ptr::null_mut(),
            std::ptr::null_mut(),
        );
        clFinish(env.queue);
    }
}
