package multinomad.individuals;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;

import multinomad.config.Configuration;
import multinomad.tools.ClosedInterval;
import multinomad.tools.CommonState;

public class Population implements Iterable<DoubleIndividual>{
	private ArrayList<DoubleIndividual> p;
	
	public Population(boolean initialize) {
		p = new ArrayList<DoubleIndividual>();
		
		if(initialize) {
			for (int i=0;i<Configuration.Psize;i++) {
				DoubleIndividual ind = new DoubleIndividual();
				//ind.setFitness(Configuration.f.evaluate(ind.getChr().asdouble()));
				p.add(ind);
			}
			
			double [] fitnesses = Configuration.f.evaluateAPopulation(this);
			for (int i=0;i<Configuration.Psize;i++) {
				p.get(i).setFitness(fitnesses[i]);
			}	
		}
	}
	
	public Population(ArrayList<DoubleIndividual> individuals) {
		p = individuals;
	}
	
   /**
	 * For Contour
	 */
	public Population() {
		p = new ArrayList<DoubleIndividual>();
		
		double[] x_aux = null;
		if(Configuration.f.getDimension()<2) {
			System.err.println("The function needs at least 2 dimensions");
			System.exit(-1);
		}else if(Configuration.f.getDimension()>2) {
			x_aux = new double[Configuration.f.getDimension()];
			int index = 0;
			for(ClosedInterval.Double i : Configuration.f.getBounds()){
				x_aux[index] = (CommonState.r.nextDouble()*(i.upper-i.lower))+i.lower;
				index++;
			}
			
		}
		
		double lowerD1 = Configuration.f.getBounds().get(0).getLower();
		double upperD1 = Configuration.f.getBounds().get(0).getUpper();

		double lowerD2 = Configuration.f.getBounds().get(1).getLower();
		double upperD2 = Configuration.f.getBounds().get(1).getUpper();
		
		int steps = (int) Math.sqrt(Configuration.Psize);
		double intervalD1 = ((upperD1 - lowerD1)) / (steps-1);
		double intervalD2 = ((upperD2 - lowerD2)) / (steps-1);
			
		
		int indexD1 = 0;
		int indexD2 = -1;
		for (int i=0;i<Configuration.Psize;i++) {

			if ((i%steps)==0) {
				indexD1 = 0;
				indexD2 ++;
			}
			
			double [] x = new double[Configuration.f.getDimension()];
			x[0] = lowerD1 + (indexD1*intervalD1);
			x[1] = lowerD2 + (indexD2*intervalD2);
			
			if(Configuration.f.getDimension()>2) {
				System.arraycopy(x_aux, 2, x, 2, x.length-2);
			}

			indexD1++;
			DoubleIndividual ind = new DoubleIndividual(x);
			ind.setFitness(Configuration.f.evaluate(ind.getChr().asdouble()));
			System.out.println("Contour:\t\t"+ ind.asString());
			p.add(ind);
		}			
	}
	
	
	/**
	 * For ContourSectionHyperplane to create a section of an hyperplane 
	 */
	public Population(int pos1, int pos2, double[] otherparameters) {
		p = new ArrayList<DoubleIndividual>();
		
	
		
		double lowerD1 = Configuration.f.getBounds().get(pos1).getLower();
		double upperD1 = Configuration.f.getBounds().get(pos1).getUpper();

		double lowerD2 = Configuration.f.getBounds().get(pos2).getLower();
		double upperD2 = Configuration.f.getBounds().get(pos2).getUpper();
		
		int steps = (int) Math.sqrt(Configuration.Psize);
		double intervalD1 = ((upperD1 - lowerD1)) / (steps-1);
		double intervalD2 = ((upperD2 - lowerD2)) / (steps-1);
			
		
		int indexD1 = 0;
		int indexD2 = -1;
		for (int i=0;i<Configuration.Psize;i++) {

			if ((i%steps)==0) {
				indexD1 = 0;
				indexD2 ++;
			}
			
			double [] x = new double[Configuration.f.getDimension()];
			System.arraycopy(otherparameters, 0, x, 0, x.length);
			x[pos1] = lowerD1 + (indexD1*intervalD1);
			x[pos2] = lowerD2 + (indexD2*intervalD2);

			indexD1++;
			DoubleIndividual ind = new DoubleIndividual(x);
			ind.setFitness(Configuration.f.evaluate(ind.getChr().asdouble()));
			System.out.println("Contour:\t\t"+ ind.asString());
			p.add(ind);
		}			
	}
	
	public ArrayList<ArrayList<Double>> getPopAsDoubleArrayList(){
		ArrayList<ArrayList<Double>> pop= new ArrayList<ArrayList<Double>>();
		for(DoubleIndividual ind : p) {
			pop.add(ind.getChr().asArrayListDouble());
		}
		return pop;
	}
	
	public ArrayList<DoubleIndividual> getP() {
		return p;
	}
	
	public int size() {
		return p.size();
	}
	
	public boolean remove(DoubleIndividual ind) {
		return p.remove(ind);
	}
	
	public DoubleIndividual remove(int index) {
		return p.remove(index);
	}
	
	public boolean removeAll (ArrayList<DoubleIndividual> list) {
		return p.removeAll(list);
	}
	
	public void clear() {
		p.clear();
	}
	
	public void eraseDuplicates() {
		ArrayList<DoubleIndividual> auxpop = new ArrayList<DoubleIndividual>();
		for(DoubleIndividual ind: p) 
			if(!auxpop.contains(ind))
				auxpop.add(ind);
		
		p = auxpop;
	}
	
	
	
	public void add(DoubleIndividual ind) {
//		boolean noduplicate = true;
//		for (DoubleIndividual i : p) {
//			if (i.equals(ind)) {
//				noduplicate = false;
//				break;
//			}
//		}
//		if (noduplicate)
			p.add(ind);
	}
	
	public void addAll(ArrayList<DoubleIndividual> list) {
		p.addAll(list);
	}
	
	public String asPyNomadString() {
		String str="";
		
		double [] gens = p.get(0).getChr().asdouble();
		str += gens[0];
		for (int i=1;i<gens.length;i++)
			str += ","+gens[i];
		
		for (int j=1;j<p.size();j++) {
			gens = p.get(j).getChr().asdouble();
			str += ":"+gens[0];
			for (int i=1;i<gens.length;i++)
				str += ","+gens[i];
		}
		
		return str;
	}
	
	/**
	 * Clean the population of individuals whose chromosome is NaN
	 */
	
	public void cleanNaN() {
		for (int i=0;i<p.size();i++) {
			if (Double.isNaN(p.get(i).getChr().asdouble()[0])) {
				p.remove(i);
				i--;
			}
			
		}
	}
	
	public void orderPopulation(){
		
		Collections.sort(p, new Comparator<DoubleIndividual>() {
	        @Override
	        public int compare(DoubleIndividual i1, DoubleIndividual i2)
	        {
	        	Double fitnessI1 = new Double(i1.getFitness());
	        	Double fitnessI2 = new Double(i2.getFitness());
	            return  fitnessI2.compareTo(fitnessI1);
	        }
	    });
	}
	
	public boolean contains(DoubleIndividual ind) {
		return p.contains(ind);
	}

	public Iterator<DoubleIndividual> iterator() {
		return p.iterator();
	}

}
