//! # Matrix Operations
//!
//! This module provides a `Matrix` struct for working with fixed-size matrices,
//! along with various operations and utility functions.
//!
//! ## Usage
//!
//! ```rust
//! use smyl::prelude::{Function, Matrix, ReLU};
//!
//! let matrix = Matrix::from([[1.0, -1.0], [-0.5, 3.0]]);
//! let activated = matrix.activate::<ReLU>();
//! let transposed = activated.transpose();
//! ```

use std::fmt::{Debug, Display, Formatter, Write};
use std::ops::{Add, AddAssign, Div, Mul, Sub, SubAssign};

use crate::maths::activation::Function;

/// A struct representing a matrix with a fixed height and width.
#[derive(Clone)]
pub struct Matrix<T> {
    height: usize,
    width:  usize,
    data:   Vec<T>,
}

impl<T> Matrix<T> {
    #[must_use]
    #[inline]
    pub const fn width(&self) -> usize { self.width }

    #[must_use]
    #[inline]
    pub const fn height(&self) -> usize { self.height }

    #[must_use]
    #[inline]
    pub const fn size(&self) -> usize { self.width * self.height }

    /// # Panics
    ///
    /// Will panic if the requested indices are out of bounds.
    #[inline]
    pub fn set(&mut self, j: usize, i: usize, value: T) {
        debug_assert!(
            j < self.height,
            "Cannot access the {}th row of a {}x{} matrix",
            j,
            self.height,
            self.width
        );
        debug_assert!(
            i < self.width,
            "Cannot access the {}th column of a {}x{} matrix",
            j,
            self.height,
            self.width
        );

        #[cfg(feature = "unsafe")]
        unsafe {
            *self.data.get_unchecked_mut(j * self.width + i) = value;
        }

        #[cfg(not(feature = "unsafe"))]
        {
            self.data[j * self.width + i] = value;
        }
    }

    /// # Panics
    ///
    /// Will panic if the requested indices are out of bounds.
    #[must_use]
    #[inline]
    pub fn get(&self, j: usize, i: usize) -> T
    where
        T: Copy,
    {
        debug_assert!(
            j < self.height,
            "Cannot access the {}th row of a {}x{} matrix",
            j,
            self.height,
            self.width
        );
        debug_assert!(
            i < self.width,
            "Cannot access the {}th column of a {}x{} matrix",
            j,
            self.height,
            self.width
        );

        #[cfg(feature = "unsafe")]
        unsafe {
            *self.data.get_unchecked(j * self.width + i)
        }

        #[cfg(not(feature = "unsafe"))]
        {
            self.data[j * self.width + i]
        }
    }

    /// # Panics
    ///
    /// Will panic if the matrix does not have the expected dimensions.
    #[inline]
    pub fn expect_dimensions<L>(&self, height: usize, width: usize, label: L)
    where
        L: Display,
    {
        debug_assert!(
            self.height == height && self.width == width,
            "{}: Expected a {}x{} matrix, got a {}x{} matrix",
            label,
            height,
            width,
            self.height,
            self.width
        );
    }

    #[inline]
    pub fn expect_dimensions_of<U, L>(&self, expected: &Matrix<U>, label: L)
    where
        L: Display,
    {
        self.expect_dimensions(expected.height, expected.width, label);
    }

    /// Constructs a new `Matrix` with the given data
    ///
    /// # Panics
    ///
    /// Will panic if the provided data has more or less elements than necessary
    /// to form the matrix with specified height and width.
    #[must_use]
    pub fn new(height: usize, width: usize, data: Vec<T>) -> Self {
        debug_assert_eq!(
            data.len(),
            height * width,
            "Expected a vector of {} elements, found {}",
            height * width,
            data.len()
        );

        Self {
            height,
            width,
            data,
        }
    }

    /// Constructs a new `Matrix` and fills it with zeros
    #[must_use]
    #[inline]
    pub fn zero(height: usize, width: usize) -> Self
    where
        T: Default + Clone,
    {
        Self::new(height, width, vec![T::default(); height * width])
    }

    /// Constructs a new `Matrix` and fills it with random values
    #[cfg(feature = "random")]
    #[must_use]
    pub fn random<R>(height: usize, width: usize, rng: &mut R) -> Self
    where
        T: Default + Clone,
        R: rand::Rng + ?Sized,
        rand::distributions::Standard: rand::distributions::Distribution<T>,
    {
        let mut matrix = Self::zero(height, width);
        rng.fill(&mut matrix);
        matrix
    }

