package multinomad.algorithms;

import java.util.ArrayList;

import multinomad.config.Configuration;
import multinomad.config.LoadProperties;
import multinomad.config.Logger;
import multinomad.individuals.DoubleIndividual;
import multinomad.individuals.Population;
import multinomad.individuals.Trajectory;
import multinomad.tools.Selection;
import multinomad.tools.convex.ConvexFunction;
import multinomad.tools.convex.UnimodalFunction;
import multinomad.tools.hierarchicalKmeans.FilterWithThreshold;
import multinomad.tools.Nomad;
import multinomad.tools.Seeds;
import multinomad.tools.kmeans.Centroid;
import multinomad.tools.kmeans.Kmeans;
import multinomad.tools.kmeansML.Centroids;
import multinomad.tools.kmeansML.SimpleKmeansJavaML;

// Exploring, Filtering, Clustering, Local search
public class EFCL implements Runnable{
	
	private Population solutions;
	private Population filteredPop;
	
public void run() {
		
	// Step 1: Explore (or sampling) the landscape with a large population
		Population pop = new Population(true);
		for(DoubleIndividual ind : pop) {
			System.out.println("Initial:\t\t"+ ind.asString());
		}
		
	// Step 2: Filter the best solutions (using cooperative selection)
		filteredPop = new Population(false);
		
		for(int i=0;i<pop.size();i++) {
			filteredPop.add(
					Selection.select(pop)
					);
		}
		
		for(DoubleIndividual ind : filteredPop) {
			System.out.println("Filtered:\t\t"+ ind.asString());
		}
		
	// Step 3: Calculate centroids (using kmeans)
		// -- Uncomment for SimpleKmeansJamaML
		// -- tested and Working
//		Centroids[] centroids = SimpleKmeansJavaML.computeCentroidsKmeans(filteredPop, Configuration.f.getNoOptima()*2, 100);
//		for (int i=0;i<centroids.length;i++) {
//			System.out.println("Centroids:\t\t"+ centroids[i].getAttributesAsString());
//		}
		
		// -- Uncomment for own implementation of Kmeans
		Kmeans kmeans = new Kmeans(filteredPop, Configuration.f.getNoOptima()*3);
		//System.out.println("Number of clusters: "+Configuration.f.getNoOptima());
		Population centroids = kmeans.cluster();

		for (DoubleIndividual c : centroids) {
			Centroid cent = (Centroid)c;
			cent.setFitness(Configuration.f.evaluate(c.getChr().asdouble()));
			System.out.println("Step3:\t"+cent.asString());
			
			for (DoubleIndividual cluster : cent) {
				//Logger.append("F"+Configuration.indexf+"Cluster", "Cluster"+i+":\t"+cluster.asString());
			}
		}
		
		
		
	// Step 4: Reducing the number of centroids (using convex/unimodal function)
		// 4.1. For every centroid, we locate its closest ones
		// 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
		
		System.err.println("\n-------------\nStep 4\n-------------\n");

		centroids = UnimodalFunction.reduceNumberOfCentroids(centroids, filteredPop); 

		
		int i=0;
		for (DoubleIndividual c : centroids) {
			Centroid cent = (Centroid)c;
			cent.setFitness(Configuration.f.evaluate(c.getChr().asdouble()));
			System.out.println("Step4:\t"+cent.asString());
			
			
			for (DoubleIndividual cluster : cent) {
				//Logger.append("F"+Configuration.indexf+"Cluster", "Cluster"+i+":\t"+cluster.asString());
			}
				
			i++;
		}
		
		
		
	// Step 5: Hierarchical kmeans++. 
		// 5.1. Let's n be the number of wished basins of attractions
		//		5.1.1. Initialize a threshold mu with the best fitness found so far
		//		5.1.2. Decrease the threshold until items in c clusters are located above the threshold
		//			   (c can be n + lambda  e.g. lambda = 5)
		//		5.1.3. Delete all clusters (and all data) that are below the threshold
		// 5.2. For every c,
		//		5.2.1. Perform kmeans on the c cluster for e.g. k = 5 (i.e. we increase the number of clusters per cluster)
		//		5.2.2. Reduce the number of clusters (as in step 4)
		//		5.2.3. If the k centroids have converged to a single one, mark the cluster as converged.
		// 5.3. Go to 5.1 and repeat until all clusters have converged	
	
	System.err.println("\n-------------\nStep 5\n-------------\n");
	// 5.1.1. and 5.1.2
	//FilterWithThreshold.filter(filteredPop, centroids, 5);
	FilterWithThreshold.filter(filteredPop, centroids, Configuration.f.getNoOptima());
	// 5.1.3
	FilterWithThreshold.deleteBelowThreshold(filteredPop, centroids);
	
//	i=0;
//	for(DoubleIndividual ind : filteredPop) {
//		System.out.println("ReduceIndividuals"+i+":\t\t"+ ind.asString());
//		i++;
//	}
	
	
	
	// 5.2.1.
	
	
	for(int h=0;h<5;h++) {
		ArrayList<DoubleIndividual> _centToErase = new ArrayList<DoubleIndividual>();
		ArrayList<DoubleIndividual> _centToAdd = new ArrayList<DoubleIndividual>();
		for (DoubleIndividual c : centroids) {
			
			Centroid cent = (Centroid)c;
			System.out.println("Centroid Initial:\t"+cent.asString());
			_centToErase.add(cent);
			// Treating the items of a cluster as a separate population
			Population auxpop = new Population(cent.getCluster());
			
//			for (DoubleIndividual auxind : auxpop) {
//				System.out.println("Individual"+i+":\t"+auxind.asString());
//			}
			
			kmeans = new Kmeans(auxpop, 5); // We search e.g. 5 clusters in the items of the clusters
			Population auxcentroids = kmeans.cluster();
			
			auxcentroids = UnimodalFunction.reduceNumberOfCentroids(auxcentroids, auxpop); 
			
			i=0;
			for (DoubleIndividual caux : auxcentroids) {
				Centroid centaux = (Centroid)caux;
				centaux.setFitness(Configuration.f.evaluate(caux.getChr().asdouble()));
				_centToAdd.add(centaux);
				//System.out.println("Centroid"+i+"Recomputed:\t"+centaux.asString());
				for (DoubleIndividual cluster : centaux) {
					//Logger.append("F"+Configuration.indexf+"Cluster", "Cluster"+i+":\t"+cluster.asString());
				}
				i++;
			}
		}
		
		centroids.removeAll(_centToErase);
		centroids = new Population(_centToAdd);
		
		filteredPop.clear();
		for (DoubleIndividual c : centroids) {
			Centroid cent = (Centroid) c;
			filteredPop.addAll(cent.getCluster());
		}
		
		//FilterWithThreshold.filter(filteredPop, centroids, Configuration.f.getNoOptima());
		//FilterWithThreshold.deleteBelowThreshold(filteredPop, centroids);
	}
	
	//centroids = UnimodalFunction.reduceNumberOfCentroids(centroids, filteredPop);
	
	FilterWithThreshold.filter(filteredPop, centroids, Configuration.f.getNoOptima());
	FilterWithThreshold.deleteBelowThreshold(filteredPop, centroids);

	//System.out.println("Number of clusters: "+Configuration.f.getNoOptima());
	
	i=0;
	for (DoubleIndividual c : centroids) {
		System.out.println("RecomputedCentroids"+i+":\t\t"+ c.asString());
		Centroid cent = (Centroid) c;
		Population auxpop = new Population(cent.getCluster());
		
		for (DoubleIndividual auxind : auxpop) {
			System.out.println("Individual"+i+":\t"+auxind.asString());
		}
		i++;
	}
	
	
	
	// Step 6: Salient solutions. int n
		// 6.1. So far we have kept a list of centroids each having a list (i.e. cluster) of items. 
		// 6.2. We erase duplicates for every cluster.
		// 6.3. For every cluster, we just keep the best solutions (or alternatively some of the best ones).
		
		ArrayList<DoubleIndividual> salientSolutions = new ArrayList<DoubleIndividual>();
		
		for(DoubleIndividual ic : centroids) {
			Centroid c = (Centroid) ic;
			c.eraseDuplicatesInCluster();
			salientSolutions.addAll(c.bestNinCluster(10));
		}
		
		i=0;
		for (DoubleIndividual ind : salientSolutions) {
			System.out.println("Salient"+i+"\t"+ind.asString());
							
			i++;
		}

	
		
		
		
	// Step 7: Local search	performed over salient solutions
		// We apply MADS (as implemented in NOMAD) as a local search
	/*---------------------------------------------------------------	
	  solutions = new Population(false);
	 

		for(DoubleIndividual ind : filteredPop) {
			// Performs a hill-climber 
			Trajectory trajectory = Nomad.callPyNomadCec2013(ind.getChr().asdouble(), 0.1, 1000);
			DoubleIndividual result = trajectory.getBest();
			result.setFitness(Configuration.f.evaluate(result.getChr().asdouble()));
			// Shows results
			System.out.println("Result:\t\t"+ result.asString());
			//System.out.println("[Trajectory] Elapsed time: "+(System.currentTimeMillis()-start));
			
			
			solutions.add(result);
		}

		Seeds seeds = Configuration.f.how_many_goptima(solutions, 0.01);
		
		System.out.println("Found "+seeds.getCount()+" optima");
	--------------------------------------------------------------------*/	
//		if (numberOfOptima == Configuration.comp.getNoGoptima(Configuration.indexf)) {
//			System.out.println("Success: "+ numberOfOptima + " optima were found");	
//		}else {
//			System.out.println("Failure: "+ numberOfOptima + " out of "+ Configuration.comp.getNoGoptima(Configuration.indexf) +" optima were found");	
//		}
		
	}

	public Population getSolutions() {
		return solutions;
	}
	
	public Population getFilteredPop() {
		return filteredPop;
	}
	
	public static void main(String[] args) {
		//System.out.println(Pattern.quote("["));
		LoadProperties lp = new LoadProperties(args,null);
		Configuration.setConfiguration(lp);
		EFCL efcl = new EFCL();
		Thread t1 =new Thread(efcl);  
		t1.start();		
	}
}
