/*
 * Copyright 2011-2013, by Vladimir Kostyukov and Contributors.
 * 
 * This file is part of la4j project (http://la4j.org)
 * 
 * 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.
 * 
 * Contributor(s): Evgenia Krivova
 *                 Julia Kostyukova
 *                 Jakob Moellers
 *                 Maxim Samoylov
 *                 Anveshi Charuvaka
 *                 Todd Brunhoff
 *                 Pavel Kalaidin
 *                 Ewald Grusk
 *                 Yuriy Drozd
 */

package org.la4j;

import org.la4j.decomposition.MatrixDecompositor;
import org.la4j.inversion.MatrixInverter;
import org.la4j.iterator.ColumnMajorMatrixIterator;
import org.la4j.iterator.MatrixIterator;
import org.la4j.iterator.RowMajorMatrixIterator;
import org.la4j.iterator.VectorIterator;
import org.la4j.linear.LinearSystemSolver;
import org.la4j.matrix.MatrixFactory;
import org.la4j.matrix.DenseMatrix;
import org.la4j.matrix.dense.Basic1DMatrix;
import org.la4j.matrix.dense.Basic2DMatrix;
import org.la4j.matrix.functor.*;
import org.la4j.matrix.ColumnMajorSparseMatrix;
import org.la4j.matrix.RowMajorSparseMatrix;
import org.la4j.matrix.SparseMatrix;
import org.la4j.operation.MatrixMatrixOperation;
import org.la4j.operation.MatrixOperation;
import org.la4j.operation.MatrixVectorOperation;
import org.la4j.vector.functor.VectorAccumulator;
import org.la4j.vector.functor.VectorFunction;
import org.la4j.vector.functor.VectorProcedure;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Random;
import java.util.StringTokenizer;

/**
 * A real matrix.
 * <p>
 * <a href="http://mathworld.wolfram.com/Matrix.html">
 * http://mathworld.wolfram.com/Matrix.html </a>
 * </p>
 */
public abstract class Matrix implements Iterable<Double> {

    private static final String DEFAULT_ROWS_DELIMITER = "\n";
    private static final String DEFAULT_COLUMNS_DELIMITER = " ";
    private static final NumberFormat DEFAULT_FORMATTER = new DecimalFormat("0.000");
    private static final String[] INDENTS = { // 9 predefined indents for alignment
            " ",
            "  ",
            "   ",
            "    ",
            "     ",
            "      ",
            "       ",
            "        ",
            "         ",
            "          "
    };

    /**
     * Creates a zero {@link Matrix} of the given shape:
     * {@code rows} x {@code columns}.
     */
    public static Matrix zero(int rows, int columns) {
        long size = (long) rows * columns;
        return size > 1000 ? SparseMatrix.zero(rows, columns) : DenseMatrix.zero(rows, columns);
    }

    /**
     * Creates a constant {@link Matrix} of the given shape and {@code value}.
     */
    public static Matrix constant(int rows, int columns, double constant) {
        return DenseMatrix.constant(rows, columns, constant);
    }

    /**
     * Creates a diagonal {@link Matrix} of the given {@code size} whose
     * diagonal elements are equal to {@code diagonal}.
     */
    public static Matrix diagonal(int size, double diagonal) {
        return SparseMatrix.diagonal(size, diagonal);
    }

    /**
     * Creates an unit {@link Matrix} of the given shape:
     * {@code rows} x {@code columns}.
     */
    public static Matrix unit(int rows, int columns) {
        return DenseMatrix.unit(rows, columns);
    }

    /**
     * Creates an identity {@link Matrix} of the given {@code size}.
     */
    public static Matrix identity(int size) {
        return SparseMatrix.identity(size);
    }

    /**
     * Creates a random {@link Matrix} of the given shape:
     * {@code rows} x {@code columns}.
     */
    public static Matrix random(int rows, int columns, Random random) {
        return DenseMatrix.random(rows, columns, random);
    }

    /**
     * Creates a random symmetric {@link Matrix} of the given {@code size}.
     */
    public static Matrix randomSymmetric(int size, Random random) {
        return DenseMatrix.randomSymmetric(size, random);
    }

    /**
     * Creates a {@link Matrix} of the given 1D {@code array} w/o
     * copying the underlying array.
     */
    public static Matrix from1DArray(int rows, int columns, double[] array) {
        return Basic1DMatrix.from1DArray(rows, columns, array);
    }

    /**
     * Creates a {@link Matrix} of the given 2D {@code array} w/o
     * copying the underlying array.
     */
    public static Matrix from2DArray(double[][] array) {
        return Basic2DMatrix.from2DArray(array);
    }

    /**
     * Creates a block {@link Matrix} of the given blocks {@code a},
     * {@code b}, {@code c} and {@code d}.
     */
    public static Matrix block(Matrix a, Matrix b, Matrix c, Matrix d) {
        return DenseMatrix.block(a, b, c, d);
    }

    /**
     * Parses {@link Matrix} from the given CSV string.
     *
     * @param csv the string in CSV format
     *
     * @return a parsed matrix
     */
    public static Matrix fromCSV(String csv) {
        StringTokenizer lines = new StringTokenizer(csv, "\n");
        Matrix result = DenseMatrix.zero(10, 10);
        int rows = 0, columns = 0;

        while (lines.hasMoreTokens()) {
            if (result.rows() == rows) {
                result = result.copyOfRows((rows * 3) / 2 + 1);
            }

            StringTokenizer elements = new StringTokenizer(lines.nextToken(), ", ");
            int j = 0;
            while (elements.hasMoreElements()) {
                if (j == result.columns()) {
                    result = result.copyOfColumns((j * 3) / 2 + 1);
                }

                double x = Double.valueOf(elements.nextToken());
                result.set(rows, j++, x);
            }

            rows++;
            columns = j > columns ? j : columns;
        }

        return result.copyOfShape(rows, columns);
    }

