package garobo;
import robocode.*;
import robocode.control.*;
import java.io.*;
import java.util.*;

public class BrainWorld 
    implements RobocodeListener {

    /* 1. CONSTRUCTOR AND HELPERS *********************************/

    /* experiment file format 
       Robocode Location
       Storage_directory
       population_size
       elitism
       crossover
       copy
       base_mutation
       num_fights
       enemy1
       enemy2 
       ...
    */

    public BrainWorld (String setupfile) throws Exception {
	genome_size = num_events * num_boxes * (input_bits * 2 + func_bits);

	BufferedReader in = new BufferedReader (new FileReader (setupfile));
	robocode_location = in.readLine();

	genome_filename = robocode_location+
	    "/robots/sample/SmallBrain.data/genome.dat";
	
	score_coordinator = new Coordinator ();
	engine_coordinator = new Coordinator ();
	re = new RobocodeEngine 
	    (new File (robocode_location), this);
	engine_coordinator.put(0);

        storage_directory = new File (in.readLine());
	if (! storage_directory.isDirectory()){ wrongUsage(); }      
	else {
	    current_generation = storage_directory.listFiles().length;
	}
	
	System.out.println ("CURGEN: "+current_generation);

	int population_size = Integer.parseInt (in.readLine());
	ga = new GeneticAlgorithm (population_size, genome_size);
	if (current_generation > 0){
	    File popfile = storage_directory.listFiles()[current_generation-1];
	    DataInputStream din = new DataInputStream (new FileInputStream (popfile));
	    System.out.println ("POPFILE: "+popfile.getName());
	    population_size = din.readInt ();
	    ga.genome_size = din.readInt ();
	    ga.best_index = din.readInt();
	    ga.best_fitness = din.readDouble();
	    ga.mean_fitness = din.readDouble();
	    ga.worst_fitness = din.readDouble();
	    ga.mutation = din.readDouble();
	    ga.crossover = din.readDouble();
	    ga.copy = din.readDouble();
	    ga.elitism = din.readDouble();
	    ga.scaling_factor = din.readDouble();
	    for (int i = 0; i < ga.population_size; i++){
		StringBuffer cur_genome = new StringBuffer();
		    for (int j = 0; j < genome_size; j++){
			cur_genome.append (din.readChar());
		    }
		    ga.addGuy (i, new String (cur_genome), din.readDouble());
	    }
	}

	ga.elitism = Double.parseDouble (in.readLine());
	ga.crossover = Double.parseDouble (in.readLine());
	ga.copy = Double.parseDouble (in.readLine());
	base_mutation = Double.parseDouble (in.readLine()) / (double) genome_size;
	num_fights = Integer.parseInt (in.readLine());
	combatants = new Vector();
	while (true){
	    String curbadguy = in.readLine();
	    if (curbadguy != null){
		combatants.addElement (curbadguy);
	    }
	    else break;
	}
	if (combatants.size() == 0) {
	    RobotSpecification tournaSpecs[] = new RobotSpecification[2];
	    tournaSpecs[0] = makeRSpec ("sample.TournaBot1");
	    tournaSpecs[1] = makeRSpec ("sample.TournaBot2");
	    
	    tourney_spec = 
		new BattleSpecification 
		    (1,
		     new BattlefieldSpecification (800, 600),
		     tournaSpecs);
	}
	else {
	    rspecs = new RobotSpecification[combatants.size()];
	    for (int i=0; i < combatants.size(); i++){
		rspecs[i] = makeRSpec ((String)combatants.elementAt(i));
	    }
	    tourney_spec = null;
	}
	starting_points = new double[num_fights][6];
	makeStartingPoints();

	if (current_generation == 0){
	    ga.initPop(genome_size);
	    evaluateAll();
	}

	System.out.println ("Storage: "+storage_directory);
	System.out.println ("Population Size: "+population_size);
	System.out.println ("Elitism: "+ ga.elitism);
	System.out.println ("Crossover: "+ga.crossover);
	System.out.println ("Copy: "+ga.copy);
	System.out.println ("Base mutation: "+base_mutation);
	System.out.println ("Number of battles: "+num_fights);
	System.out.println ("Number of combatants: "+combatants.size());
    }


    protected RobotSpecification makeRSpec (String robot1){
	engine_coordinator.get();
	RobotSpecification allbots[] = re.getLocalRepository();
	engine_coordinator.put(0);
	for (int i = 0; i < allbots.length; i++){
	    if (allbots[i].getClassName().equals (robot1)){
		return allbots[i];
    	    }
	}
        return null;
    }

    public void makeStartingPoints(){
	if (num_fights > 1){ //then we're using random starting points
	    for (int i = 0; i < num_fights; i++){
		starting_points[i][0] = 50f + 350f * Math.random();
		starting_points[i][1] = 50f + 550f * Math.random();
		starting_points[i][2] = Math.PI * 2f * Math.random();
		starting_points[i][3] = 400f + 350f * Math.random();
		starting_points[i][4] = 50f + 550f * Math.random();
		starting_points[i][5] = Math.PI * 2f * Math.random();
	    }
	}
    }


    /* RobocodeListener Stuff ***********************************************/

    public void battleAborted (BattleSpecification battle){}
    public void battleComplete
	(BattleSpecification battle, RobotResults[] results) {
	engine_coordinator.put(1);
	if (results.length == 0){
	    System.out.println ("ERROR: NO RESULTS");
	    System.exit(0);
	       
	}
	//System.out.print (score_coordinator.count+": ");
       	for (int i = 0; i < results.length; i++){
	    //System.out.print (results[i].getRobot().getClassName());
	    //System.out.print ("\t"+results[i].getScore()+"\t");
	    if (results[i].getRobot().getClassName().equals 
		("sample.SmallBrain")){
		recent_score = results[i].getScore();
	    }
	    else {
		other_score = results[i].getScore();
	    }
	}
	//System.out.println();
	/* for tournament selection */
	score_coordinator.put(1);
    }
    public void battleMessage(String message) {}

    /* GENETIC ALGORITHMS STUFF ********************************************/

    /* scores a single genome */
    public double evaluate (String genome){
	return evaluate (genome, false)[0];
    }
    public double[] evaluate (String genome, boolean visible){
	double to_return[] = new double[rspecs.length+1];
	engine_coordinator.get();
	printGenome (genome_filename, genome);
	re.setVisible (visible);
	engine_coordinator.put(0);
	double output=0;
	for (int i = 1; i < rspecs.length; i++){
	    if (re == null){
		System.err.println ("NULL ROBOENGINE");
		System.exit(0);
	    }
	    RobotSpecification roboSpecs[] = new RobotSpecification[2];
	    roboSpecs[0] = rspecs[0];
	    roboSpecs[1] = rspecs[i];
	    
	    BattleSpecification current = new BattleSpecification 
		(1, 
		 new BattlefieldSpecification (800, 600),
		 roboSpecs);
	    if (((String)combatants.elementAt(i)).length() > 13){
		System.out.print (((String)(combatants.elementAt(i))).substring(0,13));
	    }
	    else {
		System.out.print (combatants.elementAt(i));
	    }
	    double result = doBattle (current);
	    to_return[0] += result;
	    to_return[i] = result;
	}
	if (num_fights > 1 || rspecs.length > 2){
	    System.out.println ("----- total: "+to_return[0]+" ----------");
	}
	return to_return;
    }    

    public double doBattle (BattleSpecification current){
	double output = 0;
	int num_wins, num_losses;
	double avg_score, avg_other;
	num_wins = num_losses = 0;
	avg_score=avg_other = 0.0;
	if (num_fights == 1) {
	    engine_coordinator.get();
	    re.runBattle (current);
	    score_coordinator.get();
	    output += recent_score / 10f;
	    output += (300 - other_score) / 50;
	    if (recent_score > other_score) output += 5;
	}
	else {
	    for (int j = 0; j < num_fights; j++){
		engine_coordinator.get();
		re.runBattle (current,starting_points[j]);
		score_coordinator.get();
		output += recent_score / (num_fights * rspecs.length);
		output += (300 - other_score) / (100*num_fights*
						 rspecs.length);
		if (recent_score > other_score) num_wins++;
		else num_losses++;
		avg_score += recent_score;
		avg_other += other_score;
	    }
	    avg_score /= (double) num_fights;
	    avg_other /= (double) num_fights;
	    System.out.println ("\t"+output+
				"\t"+avg_score+
				"\t"+avg_other+
				"\t"+num_wins+
				"\t"+num_losses);
	}
	return output;
    }

    /* prints the genome to be interpreted by the robots */
    public void printGenome (String filename, String genome){
	try {
	    FileOutputStream fout = new FileOutputStream (filename);
	    PrintStream dout = new PrintStream (fout);
	    dout.println (genome);
	    dout.close();
	    fout.close();
	}
	catch (Exception e){
	    System.err.println ("Could not print genome");
	    e.printStackTrace();
	}
    }

    //strange robocode security forces all file i/o operations to be entirely
    //within roboworld.java.  Hence I override evaluateAll and newGeneration 

    /* returns the best fitness */
    protected void evaluateAll (){
	ga.best_fitness = Double.MIN_VALUE;
	ga.worst_fitness = Double.MAX_VALUE;
	ga.best_index = -1;
	ga.mean_fitness = 0;
	makeStartingPoints();
	double[][] all_results = new double[ga.population_size][rspecs.length];
	double[] all_means = new double[rspecs.length];
	double[] all_stdevs = new double[rspecs.length];
	for (int i = 0; i < ga.population_size; i++){
	    double[] returned_results = evaluate (ga.population[i].genome, false);
	    for (int j = 0; j < rspecs.length; j++){
		all_results[i][j] = returned_results[j];
		all_means[j] = returned_results[j] / 
		    (double) ga.population_size;
	    }
	}
	
	for (int j = 0; j < rspecs.length; j++){
	    for (int i = 0; i < ga.population_size; i++){
		all_stdevs[j] += 
		    Math.pow (all_results[i][j] - all_means[j], 2.0);
	    }
	    all_stdevs[j] = Math.sqrt (all_stdevs[j] / (ga.population_size - 1));
	}

	for (int i = 0; i < ga.population_size; i++){
	    ga.population[i].fitness = 0;
	    for (int j = 0; j < rspecs.length; j++){
		ga.population[i].fitness += 
		    Math.pow (2.0, (all_results[i][j] - all_means[j]) / all_stdevs[j]);  
	    }
	    ga.mean_fitness += ga.population[i].fitness;
	    if (ga.population[i].fitness > ga.best_fitness) {
		ga.best_fitness = ga.population[i].fitness;
		ga.best_index = i;
	    }
	    if (ga.population[i].fitness < ga.worst_fitness){
		ga.worst_fitness = ga.population[i].fitness;
	    }
	}
	ga.mean_fitness /= ga.population_size;
	ga.scaleAll(ga.scaling_factor);
    }

    /* standard new generation for GA */
    public void newGeneration (){
	//high mutation if the mean is close to the best
	//low mutation if the mean is close to the worst
	ga.mutation = base_mutation * Math.pow ((ga.mean_fitness)/
			      (ga.best_fitness), 2);
	
	System.out.println (ga.mutation);

	String elite_pop[] = ga.elitism (ga.population, ga.elitism);
	String copy_pop[] = ga.copy (ga.population, ga.copy);
	String cross_pop[] = ga.crossover (ga.population, ga.crossover);
	copy_pop = ga.mutate (copy_pop, ga.mutation);
	cross_pop = ga.mutate (cross_pop, ga.mutation);

	for (int i = 0; i < elite_pop.length; i++){
	    ga.population[i].genome = elite_pop[i];
	}
	for (int i = 0; i < copy_pop.length; i++){
	    ga.population[i+elite_pop.length].genome = copy_pop[i];
	}
	for (int i = 0; i < cross_pop.length; i++){
	    ga.population[i+elite_pop.length+copy_pop.length].genome = cross_pop[i];
	}
	//evaluate after the generation so that the correct fitnesses
	//get printed out
	current_generation++;
	evaluateAll();
    }

    /* MAIN ******************************************************************/

      public static void main (java.lang.String[] argv) {
	int num_gens=0;
	int individual = -1;
	boolean demo = false;
	boolean show_stats = false;
	boolean print_genome = false;
	boolean evaluate = false;
	BrainWorld bw = null;
	try {
	    bw = new BrainWorld (argv[0]);
	}
	catch (Exception e){ e.printStackTrace(); wrongUsage(); }
	for (int i = 1; i < argv.length; i++){
	    if (argv[i].equals ("-train")){
		try {num_gens = Integer.parseInt (argv[i+1]);}
		catch (NumberFormatException e) { wrongUsage();}
	    }
	    if (argv[i].equals ("-setupfile")){
		if (bw != null) wrongUsage();
		try {bw = new BrainWorld (argv[i+1]);}
		catch (Exception e){wrongUsage();}
	    }
	    if (argv[i].equals ("-demo")){
		demo = true;
		try {individual = Integer.parseInt (argv[i+1]);}
		catch (NumberFormatException e){wrongUsage();}
	    }
	    if (argv[i].equals ("-stats")) show_stats = true;
	    if (argv[i].equals ("-printgenome")){
		print_genome = true;
		try {individual = Integer.parseInt (argv[i+1]);}
		catch (Exception e){wrongUsage();}
	    }
	    if (argv[i].equals ("-eval")){
		evaluate = true;
		try {individual = Integer.parseInt (argv[i+1]);}
		catch (NumberFormatException e){wrongUsage();}
	    }
	    if (argv[i].equals ("?") || argv[i].equals ("-help")) wrongUsage();
	}
	if (bw == null) wrongUsage();
	if (num_gens > 0){
	    for (int i = 0; i < num_gens; i++){
		bw.newGeneration();
		bw.printPop ();
		System.out.println ("Best: "+bw.ga.best_fitness+
				    "Average: "+bw.ga.mean_fitness);
	    }
	    while (true){
		bw.engine_coordinator.get();
		if (bw.engine_coordinator.count >= 
		    bw.ga.population_size * (num_gens)){
		    bw.engine_coordinator.put(0);
		    break;
		}
		else {
		    System.out.println 
			("not done yet"+bw.engine_coordinator.count);
		    bw.engine_coordinator.put(0);
		}
	    }
	}
	if (demo || evaluate){
	    bw.evaluate (bw.ga.population[individual].genome, false);
	}
	if (demo) bw.evaluate (bw.ga.population[individual].genome, true);
	if (show_stats){
	    for (int i = 0; i < bw.ga.population.length; i++){
		System.out.println ("Genome "+i+": "+
				    bw.ga.population[i].fitness);
	    }
	}
	if (print_genome) System.out.println (bw.ga.population[individual].genome);
      }
    
    protected static void wrongUsage (){
	System.err.println ("usage: BrainWorld ([-option] [val])*");
	System.err.println ("\t-train <N>\t\tTrain for N generations");
	System.err.println ("\t-popsize <N>\t\tInitialize with a population of N");
	System.err.println ("\t-popfile <file>\t\tInitialize from a population file");
	System.err.println ("\t-demo <N>\t\tDemo an individual");
	System.err.println ("\t-eval <N>\t\tEvaluate an individual");
	System.err.println ("\t-outfile <file>\t\tOutput the population to <file>"); 
	System.err.println ("\t-stats \t\tShows stats for a population");
	System.err.println ("\t-printgenome <N>\t\tPrint an individual's genome");
	System.err.println ("\t-help\t\tPrint this message");
	System.exit(0);
    }

    class Coordinator {
	boolean available;
	int count=0;
	public Coordinator (){ available = false; }
	public synchronized void get() {
	    while (available == false) {
		try { wait(); } catch (InterruptedException e) {}
            }
            available = false;
            notifyAll();
	}
	public synchronized void put(int val) {
	    while (available == true) {
		try { wait(); } catch (InterruptedException e) {}
            }
            available = true;
            count += val;
	    notifyAll();
	}
	
    }

    public void printPop(){
	String fname = "generation";
	for (int i = 10000; i > 1; i /= 10){
	    if (current_generation < i){
		fname+="0";
	    }
	}
	fname+=current_generation;
	try {
	    //	    System.err.println ("** PRINTING POPULATION **");
	    DataOutputStream dout = 
		new DataOutputStream 
		(new FileOutputStream (new File (storage_directory, fname)));
	    dout.writeInt (ga.population_size);
	    dout.writeInt (ga.genome_size);
	    dout.writeInt (ga.best_index);
	    dout.writeDouble (ga.best_fitness);
	    dout.writeDouble (ga.mean_fitness);
	    dout.writeDouble (ga.worst_fitness);
	    dout.writeDouble (ga.mutation);
	    dout.writeDouble (ga.crossover);
	    dout.writeDouble (ga.copy);
	    dout.writeDouble (ga.elitism);
	    dout.writeDouble (ga.scaling_factor);
	    for (int i = 0; i < ga.population.length; i++){
		GeneticAlgorithm.GFPair current = ga.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();
	}
	
    }
    public static int func_bits = 4;
    //public static int system_inputs = 18;
    public static int num_boxes = 44;
    public static int input_bits = 6;
    public static int num_events = 4; //main and onscannedrobot
    public int genome_size;
    
    public GeneticAlgorithm ga;

    public int num_fights;
    public double starting_points[][];
    public Vector combatants;
    
    public File storage_directory;
    public int current_generation=0;

    public String resultsFilename,battleFilename;
    public double recent_score, other_score;

    public double base_mutation;
    
    public RobotSpecification rspecs[];
    public BattleSpecification tourney_spec;
    public RobocodeEngine re;

    Coordinator engine_coordinator, score_coordinator;

    /*    public static final String robocode_location = 
	"/home/ai2/jacobe/research/robocode";
    */
    /*    public static final String robocode_location = 
	"C:\\cygwin\\home\\jacob\\research\\robocode";
    */
    /*    public static final String robocode_location = 
	"D:\\root\\robocode";
    */
    public String robocode_location;
    public String genome_filename, tourney_filename1, tourney_filename2; 

    

}
