use crate::{element::JitElement, tensor::JitTensor, JitRuntime};
use cubecl::{calculate_cube_count_elemwise, prelude::*};
use std::ops::Range;

#[cube(launch)]
fn slice_assign_kernel<E: CubePrimitive>(
    input: &mut Tensor<E>,
    value: &Tensor<E>,
    indices: Sequence<u32>,
    #[comptime] rank: u32,
) {
    let mut offset_input = 0;
    let mut offset_value = 0;

    #[unroll]
    for i in 0..rank {
        let range_start = *indices.index(i);
        let offset_local = ABSOLUTE_POS / value.stride(i) % value.shape(i);
        let offset_local_input = offset_local + range_start;

        offset_value += offset_local * value.stride(i);
        offset_input += offset_local_input * input.stride(i);
    }

    input[offset_input] = value[offset_value];
}

pub(crate) fn slice_assign<R: JitRuntime, E: JitElement>(
    tensor: JitTensor<R>,
    indices: &[Range<usize>],
    value: JitTensor<R>,
) -> JitTensor<R> {
    let tensor = match tensor.can_mut() {
        true => tensor,
        false => tensor.copy(),
    };
    let ndims = tensor.shape.num_dims();
    let mut indices_sequence = SequenceArg::<R, u32>::new();

    for i in 0..ndims {
        let start = indices.get(i).map(|index| index.start).unwrap_or(0);
        indices_sequence.push(ScalarArg::new(start as u32));
    }

    let cube_dim = CubeDim::default();
    let cube_count = calculate_cube_count_elemwise(tensor.shape.num_elements(), cube_dim);

    slice_assign_kernel::launch::<E, R>(
        &tensor.client,
        cube_count,
        cube_dim,
        tensor.as_tensor_arg::<E>(1),
        value.as_tensor_arg::<E>(1),
        indices_sequence,
        ndims as u32,
    );

    tensor
}
