package svm;

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

import org.ojalgo.optimisation.Expression;
import org.ojalgo.optimisation.ExpressionsBasedModel;
import org.ojalgo.optimisation.Variable;

import base.DenseVector;
import base.Structure;

public class QPSolver<X extends Structure, Y extends Structure>
{
	public ArrayList<ArrayList<Double>> H;

	public QPSolver()
	{
		H = new ArrayList<ArrayList<Double>>();
	}
	

	public void updateH(List<Constraint<X, Y>> constraints)
	{
		for (int i = H.size(); i < constraints.size(); i++)
		{
			ArrayList<Double> newRow = new ArrayList<>();
			final Constraint<X, Y> ci = constraints.get(i);
			for (int j = 0; j < H.size() + 1; j++)
			{
				final Constraint<X, Y> cj = constraints.get(j);
				double h = cj.phi.multiply(ci.phi);
				// normH+=Math.abs(h);
				newRow.add(h);
				if (j != H.size())
					H.get(j).add(h);
			}
			H.add(newRow);
		}
	}

	public void pruneConstraint(int i)
	{
		for (ArrayList<Double> h : H)
			h.remove(i);
		H.remove(i);
	}
	
	public double solve(ArrayList<Constraint<X, Y>> constraints, DenseVector w, double C, int iter)
	{
//		 GRBEnv env;
//		 try
//		 {
//		 env = new GRBEnv("qp.log");
//		 //env.set(GRB.IntParam.BarHomogeneous,1);
//		 env.set(GRB.IntParam.LogToConsole, 0);
//		 //env.set(GRB.DoubleParam.FeasibilityTol,env.get(GRB.DoubleParam.FeasibilityTol)*0.01);
//		 GRBModel model = new GRBModel(env);
//		
//		 // Create variables
//		 GRBVar[] gw = model.addVars(null, null, w.x, null, null);
//		 GRBVar slack = model.addVar(0d, Double.POSITIVE_INFINITY, 0,GRB.CONTINUOUS,"slack");
//		 // Integrate new variables
//		 model.update();
//		
//		 GRBQuadExpr obj = new GRBQuadExpr();
//		 for(int i=0;i<w.length();i++)
//			 obj.addTerm(0.5,gw[i],gw[i]);
////		 obj.addTerms(constants(0.5,w.length()),gw,gw);
//		 obj.addTerm(C, slack);
//		
//		 model.setObjective(obj,GRB.MINIMIZE);
//		
//		 ArrayList<GRBConstr> gConstraints = new ArrayList<>();
//		
//		 for(Constraint<X,Y> c:constraints)
//		 {
//		 GRBLinExpr constraint = new GRBLinExpr();
//		 constraint.addTerms(c.phi.x,gw);
//		 constraint.addTerm(1,slack);
//		 GRBConstr addConstr = model.addConstr(constraint, GRB.GREATER_EQUAL, c.loss, null);
//		 gConstraints.add(addConstr);
//		 }
//		
//		 model.optimize();
//		 for(int i=0;i<w.length();i++)
//		 {
//		 try{
//		 w.x[i] = gw[i].get(GRB.DoubleAttr.X);
//		 }
//		 catch(GRBException e)
//		 {
//		 System.out.println(e.getMessage());
//		 }
//		 }
//		 for(int i=0;i<gConstraints.size();i++)
//		 {
//		 // System.out.print(Math.abs(gConstraints.get(i).get(GRB.DoubleAttr.Pi))+" ");
//		 if(Math.abs(gConstraints.get(i).get(GRB.DoubleAttr.Pi))>0.0000001)
//		 {
//		 constraints.get(i).lastUsed=iter;
//		 }
//		 }
//		 double s = slack.get(GRB.DoubleAttr.X);
//		 model.dispose();
//		 return s;
//		
//		 }
//		 catch (GRBException e)
//		 {
//		 e.printStackTrace();
//		 }
//		 return 0;
		
		updateH(constraints);
		
		Variable[] varAlpha = new Variable[constraints.size()];
		for (int i = 0; i < varAlpha.length; i++)
		{
			varAlpha[i] = Variable.make("alpha" + i).lower(0);
			varAlpha[i].setValue(0);
		}
		Variable varBeta = Variable.make("beta").lower(0);

		ExpressionsBasedModel model = new ExpressionsBasedModel(varAlpha);
		model.addVariable(varBeta);

		Expression constraint = model.addExpression("constr");
		for (int i = 0; i < varAlpha.length; i++)
			constraint.setLinearFactor(varAlpha[i], 1);
		constraint.setLinearFactor(varBeta, 1);
		constraint.level(C);
		
		Expression objective = model.addExpression("obj");

		for (int i = 0; i < constraints.size(); i++)
		{
			objective.setLinearFactor(varAlpha[i], constraints.get(i).loss);
			for (int j = 0; j < constraints.size(); j++)
			{
				objective.setQuadraticFactor(varAlpha[i], varAlpha[j], -0.5 * H.get(i).get(j));
			}
		}
		objective.weight(1);

		model.maximise();

		w.scale(0);
		
		for (int i = 0; i < varAlpha.length; i++)
		{
			w.addScaled(constraints.get(i).phi,varAlpha[i].getValue().doubleValue());
			if (varAlpha[i].getValue().doubleValue() > 0.0000001)
			{
				
				constraints.get(i).lastUsed = iter;
			}
			// System.out.print(alphas.get(i)+",\t");
		}

		if (varBeta.getValue().doubleValue() > 0.0000001)
			return 0;
		double sslack = 0;
		// double alphasum = 0;
		double maxAlpha = 0;
		for (int i = 0; i < constraints.size(); i++)
		{
			// alphasum+=alphas.get(i);
			if (varAlpha[i].getValue().doubleValue() > 0.0000001)
			{
				double slack = constraints.get(i).loss;
				for (int j = 0; j < constraints.size(); j++)
				{
					slack -= varAlpha[j].getValue().doubleValue() * H.get(i).get(j);
				}
				if (varAlpha[i].getValue().doubleValue() > maxAlpha)
				{
					sslack = slack;
					maxAlpha = varAlpha[i].getValue().doubleValue();
				}
				// System.out.println("possible slack: " + slack);

			}
		}
		// System.out.println("How abut dem alphas and betas: " +alphasum+" \t "+beta.get(GRB.DoubleAttr.X));

		return sslack;
	}
}
