use std::ops::{Add, Mul};

use mdarray::{Array, Dim, Layout, Shape, Slice};
use mdarray_linalg::{
    matvec::{Argmax, MatVec, MatVecBuilder, Outer, OuterBuilder, VecOps},
    utils::unravel_index,
};
use num_complex::ComplexFloat;
use num_traits::Zero;

use super::{
    scalar::BlasScalar,
    simple::{amax, asum, axpy, dotc, dotu, gemv, ger, nrm2},
};
use crate::Blas;

struct BlasMatVecBuilder<'a, T, D0, D1, La, Lx>
where
    D0: Dim,
    D1: Dim,
    La: Layout,
    Lx: Layout,
{
    alpha: T,
    a: &'a Slice<T, (D0, D1), La>,
    x: &'a Slice<T, (D1,), Lx>,
}

impl<'a, T, La, Lx, D0: Dim, D1: Dim> MatVecBuilder<'a, T, La, Lx, D0, D1>
    for BlasMatVecBuilder<'a, T, D0, D1, La, Lx>
where
    La: Layout,
    Lx: Layout,
    T: BlasScalar + ComplexFloat,
    T::Real: Into<T>,
    D0: Dim,
    D1: Dim,
{
    fn scale(mut self, alpha: T) -> Self {
        self.alpha = alpha * self.alpha;
        self
    }

    fn eval(self) -> Array<T, (D0,)> {
        let mut y = Array::<T, (D0,)>::from_elem(
            <(D0,) as Shape>::from_dims(&[self.a.shape().dim(0)]),
            T::zero(),
        );
        gemv(self.alpha, self.a, self.x, T::zero(), &mut y);
        y
    }

    fn write<Ly: Layout>(self, y: &mut Slice<T, (D0,), Ly>) {
        gemv(self.alpha, self.a, self.x, T::zero(), y);
    }

    fn add_to_vec<Ly: Layout>(self, y: &mut Slice<T, (D0,), Ly>) {
        gemv(self.alpha, self.a, self.x, T::one(), y);
    }

    fn add_to_scaled_vec<Ly: Layout>(self, y: &mut Slice<T, (D0,), Ly>, beta: T) {
        gemv(self.alpha, self.a, self.x, beta, y);
    }
}

impl<T, D0: Dim, D1: Dim> MatVec<T, D0, D1> for Blas
where
    T: BlasScalar + ComplexFloat,
    T::Real: Into<T>,
{
    fn matvec<'a, La, Lx>(
        &self,
        a: &'a Slice<T, (D0, D1), La>,
        x: &'a Slice<T, (D1,), Lx>,
    ) -> impl MatVecBuilder<'a, T, La, Lx, D0, D1>
    where
        La: Layout,
        Lx: Layout,
    {
        BlasMatVecBuilder {
            alpha: T::one(),
            a,
            x,
        }
    }
}

impl<T: ComplexFloat + BlasScalar + Add<Output = T> + Mul<Output = T> + Zero + Copy, D1: Dim>
    VecOps<T, D1> for Blas
{
    type Real = T::Real;

    fn add_to_scaled<Lx: Layout, Ly: Layout>(
        &self,
        alpha: T,
        x: &Slice<T, (D1,), Lx>,
        y: &mut Slice<T, (D1,), Ly>,
    ) {
        axpy(alpha, x, y);
    }

    fn dot<Lx: Layout, Ly: Layout>(&self, x: &Slice<T, (D1,), Lx>, y: &Slice<T, (D1,), Ly>) -> T {
        dotu(x, y)
    }

    fn dotc<Lx: Layout, Ly: Layout>(&self, x: &Slice<T, (D1,), Lx>, y: &Slice<T, (D1,), Ly>) -> T {
        dotc(x, y)
    }

    fn norm2<Lx: Layout>(&self, x: &Slice<T, (D1,), Lx>) -> Self::Real {
        nrm2(x)
    }

    fn norm1<Lx: Layout>(&self, x: &Slice<T, (D1,), Lx>) -> Self::Real {
        asum(x)
    }

    fn rot<Lx: Layout, Ly: Layout>(
        &self,
        _x: &mut Slice<T, (D1,), Lx>,
        _y: &mut Slice<T, (D1,), Ly>,
        _c: Self::Real,
        _s: T,
    ) {
        todo!()
    }
}

