package multinomad.tools.kmeans;

import java.util.Iterator;

import multinomad.config.Configuration;
import multinomad.config.LoadProperties;
import multinomad.individuals.DoubleIndividual;
import multinomad.individuals.Population;
import multinomad.tools.CommonState;
import multinomad.tools.kmeansML.Centroids;

public class Kmeans implements Iterable<DoubleIndividual>{
	
	private Population centroids;
	private Population rawdata;
	private int k;
	
	public Kmeans(Population poprawdata, int numClusters) {
		centroids = new Population(false);
		k = numClusters;
		rawdata = poprawdata;
		initialize();
	}
	
	private void initialize() {
		if(Configuration.kmeansinit.equals(Configuration.KMEANSRANDOM)) {
			randomInit();
		}else if (Configuration.kmeansinit.equals(Configuration.KMEANSPLUS)) {
			kmeansPlusInit();
		}
	}
	
	private void randomInit() {
		for (int i=0;i<k;i++) {
			
			boolean inserted = false;
			while(!inserted) {
				DoubleIndividual ind = rawdata.getP().get(CommonState.r.nextInt(rawdata.size()));
				if(!centroids.contains(ind)) {
					inserted = true;
					Centroid centroid = new Centroid(ind);
					centroids.add(centroid);
				}
			}
		}
	}
	
	//TODO: Implement the initialization of kmeans++
	private void kmeansPlusInit() {
		
	}
	
	/**
	 * 
	 * @return A populatin of centroids after convergence of kmeans
	 */
	public Population cluster() {
		
		boolean changed = true; // change in at least one cluster assignement
		boolean success = true; // all means computed? (no zero-count cluster)
		int maxCount = rawdata.size()*10; // sanity check
		int ct = 0;
		
		
//		System.out.println(centroids.getP().get(0).asString());
		
		while(changed == true && success == true && ct < maxCount) { // tecnically this is Lloyd's algorithm
//			System.out.println("Entro");
			changed = updateClustering(); // (re)assign tuples to clusters
			success = updateMeansLloyd(); // Compute new cluster means if possible
			ct++; // k-means typically converges very quickly
			//System.out.println(centroids.getP().get(0).asString());
			
//			if(changed)
//				System.out.println("Changed");
//			else
//				System.out.println("Not Changed");
//			
//			if(success)
//				System.out.println("Success");
//			else
//				System.out.println("Not Success");
//
//			System.out.println("maxCount: "+maxCount+" ct "+ct);
//			
//			System.out.println("Salgo");
		}
		
		
		return centroids;
	}
	
	private boolean updateClustering() {
		
		boolean changed = false;
		
		for(DoubleIndividual ind : rawdata) {
			double distance = Double.MAX_VALUE;
			int i = 0;
			int index = -1;
			int indexContains = -1;
			for(DoubleIndividual cent : centroids) {
				double distanceToCent =  cent.distance(ind);
				if (distanceToCent < distance) {
					index = i;
					distance = distanceToCent;
				}
				
				if(((Centroid)cent).isInCluster(ind)) {
					indexContains = i;
				}
				i++;
			}
			
			if (index != indexContains) { // That means that the individual has changed from cluster;
				if (indexContains != -1) {
					((Centroid)centroids.getP().get(indexContains)).removeFromCluster(ind);
				}
				((Centroid)centroids.getP().get(index)).addToCluster(ind);
				changed = true;
			}
		}
		
		return changed;
	}
	
	private boolean updateMeans() {
		if(Configuration.kmeansconvergence.equals(Configuration.KMEANSLLOYD)) {
			return updateMeansLloyd();
		}else if (Configuration.kmeansinit.equals(Configuration.KMEANSBIASED)) {
			return updateMeansBiased();
		}else
			return false;
	}
	
	private boolean updateMeansLloyd() {
		
		for(DoubleIndividual c : centroids) {
			Centroid cent = (Centroid)c;
			if (cent.clusterSize() == 0) {
				//System.out.println("EOOOO");
				//return false;
			}
			
			double[] newMean = initVectorToZero(cent.getChr().getLength());
			for(DoubleIndividual itemInCluster : cent) {
				newMean = sumV1toV2(newMean, itemInCluster.getChr().asdouble());
			}
			newMean = divideVectorByValue(newMean, cent.clusterSize());
			cent.updateMean(newMean);
		}
		
		return true;
	}
	
	private double[] initVectorToZero(int length) {
		double[] vector = new double[length];
		for(int i=0;i<vector.length;i++)
			vector[i] = 0;
		return vector;
	}
	
	private double[] sumV1toV2(double[] v1,double[] v2) {
		double[] vector = initVectorToZero(v1.length);
		for(int i=0;i<v1.length;i++)
			vector[i] = v1[i] + v2[i];
		return vector;
	}
	
	private double[] divideVectorByValue(double[] v1,double value) {
		double[] vector = initVectorToZero(v1.length);
		for(int i=0;i<v1.length;i++)
			vector[i] = v1[i] / value;
		return vector;
	}
	
	private double[] multiplyVectorByValue(double[] v1,double value) {
		double[] vector = initVectorToZero(v1.length);
		for(int i=0;i<v1.length;i++)
			vector[i] = v1[i] * value;
		return vector;
	}
	
	private boolean updateMeansBiased() {
		for(DoubleIndividual c : centroids) {
			Centroid cent = (Centroid)c;
			if (cent.clusterSize() == 0) {
				return false;
			}
			
			double minfitness = 1000000;
			double maxfitness = -1000000;
			for(DoubleIndividual itemInCluster : cent) {
				double fitness = itemInCluster.getFitness();
				if(fitness > maxfitness) {
					maxfitness = fitness;
				}
				if(fitness < minfitness) {
					minfitness = fitness;
				}
			}
			
			double[] newMean = initVectorToZero(cent.getChr().getLength());
			double sumFitness = 0;
			for(DoubleIndividual itemInCluster : cent) {
				double normFitness = normalizedFitness(itemInCluster.getFitness(), minfitness, maxfitness);
				sumFitness += normFitness;
				double [] biasedVector = multiplyVectorByValue(itemInCluster.getChr().asdouble(), normFitness);
				newMean = sumV1toV2(newMean, biasedVector);
			}
			newMean = divideVectorByValue(newMean, sumFitness);
			cent.updateMean(newMean);
		}
		
		return true;
	}
	
	private double normalizedFitness (double fitness, double min, double max) {
		return (fitness-min)/(max-min);
	}
	

	
	public Iterator<DoubleIndividual> iterator() {
		return centroids.iterator();
	}
	
	public static void main(String[] args) {
		LoadProperties lp = new LoadProperties(args,null);
		Configuration.setConfiguration(lp);
		
		double[] a= {65,220};
		double[] b= {67,220};
		double[] c= {68,230};
		double[] d= {70,220};
		double[] e= {66,210};	
		double[] f= {59,110};
		double[] g= {61,120};
		double[] h= {62,130};
		double[] i= {61,115};
		
		Population pop = new Population(false);
		
		pop.add(new DoubleIndividual(a));
		pop.add(new DoubleIndividual(b));
		pop.add(new DoubleIndividual(c));
		pop.add(new DoubleIndividual(d));
		pop.add(new DoubleIndividual(e));
		pop.add(new DoubleIndividual(f));
		pop.add(new DoubleIndividual(g));
		pop.add(new DoubleIndividual(h));
		pop.add(new DoubleIndividual(i));
		
		Kmeans kmeans = new Kmeans(pop, 2);
		
		Population centroids = kmeans.cluster();
		
	}

}
