import java.util.Arrays;

/**
 * <code>LinearAlgebra</code> performs basic linear algebra routines.
 *
 * @author <a href="mailto:knapp@american.edu">Adam Knapp</a>
 * @version 1.0
 */
@SuppressWarnings({"WeakerAccess", "SameParameterValue"})
public class LinearAlgebra {

  /**
   * Adds two vectors
   *
   * @param vec1 <code>double</code> vector
   * @param vec2 <code>double</code> vector
   * @return a new <code>double</code> array containing the result of vector addition
   * @throws IllegalArgumentException if vectors are not of the same length or are <code>null</code>
   */
  public static double[] vecAdd(double[] vec1, double[] vec2) {

    if (vec1 == null
      || vec2 == null
      || vec1.length != vec2.length) {
      throw new IllegalArgumentException();
    }

    double[] result = new double[vec1.length];

    for (int i = 0; i < vec1.length; i++) {
      result[i] = vec1[i] + vec2[i];
    }

    return result;
  }

  /**
   * performs scalar multiplication on a vector
   *
   * @param scalar <code>double</code> scalar
   * @param vec    <code>double</code> vector
   * @return a new <code>double</code> array containing the result of scalar mult
   * @throws IllegalArgumentException if <code>vec</code> is <code>null</code>
   */
  public static double[] scalarMult(double scalar, double[] vec) {

    if (vec == null) {
      throw new IllegalArgumentException();
    }

    double[] result = new double[vec.length];

    for (int i = 0; i < vec.length; i++) {
      result[i] = scalar * vec[i];
    }

    return result;
  }

  /**
   * adds two matrices
   *
   * @param mat1 <code>double</code> matrix
   * @param mat2 <code>double</code> matrix
   * @return a new <code>double</code> matrix, the result of matrix addition
   * @throws IllegalArgumentException if matrices are wrong size or <code>null</code>
   */
  public static double[][] matAdd(double[][] mat1, double[][] mat2) {

    if (mat1 == null
      || mat2 == null
      || mat1.length != mat2.length
      || mat1[0].length != mat2[0].length)
      throw new IllegalArgumentException();

    double[][] result = new double[mat1.length][mat1[0].length];

    for (int i = 0; i < mat1.length; i++) {
      for (int j = 0; j < mat1[0].length; j++) {
        result[i][j] = mat1[i][j] + mat2[i][j];
      }
    }

    return result;
  }

  /**
   * performs scalar multiplication on a matrix
   *
   * @param scalar a <code>double</code> scalar value
   * @param mat    a <code>double</code> matrix
   * @return a new <code>double</code> matrix
   * @throws IllegalArgumentException if <code>mat</code> is <code>null</code>
   */
  public static double[][] scalarMult(double scalar, double[][] mat) {

    if (mat == null) {
      throw new IllegalArgumentException();
    }

    double[][] result = new double[mat.length][mat[0].length];

    for (int i = 0; i < mat.length; i++) {
      for (int j = 0; j < mat[0].length; j++) {
        result[i][j] = scalar * mat[i][j];
      }
    }

    return result;
  }

  /**
   * performs matrix-vector multiplication
   *
   * @param mat a <code>double</code> matrix
   * @param vec a <code>double</code> vector
   * @return a <code>double[]</code> vector, the result of multiplication
   * @throws IllegalArgumentException if the number of columns of <code>mat</code> is not equal to the number of rows of
   *                                  <code>vec</code>, or either is <code>null</code>
   */
  public static double[] matVecMult(double[][] mat, double[] vec) {

    if (mat == null
      || vec == null
      || mat[0].length != vec.length)
      throw new IllegalArgumentException();

    double[] result = new double[mat.length];

    for (int i = 0; i < mat.length; i++) {
      for (int j = 0; j < vec.length; j++) {
        result[i] += mat[i][j] * vec[j];
      }
    }

    return result;
  }

  /**
   * performs matrix-matrix multiplication
   *
   * @param mat1 a <code>double</code> matrix
   * @param mat2 a <code>double</code> matrix
   * @return a <code>double[][]</code> matrix, the result of multiplication
   * @throws IllegalArgumentException if the number of columns of <code>mat1</code> is not equal to the number of rows
   *                                  of <code>mat2</code>, or either is <code>null</code>
   */
  public static double[][] matMatMult(double[][] mat1, double[][] mat2) {

    if (mat1 == null
      || mat2 == null
      || mat1[0].length != mat2.length) {
      throw new IllegalArgumentException();
    }

    int resultRows = mat1.length,
      resultCols = mat2[0].length,
      innerDimension = mat1[0].length;

    double[][] result = new double[resultRows][resultCols];

    for (int i = 0; i < resultRows; i++) {
      for (int j = 0; j < resultCols; j++) {
        for (int k = 0; k < innerDimension; k++) {
          result[i][j] += mat1[i][k] * mat2[k][j];
        }
      }
    }

    return result;
  }

  /**
   * Unit tests
   *
   * @param args ignored
   */
  public static void main(String[] args) {

    double[] vecA = {1.0, -1.0};
    double[] vecB = {0.7071, 0.7071};
    double[][] matA = {{0.7071, -0.7071}, {0.7071, 0.7071}};
    double[][] matB = {{0.0, -1.0}, {1.0, 0.0}};

    // should print: [1.7069999999999999, -0.29300000000000004]
    System.out.println(Arrays.toString(vecAdd(vecA, vecB)));

    // should print: [0.9999808199999999, 0.9999808199999999]
    System.out.println(Arrays.toString(scalarMult(1.4142, vecB)));

    // should print: [[0.7071, -1.7071], [1.7071, 0.7071]]
    System.out.println(Arrays.deepToString(matAdd(matA, matB)));

    // should print: [[-0.0, 1.0], [-1.0, -0.0]]
    System.out.println(Arrays.deepToString(scalarMult(-1.0, matB)));

    // should print: [1.1412, 0.0]
    System.out.println(Arrays.toString(matVecMult(matA, vecA)));

    // should print: [[-1.0, 0.0], [0.0, -1.0]]
    double[][] matC = matMatMult(matB, matB);
    System.out.println(Arrays.deepToString(matC));

    // should print: [[1.0, 0.0], [0.0, 1.0]]
    System.out.println(Arrays.deepToString(matMatMult(matC, matC)));

  }

}
