package org.ojalgo.matrix.store;

import static org.ojalgo.function.constant.PrimitiveMath.*;

import org.junit.jupiter.api.Test;
import org.ojalgo.TestUtils;
import org.ojalgo.data.DataProcessors;
import org.ojalgo.netio.BasicLogger;
import org.ojalgo.random.Normal;
import org.ojalgo.random.Uniform;

public class MatrixPipelineTest extends MatrixStoreTests {

    private static final Normal NORMAL = Normal.standard();
    private static final Uniform UNIFORM = Uniform.standard();

    private static ElementsSupplier<Double> initialise() {

        // Assume you have the matrices [A],[B] and[C]
        R064Store mtrxA = R064Store.FACTORY.make(5, 7);
        R064Store mtrxB = R064Store.FACTORY.make(7, 3);
        R064Store mtrxC = R064Store.FACTORY.make(5, 3);
        mtrxA.fillAll(UNIFORM);
        mtrxB.fillAll(NORMAL);
        mtrxC.fillAll(UNIFORM);

        // [D] = [A][B]
        MatrixStore<Double> expD = mtrxA.multiply(mtrxB);

        ElementsSupplier<Double> placeholderD = mtrxB.premultiply(mtrxA);
        R064Store actD = placeholderD.collect(R064Store.FACTORY);

        if (DEBUG) {
            BasicLogger.debugMatrix("expD", expD);
            BasicLogger.debugMatrix("actD", actD);
        }
        TestUtils.assertEquals(expD, actD);
        placeholderD.supplyTo(actD);
        TestUtils.assertEquals(expD, actD);

        // [E] = [D] - [C]
        PhysicalStore<Double> expE = expD.copy();
        expE.modifyMatching(SUBTRACT, mtrxC);

        ElementsSupplier<Double> placeholderE = placeholderD.onMatching(SUBTRACT, mtrxC);
        R064Store actE = placeholderE.collect(R064Store.FACTORY);

        if (DEBUG) {
            BasicLogger.debugMatrix("expE", expE);
            BasicLogger.debugMatrix("actE", actE);
        }
        TestUtils.assertEquals(expE, actE);
        placeholderE.supplyTo(actE);
        TestUtils.assertEquals(expE, actE);

        // [F] = [E]t
        MatrixStore<Double> expF = expE.transpose();

        ElementsSupplier<Double> placeholderF = placeholderE.transpose();
        R064Store actF = placeholderF.collect(R064Store.FACTORY);

        if (DEBUG) {
            BasicLogger.debugMatrix("expF", expF);
            BasicLogger.debugMatrix("actF", actF);
        }
        TestUtils.assertEquals(expF, actF);
        placeholderF.supplyTo(actF);
        TestUtils.assertEquals(expF, actF);

        return placeholderF;
    }

    @Test
    public void testOnAll() {

        ElementsSupplier<Double> startingPoint = MatrixPipelineTest.initialise();

        R064Store expected = startingPoint.collect(R064Store.FACTORY);
        expected.modifyAll(DIVIDE.by(2.0));

        ElementsSupplier<Double> nextPlaceholder = startingPoint.onAll(DIVIDE.by(2.0));
        R064Store actual = nextPlaceholder.collect(R064Store.FACTORY);

        if (DEBUG) {
            BasicLogger.debugMatrix("expected", expected);
            BasicLogger.debugMatrix("actual", actual);
        }
        TestUtils.assertEquals(expected, actual);
        nextPlaceholder.supplyTo(actual);
        TestUtils.assertEquals(expected, actual);
    }

    @Test
    public void testOnAny() {

        ElementsSupplier<Double> startingPoint = MatrixPipelineTest.initialise();

        R064Store expected = startingPoint.collect(R064Store.FACTORY);
        expected.modifyAny(DataProcessors.CENTER);

        ElementsSupplier<Double> nextPlaceholder = startingPoint.onAny(DataProcessors.CENTER);
        R064Store actual = nextPlaceholder.collect(R064Store.FACTORY);

        if (DEBUG) {
            BasicLogger.debugMatrix("expected", expected);
            BasicLogger.debugMatrix("actual", actual);
        }
        TestUtils.assertEquals(expected, actual);
        nextPlaceholder.supplyTo(actual);
        TestUtils.assertEquals(expected, actual);
    }

