/*
 * This file is part of JGAP.
 *
 * JGAP offers a dual license model containing the LGPL as well as the MPL.
 *
 * For licencing information please see the file license.txt included with JGAP
 * or have a look at the top of class org.jgap.Chromosome which representatively
 * includes the JGAP license policy applicable for any file delivered with JGAP.
 */
//package examples;

import org.jgap.*;
import org.jgap.impl.*;
import java.util.*;

/**
 * Fitness function for the SudokuSolver example.
 *
 * @author R. Morelli
 * @since 1.0
 */
public class SudokuFitnessFunction extends FitnessFunction {

  /** String containing the CVS revision. Read out via reflection!*/
  private final static String CVS_REVISION = "$Revision: 1.5 $";

  private final int m_squareSize;

  public static final int MAX_BOUND = 9;

  public SudokuFitnessFunction() { 
      m_squareSize = 4;
  }

  public SudokuFitnessFunction( int a_squareSize )
  {
      if( a_squareSize < 4 || a_squareSize > MAX_BOUND )
      {
          throw new IllegalArgumentException(
              "Square size must be between  and "+MAX_BOUND+"." );
      }
      m_squareSize = a_squareSize;
  }

  /**
   * Determine the fitness of the given Chromosome instance. The higher the
   * return value, the more fit the instance. This method should always
   * return the same fitness value for two equivalent Chromosome instances.
   *
   * @param a_subject The Chromosome instance to evaluate.
   *
   * @return A positive integer reflecting the fitness rating of the given
   *         Chromosome.
   * @since 1,0 
   */
  public double evaluate(Chromosome a_subject) {
      // The fitness value measures how many rows, columns, and boxes of
      // the Sudoku square contain the values 1..squareSize. For each row,
      // column, and box, the number of missing values is returned.  If
      // all values, 1, 2, ..., m_squareSize occur, the 0 is returned.
      // These values are summed for each row, column and box and the sum
      // is subtracted from a high value. The higher the result the better 
      // the fitness.
      // ------------------------------------------------------------------
  
    int fitness = 0; 
    Gene gene[] = a_subject.getGenes();
 
    for (int k = 0; k < m_squareSize; k++)
	fitness += checkRow(k, gene);
    for (int k = 0; k < m_squareSize; k++)
	fitness += checkColumn(k, gene);
    for (int k = 0; k < m_squareSize; k++)
	fitness += checkBox(k, gene);

    return m_squareSize * (m_squareSize -1) * 3  - fitness;
  }

  /**
   * This is a hack to specify the square numbers for a 4 x 4 puzzle.
   * A more clever approach would use some math.
   *
   * @param box  Gives the box number, 0, 1, 2, ...
   * @since 1,0 
   */
    private String makeBoxList(int box) {
	if (m_squareSize == 4) {
	    if (box == 0) return "0 1 4 5";
	    if (box == 1) return "2 3 6 7";
	    if (box == 2) return "8 9 12 13";
            if (box == 3) return "10 11 14 15";
	} else if (m_squareSize == 9) {
	    if (box == 0) return "0 1 2 9 10 11 18 19 20";
	    if (box == 1) return "3 4 5 12 13 14 21 22 23";
	    if (box == 2) return "6 7 8 15 16 17 24 25 26";
	    if (box == 3) return "27 28 29 36 37 38 45 46 47";
	    if (box == 4) return "30 31 32 39 40 41 48 49 50";
	    if (box == 5) return "33 34 35 42 43 44 51 52 53";
	    if (box == 6) return "54 55 56 63 64 65 72 73 74";
	    if (box == 7) return "57 58 59 66 67 68 75 76 77";
	    if (box == 8) return "60 61 62 69 70 71 78 79 80";
	}
	return "";
    }

  /**
   * Calculates the number of missing values for a specific row in the puzzle.
   *
   * @param row The row number 0, 1, ...
   *
   * @return A number between 0 (none missing) to m_squareSize (all missing).
   *
   * @since 1,0 
   */
  private int checkRow(int row, Gene[] gene) {
      //      System.out.print(row + ":");
      int list[] = new int[m_squareSize]; // List of 0s
      int m = row * m_squareSize;
      for (int k = m; k < m + m_squareSize; k++) {
	  //	  System.out.println(k + " " + gene[k].getPersistentRepresentation());
	  int val = ((Integer)gene[k].getAllele()).intValue();
	  //	  System.out.print(val + " ");
	  list[val - 1] = 1;   // Check this number off list
      }
      int nZeroes = 0;                            // Numbers not checked off
      for (int k = 0; k < list.length; k++) {
          if (list[k] == 0)
	      nZeroes++;
      }
      return nZeroes;
  }

  /**
   * Calculates the number of missing values for a specific column in the puzzle.
   *
   * @param row The row number 0, 1, ...
   *
   * @return A number between 0 (none missing) to m_squareSize (all missing).
   *
   * @since 1,0 
   */
  private int checkColumn(int column, Gene[] gene) {
      //      System.out.print("col " + column + ":");
      int list[] = new int[m_squareSize]; // List of 0s
      int m = column;
      for (int k = m; k <= m_squareSize*m_squareSize - (m_squareSize-column); k += m_squareSize) {
	  int val = ((Integer)gene[k].getAllele()).intValue();
	  //	  System.out.print(val + " ");
	  list[val - 1] = 1;   // Check this number off list
      }
      int nZeroes = 0;                            // Numbers not checked off
      for (int k = 0; k < list.length; k++) {
          if (list[k] == 0)
	      nZeroes++;
      }
      return nZeroes;
  }

  /**
   * Calculates the number of missing values for a specific box in the puzzle.
   *
   * @param row The row number 0, 1, ...
   *
   * @return A number between 0 (none missing) to m_squareSize (all missing).
   *
   * @since 1,0 
   */
  private int checkBox(int box, Gene[] gene) {
      StringTokenizer st = new StringTokenizer(makeBoxList(box));
      int list[] = new int[m_squareSize]; // List of 0s
      while (st.hasMoreTokens()) {
	  int index = Integer.parseInt(st.nextToken());
          int val = ((Integer)gene[index].getAllele()).intValue();
	  list[val - 1] = 1;  // Check the number off the list
      }
      int nZeroes = 0;                            // Numbers not checked off
      for (int k = 0; k < list.length; k++) {
          if (list[k] == 0)
	      nZeroes++;
      }
      return nZeroes;
  }

  /**
   * Retrieves the number of coins represented by the given potential
   * solution at the given gene position.
   *
   * @param a_potentialSolution The potential solution to evaluate.
   * @param a_position The gene position to evaluate.
   * @return the number of coins represented by the potential solution
   *         at the given gene position.
   */
  public static int getNumberOfCoinsAtGene(Chromosome a_potentialSolution,
                                           int a_position) {
    Integer numCoins =
        (Integer) a_potentialSolution.getGene(a_position).getAllele();
    return numCoins.intValue();
  }

  /**
   * Returns the total number of coins represented by all of the genes in
   * the given potential solution.
   *
   * @param a_potentialsolution The potential solution to evaluate.
   * @return The total number of coins represented by the given Chromosome.
   */
  public static int getTotalNumberOfCoins(Chromosome a_potentialsolution) {
    int totalCoins = 0;
    int numberOfGenes = a_potentialsolution.size();
    for (int i = 0; i < numberOfGenes; i++) {
      totalCoins += getNumberOfCoinsAtGene(a_potentialsolution, i);
    }
    return totalCoins;
  }
}
