import java.util.*; // Needed for Random

/**
 * <b>Weasel</b> - A Java implimentation of Richard Dawkins's
 * Weasel algorithm as described in the book: The Blind Watchmaker.
*/
public class Weasel {
  /**
   * <b>main(args)</b> is the program invoked by running `java Weasel`.
  */
  public static void main(String[] args) {
    // Introduce self and print syntax.
    System.out.println("Welcome to the Java Weasel.");
    System.out.println("\n" +
      "Syntax:\n" + "  java Weasel " +
      "<number of children> <mutation rate> \"<target string>\"\n" +
      "    <number of children> must be a whole number.\n" +
      "    <mutation rate> must be be a decimal number less than 1.\n" +
      "    <target string> should only contain letters and spaces.\n" +
      "\n" + "Example:\n" +
      "  java Weasel 30 0.07 \"METHINKS IT IS LIKE A WEASEL\"\n");

    // Too many arguments? Can't we be peaceful?
    if( args.length > 3 ) {
      System.out.println("This program takes a max of 3 arguments. Did you forget to wrap your target string in quotes?");
      System.exit(3);
    }

    // Parse the arguments.
    for(String arg: args) {
      arg = arg.toUpperCase();

      // Does the user just want syntax?
      if( isHelpRequest(arg) ) { System.exit(0); }

      if( arg.matches("^[0-9]+$") ) {
        NUMBER_OFFSPRING = Integer.parseInt(arg);
      } else if( arg.matches("^[0]*.[0-9]+$") ) {
        MUTATION_RATE = Double.parseDouble(arg);
      } else if( arg.matches("^[" + GENE_SPACE + "]+$") ) {
        TARGET = arg;
      } else {
        System.out.println("Argument [" + arg + "] has no known purpose.");
      }
    }

    // Run Weasel.
    runWeasel();

    // End Program.
  }

  public static boolean isHelpRequest(String arg) {
    boolean syntaxOnly = false;
    if( arg.equals("-H") ) { syntaxOnly = true; }
    if( arg.equals("-HELP") ) { syntaxOnly = true; }
    if( arg.equals("--HELP") ) { syntaxOnly = true; }
    if( arg.equals("--?") ) { syntaxOnly = true; }
    if( arg.equals("-?") ) { syntaxOnly = true; }
    if( arg.equals("/H") ) { syntaxOnly = true; }
    if( arg.equals("/HELP") ) { syntaxOnly = true; }
    if( arg.equals("/?") ) { syntaxOnly = true; }
    return syntaxOnly;
  }

  // Target genome String
  public static String TARGET = "METHINKS IT IS LIKE A WEASEL";

  // Possible genes (characters) in the genome (string). (Space included!)
  public static String GENE_SPACE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ ";

  // Rate at which a gene can mutate into another.
  public static double MUTATION_RATE = 0.07;

  // Number of children per Weasel.
  public static int NUMBER_OFFSPRING = 30;

  /**
   * <b>runWeasel</b> is the main program loop for use once
   * the static parameters have been set.
  */
  public static void runWeasel() {
    System.out.println("Starting...\n");

    Organism theParent = new Organism( TARGET.length() );
    Organism[] offspring = new Organism[NUMBER_OFFSPRING];
    int generations = 1;
    int bestFitness = theParent.fitness();

    System.out.println("generation: best fitness: best fitting genome");
    System.out.println(generations + " : " + bestFitness + " : " + theParent.getGenome() );

    // Create generations until we match the target.
    while( bestFitness < TARGET.length() ) {
      generations++;

      // For this generation, create offspring.
      for(int i=0; i<NUMBER_OFFSPRING; i++) {
        offspring[i] = new Organism( theParent.getGenome() );
      }

      // Find the best offspring to breed from.
      theParent = offspring[0]; // This will be replaced if 0 isn't the best.
      bestFitness = offspring[0].fitness();
      for(int i=1; i<NUMBER_OFFSPRING; i++) {
        if( offspring[i].fitness() > bestFitness ) {
          theParent = offspring[i];
          bestFitness = offspring[i].fitness();
        }
      }

      // Report our "progress"
      System.out.println(generations + " : " + bestFitness + " : " + theParent.getGenome() );
    }

    System.out.println("\nTarget genome achieved in " + generations + " generations.");
  }
} // Weasel Class -- Main Program.



/**
 * <b>Organism</b> is an individual parent/child.
*/
class Organism {
  // The 'genome' of this organism.
  private String genome = "";

  /**
   * <b>Organism(int)</b> creates a new organism with a completely
   * random genome of length int.
   *
   * @param genomeLength  Length of Organism's genome.
  */
  Organism(int genomeLength) {
    for(int i=0; i<genomeLength; i++) {
      genome += randomGene();
    }
  }

  /**
   * <b>Organism(String)</b> creates a new organism by random mutation
   * of an input genome String.
   *
   * @param parentGenome  The parent genome string to be mutated from.
  */
  Organism(String parentGenome) {
    // Check each gene to see if it will mutate.
    for(int i=0; i<parentGenome.length(); i++) {
      // Check this gene for mutation
      if( Weasel.MUTATION_RATE >= Math.random() ) {
        // Gene is mutating.
        genome += randomGene();
      } else {
        // Gene remains unchanged.
        genome += parentGenome.charAt(i);
      }
    }
  }

  public String getGenome() { return genome; }

  public int fitness() {
    // Different length genomes don't match, period.
    if( Weasel.TARGET.length() != genome.length() ) {
      System.out.println("Genome size (" + genome.length() + ") is not the same as target length (" + Weasel.TARGET.length() + ")! What did you do? Bad user, bad! There are too many ways to compare genomes of different lengths. If you want to do this, program it yourself and remove this error...or leave it and wonder why your code doesn't work.");
      System.exit(1);
    }

    // Compare each character, one at a time. If they match, increase
    // the fitness score by 1.
    int fitnessScore = 0;
    for(int i=0; i<Weasel.TARGET.length(); i++) {
      if( Weasel.TARGET.charAt(i) == genome.charAt(i) ) {
        fitnessScore++;
      }
    }

    // How well does this Organism's genome fit?
    return fitnessScore;
  }

  public static char randomGene() {
    int randIdx = (int)Math.floor( Math.random() * Weasel.GENE_SPACE.length() );
    return Weasel.GENE_SPACE.charAt( randIdx );
  }
}

// EOF