impl<
    T: ComplexFloat
        + std::cmp::PartialOrd
        + BlasScalar
        + Add<Output = T>
        + Mul<Output = T>
        + Zero
        + Copy,
> Argmax<T> for Blas
where
    T::Real: PartialOrd,
{
    fn argmax_write<Lx: Layout, S: Shape>(
        &self,
        _x: &Slice<T, S, Lx>,
        _output: &mut Vec<usize>,
    ) -> bool {
        unimplemented!();
    }

    fn argmax<Lx: Layout, S: Shape>(&self, _x: &Slice<T, S, Lx>) -> Option<Vec<usize>> {
        unimplemented!();
    }

    fn argmax_abs_write<Lx: Layout, S: Shape>(
        &self,
        x: &Slice<T, S, Lx>,
        output: &mut Vec<usize>,
    ) -> bool {
        output.clear();
        if x.is_empty() {
            return false;
        }
        if x.rank() == 0 {
            return true;
        }
        let max_flat_idx = amax(x);
        let indices = unravel_index(x, max_flat_idx);
        output.extend_from_slice(&indices);
        true
    }

    fn argmax_abs<Lx: Layout, S: Shape>(&self, x: &Slice<T, S, Lx>) -> Option<Vec<usize>> {
        let mut result = Vec::new();
        if self.argmax_abs_write(x, &mut result) {
            Some(result)
        } else {
            None
        }
    }
}

struct BlasOuterBuilder<'a, T, Dx, Dy, Lx, Ly>
where
    Lx: Layout,
    Ly: Layout,
    Dx: Dim,
    Dy: Dim,
{
    alpha: T,
    x: &'a Slice<T, (Dx,), Lx>,
    y: &'a Slice<T, (Dy,), Ly>,
}

impl<'a, T, Dx, Dy, Lx, Ly> OuterBuilder<'a, T, Lx, Ly, Dx, Dy>
    for BlasOuterBuilder<'a, T, Dx, Dy, Lx, Ly>
where
    Lx: Layout,
    Ly: Layout,
    T: BlasScalar + ComplexFloat,
    T::Real: Into<T>,
    Dx: Dim,
    Dy: Dim,
{
    fn scale(mut self, alpha: T) -> Self {
        self.alpha = alpha * self.alpha;
        self
    }

    fn eval(self) -> Array<T, (Dx, Dy)> {
        let shape = <(Dx, Dy) as Shape>::from_dims(&[self.x.len(), self.y.len()]);
        let mut a = Array::<T, (Dx, Dy)>::from_elem(shape, T::zero());
        ger(self.alpha, self.x, self.y, &mut a);
        a
    }

    fn write<La: Layout>(self, a: &mut Slice<T, (Dx, Dy), La>) {
        let zero = T::zero();
        a.fill(zero);
        ger(self.alpha, self.x, self.y, a);
    }

    fn add_to<La: Layout>(self, a: &mut Slice<T, (Dx, Dy), La>) {
        ger(self.alpha, self.x, self.y, a);
    }

}

impl<T, Dx, Dy> Outer<T, Dx, Dy> for Blas
where
    T: BlasScalar + ComplexFloat,
    T::Real: Into<T>,
    Dx: Dim,
    Dy: Dim,
{
    fn outer<'a, Lx, Ly>(
        &self,
        x: &'a Slice<T, (Dx,), Lx>,
        y: &'a Slice<T, (Dy,), Ly>,
    ) -> impl OuterBuilder<'a, T, Lx, Ly, Dx, Dy>
    where
        Lx: Layout,
        Ly: Layout,
    {
        BlasOuterBuilder {
            alpha: T::one(),
            x,
            y,
        }
    }
}
