package ec.gp.semantic.geometry;

import ec.gp.semantic.ISemantics;
import org.apache.commons.math3.linear.*;

/**
 * @author Tomasz Pawlak
 */
public class L2ConvexHullDetectorFast extends L1ConvexHullDetector {

    //private int feasible = 0;
    //private int infeasible = 0;

    public boolean isInConvexHull(ISemantics semantics) {
        assert semantics != null;

        if (this.existingSemantics.size() <= 1) {
            // there is no convex hull or there are not enough semantics in database
            // to construct unique or overdetermined system of equations
            return false;
        }

        if (!super.isInConvexHull(semantics)) {
            // if the semantics is not in Manhattan convex hull, it cannot be in Euclidean too
            //++this.infeasible;
            return false;
        }


        // construct the system of linear equations:
        // (Si's are existing semantics)
        //     [S1_1, S2_1,..., Sn_1]
        //     [S1_2, S2_2,..., Sn_2]
        // A = [        ...         ]
        //     [S1_m, S2_m,..., Sn_m]
        //     [   1,    1,...,    1]
        //
        // (S is semantics to be checked)
        // b = [S_1, S_2,..., Sm, 1]
        //
        // Constraints:
        // Ax = b
        //
        // The last row of ones means that the total sum of independent variables should be 1;


        final int semSize = this.existingSemantics.iterator().next().size();
        final double infinite = 1E14;
        double[] sData;

        RealMatrix A = new Array2DRowRealMatrix(semSize + 1, this.existingSemantics.size());
        RealVector b = new ArrayRealVector(semSize + 1);
        RealVector x;
        int col = 0;
        for (ISemantics sem : this.existingSemantics) {
            sData = (double[]) sem.getValue();
            for (int r = 0; r < sData.length; ++r) {
                double v = sData[r];

                if (v > infinite) {
                    v = infinite;
                } else if (v < -infinite) {
                    v = -infinite;
                } else if (Double.isNaN(v)) {
                    v = 0.0;
                }

                A.setEntry(r, col, v);
            }
            A.setEntry(sData.length, col++, 1.0);
        }

        sData = (double[]) semantics.getValue();
        for (int r = 0; r < sData.length; ++r) {
            double v = sData[r];

            if (v > infinite) {
                v = infinite;
            } else if (v < -infinite) {
                v = -infinite;
            } else if (Double.isNaN(v)) {
                v = 0.0;
            }

            b.setEntry(r, v);
        }
        b.setEntry(sData.length, 1.0);

        x = new SingularValueDecomposition(A).getSolver().solve(b);
        //assert x.getRowDimension() == semSize + 1 : String.format("rowDimension: %d, semSize: %d", x.getRowDimension(), semSize);

        double sum = 0.0;
        for (int r = 0; r < x.getDimension(); ++r) {
            double v = x.getEntry(r);
            sum += v;
        }

        if (Math.abs(sum - 1.0) > 1E-6) {
            //++this.infeasible;
            return false;
        }

        /*++this.feasible;
        System.out.println(String.format("Feasible: %d (%.2f), Total: %d", this.feasible, (double) this.feasible
                / (this.feasible + this.infeasible), this.feasible + this.infeasible));*/
        return true;
    }
}
