/*
 * @(#) Recombiner.java 2003/03/22
 */

import java.util.Properties;
import java.util.Arrays;



/**
 * Crossover and mutation
 * 
 * @author Sushil J. Louis
 */
public class Recombiner implements java.io.Serializable {

    private GARandom random;

    /**
     * The number of crossover points + 2. The first and last position
     * in the chromosome add 2 to nPoints value
     */
    public int nPoints;

    /**
     * Probability of Crossover. Should be close to 0.9 or higher for
     * our GA Platform.
     */
    public double pCross;

    /**
     * Probability of Mutation. Should be close to 0.05 for
     * our GA Platform.
     */
    public double pMut;

    /**
     * Constructor gets nPoints, pCross, and pMut from GA's properties.
     * Also sets up a local reference to the system's random number generator.
     *
     * @param props the GA parameters in a properties object
     * @param r the system's  random number generator.
     */
    public Recombiner(Properties props, GARandom r) {
	nPoints = Integer.parseInt(props.getProperty("CrossoverPoints", "2"));
	nPoints += 2; 
	// to make things work for n Point crossover implementation;

	pCross = Double.parseDouble(props.getProperty("PCross", "0.95"));
	pMut   = Double.parseDouble(props.getProperty("PMut", "0.05"));

	random = r;
    }


    /**
     * Crossover and mutation. recombine checks whether to do crossover
     * according to pCross and if so, crosses over and mutates the two
     * parents indexed by i1 and i2 to produce the two childern indexed by
     * j1, and j2. If no cross over occurs, the offspring are a mutated
     * copy of the two parents.
     *
     * @param pop a reference to the {@link Population}.
     * @param i1 index in {@link Population}'s <code>currentPop</code> to 
     * first parent
     * @param i2 index in {@link Population}'s <code>currentPop</code> to 
     * second parent
     * @param j1 index in {@link Population}'s <code>currentPop</code> to 
     * first child
     * @param j2 index in {@link Population}'s <code>currentPop</code> to 
     * second parent
     */
    public void recombine(Population pop, int i1, int i2, int j1, int j2) {

	Individual p1, p2, c1, c2;

	p1 = pop.currentPop[i1];
	p2 = pop.currentPop[i2];

	c1 = pop.currentPop[j1];
	c2 = pop.currentPop[j2];

	copyChrom(p1, c1);
	copyChrom(p2, c2);

	if(random.flip(pCross) == 0){
	    mutate(c1);
	    mutate(c2);
	    return;
	}
	crossover(pop, p1, p2, c1, c2);
    }

    /**
     * Implements n-point crossover. Elegantly if I might add.
     * 
     * @param pop a reference to the {@link Population}.
     * @param p1 a reference to first parent.
     * @param p2 a reference to second parent.
     * @param c1 a reference to first child.
     * @param c2 a reference to second child.
     */
    public void crossover(Population pop, Individual p1, Individual p2, 
			   Individual c1, Individual c2) { 

	int[] xpoints = new int[nPoints];
	xpoints[0] = 0;
	xpoints[nPoints - 1] = pop.chromosomeLength;
	for(int i = 1; i < nPoints - 1; i++){
	    xpoints[i] = random.rnd(1, pop.chromosomeLength - 1);
	}
	Arrays.sort(xpoints);

	//	dumpCrossPoints(xpoints, System.out);

	//	Reporter.genoPrint(p1, "out");
	//	Reporter.genoPrint(p2, "out");
	//	Reporter.genoPrint(c1, "out");
	//	Reporter.genoPrint(c2, "out");

	for(int i = 0; i < nPoints - 1; i++){
	    for(int j = xpoints[i]; j < xpoints[i + 1]; j++) {
		if(i%2 == 0){
		    //		    System.out.println(i  + "." + j);
		    c1.chromosome[j] = p1.chromosome[j];
		    c2.chromosome[j] = p2.chromosome[j];
		} else {
		    //		    System.out.println(i  + "." + j);
		    c1.chromosome[j] = p2.chromosome[j];
		    c2.chromosome[j] = p1.chromosome[j];
		}		    
	    
	    }
	}
	//	Reporter.genoPrint(p1, "out");
	//	Reporter.genoPrint(p2, "out");
	//	Reporter.genoPrint(c1, "out");
	//	Reporter.genoPrint(c2, "out");

	mutate(c1);
	mutate(c2);
    }

    private void copyChrom(Individual from, Individual to){
	for(int i = 0; i < to.chromosome.length; i++){
	    to.chromosome[i] = from.chromosome[i];
	}
    }

    /**
     * Mutates an {@link Individual} by flipping a bit on the individual's
     * chromosome with probability pMut;
     *
     * @param member the individual to be mutated.
     */
    public void mutate(Individual member){
	for(int i = 0; i < member.chromosome.length; i++){
	    member.chromosome[i] = (random.flip(pMut) == 1 ? 
			       1 - member.chromosome[i] :
			       member.chromosome[i]);
	}
    }

    private void  dumpCrossPoints(int[] xpoints, java.io.PrintStream out){
	out.print("Xpoints: |");
	for(int i = 0; i < xpoints.length; i++){
	    out.print(xpoints[i] + " " );
	}
	out.println("|");
    }

}
