package ec.gp.semantic.geometry;

import ec.gp.semantic.ISemantics;
import lpsolve.LpSolve;
import lpsolve.LpSolveException;

import java.util.HashSet;

/**
 * @author Tomasz Pawlak
 */
public class L2ConvexHullDetector 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 LP problem in form:
        // (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;

        LpSolve solver = null;

        try {

            solver = LpSolve.makeLp(0/*semantics.size() + 1*/, this.existingSemantics.size());
            //solver.setAddRowmode(true);
            final double infinite = 1E15;
            solver.setInfinite(infinite);

            // constraints
            double[][] constraints = new double[semantics.size() + 1][this.existingSemantics.size() + 1];

            double[] sData = null;
            //double[] objective = new double[this.existingSemantics.size()+1];
            //Arrays.fill(objective, 1.0);

            // no objective function, this is feasibility problem
            //solver.setObjFn(objective);
            //solver.setObjBound(1.0);

            int col = 1;
            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;
                    }

                    constraints[r][col] = v;
                }

                constraints[sData.length][col++] = 1.0;
            }

            for (int r = 0; r < constraints.length - 1; ++r) {
                double v = (double) semantics.getValue(r);

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

                solver.addConstraint(constraints[r], LpSolve.EQ, v);
            }
            solver.addConstraint(constraints[constraints.length - 1], LpSolve.EQ, 1.0);

            // constraints on values
            for (int c = 1; c <= this.existingSemantics.size(); ++c) {
                //solver.setLowbo(c, 0.0);
                solver.setBounds(c, 0.0, 1.0);
            }

            // set other solver options
            //solver.setAntiDegen(LpSolve.ANTIDEGEN_FIXEDVARS | LpSolve.ANTIDEGEN_COLUMNCHECK | LpSolve.ANTIDEGEN_STALLING | LpSolve.ANTIDEGEN_NUMFAILURE | LpSolve.ANTIDEGEN_LOSTFEAS | LpSolve.ANTIDEGEN_INFEASIBLE | LpSolve.ANTIDEGEN_DYNAMIC | LpSolve.ANTIDEGEN_DURINGBB);
            solver.setAntiDegen(LpSolve.ANTIDEGEN_NUMFAILURE | LpSolve.ANTIDEGEN_LOSTFEAS /*| LpSolve.ANTIDEGEN_INFEASIBLE*/);

            //solver.setPresolve(LpSolve.PRESOLVE_ROWS | LpSolve.PRESOLVE_COLS | LpSolve.PRESOLVE_LINDEP | LpSolve.PRESOLVE_SOS | LpSolve.PRESOLVE_REDUCEMIP | LpSolve.PRESOLVE_KNAPSACK | LpSolve.PRESOLVE_ELIMEQ2 | LpSolve.PRESOLVE_IMPLIEDFREE | LpSolve.PRESOLVE_REDUCEGCD | LpSolve.PRESOLVE_PROBEFIX | LpSolve.PRESOLVE_PROBEREDUCE | LpSolve.PRESOLVE_ROWDOMINATE | LpSolve.PRESOLVE_COLDOMINATE | LpSolve.PRESOLVE_MERGEROWS | LpSolve.PRESOLVE_COLFIXDUAL | LpSolve.PRESOLVE_BOUNDS  | LpSolve.PRESOLVE_DUALS | LpSolve.PRESOLVE_SENSDUALS, 1000);
            solver.setPresolve(LpSolve.PRESOLVE_ROWS | LpSolve.PRESOLVE_LINDEP | LpSolve.PRESOLVE_SOS | LpSolve.PRESOLVE_ROWDOMINATE
                    | LpSolve.PRESOLVE_BOUNDS, 1000);
            solver.setTimeout(3);

            solver.setVerbose(LpSolve.SEVERE);
            //solver.setVerbose(LpSolve.DETAILED);

            //solver.printLp();

            int result = solver.solve();

            switch (result) {
                case LpSolve.OPTIMAL:
                case LpSolve.FEASFOUND:
                case LpSolve.PRESOLVED:
                case LpSolve.SUBOPTIMAL:
                case LpSolve.TIMEOUT: // if we cannot prove infeasibility in a low amount of time, the problem is probably feasible
                    double[] var = solver.getPtrVariables();
                    double sum = 0.0;
                    for (int i = 0; i < var.length; i++) {
                        sum += var[i];
                        //System.out.println("Value of var[" + i + "] = " + var[i]);
                    }
                    assert Math.abs(sum - 1.0) < 1E-6: String.format("Sum=%f", sum);

                    ++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; // sure true
                case LpSolve.INFEASIBLE:
                case LpSolve.NOFEASFOUND:
                case LpSolve.NUMFAILURE:

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

                    return false; // sure false

                case LpSolve.NOMEMORY:
                case LpSolve.UNBOUNDED:
                case LpSolve.DEGENERATE:
                case LpSolve.USERABORT:
                case LpSolve.PROCFAIL:
                case LpSolve.PROCBREAK:
                default:
                    //assert false : "unexpected error: " + result;
                    return false; //an error occurred
            }

        } catch (LpSolveException e) {
            e.printStackTrace();
            return false;
        } finally {
            if (solver != null)
                solver.deleteLp();
        }
    }
}