    /**
     * Parses {@link Matrix} from the given Matrix Market string.
     *
     * @param mm the string in Matrix Market format
     *
     * @return a parsed matrix
     */
    public static Matrix fromMatrixMarket(String mm) {
        StringTokenizer body = new StringTokenizer(mm, "\n");

        String headerString = body.nextToken();
        StringTokenizer header = new StringTokenizer(headerString);

        if (!"%%MatrixMarket".equals(header.nextToken())) {
            throw new IllegalArgumentException("Wrong input file format: can not read header '%%MatrixMarket'.");
        }

        String object = header.nextToken();
        if (!"matrix".equals(object)) {
            throw new IllegalArgumentException("Unexpected object: " + object + ".");
        }

        String format = header.nextToken();
        if (!"coordinate".equals(format) && !"array".equals(format)) {
            throw new IllegalArgumentException("Unknown format: " + format + ".");
        }

        String field = header.nextToken();
        if (!"real".equals(field)) {
            throw new IllegalArgumentException("Unknown field type: " + field + ".");
        }

        String symmetry = header.nextToken();
        if (!symmetry.equals("general")) {
            throw new IllegalArgumentException("Unknown symmetry type: " + symmetry + ".");
        }

        String majority = (header.hasMoreTokens()) ? header.nextToken() : "row-major";

        String nextToken = body.nextToken();
        while (nextToken.startsWith("%")) {
            nextToken = body.nextToken();
        }

        if ("coordinate".equals(format)) {
            StringTokenizer lines = new StringTokenizer(nextToken);

            int rows = Integer.valueOf(lines.nextToken());
            int columns = Integer.valueOf(lines.nextToken());
            int cardinality = Integer.valueOf(lines.nextToken());

            Matrix result = "row-major".equals(majority) ?
                    RowMajorSparseMatrix.zero(rows, columns, cardinality) :
                    ColumnMajorSparseMatrix.zero(rows, columns, cardinality);

            for (int k = 0; k < cardinality; k++) {
                lines = new StringTokenizer(body.nextToken());

                int i = Integer.valueOf(lines.nextToken());
                int j = Integer.valueOf(lines.nextToken());
                double x = Double.valueOf(lines.nextToken());
                result.set(i - 1, j - 1, x);
            }

            return result;
        } else {
            StringTokenizer lines = new StringTokenizer(nextToken);

            int rows = Integer.valueOf(lines.nextToken());
            int columns = Integer.valueOf(lines.nextToken());
            Matrix result = DenseMatrix.zero(rows, columns);

            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < columns; j++) {
                    result.set(i, j, Double.valueOf(body.nextToken()));
                }
            }

