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 UnimodalFunction {
	
	private static int iter=0;

	/**
	 * 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;
		
		System.err.println("Reduce: #Centroids: "+centroids.size()+" #Rawdata: "+rawdata.size());
		if(centroids.size() < 2)
			return centroids;
		
		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 isunimodal = UnimodalFunction.unimodalFunction(c1, c2, list, 0, 2);

			if (isunimodal) {
				populationReduced = true;
				for (DoubleIndividual ind : list) {
					//rawdata.add(ind);
				}
				//rawdata.add(c1);
				
				//System.err.println("Absorbing"+iter+": "+c1.asString()+" absorbes "+c2.asString());

				c1.absorve(c2,c2.getCluster()) ;
				centroids.remove(c2);
				
			}else {
				//System.err.println("It isn't unimodal");
				//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();
	
//		int i=0;
//		for (DoubleIndividual c : centroids) {
//			Centroid cent = (Centroid)c;
//			cent.setFitness(Configuration.f.evaluate(c.getChr().asdouble()));
//			System.err.println("Centroid"+i+"Evolving"+iter+":\t"+cent.asString());
//			i++;
//		}
//		
//		iter++;
		
		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 unimodalFunction(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 = unimodalFunction(i1, imean, list, currentDepth+1, maxDepth);
			boolean second = unimodalFunction(imean, i2, list, currentDepth+1, maxDepth);
			if(isUnimodal(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 f: X -> R be a function f.
	 * we approach unimodality of f with:
	 * For every x1 and x2 in X, and every t in [0,1]:
	 * f(tx1+(1-t)x2) <= f(x1) || f(tx1+(1-t)x2) <= 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 inequalities above.
	 */
	private static boolean isUnimodal (DoubleIndividual i1, DoubleIndividual i2, DoubleIndividual imean) {
		double firstterm = imean.getFitness();
		double fx1 = i1.getFitness();
		double fx2 = i2.getFitness();
		
		if (firstterm >= fx1 || firstterm >= fx2) // 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 ={-2.297038934919123, 4.171347887236466};
		double [] x ={0.7868940,    3.8578661,    0.2445008,    150.,  -68.388604,  -46.869188,  -9.0835666,  -32.689128,  -3.2810566,  -44.807117,    15.906054,  -28.079183,    13.515273,  -15.205327};
		DoubleIndividual i1 = new DoubleIndividual(x);
		i1.setFitness(Configuration.f.evaluate(x));
		//double [] x2 ={-0.31563404493143793, 4.2044727087876925};
		double [] x2 ={0.8893207,    3.4572695,    0.2455034,    150.,  -74.229735,  -47.009501,  -12.310521,  -42.125446,  -6.4255412,  -45.478432,    15.164429,  -29.993531,    13.219244,  -15.173711};
		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());
//		}
		
		for(DoubleIndividual ind : pop) {
			System.out.println("distance: "+ ind.distance(i1)+" f(x) "+(-ind.getFitness()));
		}
		
		i1 = pop.getP().get(0);
		i2 = pop.getP().get(1);
		ArrayList<DoubleIndividual> list = new ArrayList<DoubleIndividual>();
		
		boolean isUnimodal = unimodalFunction(i1, i2, list, 0, 4);
		
		if(isUnimodal){
			System.err.println("Yes, it's unimodal");
		}else {
			System.err.println("No, it isn't unimodal");
		}
		
		for(DoubleIndividual ind : list) {
			System.out.println("distance: "+ ind.distance(i1)+" f(x) "+(-ind.getFitness()));
		}

	}
	
}