    @Test
    public void testOnColumns() {

        ElementsSupplier<Double> startingPoint = MatrixPipelineTest.initialise();

        R064Store args = R064Store.FACTORY.makeFilled(1, 5, NORMAL);

        R064Store expected = startingPoint.collect(R064Store.FACTORY);
        expected.modifyMatchingInRows(MULTIPLY, args);

        ElementsSupplier<Double> nextPlaceholder = startingPoint.onColumns(MULTIPLY, args);
        R064Store actual = nextPlaceholder.collect(R064Store.FACTORY);

        if (DEBUG) {
            BasicLogger.debugMatrix("expected", expected);
            BasicLogger.debugMatrix("actual", actual);
        }
        TestUtils.assertEquals(expected, actual);
        nextPlaceholder.supplyTo(actual);
        TestUtils.assertEquals(expected, actual);
    }

    @Test
    public void testOnMatchingLeft() {

        ElementsSupplier<Double> startingPoint = MatrixPipelineTest.initialise();

        R064Store left = R064Store.FACTORY.makeFilled(3, 5, UNIFORM);

        R064Store expected = startingPoint.collect(R064Store.FACTORY);
        expected.modifyMatching(left, MULTIPLY);

        ElementsSupplier<Double> nextPlaceholder = startingPoint.onMatching(left, MULTIPLY);
        R064Store actual = nextPlaceholder.collect(R064Store.FACTORY);

        if (DEBUG) {
            BasicLogger.debugMatrix("expected", expected);
            BasicLogger.debugMatrix("actual", actual);
        }
        TestUtils.assertEquals(expected, actual);
        nextPlaceholder.supplyTo(actual);
        TestUtils.assertEquals(expected, actual);
    }

    @Test
    public void testOnMatchingRight() {

        ElementsSupplier<Double> startingPoint = MatrixPipelineTest.initialise();

        R064Store right = R064Store.FACTORY.makeFilled(3, 5, NORMAL);

        R064Store expected = startingPoint.collect(R064Store.FACTORY);
        expected.modifyMatching(DIVIDE, right);

        ElementsSupplier<Double> nextPlaceholder = startingPoint.onMatching(DIVIDE, right);
        R064Store actual = nextPlaceholder.collect(R064Store.FACTORY);

        if (DEBUG) {
            BasicLogger.debugMatrix("expected", expected);
            BasicLogger.debugMatrix("actual", actual);
        }
        TestUtils.assertEquals(expected, actual);
        nextPlaceholder.supplyTo(actual);
        TestUtils.assertEquals(expected, actual);
    }

    @Test
    public void testOnRows() {

        ElementsSupplier<Double> startingPoint = MatrixPipelineTest.initialise();

        R064Store args = R064Store.FACTORY.makeFilled(3, 1, NORMAL);

        R064Store expected = startingPoint.collect(R064Store.FACTORY);
        expected.modifyMatchingInColumns(MULTIPLY, args);

        ElementsSupplier<Double> nextPlaceholder = startingPoint.onRows(MULTIPLY, args);
        R064Store actual = nextPlaceholder.collect(R064Store.FACTORY);

        if (DEBUG) {
            BasicLogger.debugMatrix("expected", expected);
            BasicLogger.debugMatrix("actual", actual);
        }
        TestUtils.assertEquals(expected, actual);
        nextPlaceholder.supplyTo(actual);
        TestUtils.assertEquals(expected, actual);
    }

    @Test
    public void testTranspose() {

        ElementsSupplier<Double> startingPoint = MatrixPipelineTest.initialise();

        MatrixStore<Double> expected = startingPoint.collect(R064Store.FACTORY).transpose();

        ElementsSupplier<Double> nextPlaceholder = startingPoint.transpose();
        R064Store actual = nextPlaceholder.collect(R064Store.FACTORY);

        if (DEBUG) {
            BasicLogger.debugMatrix("expected", expected);
            BasicLogger.debugMatrix("actual", actual);
        }
        TestUtils.assertEquals(expected, actual);
        nextPlaceholder.supplyTo(actual);
        TestUtils.assertEquals(expected, actual);
    }

}
