package svm;

import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import base.AtomicKernel;
import base.Example;
import base.Kernel;
import base.SparseVector;
import base.Structure;

public class AtomicConstraint<X extends Structure, Y extends Structure> extends DualConstraint<X, Y>
{
	public ConcurrentHashMap<String, HashMap<X, Double>> phi;
	public AtomicKernel<X, Y> kernel;

	public AtomicConstraint(AtomicKernel<X, Y> kernel)
	{
		this.kernel = kernel;
		this.phi = new ConcurrentHashMap<String, HashMap<X, Double>>();
	}

	@Override
	public void addConstraint(X x, Y y, Y ybar, Mode m, double l)
	{
		super.addConstraint(x, y, ybar, m, l);
		// this.loss+=l;
		SparseVector phiY = kernel.featureMap(x, y);
		SparseVector phiYbar = kernel.featureMap(x, ybar);

		for (Entry<String, Double> e : phiY.entrySet())
		{
			// this.phi.putIfAbsent(key, value)
			// if (!this.phi.containsKey(e.getKey()))
			this.phi.putIfAbsent(e.getKey(), new HashMap<X, Double>());
			Double prev = this.phi.get(e.getKey()).put(x, e.getValue());
			if (prev != null)
				this.phi.get(e.getKey()).put(x, e.getValue() + prev);
		}
		for (Entry<String, Double> e : phiYbar.entrySet())
		{
			// if (!this.phi.containsKey(e.getKey()))
			this.phi.putIfAbsent(e.getKey(), new HashMap<X, Double>());
			Double prev = this.phi.get(e.getKey()).put(x, -e.getValue());
			if (prev != null)
				this.phi.get(e.getKey()).put(x, -e.getValue() + prev);
		}
	}

	@Override
	public void finalizeConstraint(int n)
	{
		// TODO Auto-generated method stub
		super.finalizeConstraint(n);
		for (Entry<String, HashMap<X, Double>> e : this.phi.entrySet())
		{
			for (Entry<X, Double> f : e.getValue().entrySet())
			{
				e.getValue().put(f.getKey(), f.getValue() / n);
			}
		}
		// this.loss/=n;
	}

//	private class Job
//	{
//		String key;
//		X x;
//		double alpha;
//
//		public Job(String key, X x, double alpha)
//		{
//			super();
//			this.key = key;
//			this.x = x;
//			this.alpha = alpha;
//		}
//
//	}

	public double innerProduct(AtomicConstraint<X, Y> prime, List<Example<X, Y>> data, Kernel<X> k)
	{
//		double c = 0d;
////		int numberOfKernels = 0;
//		for (int i = 0; i < data.size(); i++)
//		{
//			SparseVector featureMap = this.kernel.featureMap(data.get(i).x, data.get(i).y);
//			SparseVector featureMap2 = this.kernel.featureMap(data.get(i).x, prime.ybar.get(i));
//			featureMap2.scale(-1);
//			featureMap.add(featureMap2);
//			for (Entry<String, Double> e : featureMap.entrySet())
//			{
//				if (this.phi.containsKey(e.getKey()))
//					for (Entry<X, Double> f : this.phi.get(e.getKey()).entrySet())
//					{
//						c += k.k(f.getKey(), data.get(i).x) * f.getValue() * e.getValue();
////						numberOfKernels++;
//					}
//			}
//		}
//		c /= data.size();
//		return c;
//		System.out.println("Number of Kernel Computations for Inner Product A: " + numberOfKernels + " yields \t" + c);
//		numberOfKernels = 0;
		double c = 0;
		for (Entry<String, HashMap<X, Double>> e : prime.phi.entrySet())
			if (phi.containsKey(e.getKey()))
			{
				for (Entry<X, Double> x1 : e.getValue().entrySet())
				{
					for (Entry<X, Double> x2 : phi.get(e.getKey()).entrySet())
					{
						c += k.k(x2.getKey(), x1.getKey()) * x1.getValue() * x2.getValue();
//						numberOfKernels++;
					}
				}
			}
//		System.out.println("Number of Kernel Computations for Inner Product B: " + numberOfKernels + " yields \t" + c);
//		System.out.println(data.size() * data.size() + "");
		return c;
	}

	public double innerProductP(AtomicConstraint<X, Y> prime, List<Example<X, Y>> data, Kernel<X> k)
	{
		return data.parallelStream().mapToDouble((e) -> {
			double c = 0;
			SparseVector featureMap = this.kernel.featureMap(e.x, data.get(e.i).y);
			SparseVector featureMap2 = this.kernel.featureMap(e.x, prime.ybar.get(e.i));
			featureMap2.scale(-1);
			featureMap.add(featureMap2);
			for (Entry<String, Double> ee : featureMap.entrySet())
			{
				if (this.phi.containsKey(ee.getKey()))
					for (Entry<X, Double> f : this.phi.get(ee.getKey()).entrySet())
					{
						c += k.k(f.getKey(), data.get(e.i).x) * f.getValue() * ee.getValue();

					}
			}
			return c;
		}).average().getAsDouble();
		// Stream<Job> j = prime.phi.entrySet().stream().filter((e) -> phi.containsKey(e.getKey()))
		// .flatMap((e) -> e.getValue().entrySet().stream().map((f) -> new Job(e.getKey(), f.getKey(), f.getValue())));
		// return j.flatMapToDouble((job) -> phi.get(job.key).entrySet().stream().
		// mapToDouble((x2) -> k.k(x2.getKey(), job.x) * job.alpha * x2.getValue())).parallel().sum();

	}

}