            return result;
        }
    }

    protected int rows;
    protected int columns;

    /**
     * Creates a zero-shape matrix.
     */
    public Matrix() {
        this(0, 0);
    }

    /**
     * Creates a matrix of given shape {@code rows} x {@code columns};
     */
    public Matrix(int rows, int columns) {
        ensureDimensionsAreCorrect(rows, columns);
        this.rows = rows;
        this.columns = columns;
    }

    //
    // ============ ABSTRACT METHODS ============
    //

    /**
     * Gets the specified element of this matrix.
     *
     * @param i element's row index
     * @param j element's column index
     *
     * @return the element of this matrix
     */
    public abstract double get(int i, int j);

    /**
     * Sets the specified element of this matrix to given {@code value}.
     * 
     * @param i element's row index
     * @param j element's column index
     * @param value element's new value
     */
    public abstract void set(int i, int j, double value);

    /**
     * Copies the specified row of this matrix into the vector.
     *
     * @param i the row index
     *
     * @return the row represented as vector
     */
    public abstract Vector getRow(int i);

    /**
     * Copies the specified column of this matrix into the vector.
     *
     * @param j the column index
     *
     * @return the column represented as vector
     */
    public abstract Vector getColumn(int j);

    /**
     * Creates the blank matrix (a zero matrix with same size) of this matrix
     * of the given shape: {@code rows} x {@code columns}.
     *
     * @return blank matrix
     */
    public abstract Matrix blankOfShape(int rows, int columns);

    /**
     * Copies this matrix into the new matrix with specified dimensions: {@code rows} and {@code columns}.
     *
     * @param rows the number of rows in new matrix
     * @param columns the number of columns in new matrix
     *
     * @return the copy of this matrix with new size
     */
    public abstract Matrix copyOfShape(int rows, int columns);

    /**
     * Pipes this matrix to a given {@code operation}.
     *
     * @param operation the matrix operation
     *                  (an operation that takes a matrix and returns {@code T})
     * @param <T> the result type
     *
     * @return the result of an operation applied to this matrix
     */
    public abstract <T> T apply(MatrixOperation<T> operation);

    /**
     * Pipes this matrix to a given {@code operation}.
     *
     * @param operation the matrix-matrix operation
     *                  (an operation that takes two matrices and returns {@code T})
     * @param that the right hand matrix of the given operation
     * @param <T> the result type
     *
     * @return the result of an operation applied to this matrix
     */
    public abstract <T> T apply(MatrixMatrixOperation<T> operation, Matrix that);

    /**
     * Pipes this matrix to a given {@code operation}.
     *
     * @param operation the matrix-vector operation
     *                  (an operation that takes matrix and vector and returns {@code T})
     * @param that the right hand vector of the given operation
     * @param <T> the result type
     *
     * @return the result of an operation applied to this matrix
     */
    public abstract <T> T apply(MatrixVectorOperation<T> operation, Vector that);

    /**
     * Encodes this matrix into a byte array.
     *
     * @return a byte array representing this matrix
     */
    public abstract byte[] toBinary();

    /**
     * Converts this matrix into the Matrix Market string using the
     * given number {@code formatter}.
     *
     * @return a string in Matrix Market format representing this matrix;
     */
    public abstract String toMatrixMarket(NumberFormat formatter);

    //
    // ============ CONCRETE METHODS ============
    //

    /**
     * Sets all elements of this matrix to the given {@code value}.
     *
     * @param value the element's new value
     */
    public void setAll(double value) {
        MatrixIterator it = iterator();

        while (it.hasNext()) {
            it.next();
            it.set(value);
        }
    }

    /**
     * <p>
     * Sets all elements of the specified row of this matrix to given {@code value}.
     * </p>
     *
     * @param i the row index
     * @param value the element's new value
     */
    public void setRow(int i, double value) {
        VectorIterator it = iteratorOfRow(i);

        while (it.hasNext()) {
            it.next();
            it.set(value);
        }
    }

    /**
     * <p>
     * Sets all elements of the specified column of this matrix to given {@code value}.
     * </p>
     *
     * @param j the column index
     * @param value the element's new value
     */
    public void setColumn(int j, double value) {
        VectorIterator it = iteratorOfColumn(j);

        while (it.hasNext()) {
            it.next();
            it.set(value);
        }
    }

    /**
     * Swaps the specified rows of this matrix.
     * 
     * @param i the row index
     * @param j the row index
     */
    public void swapRows(int i, int j) {
        if (i != j) {
            Vector ii = getRow(i);
            Vector jj = getRow(j);

            setRow(i, jj);
            setRow(j, ii);
        }
    }

    /**
     * Swaps the specified columns of this matrix.
     * 
     * @param i the column index
     * @param j the column index
     */
    public void swapColumns(int i, int j) {
        if (i != j) {
            Vector ii = getColumn(i);
            Vector jj = getColumn(j);

            setColumn(i, jj);
            setColumn(j, ii);
        }
    }

    /**
     * Returns the number of rows of this matrix.
     * 
     * @return the number of rows
     */
    public int rows() {
        return rows;
    }

    /**
     * Returns the number of columns of this matrix.
     * 
     * @return the number of columns
     */
    public int columns() {
        return columns;
    }

    /**
     * Transposes this matrix.
     * 
     * @return the transposed matrix
     */
    public Matrix transpose() {
        Matrix result = blankOfShape(columns, rows);
        MatrixIterator it = result.iterator();

        while (it.hasNext()) {
            it.next();
            int i = it.rowIndex();
            int j = it.columnIndex();
            it.set(get(j, i));
        }

        return result;
    }

    /**
     * Rotates this matrix by 90 degrees to the right.
     * 
     * @return the rotated matrix
     */
    public Matrix rotate() {
        Matrix result = blankOfShape(columns, rows);
        MatrixIterator it = result.iterator();

        while (it.hasNext()) {
            it.next();
            int i = it.rowIndex();
            int j = it.columnIndex();
            it.set(get(rows - 1 - j, i));
        }

        return result;
    }

    /**
     * Powers this matrix of given exponent {code n}.
     *
     * @param n the exponent
     *
     * @return the powered matrix
     */
    public Matrix power(int n) {
        if (n < 0) {
            fail("The exponent should be positive: " + n + ".");
        }

        Matrix result = blankOfShape(rows, rows);
        Matrix that = this;

        for (int i = 0; i < rows; i++) {
            result.set(i, i, 1.0);
        }

        while (n > 0) {
            if (n % 2 == 1) {
                result = result.multiply(that);
            }

            n /= 2;
            that = that.multiply(that);
        }

        return result;
    }

    /**
     * Scales this matrix by given {@code value} (v).
     * 
     * @param value the scale factor
     *
     * @return A * v
     */
    public Matrix multiply(double value) {
        Matrix result = blank();
        MatrixIterator it = iterator();

        while (it.hasNext()) {
            double x = it.next();
            int i = it.rowIndex();
            int j = it.columnIndex();
            result.set(i, j, x * value);
        }

        return result;
    }

    /**
     * Multiplies this matrix (A) by given {@code that} vector (x).
     *
     * @param that the vector
     *
     * @return A * x
     */
    public Vector multiply(Vector that) {
        return apply(LinearAlgebra.OO_PLACE_MATRIX_BY_VECTOR_MULTIPLICATION, that);
    }

    /**
     * Multiplies this matrix (A) by given {@code that} matrix (B).
     * 
     * @param that the right hand matrix for multiplication
     *
     * @return A * B
     */
    public Matrix multiply(Matrix that) {
        return apply(LinearAlgebra.OO_PLACE_MATRICES_MULTIPLICATION, that);
    }

    /**
     * Multiplies this matrix by its transpose.
     *
     * @return this matrix multiplied by its transpose
     */
    public Matrix multiplyByItsTranspose() {
        return apply(LinearAlgebra.OO_PLACE_MATRIX_BY_ITS_TRANSPOSE_MULTIPLICATION);
    }

    /**
     * Subtracts given {@code value} (v) from every element of this matrix (A).
     *
     * @param value the right hand value for subtraction
     *
     * @return A - v
     */
    public Matrix subtract(double value) {
        return add(-value);
    }

    /**
     * Subtracts given {@code that} matrix (B) from this matrix (A).
     *
     * @param that the right hand matrix for subtraction
     *
     * @return A - B
     */
    public Matrix subtract(Matrix that) {
        return apply(LinearAlgebra.OO_PLACE_MATRICES_SUBTRACTION, that);
    }

    /**
     * Adds given {@code value} (v) to every element of this matrix (A).
     * 
     * @param value the right hand value for addition
     *
     * @return A + v
     */
    public Matrix add(double value) {
        MatrixIterator it = iterator();
        Matrix result = blank();

        while (it.hasNext()) {
            double x = it.next();
            int i = it.rowIndex();
            int j = it.columnIndex();
            result.set(i, j, x + value);
        }

        return result;
    }

    /**
     * Adds given {@code that} matrix (B) to this matrix (A).
     * 
     * @param that the right hand matrix for addition
     *
     * @return A + B
     */
    public Matrix add(Matrix that) {
        return apply(LinearAlgebra.OO_PLACE_MATRIX_ADDITION, that);
    }

    /**
     * Inserts a given {@code that} (B) into this matrix (A). The original
     * values are overwritten by the new ones.
     * 
     * @param that the matrix to insert, from the first row and column
     * @return a matrix with the parameter inserted into it
     */
    public Matrix insert(Matrix that) {
        return insert(that, 0, 0, 0, 0, that.rows(), that.columns());
    }
    
    /**
     * Inserts a given {@code that} matrix (B) into this matrix (A). The original
     * values are overwritten by the new ones.
     * 
     * @param that the matrix to insert
     * @param rows number of rows to insert
     * @param columns number of columns to insert
     * @return a matrix with the parameter inserted into it
     */
    public Matrix insert(Matrix that, int rows, int columns) {
        return insert(that, 0, 0, 0, 0, rows, columns);
    }

    /**
     * Inserts a given {@code that} matrix (B) into this matrix (A). The original
     * values are overwritten by the new ones.
     * 
     * @param that the matrix to insert
     * @param destRow the row to insert at in the destination matrix
     * @param destColumn the column to insert at in the destination matrix
     * @param rows number of rows to insert
     * @param columns number of columns to insert
     * @return a matrix with the parameter inserted into it
     */
    public Matrix insert(Matrix that, int destRow, int destColumn, int rows, int columns) {
        return insert(that, 0, 0, destRow, destColumn, rows, columns);
    }

    /**
     * Inserts a given {@code that} matrix (B) into this matrix (A). The original
     * values are overwritten by the new ones.
     * 
     * @param that the matrix to insert
     * @param srcRow the row to start at in the source matrix
     * @param srcColumn the column to start at in the source matrix
     * @param destRow the row to insert at in the destination matrix
     * @param destColumn the column to insert at in the destination matrix
     * @param rows number of rows to insert
     * @param columns number of columns to insert
     * @return a matrix with the parameter inserted into it
     */
    public Matrix insert(Matrix that, int srcRow, int srcColumn, int destRow, int destColumn, int rows, int columns) {
        if (rows < 0 || columns < 0) {
            fail("Cannot have negative rows or columns: " + rows + "x" + columns);
        }

        if (destRow < 0 || destColumn < 0) {
            fail("Cannot have negative destination position: " + destRow + ", " + destColumn);
        }

        if (destRow > this.rows || destColumn > this.columns) {
            fail("Destination position out of bounds: " + destRow + ", " + destColumn);
        }

        if (srcRow < 0 || srcColumn < 0) {
            fail("Cannot have negative source position: " + destRow + ", " + destColumn);
        }

        if (srcRow > that.rows || srcColumn > that.columns) {
            fail("Destination position out of bounds: " + srcRow + ", " + srcColumn);
        }

        if (destRow + rows > this.rows || destColumn + columns > this.columns) {
            fail("Out of bounds: Cannot add " + rows + " rows and " + columns + " cols at "
                    + destRow + ", " + destColumn + " in a " + this.rows + "x" + this.columns + " matrix.");
        }

        if (srcRow + rows > that.rows || srcColumn + columns > that.columns) {
            fail("Out of bounds: Cannot get " + rows + " rows and " + columns + " cols at "
                    + srcRow + ", " + srcColumn + " from a " + that.rows + "x" + that.columns + " matrix.");
        }

        Matrix result = copy();

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                result.set(i + destRow, j + destColumn, that.get(i + srcRow, j + srcColumn));
            }
        }

        return result;
    }

    /**
     * Divides every element of this matrix (A) by given {@code value} (v).
     * 
     * @param value the right hand value for division
     *
     * @return A / v
     */
    public Matrix divide(double value) {
        return multiply(1.0 / value);
    }

    /**
     * Calculates the Kronecker product of this matrix (A) and given {@code that} matrix (B).
     * 
     * @param that the right hand matrix for Kronecker product
     *
     * @return A (+) B
     */
    public Matrix kroneckerProduct(Matrix that) {
        return apply(LinearAlgebra.OO_PLACE_KRONECKER_PRODUCT, that);
    }

    /**
     * Calculates the trace of this matrix.
     *
     * <p>
     * See <a href="http://mathworld.wolfram.com/MatrixTrace.html">
     * http://mathworld.wolfram.com/MatrixTrace.html</a> for more details.
     * </p>
     * 
     * @return the trace of this matrix
     */
    public double trace() {
        double result = 0.0;

        for (int i = 0; i < rows; i++) {
            result += get(i, i);
        }

        return result;
    }

    /**
     * Calculates the product of diagonal elements of this matrix.
     * 
     * @return the product of diagonal elements of this matrix
     */
    public double diagonalProduct() {
        BigDecimal result = BigDecimal.ONE;

        for (int i = 0; i < rows; i++) {
            result = result.multiply(BigDecimal.valueOf(get(i, i)));
        }

        return result.setScale(Matrices.ROUND_FACTOR,
                RoundingMode.CEILING).doubleValue();

    }

    /**
     * Calculates an Euclidean norm of this matrix, a.k.a. frobenius norm
     *
     * @return an Euclidean norm
     */
    public double norm() {
        return euclideanNorm();
    }

    /**
     * Calculates an Euclidean norm of this matrix, a.k.a. frobenius norm
     *
     * @return an Euclidean norm
     */
    public double euclideanNorm() {
        return fold(Matrices.mkEuclideanNormAccumulator());
    }
    
    /**
     * Calculates a Manhattan norm of this matrix, a.k.a. taxicab norm
     *
     * @return a Manhattan norm
     */
    public double manhattanNorm() {
        return fold(Matrices.mkManhattanNormAccumulator());
    }
    
    /**
     * Calculates an Infinity norm of this matrix.
     *
     * @return an Infinity norm
     */
    public double infinityNorm() {
        return fold(Matrices.mkInfinityNormAccumulator());
    }
    
    /**
     * Multiplies up all elements of this matrix.
     * 
     * @return the product of all elements of this matrix
     */
    public double product() {
        return fold(Matrices.asProductAccumulator(1.0));
    }

    /**
     * Summarizes up all elements of this matrix.
     *
     * @return the sum of all elements of this matrix
     */
    public double sum() {
        return fold(Matrices.asSumAccumulator(0.0));
    }

    /**
     * Calculates the Hadamard (element-wise) product of this and given {@code that} matrix.
     *
     * @param that the right hand matrix for Hadamard product
     *
     * @return the Hadamard product of two matrices
     */
    public Matrix hadamardProduct(Matrix that) {
        return apply(LinearAlgebra.OO_PLACE_MATRIX_HADAMARD_PRODUCT, that);
    }

    /**
     * Calculates the determinant of this matrix.
     *
     * <p>
     * See <a href="http://mathworld.wolfram.com/Determinant.html">
     * http://mathworld.wolfram.com/Determinant.html</a> for more details.
     * </p>
     * 
     * @return the determinant of this matrix
     */
    public double determinant() {
        if (rows != columns) {
            throw new IllegalStateException("Can not compute determinant of non-square matrix.");
        }

        if (rows == 0) {
            return 0.0;
        } else if (rows == 1) {
            return get(0, 0);
        } else if (rows == 2) {
            return get(0, 0) * get(1, 1) -
                    get(0, 1) * get(1, 0);
        } else if (rows == 3) {
            return get(0, 0) * get(1, 1) * get(2, 2) +
                    get(0, 1) * get(1, 2) * get(2, 0) +
                    get(0, 2) * get(1, 0) * get(2, 1) -
                    get(0, 2) * get(1, 1) * get(2, 0) -
                    get(0, 1) * get(1, 0) * get(2, 2) -
                    get(0, 0) * get(1, 2) * get(2, 1);
        }

        MatrixDecompositor decompositor = withDecompositor(LinearAlgebra.LU);
        Matrix lup[] = decompositor.decompose();
        // TODO: Why Java doesn't support pattern matching?
        Matrix u = lup[1];
        Matrix p = lup[2];

        double result = u.diagonalProduct();

        // TODO: we can do that in O(n log n)
        //       just google: "counting inversions divide and conqueror"
        int permutations[] = new int[p.rows()];
        for (int i = 0; i < p.rows(); i++) {
            for (int j = 0; j < p.columns(); j++) {
                if (p.get(i, j) > 0.0) {
                    permutations[i] = j;
                    break;
                }
            }
        }

        int sign = 1;
        for (int i = 0; i < permutations.length; i++) {
            for (int j = i + 1; j < permutations.length; j++) {
                if (permutations[j] < permutations[i]) {
                    sign *= -1;
                }
            }
        }

        return sign * result;
    }

    /**
     * Calculates the rank of this matrix.
     *
     * <p>
     * See <a href="http://mathworld.wolfram.com/MatrixRank.html">
     * http://mathworld.wolfram.com/MatrixRank.html</a> for more details.
     * </p>
     *
     * @return the rank of this matrix
     */
    public int rank() {
        if (rows == 0 || columns == 0) {
            return 0;
        }

        // TODO:
        // handle small (1x1, 1xn, nx1, 2x2, 2xn, nx2, 3x3, 3xn, nx3)
        // matrices without SVD

        MatrixDecompositor decompositor = withDecompositor(LinearAlgebra.SVD);
        Matrix usv[] = decompositor.decompose();
        // TODO: Where is my pattern matching?
        Matrix s = usv[1];
        double tolerance = Math.max(rows, columns) * s.get(0, 0) * Matrices.EPS;

        int result = 0;
        for (int i = 0; i < s.rows(); i++) {
            if (s.get(i, i) > tolerance) {
                result++;
            }
        }

        return result;
    }

    /**
     * Copies given {@code row} into the specified row of this matrix.
     *
     * @param i the row index
     * @param row the row represented as vector
     */
    public void setRow(int i, Vector row) {
        if (columns != row.length()) {
            fail("Wrong vector length: " + row.length() + ". Should be: " + columns + ".");
        }

        for (int j = 0; j < row.length(); j++) {
            set(i, j, row.get(j));
        }
    }

    /**
     * Copies given {@code column} into the specified column of this matrix.
     *
     * @param j the column index
     * @param column the column represented as vector
     */
    public void setColumn(int j, Vector column) {
        if (rows != column.length()) {
            fail("Wrong vector length: " + column.length() + ". Should be: " + rows + ".");
        }

        for (int i = 0; i < column.length(); i++) {
            set(i, j, column.get(i));
        }
    }

    /**
     * Adds one row to matrix.
     * @param i the row index
     * @return matrix with row.
     */
    public Matrix insertRow(int i, Vector row) {
        if (i >= rows || i < 0) {
            throw new IndexOutOfBoundsException("Illegal row number, must be 0.." + (rows - 1));
        }

        Matrix result = blankOfShape(rows + 1, columns);

        for (int ii = 0; ii < i; ii++) {
            result.setRow(ii, getRow(ii));
        }

        result.setRow(i, row);

        for (int ii = i; ii < rows; ii++) {
            result.setRow(ii + 1, getRow(ii));
        }

        return result;
    }

    /**
     * Adds one column to matrix.
     * @param j the column index
     * @return matrix with column.
     */
    public Matrix insertColumn(int j, Vector column) {
        if (j >= columns || j < 0) {
            throw new IndexOutOfBoundsException("Illegal row number, must be 0.." + (columns - 1));
        }

        Matrix result = blankOfShape(rows, columns + 1);

        for (int jj = 0; jj < j; jj++) {
            result.setColumn(jj, getColumn(jj));
        }

        result.setColumn(j, column);

        for (int jj = j; jj < columns; jj++) {
            result.setColumn(jj + 1, getColumn(jj));
        }

        return result;
    }

    /**
     * Removes one row from matrix.
     * @param i the row index
     * @return matrix without row.
     */
    public Matrix removeRow(int i) {
        if (i >= rows || i < 0) {
            throw new IndexOutOfBoundsException("Illegal row number, must be 0.." + (rows - 1));
        }

        Matrix result = blankOfShape(rows - 1, columns);

        for (int ii = 0; ii < i; ii++) {
            result.setRow(ii, getRow(ii));
        }

        for (int ii = i + 1; ii < rows; ii++) {
            result.setRow(ii - 1, getRow(ii));
        }

        return result;
    }

    /**
     * Removes one column from matrix.
     * @param j the column index
     * @return matrix without column.
     */
    public Matrix removeColumn(int j) {
        if (j >= columns || j < 0) {
            throw new IndexOutOfBoundsException("Illegal row number, must be 0.." + (columns - 1));
        }

        Matrix result = blankOfShape(rows, columns - 1);

        for (int jj = 0; jj < j; jj++) {
            result.setColumn(jj, getColumn(jj));
        }

        for (int jj = j + 1; jj < columns; jj++) {
            result.setColumn(jj - 1, getColumn(jj));
        }

        return result;
    }

    /**
     * Removes first row from matrix.
     * @return matrix without first row.
     */
    public Matrix removeFirstRow() {
        return removeRow(0);
    }

    /**
     * Removes first column from matrix.
     * @return matrix without first column
     */
    public Matrix removeFirstColumn() {
        return removeColumn(0);
    }

    /**
     * Removes last row from matrix.
     * @return matrix without last row
     */
    public Matrix removeLastRow() {
        return removeRow(rows - 1);
    }

    /**
     * Removes last column from matrix.
     * @return matrix without last column
     */
    public Matrix removeLastColumn() {
        return removeColumn(columns - 1);
    }

    /**
     * Creates the blank matrix (a zero matrix with same size) of this matrix.
     * 
     * @return blank matrix
     */
    public Matrix blank() {
        return blankOfShape(rows, columns);
    }

    /**
     * Creates the blank matrix (a zero matrix with same size) of this matrix
     * of the given shape: {@code rows}. The {@code columns} number remains the
     * same.
     *
     * @return blank matrix
     */
    public Matrix blankOfRows(int rows) {
        return blankOfShape(rows, columns);
    }

    /**
     * Creates the blank matrix (a zero matrix with same size) of this matrix
     * of the given shape: {@code columns}. The {@code rows} number remains the
     * same.
     *
     * @return blank matrix
     */
    public Matrix blankOfColumns(int columns) {
        return blankOfShape(rows, columns);
    }

    /**
     * Copies this matrix.
     *
     * @return the copy of this matrix
     */
    public Matrix copy() {
        return copyOfShape(rows, columns);
    }

    /**
     * Copies this matrix into the new matrix with specified row dimension: {@code rows}.
     *
     * @param rows the number of rows in new matrix
     *
     * @return the copy of this matrix with new size
     */
    public Matrix copyOfRows(int rows) {
        return copyOfShape(rows, columns);
    }

    /**
     * Copies this matrix into the new matrix with specified column dimension: {@code columns}.
     *
     * @param columns the number of columns in new matrix
     *
     * @return the copy of this matrix with new size
     */
    public Matrix copyOfColumns(int columns) {
        return copyOfShape(rows, columns);
    }

    /**
     * Shuffles this matrix.
     *
     * <p>
     * Copies this matrix into the matrix that contains the same elements but with the elements
     * shuffled around (which might also result in the same matrix (with a small likelihood)).
     * </p>
     *
     * @return the shuffled matrix
     */
    public Matrix shuffle() {
        Matrix result = copy();

        // Conduct Fisher-Yates shuffle
        Random random = new Random();

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                int ii = random.nextInt(rows - i) + i;
                int jj = random.nextInt(columns - j) + j;

                double a = result.get(ii, jj);
                result.set(ii, jj, result.get(i, j));
                result.set(i, j, a);
            }
        }

        return result;
    }

    /**
     * Retrieves the specified sub-matrix of this matrix. The sub-matrix is specified by
     * intervals for row indices and column indices.
     *
     * @param fromRow the beginning of the row indices interval
     * @param fromColumn the beginning of the column indices interval
     * @param untilRow the ending of the row indices interval
     * @param untilColumn the ending of the column indices interval
     *
     * @return the sub-matrix of this matrix
     */
    public Matrix slice(int fromRow, int fromColumn, int untilRow, int untilColumn) {
        if (untilRow - fromRow < 0 || untilColumn - fromColumn < 0) {
            fail("Wrong slice range: [" + fromRow + ".." + untilRow + "][" + fromColumn + ".." + untilColumn + "].");
        }

        Matrix result = blankOfShape(untilRow - fromRow, untilColumn - fromColumn);

        for (int i = fromRow; i < untilRow; i++) {
            for (int j = fromColumn; j < untilColumn; j++) {
                result.set(i - fromRow, j - fromColumn, get(i, j));
            }
        }

        return result;
    }

    /**
     * Retrieves the specified sub-matrix of this matrix. The sub-matrix is specified by
     * intervals for row indices and column indices. The top left points of both intervals
     * are fixed to zero.
     *
     * @param untilRow the ending of the row indices interval
     * @param untilColumn the ending of the column indices interval
     *
     * @return the sub-matrix of this matrix
     */
    public Matrix sliceTopLeft(int untilRow, int untilColumn) {
        return slice(0, 0, untilRow, untilColumn);
    }

    /**
     * Retrieves the specified sub-matrix of this matrix. The sub-matrix is specified by
     * intervals for row indices and column indices. The bottom right points of both intervals
     * are fixed to matrix dimensions - it's rows and columns correspondingly.
     *
     * @param fromRow the beginning of the row indices interval
     * @param fromColumn the beginning of the column indices interval
     *
     * @return the sub-matrix of this matrix
     */
    public Matrix sliceBottomRight(int fromRow, int fromColumn) {
        return slice(fromRow, fromColumn, rows, columns);
    }

    /**
     * Returns a new matrix with the selected rows and columns. This method can
     * be used either return a specific subset of rows and/or columns or to
     * permute the indices in an arbitrary order. The list of indices are
     * allowed to contain duplicates indices. This is more general than slice()
     * which selects only contiguous blocks. However, where applicable slice()
     * is probably more efficient.
     * 
     * @param rowIndices the array of row indices
     * @param columnIndices the array of column indices
     *
     * @return the new matrix with the selected rows and columns
     *
     * @throws IllegalArgumentException if invalid row or column indices are provided
     */
    public Matrix select(int[] rowIndices, int[] columnIndices) {
        int m = rowIndices.length;
        int n = columnIndices.length;

        if (m == 0 || n == 0) {
            fail("No rows or columns selected.");
        }

        Matrix result = blankOfShape(m, n);

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                result.set(i, j, get(rowIndices[i], columnIndices[j]));
            }
        }

        return result;
    }

    /**
     * Applies given {@code procedure} to each element of this matrix.
     *
     * @param procedure the matrix procedure
     */
    public void each(MatrixProcedure procedure) {
        MatrixIterator it = iterator();

        while (it.hasNext()) {
            double x = it.next();
            int i = it.rowIndex();
            int j = it.columnIndex();
            procedure.apply(i, j, x);
        }
    }

    /**
     * Applies given {@code procedure} to each element of specified row of this matrix.
     *
     * @param i the row index
     * @param procedure the vector procedure
     */
    public void eachInRow(int i, VectorProcedure procedure) {
        VectorIterator it = iteratorOfRow(i);

        while (it.hasNext()) {
            double x = it.next();
            int j = it.index();
            procedure.apply(j, x);
        }
    }

    /**
     * Applies given {@code procedure} to each element of specified column of this matrix.
     *
     * @param j the column index
     * @param procedure the vector procedure
     */
    public void eachInColumn(int j, VectorProcedure procedure) {
        VectorIterator it = iteratorOfColumn(j);

        while (it.hasNext()) {
            double x = it.next();
            int i = it.index();
            procedure.apply(i, x);
        }
    }

    /**
     * Searches for the maximum value of the elements of this matrix.
     *
     * @return maximum value of this matrix
     */
    public double max() {
        return fold(Matrices.mkMaxAccumulator());
    }

    /**
     * Searches for the minimum value of the elements of this matrix.
     *
     * @return minimum value of this matrix
     */
    public double min() {
        return fold(Matrices.mkMinAccumulator());
    }

    /**
     * Searches for the maximum value of specified row in this matrix.
     *
     * @param i the row index
     *
     * @return maximum value of specified row in this matrix
     */
    public double maxInRow(int i) {
        return foldRow(i, Vectors.mkMaxAccumulator());
    }

    /**
     * Searches for the minimum value of specified row in this matrix.
     *
     * @param i the row index
     *
     * @return minimum value of specified row in this matrix
     */
    public double minInRow(int i) {
        return foldRow(i, Vectors.mkMinAccumulator());
    }

    /**
     * Searches for the maximum value of specified column in this matrix.
     *
     * @param j the column index
     *
     * @return maximum value of specified column in this matrix
     */
    public double maxInColumn(int j) {
        return foldColumn(j, Vectors.mkMaxAccumulator());
    }

    /**
     * Searches for the minimum value of specified column in this matrix.
     *
     * @param j the column index
     *
     * @return minimum value of specified column in this matrix
     */
    public double minInColumn(int j) {
        return foldColumn(j, Vectors.mkMinAccumulator());
    }

    /**
     * Builds a new matrix by applying given {@code function} to each element of this matrix.
     *
     * @param function the matrix function
     *
     * @return the transformed matrix
     */
    public Matrix transform(MatrixFunction function) {
        Matrix result = blank();
        MatrixIterator it = iterator();

        while (it.hasNext()) {
            double x  = it.next();
            int i = it.rowIndex();
            int j = it.columnIndex();
            result.set(i, j, function.evaluate(i, j, x));
        }

        return result;
    }

    /**
     * Builds a new matrix by applying given {@code function} to each element of specified
     * row in this matrix.
     *
     * @param i the row index
     * @param function the vector function
     *
     * @return the transformed matrix
     */
    public Matrix transformRow(int i, VectorFunction function) {
        Matrix result = copy();
        VectorIterator it = result.iteratorOfRow(i);

        while (it.hasNext()) {
            double x = it.next();
            int j = it.index();
            it.set(function.evaluate(j, x));
        }

        return result;
    }

    /**
     * Builds a new matrix by applying given {@code function} to each element of specified
     * column in this matrix.
     *
     * @param j the column index
     * @param function the vector function
     *
     * @return the transformed matrix
     */
    public Matrix transformColumn(int j, VectorFunction function) {
        Matrix result = copy();
        VectorIterator it = result.iteratorOfColumn(j);

        while (it.hasNext()) {
            double x = it.next();
            int i = it.index();
            it.set(function.evaluate(i, x));
        }

        return result;
    }

    /**
     * Updates all elements of this matrix by applying given {@code function}.
     *
     * @param function the matrix function
     */
    public void update(MatrixFunction function) {
        MatrixIterator it = iterator();

        while (it.hasNext()) {
            double x = it.next();
            int i = it.rowIndex();
            int j = it.columnIndex();
            it.set(function.evaluate(i, j, x));
        }
    }

    /**
     * Updates the specified element of this matrix by applying given {@code function}.
     *
     * @param i the row index
     * @param j the column index
     * @param function the matrix function
     */
    public void updateAt(int i, int j, MatrixFunction function) {
        set(i, j, function.evaluate(i, j, get(i, j)));
    }

    /**
     * Updates all elements of the specified row in this matrix by applying given {@code function}.
     *
     * @param i the row index
     * @param function the vector function
     */
    public void updateRow(int i, VectorFunction function) {
        VectorIterator it = iteratorOfRow(i);

        while (it.hasNext()) {
            double x = it.next();
            int j = it.index();
            it.set(function.evaluate(j, x));
        }
    }

    /**
     * Updates all elements of the specified column in this matrix by applying given {@code function}.
     *
     * @param j the column index
     * @param function the vector function
     */
    public void updateColumn(int j, VectorFunction function) {
        VectorIterator it = iteratorOfColumn(j);

        while (it.hasNext()) {
            double x = it.next();
            int i = it.index();
            it.set(function.evaluate(i, x));
        }
    }

    /**
     * Folds all elements of this matrix with given {@code accumulator}.
     * 
     * @param accumulator the matrix accumulator
     *
     * @return the accumulated value
     */
    public double fold(MatrixAccumulator accumulator) {
        each(Matrices.asAccumulatorProcedure(accumulator));
        return accumulator.accumulate();
    }

    /**
     * Folds all elements of specified row in this matrix with given {@code accumulator}.
     *
     * @param i the row index
     * @param accumulator the vector accumulator
     *
     * @return the accumulated value
     */
    public double foldRow(int i, VectorAccumulator accumulator) {
        eachInRow(i, Vectors.asAccumulatorProcedure(accumulator));
        return accumulator.accumulate();
    }

    /**
     * Folds all elements (in row-by-row manner) of this matrix with given {@code accumulator}.
     *
     * @param accumulator the vector accumulator
     *
     * @return the accumulated double array
     */
    public double[] foldRows(VectorAccumulator accumulator) {
        double[] result = new double[rows];

        for (int i = 0; i < rows; i++) {
            result[i] = foldRow(i, accumulator);
        }

        return result;
    }

    /**
     * Folds all elements of specified column in this matrix with given {@code accumulator}.
     *
     * @param j the column index
     * @param accumulator the vector accumulator
     *
     * @return the accumulated value
     */
    public double foldColumn(int j, VectorAccumulator accumulator) {
        eachInColumn(j, Vectors.asAccumulatorProcedure(accumulator));
        return accumulator.accumulate();
    }

    /**
     * Folds all elements (in a column-by-column manner) of this matrix with given {@code accumulator}.
     *
     * @param accumulator the vector accumulator
     *
     * @return the accumulated double array
     */
    public double[] foldColumns(VectorAccumulator accumulator) {
        double[] result = new double[columns];

        for (int i = 0; i < columns; i++) {
            result[i] = foldColumn(i, accumulator);
        }

        return result;
    }

    /**
     * Checks whether this matrix compiles with given {@code predicate} or not.
     * 
     * @param predicate the matrix predicate
     *
     * @return whether this matrix compiles with predicate
     */
    public boolean is(MatrixPredicate predicate) {
        MatrixIterator it = iterator();
        boolean result = predicate.test(rows, columns);

        while (it.hasNext() && result) {
            double x = it.next();
            int i = it.rowIndex();
            int j = it.columnIndex();
            result = predicate.test(i, j, x);
        }

        return result;
    }

    /**
     * Checks whether this matrix compiles with given {@code predicate} or not.
     *
     * @param predicate the advanced matrix predicate
     *
     * @return whether this matrix compiles with predicate
     */
    public boolean is(AdvancedMatrixPredicate predicate) {
        return predicate.test(this);
    }

    /**
     * Checks whether this matrix compiles with given {@code predicate} or not.
     *
     * @param predicate the matrix predicate
     *
     * @return whether this matrix compiles with predicate
     */
    public boolean non(MatrixPredicate predicate) {
        return !is(predicate);
    }

    /**
     * Checks whether this matrix compiles with given {@code predicate} or not.
     *
     * @param predicate the advanced matrix predicate
     *
     * @return whether this matrix compiles with predicate
     */
    public boolean non(AdvancedMatrixPredicate predicate) {
        return !is(predicate);
    }

    /**
     * Converts this matrix into the row vector.
     *
     * @return the row vector of this matrix
     */
    public Vector toRowVector() {
        return getRow(0);
    }

    /**
     * Converts this matrix into the column vector.
     *
     * @return the column vector of this matrix
     */
    public Vector toColumnVector() {
        return getColumn(0);
    }

    /**
     * Creates a new solver by given {@code factory} of this matrix.
     *
     * @param factory the solver factory
     *
     * @return the linear system solver of this matrix
     */
    public LinearSystemSolver withSolver(LinearAlgebra.SolverFactory factory) {
        return factory.create(this);
    }

    /**
     * Creates a new inverter by given {@code factory} of this matrix.
     *
     * @param factory the inverter factory
     *
     * @return the inverter of this matrix
     */
    public MatrixInverter withInverter(LinearAlgebra.InverterFactory factory) {
        return factory.create(this);
    }

    /**
     * Creates a new decompositor by given {@code factory} of this matrix.
     *
     * @param factory the decompositor factory
     *
     * @return the decompositor of this matrix
     */
    public MatrixDecompositor withDecompositor(LinearAlgebra.DecompositorFactory factory) {
        return factory.create(this);
    }

    /**
     * Returns true when matrix is equal to given {@code matrix} with given {@code precision}
     *
     * @param matrix matrix
     * @param precision given precision
     *
     * @return equals of this matrix to that
     */
    public boolean equals(Matrix matrix, double precision) {
        if (rows != matrix.rows() || columns != matrix.columns()) {
            return false;
        }

        boolean result = true;

        for (int i = 0; result && i < rows; i++) {
            for (int j = 0; result && j < columns; j++) {
                double a = get(i, j);
                double b = matrix.get(i, j);
                double diff = Math.abs(a - b);

                result = (a == b) || (diff < precision || diff / Math.max(Math.abs(a), Math.abs(b)) < precision);
            }
        }

        return result;
    }

    /**
     * Converts this matrix into the string representation.
     *
     * @param formatter the number formatter
     *
     * @return the matrix converted to a string
     */
    public String mkString(NumberFormat formatter) {
        return mkString(formatter, DEFAULT_ROWS_DELIMITER, DEFAULT_COLUMNS_DELIMITER);
    }

    /**
     * Converts this matrix into the string representation.
     *
     * @param rowsDelimiter the rows' delimiter
     * @param columnsDelimiter the columns' delimiter
     *
     * @return the matrix converted to a string
     */
    public String mkString(String rowsDelimiter, String columnsDelimiter) {
        return mkString(DEFAULT_FORMATTER, rowsDelimiter, columnsDelimiter);
    }

    /**
     * Converts this matrix into the string representation.
     *
     * @param formatter the number formatter
     * @param rowsDelimiter the rows' delimiter
     * @param columnsDelimiter the columns' delimiter
     *
     * @return the matrix converted to a string
     */
    public String mkString(NumberFormat formatter, String rowsDelimiter, String columnsDelimiter) {
        // TODO: rewrite using iterators
        int formats[] = new int[columns];

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                double value = get(i, j);
                String output = formatter.format(value);
                int size = output.length();
                formats[j] = size > formats[j] ? size : formats[j];
            }
        }

        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                String output = formatter.format(get(i, j));
                int outputLength = output.length();

                if (outputLength < formats[j]) {
                    int align = formats[j] - outputLength;
                    if (align > INDENTS.length - 1) {
                        indent(sb, align);
                    } else {
                        sb.append(INDENTS[align - 1]);
                    }
                }

                sb.append(output)
                        .append(j < columns - 1 ? columnsDelimiter : "");
            }
            sb.append(rowsDelimiter);
        }

        return sb.toString();
    }

    @Override
    public String toString() {
        return mkString(DEFAULT_FORMATTER, DEFAULT_ROWS_DELIMITER, DEFAULT_COLUMNS_DELIMITER);
    }

    /**
     * Returns a matrix iterator.
     *
     * @return a matrix iterator
     */
    @Override
    public MatrixIterator iterator() {
        return rowMajorIterator();
    }

    /**
     * Returns a row-major matrix iterator.
     *
     * @return a row-major matrix iterator.
     */
    public RowMajorMatrixIterator rowMajorIterator() {
        return new RowMajorMatrixIterator(rows, columns) {
            private long limit = (long) rows * columns;
            private int i = - 1;

            @Override
            public int rowIndex() {
                return i / columns;
            }

            @Override
            public int columnIndex() {
                return i - rowIndex() * columns;
            }

            @Override
            public double get() {
                return Matrix.this.get(rowIndex(), columnIndex());
            }

            @Override
            public void set(double value) {
                Matrix.this.set(rowIndex(), columnIndex(), value);
            }

            @Override
            public boolean hasNext() {
                return i + 1 < limit;
            }

            @Override
            public Double next() {
                i++;
                return get();
            }
        };
    }

    /**
     * Returns a column-major matrix iterator.
     *
     * @return a column-major matrix iterator.
     */
    public ColumnMajorMatrixIterator columnMajorIterator() {
        return new ColumnMajorMatrixIterator(rows, columns) {
            private long limit = (long) rows * columns;
            private int i = -1;

            @Override
            public int rowIndex() {
                return i - columnIndex() * rows;
            }

            @Override
            public int columnIndex() {
                return i / rows;
            }

            @Override
            public double get() {
                return Matrix.this.get(rowIndex(), columnIndex());
            }

            @Override
            public void set(double value) {
                Matrix.this.set(rowIndex(), columnIndex(), value);
            }

            @Override
            public boolean hasNext() {
                return i + 1 < limit;
            }

            @Override
            public Double next() {
                i++;
                return get();
            }
        };
    }

    /**
     * Returns a vector iterator of the given row {code i}.
     *
     * @return a vector iterator
     */
    public VectorIterator iteratorOfRow(int i) {
        final int ii = i;
        return new VectorIterator(columns) {
            private int j = -1;

            @Override
            public int index() {
                return j;
            }

            @Override
            public double get() {
                return Matrix.this.get(ii, j);
            }

            @Override
            public void set(double value) {
                Matrix.this.set(ii, j, value);
            }

            @Override
            public boolean hasNext() {
                return j + 1 < columns;
            }

            @Override
            public Double next() {
                j++;
                return get();
            }
        };
    }

    /**
     * Returns a vector iterator of the given column {code j}.
     *
     * @return a vector iterator
     */
     public VectorIterator iteratorOfColumn(int j) {
         final int jj = j;
         return new VectorIterator(rows) {
             private int i = -1;
             @Override
             public int index() {
                 return i;
             }

             @Override
             public double get() {
                 return Matrix.this.get(i, jj);
             }

             @Override
             public void set(double value) {
                 Matrix.this.set(i, jj, value);
             }

             @Override
             public boolean hasNext() {
                 return i + 1 < rows;
             }

             @Override
             public Double next() {
                 i++;
                 return get();
             }
         };
     }

    @Override
    public int hashCode() {
        MatrixIterator it = iterator();
        int result = 17;

        while (it.hasNext()) {
            long value = it.next().longValue();
            result = 37 * result + (int) (value ^ (value >>> 32));
        }

        return result;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null) {
            return false;
        }

        if (!(o instanceof Matrix)) {
            return false;
        }

        Matrix matrix = (Matrix) o;

        return equals(matrix, Matrices.EPS);
    }

    /**
     * Converts this matrix using the given {@code factory}.
     *
     * @param factory the factory that creates an output matrix
     * @param <T> type of the result matrix
     *
     * @return converted matrix
     */
    public <T extends Matrix> T to(MatrixFactory<T> factory) {
        T result = factory.apply(rows, columns);
        apply(LinearAlgebra.IN_PLACE_COPY_MATRIX_TO_MATRIX, result);
        return result;
    }

    /**
     * Converts this matrix into a sparse matrix.
     *
     * @return a sparse matrix
     */
    public SparseMatrix toSparseMatrix() {
        return to(Matrices.SPARSE);
    }

    /**
     * Converts this matrix into a dense matrix.
     *
     * @return a dense matrix
     */
    public DenseMatrix toDenseMatrix() {
        return to(Matrices.DENSE);
    }

    /**
     * Converts this matrix into a row-major sparse matrix.
     *
     * @return a row-major sparse matrix
     */
    public RowMajorSparseMatrix toRowMajorSparseMatrix() {
        return to(Matrices.SPARSE_ROW_MAJOR);
    }

    /**
     * Converts this matrix into a column-major sparse matrix.
     *
     * @return a row-major sparse matrix
     */
    public ColumnMajorSparseMatrix toColumnMajorSparseMatrix() {
        return to(Matrices.SPARSE_COLUMN_MAJOR);
    }

    /**
     * Converts this matrix into the CSV (Comma Separated Value) string.
     *
     * @return a CSV string representing this matrix
     */
    public String toCSV() {
        return toCSV(DEFAULT_FORMATTER);
    }

    /**
     * Converts this matrix into the Matrix Market string.
     *
     * @return a string in Matrix Market format representing this matrix;
     */
    public String toMatrixMarket() {
        return toMatrixMarket(DEFAULT_FORMATTER);
    }

    /**
     * Converts this matrix into the CSV (Comma Separated Value) string
     * using the given {@code formatter}.
     *
     * @param formatter the number formatter
     *
     * @return a CSV string representing this matrix
     */
    public String toCSV(NumberFormat formatter) {
        return mkString(formatter, "\n", ", ");
    }

    protected void ensureDimensionsAreCorrect(int rows, int columns) {
        if (rows < 0 || columns < 0) {
            fail("Wrong matrix dimensions: " + rows + "x" + columns);
        }
        if (rows == Integer.MAX_VALUE || columns == Integer.MAX_VALUE) {
            fail("Wrong matrix dimensions: use 'Integer.MAX_VALUE - 1' instead.");
        }
    }

    protected void ensureIndexesAreInBounds(int i, int j) {
        if (i < 0 || i >= rows) {
            throw new IndexOutOfBoundsException("Row '" + i + "' is invalid.");
        }

        if (j < 0 || j >= columns) {
            throw new IndexOutOfBoundsException("Column '" + j + "' is invalid.");
        }
    }

    protected void fail(String message) {
        throw new IllegalArgumentException(message);
    }

    private void indent(StringBuilder sb, int howMany) {
        while (howMany > 0) {
            sb.append(" ");
            howMany--;
        }
    }
}