    /// Returns the rows of the matrix as an array of single-row matrices.
    pub fn rows(&self) -> impl Iterator<Item = Self> + '_
    where
        T: Copy,
    {
        (0..self.height).map(|j| {
            Self::new(
                1,
                self.width,
                self.data[j * self.width..(j + 1) * self.width].to_vec(),
            )
        })
    }

    /// Returns the columns of the matrix as an array of single-column matrices.
    ///
    /// # Deprecated
    ///
    /// Please use this only for debugging purposes as it uses
    /// `Iterator::collect`, which reallocate a vector for each column.
    #[deprecated]
    pub fn cols(&self) -> impl Iterator<Item = Self> + '_
    where
        T: Copy,
    {
        (0..self.width).map(|i| {
            Self::new(
                self.height,
                1,
                (0..self.height)
                    .map(|j| self.data[j * self.width + i])
                    .collect(),
            )
        })
    }

    /// Applies an activation function to each element of the matrix.
    ///
    /// # Examples
    ///
    /// ```
    /// use smyl::prelude::{Matrix, ReLU};
    /// let matrix = Matrix::from([[1.0, -1.0], [-0.5, 3.0]]);
    /// let activated = matrix.activate::<ReLU>();
    /// assert_eq!(activated, Matrix::from([[1.0, 0.0], [0.0, 3.0]]))
    /// ```
    #[must_use]
    #[inline]
    pub fn activate<A>(self) -> Self
    where
        A: Function<T>,
        T: Clone,
    {
        Self {
            data: self.data.iter().map(|x| A::activate(x.clone())).collect(),
            ..self
        }
    }

    /// Applies the derivative of an activation function to each element of the
    /// matrix.
    ///
    /// # Examples
    ///
    /// ```
    /// use smyl::prelude::{Matrix, ReLU};
    /// let matrix = Matrix::from([[1.0, -1.0], [-0.5, 3.0]]);
    /// let activated = matrix.activate_derivative::<ReLU>();
    /// assert_eq!(activated, Matrix::from([[1.0, 0.0], [0.0, 1.0]]))
    /// ```
    #[must_use]
    #[inline]
    pub fn activate_derivative<A>(self) -> Self
    where
        A: Function<T>,
        T: Clone,
    {
        Self {
            data: self
                .data
                .iter()
                .map(|x| A::activate_derivative(x.clone()))
                .collect(),

            ..self
        }
    }

    /// Transposes the matrix, swapping rows and columns.
    ///
    /// # Examples
    ///
    /// ```
    /// use smyl::maths::matrix::Matrix;
    /// let matrix = Matrix::from([[1, 2], [3, 4]]);
    /// let transposed = matrix.transpose();
    /// assert_eq!(transposed, Matrix::from([[1, 3], [2, 4]]));
    /// ```
    #[must_use]
    pub fn transpose(&self) -> Self
    where
        T: Default + Copy,
    {
        let mut data = Vec::<T>::with_capacity(self.height * self.width);

        for i in 0..self.width {
            data.extend((0..self.height).map(|j| self.data[j * self.width + i]));
        }

        Self {
            height: self.width,
            width: self.height,
            data,
        }
    }

    /// Computes the dot product of the matrix with another single-column
    /// matrix.
    /// # Panics
    ///
    /// Will panic if the height of the second matrix does not match the width
    /// from the first matrix.
    ///
    /// # Examples
    ///
    /// ```
    /// use smyl::prelude::Matrix;
    /// let a = Matrix::from([[1, 2, 3]]);
    /// let b = Matrix::from([[4], [5], [6]]);
    /// let result = a.dot(&b);
    /// assert_eq!(result, Matrix::from([[32]])); // 1*4 + 2*5 + 3*6
    /// ```
    #[must_use]
    pub fn dot(self, other: &Self) -> Self
    where
        T: Default + Mul<T, Output = T> + AddAssign<T> + Copy + std::iter::Sum + Debug,
    {
        debug_assert_eq!(
            self.width, other.height,
            "Cannot multiply a {}x{} matrix by a {}x{} matrix. The width from the first matrix \
             must equals the height from the second matrix.",
            self.height, self.width, other.height, other.width
        );

        if self.height == 1 {
            let mut data = vec![T::default(); other.width];

            for (k, x) in self.data.iter().enumerate() {
                for (j, sum) in data.iter_mut().enumerate() {
                    *sum += x.mul(other.get(k, j));
                }
            }

            return Self::new(1, other.width, data);
        }

        let mut result = Self::zero(self.height, other.width);

        for i in 0..self.height {
            for j in 0..other.width {
                let mut sum = T::default();
                for k in 0..self.width {
                    sum += self.get(i, k) * other.get(k, j);
                }
                result.set(i, j, sum);
            }
        }

        result
    }

