package aalgTest;

import java.util.Arrays;
import java.util.List;

import org.junit.Test;
import org.junit.Assert;

import aalg.algebra.DenseVector;
import aalg.algebra.Density;
import aalg.algebra.EquationSystemSolution;
import aalg.algebra.IElement;
import aalg.algebra.IVector;
import aalg.algebra.Matrix;
import aalg.algebra.RationalField;
import aalg.algebra.RationalFieldElement;
import aalg.algebra.VectorImpl;

public class MatrixTest {
	
	private static class TestCase<T extends IElement> {
		private final Matrix<T> inputMatrix;
		private final IVector<T> inputRightSide;
		private final Matrix<T> eliminatedMatrix;
		private final IVector<T> eliminatedRightSide;
		private final EquationSystemSolution<T> generalSoltion;
		private final IVector<T> parameters;
		private final IVector<T> particularSolution;
		
		public TestCase(final Matrix<T> inputMatrix, final IVector<T> inputRightSide, final Matrix<T> eliminatedMatrix, final IVector<T> eliminatedRightSide, final EquationSystemSolution<T> generalSoltion, final IVector<T> parameters, final IVector<T> particularSolution) {
			this.inputMatrix = inputMatrix;
			this.inputRightSide = inputRightSide;
			this.eliminatedMatrix = eliminatedMatrix;
			this.eliminatedRightSide = eliminatedRightSide;
			this.generalSoltion = generalSoltion;
			this.parameters = parameters;
			this.particularSolution = particularSolution;
		}
	};
	
	final RationalField field = RationalField.INSTANCE;
	
	final List<TestCase<RationalFieldElement>> testCaseList = Arrays.asList(
		new TestCase<RationalFieldElement> (
			createMatrix(2, 2, "0", "0", "0", "0"),
			createVector("0", "0"),
			createMatrix(2, 2, "0", "0", "0", "0"),
			createVector("0", "0"),
			new EquationSystemSolution<RationalFieldElement>(
				createVector("0", "0"),
				createMatrix(2, 2, "1", "0", "0", "1")
			),
			createVector("-25698", "466164"),
			createVector("-25698", "466164")
		),
		new TestCase<RationalFieldElement> (
			createMatrix(2, 2, "0", "0", "0", "0"),
			createVector("1", "2"),
			createMatrix(2, 2, "0", "0", "0", "0"),
			createVector("1", "2"),
			null,
			null,
			null
		),
		new TestCase<RationalFieldElement> (
			createMatrix(2, 2, "1", "2", "3", "4"),
			createVector("1", "2"),
			createMatrix(2, 2, "1", "2", "0", "2"),
			createVector("1", "1"),
			new EquationSystemSolution<RationalFieldElement>(
				createVector("0", "1/2"),
				createMatrix(2, 0)
			),
			createVector(),
			createVector("0", "1/2")
		),
		new TestCase<RationalFieldElement> (
			createMatrix(4, 3, "1", "2", "3", "6", "9", "12", "4", "5", "6", "4", "5", "6"),
			createVector("1", "2", "0", "0"),
			createMatrix(4, 3, "1", "2", "3", "0", "3", "6", "0", "0", "0", "0", "0", "0"),
			createVector("1", "4", "0", "0"),
			new EquationSystemSolution<RationalFieldElement>(
				createVector("-5/3", "4/3", "0"),
				createMatrix(3, 1, "1", "-2", "1")
			),
			createVector("25"),
			createVector("70/3", "-146/3", "25")
		),
		new TestCase<RationalFieldElement> (
			createMatrix(2, 3, "1", "2", "3", "1", "2", "5"),
			createVector("3", "4"),
			createMatrix(2, 3, "1", "2", "3", "0", "0", "-2"),
			createVector("3", "-1"),
			new EquationSystemSolution<RationalFieldElement>(
				createVector("3/2", "0", "1/2"),
				createMatrix(3, 1, "-2", "1", "0")
			),
			createVector("37"),
			createVector("-145/2", "37", "1/2")
		)
	);
	
	private <T extends IElement> void testElimination(final TestCase<T> testCase, final Density density) {
		final Matrix<T> resultMatrix = new Matrix<T> (
			testCase.inputMatrix.getField(),
			testCase.inputMatrix.getRowCount(),
			testCase.inputMatrix.getColumnCount(),
			density
		);
		
		resultMatrix.assign (testCase.inputMatrix);
		
		final IVector<T> resultRightSide = testCase.inputRightSide.copy();
		
		final EquationSystemSolution<T> soltion = resultMatrix.solveEquationSystem(resultRightSide);
		
		Assert.assertEquals(testCase.eliminatedMatrix, resultMatrix);
		Assert.assertEquals(testCase.eliminatedRightSide, resultRightSide);
		Assert.assertEquals(testCase.generalSoltion, soltion);
	}

	private <T extends IElement> void testparticularSolution (final TestCase<T> testCase) {
		if (testCase.generalSoltion != null) {
			final IVector<T> particularSolution = testCase.generalSoltion.getSolution(testCase.parameters);
			
			Assert.assertEquals(testCase.particularSolution, particularSolution);
		}
	}
	
	private IVector<RationalFieldElement> createVector (final String... elements) {
		final IVector<RationalFieldElement> vector = new DenseVector<RationalFieldElement>(field, elements.length);
		
		for (int i = 0; i < elements.length; i++)
			vector.set(i, field.elementFromString (elements[i]));
		
		return vector;
	}

	private Matrix<RationalFieldElement> createMatrix (final int rowCount, final int columnCount, final String... elements) {
		if (elements.length != rowCount * columnCount)
			throw new RuntimeException("Wrong count of objects");
		
		final Matrix<RationalFieldElement> matrix = new Matrix<RationalFieldElement>(field, rowCount, columnCount);
		
		for (int i = 0; i < rowCount; i++) {
			for (int j = 0; j < columnCount; j++)
				matrix.set(i, j, field.elementFromString (elements[i * columnCount + j]));
		}
		
		return matrix;
	}

	@Test
	public void generalSolutionTest() {
		for (Density density: Density.values()) {
			for (TestCase<RationalFieldElement> testCase: testCaseList) {
				testElimination(testCase, density);
			}
		}
	}
	
	@Test
	public void particularSolutionTest() {
		for (TestCase<RationalFieldElement> testCase: testCaseList) {
			testparticularSolution(testCase);
		}
	}
}
