package garobo;

//implements only tournament selection

import java.lang.Math;
import java.util.Arrays;
import java.io.*;
import java.text.DecimalFormat;

public abstract class GeneticAlgorithm {
    public int population_size;
    public int genome_size;
    public GFPair population[];
    public int best_index;
    public double best_fitness = Double.MIN_VALUE;
    public double mutation = 0.03;
    public double crossover = 0.9;
    public double copy = 0.1;
    public int tourney_size = 3;

    public GeneticAlgorithm (int p_popsize, int p_gensize){
	population_size = p_popsize;
	genome_size = p_gensize;
	population = new GFPair [population_size];
	mutation = 2f / genome_size;
    }    

    public GeneticAlgorithm (File fname){
	try {
	    DataInputStream din = 
		new DataInputStream 
		(new FileInputStream (fname));
	    population_size = din.readInt ();
	    //System.out.println ("POP SIZE: "+population_size);
	    genome_size = din.readInt ();
	    //System.out.println ("GENOME SIZE: "+population_size);
	    best_index = din.readInt();
	    //System.out.println ("BEST INDEX: "+population_size);
	    best_fitness = din.readDouble();
	    //System.out.println ("BEST FITNESS: "+population_size);
	    //mean_fitness = din.readDouble();
	    //System.out.println ("MEAN FITNESS: "+population_size);
	    //worst_fitness = din.readDouble();
	    //System.out.println ("POP SIZE: "+population_size);
	    mutation = din.readDouble();
	    crossover = din.readDouble();
	    copy = din.readDouble();
	    //elitism = din.readDouble();
	    //scaling_factor = din.readDouble();
	    population = new GFPair [population_size];
	    for (int i = 0; i < population_size; i++){
		StringBuffer cur_genome = new StringBuffer();
		for (int j = 0; j < genome_size; j++){
		    cur_genome.append (din.readChar());
		}
		population[i] = new GFPair(new String(cur_genome), 
					   din.readDouble());
	    }
	}
	catch (Exception e){
	    System.err.println ("Problem reading in population");
	    e.printStackTrace();
	    population_size = 0;
	    genome_size = 0;
	    population = null;
	}
    }

    //    abstract public double evaluate (String genome);

    public class GFPair implements Comparable {
      public String genome;
      public double fitness;
      public GFPair (String p_g, double p_f){
	genome = p_g;
	    fitness = p_f;
      }
	public int compareTo (Object o) throws ClassCastException {
	    GFPair other = (GFPair) o;
	    if (fitness == other.fitness){
		return 0;
	    }
	    if (fitness > other.fitness){
		return -1;
	    }
	    return 1;
	}
    }

    public abstract double evaluate (String genome);

    public static String mutate (String genome, double p_mutation){
	StringBuffer genome_b = new StringBuffer (genome);
	for (int point = 0; point < genome.length(); point++){
	    if (Math.random() < p_mutation){
		//	int point = (int) (genome.length() * Math.random());
		if (genome_b.charAt (point) == '1'){
		    genome_b.setCharAt(point, '0');
		}
		else {
		    genome_b.setCharAt (point, '1');
		}
	    }
	}
	return new String (genome_b);
    }

    public String[] mutate (String population[], double p_mutation){
	String output[] = new String[population.length];
	for (int i = 0; i < output.length; i++){
	    output[i] = mutate (population[i], p_mutation);
	}
	return output;
    }

    public static String crossover (String g1, String g2){
	int num_points = (int) Math.round (Math.random() * 4f);
	// implement multipoint crossover later
	int point = (int) (g1.length() * Math.random());
	return g1.substring (0, point) + g2.substring (point);
    }
    
    public void initPop (int genome_size){
	for (int i = 0; i < population_size; i++){
            StringBuffer current = new StringBuffer ();
	    for (int j = 0; j < genome_size; j++){
		if (Math.random() > 0.5){
		    current.append ("1");
		}
		else current.append ("0");
	    }
	    population[i] = new GFPair (new String (current), -1);
	}
	evaluateAll();
    }