    /// Performs element-wise multiplication with another matrix.
    ///
    /// # Panics
    ///
    /// Will panic if the two matrices have not the same dimensions
    ///
    /// # Examples
    ///
    /// ```
    /// use smyl::prelude::Matrix;
    /// let a = Matrix::from([[1, 2], [3, 4]]);
    /// let b = Matrix::from([[5, 6], [7, 8]]);
    /// let result = a.hadamard(&b);
    /// assert_eq!(result, Matrix::from([[5, 12], [21, 32]]));
    /// ```
    #[must_use]
    pub fn hadamard(self, other: &Self) -> Self
    where
        T: Default + Copy + Mul<T, Output = T>,
    {
        debug_assert!(
            self.height == other.height && self.width == other.width,
            "Cannot perform the Hadamard product of a {}x{} matrix and a {}x{} matrix. The two \
             matrices must have the same dimensions.",
            self.height,
            self.width,
            other.height,
            other.width
        );

        Self {
            data: self
                .data
                .iter()
                .zip(other.data.iter())
                .map(|(a, b)| a.mul(*b))
                .collect(),

            ..self
        }
    }

    #[inline]
    pub fn empty(&mut self)
    where
        T: Clone + Default,
    {
        self.data.fill(T::default());
    }
}

impl<T> Debug for Matrix<T>
where
    T: Debug + Copy,
{
    /// Formats the matrix for debugging output.
    ///
    /// This implementation of the `Debug` trait displays the matrix in a
    /// readable format, with each row enclosed in vertical bars (`|`). Each
    /// element is printed using its own `Debug` implementation.
    ///
    /// # Examples
    ///
    /// ```
    /// use smyl::prelude::Matrix;
    /// let matrix = Matrix::from([[1, 2, 3], [4, 5, 6]]);
    /// println!("{:?}", matrix);
    /// // Output:
    /// // | 1 2 3 |
    /// // | 4 5 6 |
    /// ```
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        if self.height > 1 {
            f.write_char('\n')?;
        }

        for (j, row) in self.rows().enumerate() {
            write!(
                f,
                "| {} |",
                row.data
                    .iter()
                    .map(|x| format!("{x:?}"))
                    .collect::<Vec<String>>()
                    .join(" ")
            )?;
            if j < self.height - 1 {
                f.write_char('\n')?;
            }
        }
        Ok(())
    }
}

impl<T> PartialEq for Matrix<T>
where
    T: PartialEq,
{
    fn eq(&self, other: &Self) -> bool {
        self.width == other.width && self.height == other.height && self.data == other.data
    }
}

impl<T> Add<Self> for Matrix<T>
where
    T: Add<T, Output = T> + Copy,
{
    type Output = Self;

    /// Adds two matrices element-wise.
    ///
    /// # Examples
    ///
    /// ```
    /// use smyl::prelude::Matrix;
    /// let a = Matrix::from([[1, 2], [3, 4]]);
    /// let b = Matrix::from([[5, 6], [7, 8]]);
    /// let result = a + b;
    /// assert_eq!(result, Matrix::from([[6, 8], [10, 12]]));
    /// ```
    fn add(self, rhs: Self) -> Self::Output {
        debug_assert!(
            self.height == rhs.height && self.width == rhs.width,
            "Cannot add a {}x{} matrix to a {}x{} matrix. The two matrices must have the same \
             dimensions.",
            rhs.height,
            rhs.width,
            self.height,
            self.width
        );

        Self {
            data: self
                .data
                .iter()
                .zip(rhs.data.iter())
                .map(|(a, b)| a.add(*b))
                .collect(),

            ..self
        }
    }
}

