use cblas_sys::{CBLAS_LAYOUT, CBLAS_TRANSPOSE};
use mdarray::{Dim, Layout, Shape, Slice};
use mdarray_linalg::utils::into_i32;
use num_complex::ComplexFloat;

use super::scalar::BlasScalar;
use crate::trans_stride;

pub(super) fn gemv<T, D0: Dim, D1: Dim, La, Lx, Ly>(
    alpha: T,
    a: &Slice<T, (D0, D1), La>,
    x: &Slice<T, (D1,), Lx>,
    beta: T,
    y: &mut Slice<T, (D0,), Ly>,
) where
    T: BlasScalar + ComplexFloat,
    La: Layout,
    Lx: Layout,
    Ly: Layout,
{
    let ash = *a.shape();
    let (m, n) = (ash.dim(0), ash.dim(1));

    if a.stride(1) == 1 {
        assert_eq!(x.len(), n, "x length must match number of columns in a");
    } else {
        assert_eq!(x.len(), m, "x length must match number of rows in a");
    }

    assert_eq!(
        y.len(),
        if a.stride(1) == 1 { m } else { n },
        "y length must match the output dimension"
    );

    let row_major = a.stride(1) == 1;
    assert!(
        row_major || a.stride(0) == 1,
        "a must be contiguous in one dimension"
    );

    let (same_order, other_order) = if row_major {
        (CBLAS_TRANSPOSE::CblasNoTrans, CBLAS_TRANSPOSE::CblasTrans)
    } else {
        (CBLAS_TRANSPOSE::CblasTrans, CBLAS_TRANSPOSE::CblasNoTrans)
    };
    let (a_trans, a_stride) = trans_stride(a, same_order, other_order);

    let x_inc = into_i32(x.stride(0));
    let y_inc = into_i32(y.stride(0));

    unsafe {
        T::cblas_gemv(
            if row_major {
                CBLAS_LAYOUT::CblasRowMajor
            } else {
                CBLAS_LAYOUT::CblasColMajor
            },
            a_trans,
            into_i32(m),
            into_i32(n),
            alpha,
            a.as_ptr(),
            a_stride,
            x.as_ptr(),
            x_inc,
            beta,
            y.as_mut_ptr(),
            y_inc,
        )
    }
}

pub(super) fn ger<T, La, Lx, Ly, D0: Dim, D1: Dim>(
    beta: T,
    x: &Slice<T, (D0,), Lx>,
    y: &Slice<T, (D1,), Ly>,
    a: &mut Slice<T, (D0, D1), La>,
) where
    T: BlasScalar + ComplexFloat,
    La: Layout,
    Lx: Layout,
    Ly: Layout,
{
    let ash = *a.shape();
    let (m, n) = (ash.dim(0), ash.dim(1));

    assert_eq!(x.len(), m, "x length must match number of rows in a");
    assert_eq!(y.len(), n, "y length must match number of columns in a");

    let x_inc = into_i32(x.stride(0));
    let y_inc = into_i32(y.stride(0));

    let row_major = a.stride(1) == 1;
    assert!(
        row_major || a.stride(0) == 1,
        "a must be contiguous in one dimension"
    );

    let lda = if row_major {
        into_i32(a.stride(0))
    } else {
        into_i32(a.stride(1))
    };

    unsafe {
        T::cblas_ger(
            if row_major {
                CBLAS_LAYOUT::CblasRowMajor
            } else {
                CBLAS_LAYOUT::CblasColMajor
            },
            into_i32(m),
            into_i32(n),
            beta,
            x.as_ptr(),
            x_inc,
            y.as_ptr(),
            y_inc,
            a.as_mut_ptr(),
            lda,
        )
    }
}

pub(super) fn asum<T, D1: Dim, Lx>(x: &Slice<T, (D1,), Lx>) -> T::Real
where
    T: BlasScalar + ComplexFloat,
    Lx: Layout,
{
    let n = into_i32(x.len());
    let incx = into_i32(x.stride(0));

    unsafe { T::cblas_asum(n, x.as_ptr(), incx) }
}

pub(super) fn axpy<T, D1: Dim, Lx, Ly>(alpha: T, x: &Slice<T, (D1,), Lx>, y: &mut Slice<T, (D1,), Ly>)
where
    T: BlasScalar + ComplexFloat,
    Lx: Layout,
    Ly: Layout,
{
    assert_eq!(x.len(), y.len(), "Vector lengths must match");

    let n = into_i32(x.len());
    let incx = into_i32(x.stride(0));
    let incy = into_i32(y.stride(0));

    unsafe { T::cblas_axpy(n, alpha, x.as_ptr(), incx, y.as_mut_ptr(), incy) }
}

pub(super) fn nrm2<T, D1: Dim, Lx>(x: &Slice<T, (D1,), Lx>) -> T::Real
where
    T: BlasScalar + ComplexFloat,
    Lx: Layout,
{
    let n = into_i32(x.len());
    let incx = into_i32(x.stride(0));

    unsafe { T::cblas_nrm2(n, x.as_ptr(), incx) }
}

pub(super) fn dotu<T: BlasScalar + ComplexFloat, D1: Dim, Lx: Layout, Ly: Layout>(
    x: &Slice<T, (D1,), Lx>,
    y: &Slice<T, (D1,), Ly>,
) -> T {
    assert_eq!(x.len(), y.len());
    let n = into_i32(x.len());
    unsafe {
        T::cblas_dotu_or_dot(
            n,
            x.as_ptr(),
            into_i32(x.stride(0)),
            y.as_ptr(),
            into_i32(y.stride(0)),
        )
    }
}

pub(super) fn dotc<T: BlasScalar + ComplexFloat, D1: Dim, Lx: Layout, Ly: Layout>(
    x: &Slice<T, (D1,), Lx>,
    y: &Slice<T, (D1,), Ly>,
) -> T {
    assert_eq!(x.len(), y.len());
    let n = into_i32(x.len());
    unsafe {
        T::cblas_dotc_or_dot(
            n,
            x.as_ptr(),
            into_i32(x.stride(0)),
            y.as_ptr(),
            into_i32(y.stride(0)),
        )
    }
}

pub(super) fn amax<T, S, L>(x: &Slice<T, S, L>) -> usize
where
    T: BlasScalar + ComplexFloat,
    S: Shape,
    L: Layout,
{
    assert!(!x.is_empty(), "Cannot find amax of empty slice");

    let n = into_i32(x.len());
    let incx = if x.rank() == 1 {
        into_i32(x.stride(0))
    } else {
        1
    };

    (unsafe { T::cblas_amax(n, x.as_ptr(), incx) } as usize)
}