  public void evaluateAll(){
    best_fitness = 0;
    best_index = -1;
    for (int i = 0; i < population.length; i++){
      population[i].fitness = evaluate (population[i].genome);
      if (population[i].fitness > best_fitness){
	best_fitness = population[i].fitness;
	best_index = i;
      }
      //      System.out.print (df.format(population[i].fitness)+": ");
    }
  }

    public String[] elitism (GFPair population[], double elitism){
	Arrays.sort (population);
	String output[] = new String [(int)(elitism * population.length) ];
	for (int i = 0; i < output.length; i++){
	    output[i] = population[i].genome;
	}
	//System.out.println ("ELITISM POP: "+output.length);
	return output;
    }

    public String[] copy (){
	String output[] = new String [(int)(copy * population.length) ];
	for (int i = 0; i < output.length; i++){
	    output[i] = tourneySelect ();
	}
	//System.out.println ("COPY POP: "+output.length);
	return output;
    }

    public String[] crossover (){
	String output[] = new String [(int)(crossover * population.length) ];
	for (int i = 0; i < output.length; i++){
	    String p1 = tourneySelect ();
	    String p2 = tourneySelect ();
	    output[i] = crossover (p1, p2);
	}
	//	System.out.println ("CROSSOVER POP: "+output.length);
	return output;
    }

    public String tourneySelect (){
	double best_fit = -1;
	//	int best_guy = (int) (Math.random() * population.length);
	int best_guy = -1;
	for (int i = 0; i < tourney_size; i++){
	    int cur_guy = (int) (Math.random() * population.length);
	    /*if (population[cur_guy].needs_update){
		population[cur_guy].fitness = evaluate (population[cur_guy].genome);
		population[cur_guy].needs_update = false;
	    } */
	    if (population[cur_guy].fitness > best_fit){
		best_fit = population[cur_guy].fitness;
		best_guy = cur_guy;
	    }
	}
	if (best_guy == -1) {
	    System.out.println ("no fitness above zero!");
	    return tourneySelect();
	}
	return population[best_guy].genome;
    }
    
    public void newGeneration(){
      best_fitness = 0;
      /*for (int i = 0; i < population.length; i++){
	    population[i].needs_update = true;
	    }*/
	String copy_pop[] = copy ();
	String cross_pop[] = crossover ();
	copy_pop = mutate (copy_pop, mutation);
	cross_pop = mutate (cross_pop, mutation);

	for (int i = 0; i < copy_pop.length; i++){
	    population[i].genome = copy_pop[i];
	}
	for (int i = 0; i < cross_pop.length; i++){
	    population[i].genome = cross_pop[i];
	}
	evaluateAll();
    }

    public void printPop (String fname){
	try {
	    //	    System.err.println ("** PRINTING POPULATION **");
	    DataOutputStream dout = 
		new DataOutputStream 
		(new FileOutputStream (fname));
	    dout.writeInt (population_size);
	    dout.writeInt (genome_size);
	    dout.writeInt (best_index);
	    dout.writeDouble (best_fitness);
	    //dout.writeDouble (mean_fitness);
	    //dout.writeDouble (worst_fitness);
	    dout.writeDouble (mutation);
	    dout.writeDouble (crossover);
	    dout.writeDouble (copy);
	    //	    dout.writeDouble (elitism);
	    //dout.writeDouble (scaling_factor);
	    for (int i = 0; i < population.length; i++){
		GFPair current = population[i];
		dout.writeChars (current.genome);
		dout.writeDouble (current.fitness);
	    }
	    dout.close();
	    //	    System.err.println ("ALL DONE");
	}
	catch (Exception e){
	    System.err.println ("File output error while writing population.");
	    e.printStackTrace();
	}
    }
}
