/*
 * Copyright 2014 OpenRQ Team
 * 
 * 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.
 */

/*
 * Copyright 2011-2014, 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
 * Pavel Kalaidin
 * Jakob Moellers
 * Ewald Grusk
 * Yuriy Drozd
 * Maxim Samoylov
 * Anveshi Charuvaka
 * Todd Brunhoff
 */
package net.fec.openrq.util.linearalgebra.matrix;


import static net.fec.openrq.util.math.OctetOps.aDividedByB;
import static net.fec.openrq.util.math.OctetOps.aMinusB;
import static net.fec.openrq.util.math.OctetOps.aPlusB;
import static net.fec.openrq.util.math.OctetOps.aTimesB;

import java.util.Random;

import net.fec.openrq.util.checking.Indexables;
import net.fec.openrq.util.linearalgebra.factory.Factory;
import net.fec.openrq.util.linearalgebra.io.ByteVectorIterator;
import net.fec.openrq.util.linearalgebra.matrix.functor.AdvancedMatrixPredicate;
import net.fec.openrq.util.linearalgebra.matrix.functor.MatrixAccumulator;
import net.fec.openrq.util.linearalgebra.matrix.functor.MatrixFunction;
import net.fec.openrq.util.linearalgebra.matrix.functor.MatrixPredicate;
import net.fec.openrq.util.linearalgebra.matrix.functor.MatrixProcedure;
import net.fec.openrq.util.linearalgebra.vector.ByteVector;


public abstract class AbstractByteMatrix implements ByteMatrix {

    private final int rows;
    private final int columns;

    private final Factory factory;


    protected AbstractByteMatrix(Factory factory, int rows, int columns) {

        ensureDimensionsAreCorrect(rows, columns);

        this.factory = factory;

        this.rows = rows;
        this.columns = columns;
    }

    @Override
    public final byte get(int i, int j) {

        checkBounds(i, j);
        return safeGet(i, j);
    }

    protected abstract byte safeGet(int i, int j);

    @Override
    public final void set(int i, int j, byte value) {

        checkBounds(i, j);
        safeSet(i, j, value);
    }

    protected abstract void safeSet(int i, int j, byte value);

    @Override
    public final void update(int i, int j, MatrixFunction function) {

        checkBounds(i, j);
        safeUpdate(i, j, function);
    }

    protected void safeUpdate(int i, int j, MatrixFunction function) {

        safeSet(i, j, function.evaluate(i, j, safeGet(i, j)));
    }