impl<T> Add<&Self> for Matrix<T>
where
    T: Add<T, Output = T> + Copy,
{
    type Output = Self;

    /// Adds two matrices element-wise.
    ///
    /// # Examples
    ///
    /// ```
    /// use smyl::prelude::Matrix;
    /// let a = Matrix::from([[1, 2], [3, 4]]);
    /// let b = Matrix::from([[5, 6], [7, 8]]);
    /// let result = a + &b;
    /// assert_eq!(result, Matrix::from([[6, 8], [10, 12]]));
    /// ```
    fn add(self, rhs: &Self) -> Self::Output {
        debug_assert!(
            self.height == rhs.height && self.width == rhs.width,
            "Cannot add a {}x{} matrix to a {}x{} matrix. The two matrices must have the same \
             dimensions.",
            rhs.height,
            rhs.width,
            self.height,
            self.width
        );

        Self {
            data: self
                .data
                .iter()
                .zip(rhs.data.iter())
                .map(|(a, b)| a.add(*b))
                .collect(),

            ..self
        }
    }
}

impl<T> AddAssign<Self> for Matrix<T>
where
    T: AddAssign<T> + Copy,
{
    /// Adds two matrices element-wise and put the results in the first matrix.
    ///
    /// # Examples
    ///
    /// ```
    /// use smyl::prelude::Matrix;
    /// let mut a = Matrix::from([[1, 2], [3, 4]]);
    /// let b = Matrix::from([[5, 6], [7, 8]]);
    /// a += b;
    /// assert_eq!(a, Matrix::from([[6, 8], [10, 12]]));
    /// ```
    fn add_assign(&mut self, rhs: Self) {
        debug_assert!(
            self.height == rhs.height && self.width == rhs.width,
            "Cannot add a {}x{} matrix to a {}x{} matrix. The two matrices must have the same \
             dimensions.",
            rhs.height,
            rhs.width,
            self.height,
            self.width
        );

        for (a, b) in self.data.iter_mut().zip(rhs.data.iter()) {
            a.add_assign(*b);
        }
    }
}

impl<T> Sub<Self> for Matrix<T>
where
    T: Sub<T, Output = T> + Copy,
{
    type Output = Self;

    /// Subtracts two matrices element-wise.
    ///
    /// # Examples
    ///
    /// ```
    /// use smyl::prelude::Matrix;
    /// let a = Matrix::from([[5, 6], [7, 8]]);
    /// let b = Matrix::from([[1, 2], [3, 4]]);
    /// let result = a - b;
    /// assert_eq!(result, Matrix::from([[4, 4], [4, 4]]));
    /// ```
    fn sub(self, rhs: Self) -> Self::Output {
        debug_assert!(
            self.height == rhs.height && self.width == rhs.width,
            "Cannot subtract a {}x{} matrix from a {}x{} matrix. The two matrices must have the \
             same dimensions.",
            rhs.height,
            rhs.width,
            self.height,
            self.width
        );

        Self {
            data: self
                .data
                .iter()
                .zip(rhs.data.iter())
                .map(|(a, b)| a.sub(*b))
                .collect(),

            ..self
        }
    }
}

impl<T> SubAssign<Self> for Matrix<T>
where
    T: SubAssign<T> + Copy,
{
    /// Subtracts two matrices element-wise  and put the results in the first
    /// matrix.
    ///
    /// # Examples
    ///
    /// ```
    /// use smyl::prelude::Matrix;
    /// let mut a = Matrix::from([[1, 2], [3, 4]]);
    /// let b = Matrix::from([[5, 6], [7, 8]]);
    /// a += b;
    /// assert_eq!(a, Matrix::from([[6, 8], [10, 12]]));
    /// ```
    fn sub_assign(&mut self, rhs: Self) {
        debug_assert!(
            self.height == rhs.height && self.width == rhs.width,
            "Cannot add a {}x{} matrix to a {}x{} matrix. The two matrices must have the same \
             dimensions.",
            rhs.height,
            rhs.width,
            self.height,
            self.width
        );

        for (a, b) in self.data.iter_mut().zip(rhs.data.iter()) {
            a.sub_assign(*b);
        }
    }
}

impl<T> Div<T> for Matrix<T>
where
    T: Div<T, Output = T> + Copy,
{
    type Output = Self;

    /// Divides each element of the matrix by a scalar.
    ///
    /// # Examples
    ///
    /// ```
    /// use smyl::prelude::Matrix;
    /// let a = Matrix::from([[2, 4], [6, 8]]);
    /// let result = a / 2;
    /// assert_eq!(result, Matrix::from([[1, 2], [3, 4]]));
    /// ```
    fn div(self, rhs: T) -> Self::Output {
        Self {
            data: self.data.iter().map(|x| x.div(rhs)).collect(),
            ..self
        }
    }
}

