/*
 * Copyright 2009-2010 Yuichiro Moriguchi
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.morilib.awk.matrix;

/**
 *
 *
 * @author MORIGUCHI, Yuichiro 2011/09/21
 */
public abstract class AbstractDoubleMatrix implements DoubleMatrix {

	/* (non-Javadoc)
	 * @see net.morilib.lisp.matrix.ILispMatrix#getRowVector(int)
	 */
	public DoubleVector getRowVector(int row) {
		double[] r = new double[columnSize()];

		if(row < 0 || row >= rowSize()) {
			throw new IndexOutOfBoundsException();
		} else {
			for(int i = 0; i < columnSize(); i++) {
				r[i] = get(row, i);
			}
			return new ArrayDoubleVector(r);
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.matrix.ILispMatrix#getColumnVector(int)
	 */
	public DoubleVector getColumnVector(int column) {
		double[] ra = new double[columnSize()];

		if(column < 0 || column >= columnSize()) {
			throw new IndexOutOfBoundsException();
		} else {
			for(int i = 0; i < rowSize(); i++) {
				ra[i] = get(i, column);
			}
			return new ArrayDoubleVector(ra);
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.matrix.ILispMatrix#transpose()
	 */
	public DoubleMatrix transpose() {
		ArrayDoubleMatrix r = new ArrayDoubleMatrix(this);

		for(int i = 0; i < rowSize(); i++) {
			for(int j = 0; j < columnSize(); j++) {
				r.set(j, i, get(i, j));
			}
		}
		return r;
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.matrix.ILispNumberMatrix#add(net.morilib.lisp.matrix.ILispNumberMatrix)
	 */
	public DoubleMatrix add(
			DoubleMatrix a) throws AwkMatrixException {
		if(rowSize() != a.rowSize() ||
				columnSize() != a.columnSize()) {
			throw new AwkMatrixException(
					"cannot add the given matrix");
		} else {
			ArrayDoubleMatrix r = new ArrayDoubleMatrix(this);

			for(int i = 0; i < rowSize(); i++) {
				for(int j = 0; j < columnSize(); j++) {
					r.set(i, j, r.get(i, j) + a.get(i, j));
				}
			}
			return r;
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.matrix.ILispNumberMatrix#subtract(net.morilib.lisp.matrix.ILispNumberMatrix)
	 */
	public DoubleMatrix sub(
			DoubleMatrix a) throws AwkMatrixException {
		if(rowSize() != a.rowSize() ||
				columnSize() != a.columnSize()) {
			throw new AwkMatrixException(
					"cannot subtract the given matrix");
		} else {
			ArrayDoubleMatrix r = new ArrayDoubleMatrix(this);

			for(int i = 0; i < rowSize(); i++) {
				for(int j = 0; j < columnSize(); j++) {
					r.set(i, j, r.get(i, j) - a.get(i, j));
				}
			}
			return r;
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.matrix.ILispNumberMatrix#multiply(net.morilib.lisp.matrix.ILispNumberMatrix)
	 */
	public DoubleVector mul(
			DoubleVector a) throws AwkMatrixException {
		double x;
		ArrayDoubleVector v;

		if(rowSize() != a.size()) {
			throw new AwkMatrixException(
					"cannot multiply the given matrix");
		} else {
			v = new ArrayDoubleVector(columnSize());
			for(int i = 0; i < columnSize(); i++) {
				x = 0.0;
				for(int k = 0; k < rowSize(); k++) {
					x = x + get(k, i) * a.get(k);
				}
				v.set(i, x);
			}
			return v;
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.matrix.ILispNumberMatrix#multiply(net.morilib.lisp.LispNumber)
	 */
	public DoubleMatrix mul(double a) {
		ArrayDoubleMatrix r = new ArrayDoubleMatrix(this);

		for(int i = 0; i < rowSize(); i++) {
			for(int j = 0; j < columnSize(); j++) {
				r.set(i, j, r.get(i, j) * a);
			}
		}
		return r;
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.matrix.ILispNumberMatrix#negate()
	 */
	public DoubleMatrix uminus() {
		ArrayDoubleMatrix r = new ArrayDoubleMatrix(this);

		for(int i = 0; i < rowSize(); i++) {
			for(int j = 0; j < columnSize(); j++) {
				r.set(i, j, -r.get(i, j));
			}
		}
		return r;
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.matrix.ILispNumberMatrix#multiply(net.morilib.lisp.matrix.ILispNumberMatrix)
	 */
	public DoubleMatrix mul(
			DoubleMatrix a) throws AwkMatrixException {
		double x;
		ArrayDoubleMatrix m;

		if(columnSize() != a.rowSize()) {
			throw new AwkMatrixException(
					"cannot multiply the given matrix");
		} else {
			m = new ArrayDoubleMatrix(rowSize(), a.columnSize());
			for(int i = 0; i < rowSize(); i++) {
				for(int j = 0; j < a.columnSize(); j++) {
					x = 0.0;
					for(int k = 0; k < columnSize(); k++) {
						x = x + get(i, k) * a.get(k, j);
					}
					m.set(i, j, x);
				}
			}
			return m;
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.matrix.ILispMatrix#determinant()
	 */
	public double determinant() throws AwkMatrixException {
		DoubleMatrix[] dc;

		if(columnSize() != rowSize()) {
			throw new AwkMatrixException("square matrix required");
		} else {
			dc = DoubleMatrices.decomposeLU(this);
			// det(P) * det(U)
			return dc[0].determinant() * dc[2].determinant();
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.matrix.ILispMatrix#invert()
	 */
	public DoubleMatrix inv() throws AwkMatrixException {
		DoubleMatrix x, y;
		DoubleMatrix[] decomposed;

		if(columnSize() != rowSize()) {
			throw new AwkMatrixException("square matrix required");
		} else {
			decomposed = DoubleMatrices.decomposeLU(this);
			x = decomposed[2].inv();
			y = decomposed[1].inv();
			x = x.mul(y);
			y = decomposed[0].inv();
			x = x.mul(y);
			return x;
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.matrix.ILispMatrix#changeRow(net.morilib.lisp.math.LispPermutation)
	 */
	public DoubleMatrix changeRow(final Permutation rowp) {
		if(rowp.size() != rowSize()) {
			throw new IllegalArgumentException();
		}
		return new AbstractDoubleMatrix() {

			public double get(int row, int column) {
				return AbstractDoubleMatrix.this.get(
						rowp.get(row), column);
			}

			public void set(int row, int column,
					double x) throws AwkMatrixException {
				AbstractDoubleMatrix.this.set(
						rowp.get(row), column, x);
			}

			public int rowSize() {
				return AbstractDoubleMatrix.this.rowSize();
			}

			public int columnSize() {
				return AbstractDoubleMatrix.this.columnSize();
			}

		};
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.matrix.ILispMatrix#changeColumn(net.morilib.lisp.math.LispPermutation)
	 */
	public DoubleMatrix changeColumn(final Permutation colp) {
		if(colp.size() != columnSize()) {
			throw new IllegalArgumentException();
		}
		return new AbstractDoubleMatrix() {

			public double get(int row, int column) {
				return AbstractDoubleMatrix.this.get(
						row, colp.get(column));
			}

			public void set(int row, int column,
					double x) throws AwkMatrixException {
				AbstractDoubleMatrix.this.set(
						row, colp.get(column), x);
			}

			public int rowSize() {
				return AbstractDoubleMatrix.this.rowSize();
			}

			public int columnSize() {
				return AbstractDoubleMatrix.this.columnSize();
			}

		};
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.matrix.ILispMatrix#submatrix(int, int, int, int)
	 */
	public DoubleMatrix submatrix(
			final int rowb, final int rowe,
			final int colb, final int cole) {
		if(rowb < 0 || rowb >= rowSize()) {
			throw new IndexOutOfBoundsException();
		} else if(rowe < 0 || rowe >= rowSize()) {
			throw new IndexOutOfBoundsException();
		} else if(colb < 0 || colb >= columnSize()) {
			throw new IndexOutOfBoundsException();
		} else if(cole < 0 || cole >= columnSize()) {
			throw new IndexOutOfBoundsException();
		} else if(rowe <= rowb) {
			throw new IllegalArgumentException();
		} else if(cole <= colb) {
			throw new IllegalArgumentException();
		}

		return new AbstractDoubleMatrix() {

			public double get(int row, int column) {
				if(row < 0 || row >= rowe - rowb) {
					throw new IndexOutOfBoundsException();
				} else if(column < 0 || column >= cole - colb) {
					throw new IndexOutOfBoundsException();
				}
				return AbstractDoubleMatrix.this.get(
						row + rowb, column + colb);
			}

			public void set(int row, int column,
					double x) throws AwkMatrixException {
				if(row < 0 || row >= rowe - rowb) {
					throw new IndexOutOfBoundsException();
				} else if(column < 0 || column >= cole - colb) {
					throw new IndexOutOfBoundsException();
				}
				AbstractDoubleMatrix.this.set(
						row + rowb, column + colb, x);
			}

			public int rowSize() {
				return rowe - rowb;
			}

			public int columnSize() {
				return cole - colb;
			}

		};
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.matrix.ILispMatrix#submatrix(net.morilib.lisp.uvector.HomogeneousUnsignedArray, net.morilib.lisp.uvector.HomogeneousUnsignedArray)
	 */
	public DoubleMatrix submatrix(
			final int[] rowa,
			final int[] cola) throws AwkMatrixException {
		// check row indices
		for(int i = 0; i < rowa.length; i++) {
			if(rowa[i] >= rowSize()) {
				throw new AwkMatrixException("invaild range");
			}
		}

		// check column indices
		for(int i = 0; i < cola.length; i++) {
			if(cola[i] >= columnSize()) {
				throw new AwkMatrixException("invalid range");
			}
		}

		return new AbstractDoubleMatrix() {

			public double get(int row, int column) {
				return AbstractDoubleMatrix.this.get(
						rowa[row], cola[column]);
			}

			public void set(int row, int column,
					double x) throws AwkMatrixException {
				AbstractDoubleMatrix.this.set(
						rowa[row], cola[column], x);				
			}

			public int rowSize() {
				return rowa.length;
			}

			public int columnSize() {
				return cola.length;
			}

		};
	}

	/**
	 * 
	 * @param y
	 * @return
	 */
	public boolean isEqualTo(DoubleMatrix y) {
		if(rowSize() != y.rowSize() ||
				columnSize() != y.columnSize()) {
			return false;
		} else {
			for(int i = 0; i < rowSize(); i++) {
				for(int j = 0; j < columnSize(); j++) {
					if(get(i, j) != y.get(i, j)) {
						return false;
					}
				}
			}
			return true;
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.matrix.ILispDatumMatrix#isSquare()
	 */
	public boolean isSquare() {
		return rowSize() == columnSize();
	}

}
