package multinomad.tools.convex;

import java.util.ArrayList;

import multinomad.config.Configuration;
import multinomad.config.LoadProperties;
import multinomad.individuals.DoubleIndividual;
import multinomad.individuals.Population;
import multinomad.tools.kmeans.Centroid;
import multinomad.tools.kmeans.Kmeans;

public class ConvexFunction {

	/**
	 * Step 4: Reducing the number of centroids (using convex function)
	 * 		4.1. For every centroid, we locate its closest one
	 * 		4.2. Using the definition of convex function, we establish whether both centroids belong to the same basin of attraction
	 *      	4.2.1. If yes: we keep a single centroid and merge the items of both clusters
	 *      	4.2.2. If not: we do nothing
	 *      4.3. We rerun kmeans.cluster() in order to recompute the centroids
	 * @param centroids a population of centroids
	 * @param rawdata a population of items (usually filtered after selection)
	 * @return the new population of reduced centroids
	 */
	public static Population reduceNumberOfCentroids(Population centroids, Population rawdata) {
		
		boolean populationReduced = false;
		
		for (int i=0;i < centroids.size();i++) {
			Centroid c1 = (Centroid) centroids.getP().get(i);
			Centroid c2 = null;
			// -- We calculate the closest centroid c2 to c1
			double minDistance = Double.MAX_VALUE;
			for (int j=0;j < centroids.size();j++) {
				Centroid c = (Centroid) centroids.getP().get(j);
				if (!c.equals(c1)) {
					double distance = c1.distance(c);
					if (distance < minDistance) {
						minDistance = distance;
						c2 = c;
					}
				}
			}
			// -- c2 is the closest to c1

			
			ArrayList<DoubleIndividual> list = new ArrayList<DoubleIndividual>();

			boolean isconvex = ConvexFunction.convexFunction(c1, c2, list, 0, 2);

			if (isconvex) {
				populationReduced = true;
				for (DoubleIndividual ind : list) {
					rawdata.add(ind);
				}
				rawdata.add(c1);

				c1.absorve(c2,c2.getCluster());
				centroids.remove(c2);
				
			}else {
				//System.err.println("It isn't convex");
				//Centroid newcentroid = new Centroid(list.get(0));
				//newcentroid.setFitness(list.get(0).getFitness());
				//newcentroid.addToCluster(newcentroid);
			}
			
		}
		
		Kmeans kmeans = new Kmeans(rawdata, centroids);
		centroids = kmeans.clusterRecalculate();
		
		if (populationReduced) {
			reduceNumberOfCentroids(centroids, rawdata);
		}
		
		return centroids;
			
	}
	/**
	 * Let X be a convex set in a real vector space and let f: X -> R be a function f.
	 * f is called convex if:
	 * For every x1 and x2 in X, and every t in [0,1]:
	 * f(tx1+(1-t)x2) <= tf(x1) + (1-t)f(x2)
	 * 
	 * We make t=0.5. We can explore a set of points between x1 and x2 by recursively halving the interval. 
	 * 
	 * @param i1 first individual containing the vector x1
	 * @param i2 second individual containing the vector x2
	 * @param list Must be initialized. It keeps a list with all the individuals explored
	 * @param currentDepth initially 0
	 * @param maxDepth The depth of the tree to explore. 1 means we just test a single intermediate point; 
	 * a depth of 2 means 1 + 2 intermediate points to explore (i.e. 3). A depth of 3 would explore a max of 1 + 2 + 4 intermediate points (i.e. 7)   
	 * @return true if in maxDepth we did not detect any violation of convexity.
	 */
	public static boolean convexFunction(DoubleIndividual i1, DoubleIndividual i2, ArrayList<DoubleIndividual> list, int currentDepth, int maxDepth) {
		if (currentDepth >= maxDepth)
			return true;
		else {
			DoubleIndividual imean = averageIndividual(i1, i2);
			list.add(imean);
			boolean first = convexFunction(i1, imean, list, currentDepth+1, maxDepth);
			boolean second = convexFunction(imean, i2, list, currentDepth+1, maxDepth);
			if(isConvex(i1, i2, imean)) {
				return first && second;
			}else
				return false;
		}
	}
	
	
	private static DoubleIndividual averageIndividual(DoubleIndividual i1, DoubleIndividual i2) {
		double[] xx = new double[i1.getChr().getLength()];
		double[] x1 = i1.getChr().asdouble();
		double[] x2 = i2.getChr().asdouble();
		for(int i=0; i< xx.length;i++) {
			xx[i] = (x1[i]+x2[i])/2.0;
		}
		DoubleIndividual imean = new DoubleIndividual(xx);
		imean.setFitness(Configuration.f.evaluate(xx));
		return imean;
	}
	
	/**
	 * Let X be a convex set in a real vector space and let f: X -> R be a function f.
	 * f is called convex if:
	 * For every x1 and x2 in X, and every t in [0,1]:
	 * f(tx1+(1-t)x2) <= tf(x1) + (1-t)f(x2)
	 * 
	 * We assume that (tx1+(1-t)x2) is imean (i.e. the average between i1 and i2), therefore t=0.5
	 * @return true if imean doesn't violate the inequality above.
	 */
	private static boolean isConvex (DoubleIndividual i1, DoubleIndividual i2, DoubleIndividual imean) {
		double firstterm = imean.getFitness();
		double tfx1 = 0.5*i1.getFitness();
		double tfx2 = 0.5*i2.getFitness();
		
		if (firstterm >= (tfx1 + tfx2)) // Without lost of generality we talk about a maximization problem 
			return true;
		else
			return false;
		
	}
	
	public static void main(String[] args) {
		LoadProperties lp = new LoadProperties(args,null);
		Configuration.setConfiguration(lp);
		
//		int lbound = (int) Configuration.f.getBounds().get(0).lower;
//		int ubound = (int) Configuration.f.getBounds().get(0).upper;
//		
//		for(double i=lbound; i<=ubound; i+=0.1) {
//			double[] x= {i};
//			DoubleIndividual ind = new DoubleIndividual(x);
//			ind.setFitness(Configuration.f.evaluate(x));
//			System.out.println(x[0]+" "+Configuration.f.evaluate(x));
//		}
		
		Population pop = new Population(false);
		double [] x ={5};
		DoubleIndividual i1 = new DoubleIndividual(x);
		i1.setFitness(Configuration.f.evaluate(x));
		double [] x2 ={8};
		DoubleIndividual i2 = new DoubleIndividual(x2);
		i2.setFitness(Configuration.f.evaluate(x2));
		pop.add(i1);
		pop.add(i2);

		for(DoubleIndividual ind : pop) {
			System.out.println("Initial: "+ ind.asString());
		}
		
		i1 = pop.getP().get(0);
		i2 = pop.getP().get(1);
		ArrayList<DoubleIndividual> list = new ArrayList<DoubleIndividual>();
		
		boolean isConvex = convexFunction(i1, i2, list, 0, 2);
		
		if(isConvex){
			System.err.println("Yes, it's convex");
		}else {
			System.err.println("No, it isn't convex");
		}
		
		for (DoubleIndividual ind : list) {
			System.out.println("List: "+ ind.asString());
		}
	}
	
}