impl<T> Mul<T> for Matrix<T>
where
    T: Mul<T, Output = T> + Copy,
{
    type Output = Self;

    /// Multiplies each element of the matrix by a scalar.
    ///
    /// # Examples
    ///
    /// ```
    /// use smyl::prelude::Matrix;
    /// let a = Matrix::from([[1, 2], [3, 4]]);
    /// let result = a * 2;
    /// assert_eq!(result, Matrix::from([[2, 4], [6, 8]]));
    /// ```
    fn mul(self, rhs: T) -> Self::Output {
        Self {
            data: self.data.iter().map(|x| x.mul(rhs)).collect(),
            ..self
        }
    }
}

impl<T> Mul<T> for &Matrix<T>
where
    T: Mul<T, Output = T> + Copy,
{
    type Output = Matrix<T>;

    /// Multiplies each element of a &matrix by a scalar.
    ///
    /// # Examples
    ///
    /// ```
    /// use smyl::prelude::Matrix;
    /// let a = Matrix::from([[1, 2], [3, 4]]);
    /// let result = a * 2;
    /// assert_eq!(result, Matrix::from([[2, 4], [6, 8]]));
    /// ```
    fn mul(self, rhs: T) -> Self::Output {
        Self::Output {
            data:   self.data.iter().map(|x| x.mul(rhs)).collect(),
            width:  self.width,
            height: self.height,
        }
    }
}

#[cfg(feature = "serde")]
impl<T> serde::Serialize for Matrix<T>
where
    T: serde::Serialize,
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        use serde::ser::SerializeStruct;

        let mut matrix = serializer.serialize_struct("Matrix", 3)?;
        matrix.serialize_field("height", &self.height)?;
        matrix.serialize_field("width", &self.width)?;
        matrix.serialize_field("data", &self.data)?;
        matrix.serialize_field("t", std::any::type_name::<T>())?;
        matrix.end()
    }
}

impl<T, const HEIGHT: usize, const WIDTH: usize> From<[[T; WIDTH]; HEIGHT]> for Matrix<T>
where
    T: Default + Copy,
{
    fn from(value: [[T; WIDTH]; HEIGHT]) -> Self {
        Self::new(HEIGHT, WIDTH, value.into_iter().flatten().collect())
    }
}

