/*
 * 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 RowGeneFitnessFunction 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 RowGeneFitnessFunction() { 
	m_squareSize = 4;
    }

    public RowGeneFitnessFunction( 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) * 2  - fitness;
	//most possible repeats - repeats actually found
    }

    /**
     * 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;
     }
    */

    /**
     * Counts the number of duplicate elements in an int array.  The range of values is
     * assumed to be 1 to array.length inclusive.
     *
     * @param array The array.
     * @return The number of elements that are the same as some other element.
     * @since 1.0
     * @author Ariel Isaacson
     */
    private int countDuplicatesIn(int[] array) {
	int nDuplicates = 0;
	boolean[] taken = new boolean[array.length];
	for (int index = 0; index < array.length; index++)
	    if (taken[array[index] - 1]) //convert array element to index into taken
		nDuplicates++;
	    else
		taken[array[index]-1] = true;
	return nDuplicates;
    }

    /**
     * Calculates the number of duplicate values for a specific column in the puzzle.
     *
     * @param column The column number 0, 1, ...
     *
     * @return A number between 0 (no duplicates) to m_squareSize - 1 (all duplicates).
     *
     * @since 1,0 
     * @author Ariel Isaacson
     */
    private int checkColumn(int column, Gene[] genes) {
	//**/System.out.println("Checking column " + column);
	int[] columnContents = new int[m_squareSize];
	//assemble column contents to be checked for duplicates:
	for (int index = 0; index < m_squareSize; index++)
	    columnContents[index] =
		((SudokuRowGene.Square[]) ((SudokuRowGene) genes[index]).getAllele())[column].getValue();
	//**/System.out.println("Its contents are " + intArrayToString(columnContents));
	return countDuplicatesIn(columnContents);
    }

    /**
     * Calculates the number of duplicate values for a specific box in the puzzle.
     *
     * @param row The row number 0, 1, ...
     *
     * @return A number between 0 (no duplicates) to m_squareSize-1 (all duplicates).
     *
     * @since 1,0 
     */
    private int checkBox(int box, Gene[] genes) {
	//**/System.out.println("Checking box " + box);
	int[] boxContents = new int[m_squareSize];
	int boxLength = (int) Math.sqrt(m_squareSize);
	/* Assemble box contents to be checked for duplicates.  This
	 * means finding row and column that start the box: row #
	 * happens to be same as box # rounded down to nearest boxLength
	 * while column = (index of box _in row of boxes_) x
	 * boxLength. */
	int index = 0;
	for (int vOffset = (box / boxLength) * boxLength, v = 0;
	     v < boxLength; v++) { //easier to define loop end this way
	    SudokuRowGene.Square[] row = (SudokuRowGene.Square[]) genes[vOffset + v].getAllele();
	    for (int hOffset = boxLength * (box % boxLength), h = 0;
		 h < boxLength; h++)
		boxContents[index++] = row[hOffset + h].getValue();
	}
	//**/System.out.println("Its contents are:" + intArrayToString(boxContents));
	return countDuplicatesIn(boxContents);
    }

    private String intArrayToString(int[] array) {
	String returnValue = "[";
	for (int index = 0; index < array.length; index++) {
	    returnValue += array[index];
	    if (index < array.length-1)
		returnValue += ' ';
	}
	return returnValue + ']';
    }

    /**
     * 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;
    }
    */

    public static void main(String[] args) {
	Gene[] g = new Gene[9];
	// Test set of random chromosomes I had this thing generate:
	/*int[][] alleles = new int[][] {
	    {9,3,6,7,4,1,2,8,5},
	    {1,3,2,8,7,5,9,4,6},
	    {5,9,4,7,1,2,6,8,3},
	    {4,9,5,6,8,7,2,1,3},
	    {9,5,7,1,6,8,4,3,2},
	    {9,6,2,8,3,4,7,5,1},
	    {9,4,7,5,8,3,1,6,2},
	    {8,4,2,7,1,6,3,5,9},
	    {2,1,9,6,8,5,7,4,3}};*/
	// Test set - correctly solved puzzle:
	int[][] alleles = new int[][] {
	    {7,1,5,8,6,2,9,3,4},
	    {2,9,4,3,1,5,6,7,8},
	    {3,8,6,4,7,9,2,5,1},
	    {8,4,3,6,2,1,5,9,7},
	    {1,7,2,5,9,8,3,4,6},
	    {5,6,9,7,3,4,1,8,2},
	    {6,2,8,9,5,7,4,1,3},
	    {9,3,7,1,4,6,8,2,5},
	    {4,5,1,2,8,3,7,6,9}};
	for (int index = 0; index < 9; index++) {
	    System.out.println("Constructing gene " + index);
	    g[index] = new SudokuRowGene(alleles[index]);
	}
	RowGeneFitnessFunction f = new RowGeneFitnessFunction(9);
	for (int index = 0; index < 9; index++) {
	    int boxCheck = f.checkBox(index, g);
	    System.out.println("Box " + index + " has " + boxCheck + " duplicates");
	    int columnCheck = f.checkColumn(index, g);
	    System.out.println("Column " + index + " has " + columnCheck + " duplicates");
	}
	System.out.println("TOTAL FITNESS: " + f.evaluate(new Chromosome(g)));
    }

}
