package svm;

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

import base.AtomicKernel;
import base.Example;
import base.Structure;

public abstract class DualQPSolver<X extends Structure, Y extends Structure>
{
	public Mode m;
	public ArrayList<ArrayList<Double>> H;

	// public double normH = 0;

	public DualQPSolver(Mode m)
	{
		this.m = m;
		this.H = new ArrayList<ArrayList<Double>>();
	}

	protected double[] constants(double c, int n)
	{
		double[] ret = new double[n];
		for (int i = 0; i < n; i++)
			ret[i] = c;
		return ret;
	}

	public void updateH(List<DualConstraint<X, Y>> constraints, List<Example<X, Y>> data, SVMStructKernel<X, Y> svm)
	{
		for (int i = H.size(); i < constraints.size(); i++)
		{
			if (!(svm.outputKernel instanceof AtomicKernel))
			{
				ArrayList<Double> newRow = new ArrayList<>();
				final DualConstraint<X, Y> ci = constraints.get(i);
				for (int j = 0; j < H.size() + 1; j++)
				{
					final DualConstraint<X, Y> cj = constraints.get(j);
					double h = data.parallelStream().mapToDouble((xy) ->
					{
						double h2 = 0;
						for (int b = 0; b < data.size(); b++)
						{
							X xi = xy.x;
							X xj = data.get(b).x;
							Y yi = xy.y;
							Y yj = data.get(b).y;
							Y ybari = ci.ybar.get(xy.i);
							Y ybarprimej = cj.ybar.get(b);

							double kx = svm.inputKernel.k(xi, xj);

							h2 += kx * svm.outputKernel.k(yi, yj);
							h2 -= kx * svm.outputKernel.k(yi, ybarprimej);
							h2 -= kx * svm.outputKernel.k(ybari, yj);
							h2 += kx * svm.outputKernel.k(ybari, ybarprimej);
						}
						return h2;
					}).sum();
					h /= (data.size() * data.size());
					// normH+=Math.abs(h);
					newRow.add(h);
					if (j != H.size())
						H.get(j).add(h);
				}
				H.add(newRow);
			}
			else
			{
				ArrayList<Double> newRow = new ArrayList<>();
				AtomicConstraint<X, Y> ci = (AtomicConstraint<X, Y>) constraints.get(i);

				// Two different Strategies: Either compute multiple inner products in parallel or compute the inner products sequentially but parallelize each computation

				// if (H.size() * 3 > ForkJoinPool.commonPool().getParallelism())
				// {
				// DoubleStream innerProducts = IntStream.range(0,H.size()+1).parallel().mapToDouble((j)->
				// {
				// AtomicConstraint<X, Y> cj = (AtomicConstraint<X, Y>) constraints.get(j);
				// double h = ci.innerProduct(cj, data, svm.inputKernel);
				// if (j != H.size())
				// H.get(j).add(h);
				// return h;});
				// innerProducts.forEachOrdered(a -> newRow.add(a));
				// }
				// else
				// {
				
				// This should be the best for large systems, because the threads have the most balanced work load.
				for (int j = 0; j < H.size() + 1; j++)
				{
					AtomicConstraint<X, Y> cj = (AtomicConstraint<X, Y>) constraints.get(j);
					double h = ci.innerProductP(cj, data, svm.inputKernel);
					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 abstract double solve(List<DualConstraint<X, Y>> constraints, List<Example<X, Y>> data, ArrayList<Double> alphas, double C, SVMStructKernel<X, Y> svm, int iter);

}