    @Override
    public void clear() {

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                safeSet(i, j, (byte)0);
            }
        }
    }

    @Override
    public void clearRow(int i) {

        ByteVectorIterator it = rowIterator(i);
        while (it.hasNext()) {
            it.next();
            it.set((byte)0);
        }
    }

    @Override
    public void clearColumn(int j) {

        ByteVectorIterator it = columnIterator(j);
        while (it.hasNext()) {
            it.next();
            it.set((byte)0);
        }
    }

    @Override
    public void assign(byte value) {

        update(ByteMatrices.asConstFunction(value));
    }

    @Override
    public void assignRow(int i, byte value) {

        updateRow(i, ByteMatrices.asConstFunction(value));
    }

    @Override
    public void assignColumn(int j, byte value) {

        updateColumn(j, ByteMatrices.asConstFunction(value));
    }

    @Override
    public int rows() {

        return rows;
    }

    @Override
    public int columns() {

        return columns;
    }

    @Override
    public ByteVector getRow(int i) {

        return getRow(i, factory);
    }

    @Override
    public ByteVector getRow(int i, Factory factory) {

        Indexables.checkIndexBounds(i, rows());
        ensureFactoryIsNotNull(factory);

        ByteVector result = factory.createVector(columns);

        ByteVectorIterator it = nonZeroRowIterator(i);
        while (it.hasNext()) {
            it.next();
            result.set(it.index(), it.get());
        }

        return result;
    }

    @Override
    public ByteVector getRow(int i, int fromColumn, int toColumn) {

        return getRow(i, fromColumn, toColumn, factory);
    }

    @Override
    public ByteVector getRow(int i, int fromColumn, int toColumn, Factory factory) {

        Indexables.checkIndexBounds(i, rows());
        Indexables.checkFromToBounds(fromColumn, toColumn, columns());
        ensureFactoryIsNotNull(factory);

        ByteVector result = factory.createVector(toColumn - fromColumn);

        ByteVectorIterator it = nonZeroRowIterator(i, fromColumn, toColumn);
        while (it.hasNext()) {
            it.next();
            result.set(it.index() - fromColumn, it.get());
        }

        return result;
    }

    @Override
    public ByteVector getColumn(int j) {

        return getColumn(j, factory);
    }

    @Override
    public ByteVector getColumn(int j, Factory factory) {

        Indexables.checkIndexBounds(j, columns());
        ensureFactoryIsNotNull(factory);

        ByteVector result = factory.createVector(rows);

        ByteVectorIterator it = nonZeroColumnIterator(j);
        while (it.hasNext()) {
            it.next();
            result.set(it.index(), it.get());
        }

        return result;
    }

    @Override
    public ByteVector getColumn(int j, int fromRow, int toRow) {

        return getColumn(j, fromRow, toRow, factory);
    }

    @Override
    public ByteVector getColumn(int j, int fromRow, int toRow, Factory factory) {

        Indexables.checkIndexBounds(j, columns());
        Indexables.checkFromToBounds(fromRow, toRow, rows());
        ensureFactoryIsNotNull(factory);

        ByteVector result = factory.createVector(toRow - fromRow);

        ByteVectorIterator it = nonZeroColumnIterator(j, fromRow, toRow);
        while (it.hasNext()) {
            it.next();
            result.set(it.index() - fromRow, it.get());
        }

        return result;
    }

    @Override
    public void setRow(int i, ByteVector row) {

        Indexables.checkIndexBounds(i, rows());
        ensureArgumentIsNotNull(row, "vector");

        if (columns != row.length()) {
            fail("Wrong vector length: " + row.length() + ". Should be: " + columns + ".");
        }

        ByteVectorIterator newIt = row.iterator();
        ByteVectorIterator oldIt = rowIterator(i);
        while (newIt.hasNext()) { // && oldIt.hasNext()
            newIt.next();
            oldIt.next();
            oldIt.set(newIt.get());
        }
    }

    @Override
    public void setRow(int i, int fromColumn, ByteVector row, int fromIndex, int length) {

        Indexables.checkIndexBounds(i, rows());
        Indexables.checkOffsetLengthBounds(fromColumn, length, columns());
        Indexables.checkOffsetLengthBounds(fromIndex, length, row.length());

        ByteVectorIterator newIt = row.iterator(fromIndex, fromIndex + length);
        ByteVectorIterator oldIt = rowIterator(i, fromColumn, fromColumn + length);
        while (newIt.hasNext()) { // && oldIt.hasNext()
            newIt.next();
            oldIt.next();
            oldIt.set(newIt.get());
        }
    }

    @Override
    public void setColumn(int j, ByteVector column) {

        Indexables.checkIndexBounds(j, columns());
        ensureArgumentIsNotNull(column, "vector");

        if (rows != column.length()) {
            fail("Wrong vector length: " + column.length() + ". Should be: " + rows + ".");
        }

        ByteVectorIterator newIt = column.iterator();
        ByteVectorIterator oldIt = columnIterator(j);
        while (newIt.hasNext()) { // && oldIt.hasNext()
            newIt.next();
            oldIt.next();
            oldIt.set(newIt.get());
        }
    }

    @Override
    public void setColumn(int j, int fromRow, ByteVector column, int fromIndex, int length) {

        Indexables.checkIndexBounds(j, columns());
        Indexables.checkOffsetLengthBounds(fromRow, length, rows());
        Indexables.checkOffsetLengthBounds(fromIndex, length, column.length());

        ByteVectorIterator newIt = column.iterator(fromIndex, fromIndex + length);
        ByteVectorIterator oldIt = columnIterator(j, fromRow, fromRow + length);
        while (newIt.hasNext()) { // && oldIt.hasNext()
            newIt.next();
            oldIt.next();
            oldIt.set(newIt.get());
        }
    }

    @Override
    public void swapRows(int i, int j) {

        Indexables.checkIndexBounds(i, rows());
        Indexables.checkIndexBounds(j, rows());

        if (i != j) {
            ByteVector ii = getRow(i);
            ByteVector jj = getRow(j);

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

    @Override
    public void swapColumns(int i, int j) {

        Indexables.checkIndexBounds(i, columns());
        Indexables.checkIndexBounds(j, columns());

        if (i != j) {
            ByteVector ii = getColumn(i);
            ByteVector jj = getColumn(j);

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

    @Override
    public ByteMatrix transpose() {

        return transpose(factory);
    }

    @Override
    public ByteMatrix transpose(Factory factory) {

        ensureFactoryIsNotNull(factory);

        ByteMatrix result = factory.createMatrix(columns, rows);

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

        return result;
    }

    @Override
    public ByteMatrix rotate() {

        return rotate(factory);
    }

    @Override
    public ByteMatrix rotate(Factory factory) {

        ensureFactoryIsNotNull(factory);

        ByteMatrix result = factory.createMatrix(columns, rows);

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                result.set(j, rows - 1 - i, safeGet(i, j));
            }
        }

        return result;
    }

    @Override
    public ByteMatrix power(int n) {

        return power(n, factory);
    }

    @Override
    public ByteMatrix power(int n, Factory factory) {

        if (n < 0) {
            fail("The exponent should be non-negative: " + n + ".");
        }

        ByteMatrix result = factory.createIdentityMatrix(rows);
        ByteMatrix that = this;

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

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

        return result;
    }

    @Override
    public ByteMatrix multiply(byte value) {

        return multiply(value, factory);
    }

    @Override
    public ByteMatrix multiply(byte value, Factory factory) {

        ensureFactoryIsNotNull(factory);

        ByteMatrix result = blank(factory);

        if (value != 0) {
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < columns; j++) {
                    result.set(i, j, aTimesB(safeGet(i, j), value));
                }
            }
        }

        return result;
    }

    @Override
    public ByteVector multiply(ByteVector vector) {

        return multiply(vector, factory);
    }

    @Override
    public ByteVector multiply(ByteVector vector, Factory factory) {

        ensureFactoryIsNotNull(factory);
        ensureArgumentIsNotNull(vector, "vector");

        if (columns != vector.length()) {
            fail("Wrong vector length: " + vector.length() + ". Should be: " + columns + ".");
        }

        ByteVector result = factory.createVector(rows);

        for (int i = 0; i < rows; i++) {
            byte acc = 0;
            for (int j = 0; j < columns; j++) {
                final byte prod = aTimesB(safeGet(i, j), vector.get(j));
                acc = aPlusB(acc, prod);
            }

            if (acc != 0) {
                result.set(i, acc);
            }
        }

        return result;
    }

    @Override
    public ByteMatrix multiply(ByteMatrix matrix) {

        return multiply(matrix, factory);
    }

    @Override
    public ByteMatrix multiply(ByteMatrix matrix, Factory factory) {

        ensureFactoryIsNotNull(factory);
        ensureArgumentIsNotNull(matrix, "matrix");

        if (columns != matrix.rows()) {
            fail("Wrong matrix dimensions: " + matrix.rows() + "x" + matrix.columns() +
                 ". Should be: " + columns + "x_.");
        }

        ByteMatrix result = factory.createMatrix(rows, matrix.columns());

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < result.columns(); j++) {
                byte acc = 0;
                for (int k = 0; k < columns; k++) {
                    final byte prod = aTimesB(safeGet(i, k), matrix.get(k, j));
                    acc = aPlusB(acc, prod);
                }

                if (acc != 0) {
                    result.set(i, j, acc);
                }
            }
        }

        return result;
    }

    @Override
    public ByteMatrix multiply(
        ByteMatrix matrix,
        int fromThisRow,
        int toThisRow,
        int fromThisColumn,
        int toThisColumn,
        int fromOtherRow,
        int toOtherRow,
        int fromOtherColumn,
        int toOtherColumn)
    {

        return multiply(
            matrix,
            fromThisRow, toThisRow,
            fromThisColumn, toThisColumn,
            fromOtherRow, toOtherRow,
            fromOtherColumn, toOtherColumn,
            factory);
    }

    @Override
    public ByteMatrix multiply(
        ByteMatrix matrix,
        int fromThisRow,
        int toThisRow,
        int fromThisColumn,
        int toThisColumn,
        int fromOtherRow,
        int toOtherRow,
        int fromOtherColumn,
        int toOtherColumn,
        Factory factory)
    {

        ensureFactoryIsNotNull(factory);
        ensureArgumentIsNotNull(matrix, "matrix");
        Indexables.checkFromToBounds(fromThisRow, toThisRow, rows);
        Indexables.checkFromToBounds(fromThisColumn, toThisColumn, columns);
        Indexables.checkFromToBounds(fromOtherRow, fromOtherRow, matrix.rows());
        Indexables.checkFromToBounds(fromOtherColumn, toOtherColumn, matrix.columns());

        if ((toThisColumn - fromThisColumn) != (toOtherRow - fromOtherRow)) {
            fail("Wrong matrix dimensions: " +
                 (toOtherRow - fromOtherRow) + "x" + (toOtherColumn - fromOtherColumn) +
                 ". Should be: " + (toThisColumn - fromThisColumn) + "x_.");
        }

        ByteMatrix result = factory.createMatrix(toThisRow - fromThisRow, toOtherColumn - fromOtherColumn);

        for (int i = fromThisRow; i < toThisRow; i++) {
            for (int j = fromOtherColumn; j < toOtherColumn; j++) {
                byte acc = 0;
                for (int k = fromThisColumn; k < toThisColumn; k++) {
                    final byte prod = aTimesB(safeGet(i, k), matrix.get(k, j));
                    acc = aPlusB(acc, prod);
                }

                if (acc != 0) {
                    result.set(i - fromThisRow, j - fromOtherColumn, acc);
                }
            }
        }

        return result;
    }

    @Override
    public ByteVector multiplyRow(int i, ByteMatrix matrix) {

        return multiplyRow(i, matrix, factory);
    }

    @Override
    public ByteVector multiplyRow(int i, ByteMatrix matrix, Factory factory) {

        ensureFactoryIsNotNull(factory);
        ensureArgumentIsNotNull(matrix, "matrix");

        if (columns != matrix.rows()) {
            fail("Wrong matrix dimensions: " + matrix.rows() + "x" + matrix.columns() +
                 ". Should be: " + columns + "x_.");
        }

        ByteVector result = factory.createVector(matrix.columns());

        for (int j = 0; j < matrix.columns(); j++) {
            byte acc = 0;

            ByteVectorIterator it = rowIterator(i);
            while (it.hasNext()) {
                it.next();
                final byte prod = aTimesB(it.get(), matrix.get(it.index(), j));
                acc = aPlusB(acc, prod);
            }

            result.set(j, acc);
        }

        return result;
    }

    @Override
    public ByteVector multiplyRow(int i, ByteMatrix matrix, int fromColumn, int toColumn) {

        return multiplyRow(i, matrix, fromColumn, toColumn, factory);
    }

    @Override
    public ByteVector multiplyRow(int i, ByteMatrix matrix, int fromColumn, int toColumn, Factory factory) {

        ensureFactoryIsNotNull(factory);
        ensureArgumentIsNotNull(matrix, "matrix");
        Indexables.checkFromToBounds(fromColumn, toColumn, columns());

        if ((toColumn - fromColumn) != matrix.rows()) {
            fail("Wrong matrix dimensions: " + matrix.rows() + "x" + matrix.columns() +
                 ". Should be: " + (toColumn - fromColumn) + "x_.");
        }

        ByteVector result = factory.createVector(matrix.columns());

        for (int j = 0; j < matrix.columns(); j++) {
            byte acc = 0;

            ByteVectorIterator it = rowIterator(i, fromColumn, toColumn);
            while (it.hasNext()) {
                it.next();
                final byte prod = aTimesB(it.get(), matrix.get(it.index() - fromColumn, j));
                acc = aPlusB(acc, prod);
            }

            result.set(j, acc);
        }

        return result;
    }

    @Override
    public ByteMatrix subtract(byte value) {

        return subtract(value, factory);
    }

    @Override
    public ByteMatrix subtract(ByteMatrix matrix) {

        return subtract(matrix, factory);
    }

    @Override
    public ByteMatrix subtract(byte value, Factory factory) {

        ensureFactoryIsNotNull(factory);

        ByteMatrix result = blank(factory);

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                result.set(i, j, aMinusB(safeGet(i, j), value));
            }
        }

        return result;
    }

    @Override
    public ByteMatrix subtract(ByteMatrix matrix, Factory factory) {

        ensureFactoryIsNotNull(factory);
        ensureArgumentIsNotNull(matrix, "matrix");

        if (rows != matrix.rows() || columns != matrix.columns()) {
            fail("Wrong matrix dimensions: " + matrix.rows() + "x" + matrix.columns() +
                 ". Should be: " + rows + "x" + columns + ".");
        }

        ByteMatrix result = blank(factory);

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                result.set(i, j, aMinusB(safeGet(i, j), matrix.get(i, j)));
            }
        }

        return result;
    }

    @Override
    public ByteMatrix add(byte value) {

        return add(value, factory);
    }

    @Override
    public ByteMatrix add(byte value, Factory factory) {

        ensureFactoryIsNotNull(factory);

        ByteMatrix result = blank(factory);

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                result.set(i, j, aPlusB(safeGet(i, j), value));
            }
        }

        return result;
    }

    @Override
    public ByteMatrix add(ByteMatrix matrix) {

        return add(matrix, factory);
    }

    @Override
    public ByteMatrix add(ByteMatrix matrix, Factory factory) {

        ensureFactoryIsNotNull(factory);
        ensureArgumentIsNotNull(matrix, "matrix");

        if (rows != matrix.rows() || columns != matrix.columns()) {
            fail("Wrong matrix dimensions: " + matrix.rows() + "x" + matrix.columns() +
                 ". Should be: " + rows + "x" + columns + ".");
        }

        ByteMatrix result = blank(factory);

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                result.set(i, j, aPlusB(safeGet(i, j), matrix.get(i, j)));
            }
        }

        return result;
    }

    @Override
    public ByteMatrix divide(byte value) {

        return divide(value, factory);
    }

    @Override
    public ByteMatrix divide(byte value, Factory factory) {

        ensureFactoryIsNotNull(factory);

        ByteMatrix result = blank(factory);

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                result.set(i, j, aDividedByB(safeGet(i, j), value));
            }
        }

        return result;
    }

    @Override
    public void divideInPlace(byte value) {

        if (value != 1) {
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < columns; j++) {
                    safeSet(i, j, aDividedByB(safeGet(i, j), value));
                }
            }
        }
    }

    @Override
    public void divideRowInPlace(int i, byte value) {

        Indexables.checkIndexBounds(i, rows());

        if (value != 1) {
            ByteVectorIterator it = rowIterator(i);
            while (it.hasNext()) {
                it.next();
                it.set(aDividedByB(it.get(), value));
            }
        }
    }

    @Override
    public void divideRowInPlace(int i, byte value, int fromColumn, int toColumn) {

        Indexables.checkIndexBounds(i, rows());
        Indexables.checkFromToBounds(fromColumn, toColumn, columns());

        if (value != 1) {
            ByteVectorIterator it = rowIterator(i, fromColumn, toColumn);
            while (it.hasNext()) {
                it.next();
                it.set(aDividedByB(it.get(), value));
            }
        }
    }

    @Override
    public void divideColumnInPlace(int j, byte value) {

        Indexables.checkIndexBounds(j, columns());

        if (value != 1) {
            ByteVectorIterator it = columnIterator(j);
            while (it.hasNext()) {
                it.next();
                it.set(aDividedByB(it.get(), value));
            }
        }
    }

    @Override
    public void divideColumnInPlace(int j, byte value, int fromRow, int toRow) {

        Indexables.checkIndexBounds(j, columns());
        Indexables.checkFromToBounds(fromRow, toRow, rows());

        if (value != 1) {
            ByteVectorIterator it = columnIterator(j, fromRow, toRow);
            while (it.hasNext()) {
                it.next();
                it.set(aDividedByB(it.get(), value));
            }
        }
    }

    @Override
    public byte trace() {

        byte result = 0;

        final int fence = Math.min(rows, columns);
        for (int i = 0; i < fence; i++) {
            result = aPlusB(result, safeGet(i, i));
        }

        return result;
    }

    @Override
    public byte diagonalProduct() {

        byte result = 1;

        final int fence = Math.min(rows, columns);
        for (int i = 0; i < fence; i++) {
            result = aTimesB(result, safeGet(i, i));
        }

        return result;
    }

    @Override
    public byte product() {

        return fold(ByteMatrices.asProductAccumulator((byte)1));
    }

    @Override
    public ByteMatrix hadamardProduct(ByteMatrix matrix) {

        return hadamardProduct(matrix, factory);
    }

    @Override
    public ByteMatrix hadamardProduct(ByteMatrix matrix, Factory factory) {

        ensureFactoryIsNotNull(factory);
        ensureArgumentIsNotNull(matrix, "matrix");

        if ((columns != matrix.columns()) || (rows != matrix.rows())) {
            fail("Wrong matrix dimensions: " + matrix.rows() + "x" + matrix.columns() +
                 ". Should be: " + rows + "x" + columns + ".");
        }

        ByteMatrix result = factory.createMatrix(rows, columns);

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                result.set(i, j, aTimesB(matrix.get(i, j), safeGet(i, j)));
            }
        }

        return result;
    }

    @Override
    public byte sum() {

        return fold(ByteMatrices.asSumAccumulator((byte)0));
    }

    @Override
    public ByteMatrix blank() {

        return blank(factory);
    }

    @Override
    public ByteMatrix blank(Factory factory) {

        ensureFactoryIsNotNull(factory);

        return factory.createMatrix(rows, columns);
    }

    @Override
    public ByteMatrix copy() {

        return copy(factory);
    }

    @Override
    public ByteMatrix copy(Factory factory) {

        ensureFactoryIsNotNull(factory);

        return factory.createMatrix(this);
    }

    @Override
    public ByteMatrix resize(int rows, int columns) {

        return resize(rows, columns, factory);
    }

    @Override
    public ByteMatrix resizeRows(int rows) {

        return resize(rows, columns, factory);
    }

    @Override
    public ByteMatrix resizeRows(int rows, Factory factory) {

        return resize(rows, columns, factory);
    }

    @Override
    public ByteMatrix resizeColumns(int columns) {

        return resize(rows, columns, factory);
    }

    @Override
    public ByteMatrix resizeColumns(int columns, Factory factory) {

        return resize(rows, columns, factory);
    }

    @Override
    public ByteMatrix resize(int rows, int columns, Factory factory) {

        ensureFactoryIsNotNull(factory);

        ByteMatrix result = factory.createMatrix(rows, columns);

        for (int i = 0; i < Math.min(rows, this.rows); i++) {
            for (int j = 0; j < Math.min(columns, this.columns); j++) {
                result.set(i, j, safeGet(i, j));
            }
        }

        return result;
    }

    @Override
    public ByteMatrix shuffle() {

        return shuffle(factory);
    }

    @Override
    public ByteMatrix shuffle(Factory factory) {

        ensureFactoryIsNotNull(factory);

        ByteMatrix result = copy(factory);

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

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

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

        return result;
    }

    @Override
    public ByteMatrix slice(
        int fromRow,
        int fromColumn,
        int untilRow,
        int untilColumn)
    {

        return slice(fromRow, fromColumn, untilRow, untilColumn, factory);
    }

    @Override
    public ByteMatrix slice(
        int fromRow,
        int fromColumn,
        int untilRow,
        int untilColumn,
        Factory factory)
    {

        Indexables.checkFromToBounds(fromRow, untilRow, rows());
        Indexables.checkFromToBounds(fromColumn, untilColumn, columns());
        ensureFactoryIsNotNull(factory);

        ByteMatrix result = factory.createMatrix(untilRow - fromRow, untilColumn - fromColumn);

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

        return result;
    }

    @Override
    public ByteMatrix sliceTopLeft(int untilRow, int untilColumn) {

        return slice(0, 0, untilRow, untilColumn, factory);
    }

    @Override
    public ByteMatrix sliceTopLeft(int untilRow, int untilColumn, Factory factory) {

        return slice(0, 0, untilRow, untilColumn, factory);
    }

    @Override
    public ByteMatrix sliceBottomRight(int fromRow, int fromColumn) {

        return slice(fromRow, fromColumn, rows, columns, factory);
    }

    @Override
    public ByteMatrix sliceBottomRight(int fromRow, int fromColumn, Factory factory) {

        return slice(fromRow, fromColumn, rows, columns, factory);
    }

    @Override
    public ByteMatrix select(int[] rowIndices, int[] columnIndices) {

        return select(rowIndices, columnIndices, factory);
    }

    @Override
    public ByteMatrix select(int[] rowIndices, int[] columnIndices, Factory factory) {

        int newRows = rowIndices.length;
        int newCols = columnIndices.length;

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

        ByteMatrix result = factory.createMatrix(newRows, newCols);

        for (int i = 0; i < newRows; i++) {
            for (int j = 0; j < newCols; j++) {
                result.set(i, j, get(rowIndices[i], columnIndices[j])); // use normal get for row/column bounds check
            }
        }

        return result;
    }

    @Override
    public Factory factory() {

        return factory;
    }

    @Override
    public boolean isZeroAt(int i, int j) {

        checkBounds(i, j);
        return safeGet(i, j) == 0;
    }

    @Override
    public boolean nonZeroAt(int i, int j) {

        checkBounds(i, j);
        return safeGet(i, j) != 0;
    }

    @Override
    public int nonZeros() {

        int nonZeros = 0;

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                if (nonZeroAt(i, j)) {
                    nonZeros++;
                }
            }
        }

        return nonZeros;
    }

    @Override
    public int nonZerosInRow(int i) {

        ByteVectorIterator it = nonZeroRowIterator(i);
        int nonZeros = 0;
        while (it.hasNext()) {
            it.next();
            nonZeros++;
        }

        return nonZeros;
    }

    @Override
    public int nonZerosInRow(int i, int fromColumn, int toColumn) {

        ByteVectorIterator it = nonZeroRowIterator(i, fromColumn, toColumn);
        int nonZeros = 0;
        while (it.hasNext()) {
            it.next();
            nonZeros++;
        }

        return nonZeros;
    }

    @Override
    public int[] nonZeroPositionsInRow(int i) {

        int[] positions = new int[nonZerosInRow(i)];

        int n = 0;
        ByteVectorIterator it = nonZeroRowIterator(i);
        while (it.hasNext()) {
            it.next();
            positions[n++] = it.index();
        }

        return positions;
    }

    @Override
    public int[] nonZeroPositionsInRow(int i, int fromColumn, int toColumn) {

        int[] positions = new int[nonZerosInRow(i, fromColumn, toColumn)];

        int n = 0;
        ByteVectorIterator it = nonZeroRowIterator(i, fromColumn, toColumn);
        while (it.hasNext()) {
            it.next();
            positions[n++] = it.index();
        }

        return positions;
    }

    @Override
    public int nonZerosInColumn(int j) {

        ByteVectorIterator it = nonZeroColumnIterator(j);
        int nonZeros = 0;
        while (it.hasNext()) {
            it.next();
            nonZeros++;
        }

        return nonZeros;
    }

    @Override
    public int nonZerosInColumn(int j, int fromRow, int toRow) {

        ByteVectorIterator it = nonZeroColumnIterator(j, fromRow, toRow);
        int nonZeros = 0;
        while (it.hasNext()) {
            it.next();
            nonZeros++;
        }

        return nonZeros;
    }

    @Override
    public int[] nonZeroPositionsInColumn(int j) {

        int[] positions = new int[nonZerosInColumn(j)];

        int n = 0;
        ByteVectorIterator it = nonZeroColumnIterator(j);
        while (it.hasNext()) {
            it.next();
            positions[n++] = it.index();
        }

        return positions;
    }

    @Override
    public int[] nonZeroPositionsInColumn(int j, int fromRow, int toRow) {

        int[] positions = new int[nonZerosInColumn(j, fromRow, toRow)];

        int n = 0;
        ByteVectorIterator it = nonZeroColumnIterator(j, fromRow, toRow);
        while (it.hasNext()) {
            it.next();
            positions[n++] = it.index();
        }

        return positions;
    }

    @Override
    public void each(MatrixProcedure procedure) {

        for (int i = 0; i < rows; i++) {
