/*-
 *  Copyright 2020 Beuth Hochschule für Technik Berlin, Hochschule für Technik Stuttgart
 * 
 *  This file is part of CityDoctor2.
 *
 *  CityDoctor2 is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  CityDoctor2 is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with CityDoctor2.  If not, see <https://www.gnu.org/licenses/>.
 */
package de.hft.stuttgart.citydoctor2.math;

import java.util.Arrays;

/**
 * A 3x3 Matrix used for rotation of vectors
 * 
 * @author Matthias Betz
 *
 */
public class Matrix3x3d {

	private double[][] values;

	/**
	 * Identity matrix
	 */
	public static final Matrix3x3d IDENTITY = new Matrix3x3d(1d, 1d, 1d);

	/**
	 * Creates an empty matrix, set with all 0.
	 */
	public Matrix3x3d() {
		values = new double[3][3];
	}

	private Matrix3x3d(double[][] values, boolean copy) {
		if (values.length != 3 || values[0].length != 3 || values[1].length != 3 || values[2].length != 3) {
			throw new IllegalArgumentException("Only 3x3 arrays allowed");
		}
		if (copy) {
			this.values = new double[3][];
			this.values[0] = Arrays.copyOf(values[0], 3);
			this.values[1] = Arrays.copyOf(values[1], 3);
			this.values[2] = Arrays.copyOf(values[2], 3);
		} else {
			this.values = values;
		}
	}

	/**
	 * creates a new matrix with the given 3 x 3 array. The array will be copied.
	 * 
	 * @param values the 3x3 array containing the matrix values
	 */
	public Matrix3x3d(double[][] values) {
		this(values, true);
	}

	/**
	 * Adds two matrices. A + B
	 * 
	 * @param other the other matrix
	 * @return a new matrix object containing the result.
	 */
	public Matrix3x3d plus(Matrix3x3d other) {
		double[][] newValues = new double[3][3];
		for (int row = 0; row < 3; row++) {
			for (int column = 0; column < 3; column++) {
				newValues[row][column] = values[row][column] + other.getValue(row, column);
			}
		}
		return new Matrix3x3d(newValues, false);
	}

	/**
	 * Matrix multiplication with a scalar A * s.
	 * 
	 * @param s the scalar
	 * @return the result in a new matrix object
	 */
	public Matrix3x3d mult(double s) {
		double[][] newValues = new double[3][3];
		for (int row = 0; row < 3; row++) {
			for (int column = 0; column < 3; column++) {
				newValues[row][column] = values[row][column] * s;
			}
		}
		return new Matrix3x3d(newValues, false);
	}

	/**
	 * Matrix multiplication with another matrix A * B.
	 * 
	 * @param other the other matrix
	 * @return the result in a new matrix object
	 */
	public Matrix3x3d mult(Matrix3x3d other) {
		double[][] newValues = new double[3][3];
		for (int row = 0; row < 3; row++) {
			for (int column = 0; column < 3; column++) {
				newValues[row][column] = values[row][0] * other.getValue(0, column);
				newValues[row][column] += values[row][1] * other.getValue(1, column);
				newValues[row][column] += values[row][2] * other.getValue(2, column);
			}
		}
		return new Matrix3x3d(newValues, false);
	}

	/**
	 * Transposes this matrix
	 * 
	 * @return A new matrix with the transposed values, this instance is not
	 *         changed.
	 */
	public Matrix3x3d transpose() {
		Matrix3x3d x = new Matrix3x3d();
		double[][] copyValues = x.values;
		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < 3; j++) {
				copyValues[j][i] = values[i][j];
			}
		}
		return x;
	}

	/**
	 * Multiplies this matrix with a 3-dim vector.
	 * 
	 * @param the multiplied vector
	 * @return the result stored in a new vector
	 */
	public Vector3d mult(Vector3d other) {
		double x = values[0][0] * other.getX() + values[0][1] * other.getY() + values[0][2] * other.getZ();
		double y = values[1][0] * other.getX() + values[1][1] * other.getY() + values[1][2] * other.getZ();
		double z = values[2][0] * other.getX() + values[2][1] * other.getY() + values[2][2] * other.getZ();
		return new Vector3d(x, y, z);
	}

	/**
	 * Returns the value at the requested location
	 * 
	 * @param row    the row
	 * @param column the column
	 * @return the value
	 */
	public double getValue(int row, int column) {
		return values[row][column];
	}

	private Matrix3x3d(double m00, double m11, double m22) {
		this();
		values[0][0] = m00;
		values[1][1] = m11;
		values[2][2] = m22;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < 3; i++) {
			sb.append(Arrays.toString(values[i]));
			sb.append('\n');
		}
		return sb.toString();
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + Arrays.deepHashCode(values);
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		Matrix3x3d other = (Matrix3x3d) obj;
		return Arrays.deepEquals(values, other.values);
	}

}
