package geneticAlgorithm;

import java.io.*;
import java.lang.Math;

class Function implements Functionable, java.io.Serializable {
  private final double score = 0.0;
  private final int nodes = 10;

  // adjacent matrix
  private static int mat[][] =
  {{0, 40, 5, 16, 43, 98, 97, 73, 11, 72, 78, 47, 7, 15, 67, 66, 94, 77, 73, 48 },
   {40, 0, 93, 11, 91, 25, 25, 65, 73, 89, 95, 49, 23, 94, 84, 6, 5, 90, 18, 63 },
   {5, 93, 0, 0, 37, 84, 83, 5, 77, 11, 35, 20, 80, 58, 63, 25, 8, 52, 95, 71 },
   {16, 11, 0, 0, 43, 39, 49, 6, 44, 67, 98, 50, 87, 83, 4, 11, 26, 74, 18, 87 },
   {43, 91, 37, 43, 0, 1, 9, 0, 12, 71, 12, 65, 54, 18, 44, 2, 67, 22, 87, 28 },
   {98, 25, 84, 39, 1, 0, 39, 32, 96, 98, 23, 98, 35, 71, 7, 52, 10, 42, 43, 30 },
   {97, 25, 83, 49, 9, 39, 0, 7, 66, 82, 26, 61, 56, 33, 86, 4, 29, 19, 93, 99 },
   {73, 65, 5, 6, 0, 32, 7, 0, 36, 29, 13, 24, 74, 48, 86, 45, 17, 62, 61, 8 },
   {11, 73, 77, 44, 12, 96, 66, 36, 0, 35, 16, 61, 7, 31, 99, 48, 81, 96, 54, 40 },
   {72, 89, 11, 67, 71, 98, 82, 29, 35, 0, 64, 43, 58, 2, 37, 34, 43, 93, 34, 46 },
   {78, 95, 35, 98, 12, 23, 26, 13, 16, 64, 0, 24, 76, 14, 75, 63, 85, 32, 66, 7 },
   {47, 49, 20, 50, 65, 98, 61, 24, 61, 43, 24, 0, 18, 50, 6, 41, 89, 71, 91, 40 },
   {7, 23, 80, 87, 54, 35, 56, 74, 7, 58, 76, 18, 0, 64, 21, 96, 39, 6, 73, 39 },
   {15, 94, 58, 83, 18, 71, 33, 48, 31, 2, 14, 50, 64, 0, 0, 58, 97, 27, 44, 69 },
   {67, 84, 63, 4, 44, 7, 86, 86, 99, 37, 75, 6, 21, 0, 0, 90, 20, 89, 36, 85 },
   {66, 6, 25, 11, 2, 52, 4, 45, 48, 34, 63, 41, 96, 58, 90, 0, 26, 53, 20, 50 },
   {94, 5, 8, 26, 67, 10, 29, 17, 81, 43, 85, 89, 39, 97, 20, 26, 0, 56, 20, 21 },
   {77, 90, 52, 74, 22, 42, 19, 62, 96, 93, 32, 71, 6, 27, 89, 53, 56, 0, 18, 53 },
   {73, 18, 95, 18, 87, 43, 93, 61, 54, 34, 66, 91, 73, 44, 36, 20, 20, 18, 0, 59 },
   {48, 63, 71, 87, 28, 30, 99, 8, 40, 46, 7, 40, 39, 69, 85, 50, 21, 53, 59, 0}};

  public void start(){
  }

  public void done(){
  }

  // system parameters
  public int indSize() { return nodes;}
  public int abcSize() { return nodes;}
  public int popSize() { return 240;}
  public int generations() { return 6;}
  public int subPopulations() { return 2;}
  public int subGenerations() { return 3;}
  public int maxTrials() { return 100; }
  public int mutationRatio() { return 1000; } // 0.1%
  public int swapping() { return 2 * nodes; }

  public boolean finished(double fitness){
    return better(fitness, score);
  }

  public boolean better(double fitness1, double fitness2){
    return (fitness1 < fitness2);
  }

  public double calc(Ind ind){
    int dist = 0;

    for (int i = 0; i < nodes - 1; i++){
      dist += mat[ind.getGeneValue(i)][ind.getGeneValue(i + 1)];
    }

    dist += mat[ind.getGeneValue(nodes-1)][ind.getGeneValue(0)];

    return (double) dist;
  }

  public Ind crossOver(Ind oldInd, Ind ind2){
    Ind newInd = new Ind(ind2);
    int pos = getRand(newInd.size() + 1);

    for (int i = 0; i < pos; i++){
      int oldVal = oldInd.getGeneValue(i);
      int newVal = newInd.getGeneValue(i);
      if (oldVal != newVal){
	int newPos = newInd.indexOf(oldVal);
	if (newPos == -1){
	  System.err.println("cannot find " + oldVal);
	  System.exit(1);
	}
	newInd.setGene(newInd.getGene(i), newPos);
	newInd.setGene(oldInd.getGene(i), i);
      }
    }
    newInd.calcFitness();
    return newInd;
  }

  public void mutate(Ind ind){
    int pos1 = getRand(ind.size());
    int pos2 = getRand(ind.size());
    Integer tmp = ind.getGene(pos1);

    ind.setGene(ind.getGene(pos2), pos1);
    ind.setGene(tmp, pos2);
  }

  /**
   * Prints genes and fitness value into stdout and file output stream.
   */
  public void printInd(Ind ind){
    for (int i = 0; i < ind.size(); i++){
      System.out.print(ind.getGene(i) + " ");
    }
    System.out.println("  fitness: " + ind.getFitness());
  }

  /**
   * Prints population size, best and average fitnesses
   */
  public void printPop(Pop pop){
    if (pop.size() == 0){
      System.err.println("empty population");
      return;
    }

    System.out.println("size: " + pop.size());
    Ind best = pop.getBestInd();
    System.out.print("best: ");
    printInd(best);
    System.out.println("avg fitness: " + pop.getAvgFitness());
  }

  private int getRand(int n){
    return (int) Math.ceil(Math.random() * n - 1.0);
  }
}

