package ec.gp.semantic.geometry;

import Jama.Matrix;
import ec.EvolutionState;
import ec.app.semanticGP.func.numeric.Constant;
import ec.app.semanticGP.func.numeric.Mul;
import ec.app.semanticGP.func.numeric.Sum;
import ec.gp.GPNode;
import ec.gp.semantic.ISemantics;
import ec.gp.semantic.func.SimpleNodeBase;
import ec.gp.semantic.utils.InstructionJoiner;
import ec.util.Parameter;

import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;

/**
 * @author Tomasz Pawlak
 */
public class DoubleBasisProcessor implements IBasisProcessor {

	// 2^-52 = 0x3FF0000000000000L - 0x0340000000000000L
	//protected final static double SENSITIVITY = Double.longBitsToDouble(0x3CB0000000000000L);
	protected final static double SENSITIVITY = 1E-6;

	private EvolutionState state;

	public void setup(EvolutionState state, Parameter base) {
		this.state = state;
	}

	@Override
	public SimpleNodeBase<?> makeLinearCombination(ISemantics target, List<SimpleNodeBase<?>> basis) {
		double[] t = (double[]) target.getValue();

		Matrix A = this.getMatrix(basis);
		Matrix b = new Matrix(t, t.length);
		Matrix x = A.solve(b);

		// xs are weights here

		/*Matrix Ab = new Matrix(A.getRowDimension(), A.getColumnDimension()+1);
		Ab.setMatrix(0, A.getRowDimension()-1, 0, A.getColumnDimension()-1, A);
		Ab.setMatrix(0, A.getRowDimension()-1, A.getColumnDimension(), A.getColumnDimension(), b);
		ToRowReducedEchelonForm(Ab);
		Ab.print(5,1);

		x.print(5, 3);
		b.print(5, 3);
		A.times(x).print(5, 3);

		// override x here
		//x = Ab.getMatrix(0, A.getRowDimension()-1, A.getColumnDimension(), A.getColumnDimension());
		*/

		List<SimpleNodeBase<?>> toJoin = new ArrayList<SimpleNodeBase<?>>();

		for (int i = 0; i < basis.size(); ++i) {
			SimpleNodeBase<?> program = basis.get(i);

			double weight = x.get(i, 0);

			if (Math.abs(weight) > SENSITIVITY) {
				Mul mul = new Mul();
				mul.children = new GPNode[]{new Constant(x.get(i, 0)), program};

				toJoin.add(mul);
			}
		}

		return InstructionJoiner.join(toJoin, Sum.class, 2);
	}

	@Override
	public Matrix getMatrix(List<SimpleNodeBase<?>> programs) {
		int rows = programs.get(0).getSemantics().size();
		int cols = programs.size();
		double[] semantics;

		Matrix A = new Matrix(rows, cols);
		for (int p = 0; p < programs.size(); ++p) {
			semantics = (double[]) programs.get(p).getSemantics().getValue();

			for (int row = 0; row < semantics.length; ++row) {
				A.set(row, p, semantics[row]);
			}
		}
		return A;
	}

	@Override
	public void extractBasis(List<SimpleNodeBase<?>> programs) {
		assert programs.size() > 0;

		Matrix A = getMatrix(programs);
		int rows = A.getRowDimension();
		int cols = A.getColumnDimension();

		TreeSet<Integer> toRemove = new TreeSet<Integer>();

		ToRowReducedEchelonForm(A);
		for (int r = 0, c = 0; r < rows; ++r) {
			for (; c < cols; ++c) {
				if (Math.abs(A.get(r, c) - 1.0) > SENSITIVITY) {
					toRemove.add(c);
				} else {
					++c;
					break;
				}
			}
		}

		for (Integer idx : toRemove.descendingSet()) {
			programs.remove((int) idx);
		}

		this.state.output.message("Basis size: " + programs.size());
	}

	protected static final void ToRowReducedEchelonForm(Matrix matrix) {
		//matrix.print(2, 1);

		int lead = 0, rowCount = matrix.getRowDimension(), columnCount = matrix.getColumnDimension();
		for (int r = 0; r < rowCount; r++) {
			if (columnCount <= lead) break;
			int i = r;
			while (Math.abs(matrix.get(i, lead)) <= SENSITIVITY) {
				i++;
				if (i == rowCount) {
					i = r;
					lead++;
					if (columnCount == lead) {
						lead--;
						break;
					}
				}
			}
			for (int j = 0; j < columnCount; j++) {
				double temp = matrix.get(r, j);
				matrix.set(r, j, matrix.get(i, j));
				matrix.set(i, j, temp);
			}
			double div = matrix.get(r, lead);
			if (Math.abs(div) > SENSITIVITY) {
				for (int j = 0; j < columnCount; j++) {
					matrix.set(r, j, matrix.get(r, j) / div);
				}
			}

			for (int j = 0; j < rowCount; j++) {
				if (j != r) {
					double sub = matrix.get(j, lead);
					for (int k = 0; k < columnCount; k++) {
						matrix.set(j, k, matrix.get(j, k) - (sub * matrix.get(r, k)));
					}
				}
			}
			lead++;
		}

		//matrix.print(2, 1);
	}

	@Override
	public boolean isInFeasibleRange(ISemantics semantics) {
		double[] v = (double[]) semantics.getValue();
		for (int i = 0; i < v.length; ++i) {
			if (v[i] <= -1E14 || 1E14 <= v[i]) {
				return false;
			}
		}
		return true;
	}
}