#[cfg(feature = "random")]
impl<T> rand::Fill for Matrix<T>
where
    rand::distributions::Standard: rand::distributions::Distribution<T>,
{
    fn try_fill<R: rand::Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
        for i in 0..self.height {
            for j in 0..self.width {
                self.data[i * self.width + j] = rng.gen();
            }
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use std::num::NonZeroU32;

    use rand::{Error, Rng, RngCore};

    use super::*;
    use crate::prelude::ReLU;

    #[test]
    fn test_size() {
        let matrix = Matrix::<u8>::zero(10, 20);
        assert_eq!(matrix.size(), 200);
    }

    #[test]
    fn test_set() {
        let mut matrix = Matrix::<u8>::zero(3, 5);
        matrix.set(1, 3, 1);
        assert_eq!(matrix.data, vec![
            0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0
        ]);
    }

    #[test]
    fn test_get() {
        let matrix = Matrix::<u8>::new(3, 5, vec![0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]);

        assert_eq!(matrix.get(1, 3), 1);
    }

    #[test]
    fn test_new() {
        let matrix = Matrix::<u8>::new(3, 5, vec![0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]);

        assert_eq!(matrix, Matrix {
            height: 3,
            width:  5,
            data:   vec![0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
        });
    }

    #[test]
    fn test_zero() {
        let matrix = Matrix::<u8>::zero(3, 5);

        assert_eq!(matrix, Matrix {
            height: 3,
            width:  5,
            data:   vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        });
    }

    struct IncrementalRng(u32);

    impl RngCore for IncrementalRng {
        fn next_u32(&mut self) -> u32 {
            let n = self.0;
            self.0 += 1;
            n
        }

        fn next_u64(&mut self) -> u64 { u64::from(self.next_u32()) }

        fn fill_bytes(&mut self, dest: &mut [u8]) { self.try_fill(dest).unwrap(); }

        fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
            for dst in dest {
                *dst = (self.next_u32() % 256)
                    .try_into()
                    .map_err(|_| Error::from(NonZeroU32::new(1).unwrap()))?;
            }
            Ok(())
        }
    }

    #[test]
    fn test_random() {
        let matrix = Matrix::<u8>::random(3, 5, &mut IncrementalRng(0));

        assert_eq!(matrix, Matrix {
            height: 3,
            width:  5,
            data:   vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
        });
    }

    #[test]
    fn test_rows() {
        let matrix =
            Matrix::<u8>::new(3, 5, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]);

        assert!(matrix.rows().eq([
            Matrix::<u8>::new(1, 5, vec![0, 1, 2, 3, 4]),
            Matrix::<u8>::new(1, 5, vec![5, 6, 7, 8, 9]),
            Matrix::<u8>::new(1, 5, vec![10, 11, 12, 13, 14]),
        ]));
    }

    #[test]
    #[allow(deprecated)]
    fn test_cols() {
        let matrix =
            Matrix::<u8>::new(3, 5, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]);

        assert!(matrix.cols().eq([
            Matrix::<u8>::new(3, 1, vec![0, 5, 10]),
            Matrix::<u8>::new(3, 1, vec![1, 6, 11]),
            Matrix::<u8>::new(3, 1, vec![2, 7, 12]),
            Matrix::<u8>::new(3, 1, vec![3, 8, 13]),
            Matrix::<u8>::new(3, 1, vec![4, 9, 14]),
        ]));
    }

    #[test]
    fn test_activate() {
        let matrix = Matrix::<i32>::new(3, 5, vec![
            0, -1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14,
        ]);

        assert_eq!(matrix.activate::<ReLU>(), Matrix {
            height: 3,
            width:  5,
            data:   vec![0, 0, 2, 0, 4, 0, 6, 0, 8, 0, 10, 0, 12, 0, 14],
        });
    }

    #[test]
    fn test_activate_derivative() {
        let matrix = Matrix::<i32>::new(3, 5, vec![
            0, -1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14,
        ]);

        assert_eq!(matrix.activate_derivative::<ReLU>(), Matrix {
            height: 3,
            width:  5,
            data:   vec![0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
        });
    }

    #[test]
    fn test_transpose() {
        let matrix =
            Matrix::<u8>::new(3, 5, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]);

        assert_eq!(matrix.transpose(), Matrix {
            height: 5,
            width:  3,
            data:   vec![0, 5, 10, 1, 6, 11, 2, 7, 12, 3, 8, 13, 4, 9, 14],
        });
    }

    #[test]
    fn test_dot() {
        let matrix1 = Matrix::<i32>::new(3, 2, vec![0, 1, 2, 3, 4, 5]);
        let matrix2 = Matrix::<i32>::new(2, 3, vec![6, 7, 8, 9, 10, 11]);

        assert_eq!(matrix1.dot(&matrix2), Matrix {
            height: 3,
            width:  3,
            data:   vec![9, 10, 11, 39, 44, 49, 69, 78, 87],
        });
    }

    #[test]
    fn test_hadamard() {
        let matrix1 = Matrix::<i32>::new(3, 2, vec![0, 1, 2, 3, 4, 5]);
        let matrix2 = Matrix::<i32>::new(3, 2, vec![6, 7, 8, 9, 10, 11]);

        assert_eq!(matrix1.hadamard(&matrix2), Matrix {
            height: 3,
            width:  2,
            data:   vec![0, 7, 16, 27, 40, 55],
        });
    }

    #[test]
    fn test_empty() {
        let mut matrix =
            Matrix::<u8>::new(3, 5, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]);

        matrix.empty();

        assert_eq!(matrix, Matrix {
            height: 3,
            width:  5,
            data:   vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        });
    }

    #[test]
    fn test_debug() {
        let inline_matrix = Matrix::<u8>::new(1, 15, vec![
            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
        ]);

        let matrix =
            Matrix::<u8>::new(3, 5, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]);

        assert_eq!(
            format!("{inline_matrix:?}"),
            "| 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |".to_string()
        );

        assert_eq!(
            format!("{matrix:?}"),
            "\n| 0 1 2 3 4 |\n| 5 6 7 8 9 |\n| 10 11 12 13 14 |".to_string()
        );
    }

    #[test]
    fn test_eq() {
        let witness =
            Matrix::<u8>::new(3, 5, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]);

        let same = Matrix::<u8>::new(3, 5, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]);

        let diff_dimensions =
            Matrix::<u8>::new(5, 3, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]);

        let diff_data = Matrix::<u8>::new(3, 5, vec![
            0, 1, 2, 3, 4, 5, 6, 7, 255, 9, 10, 11, 12, 13, 0,
        ]);

        assert!(witness.eq(&same));
        assert!(!witness.eq(&diff_dimensions));
        assert!(!witness.eq(&diff_data));
    }
}
