//  TwoPointsCrossover.java
//
//  Author:
//       Antonio J. Nebro <antonio@lcc.uma.es>
//       Juan J. Durillo <durillo@lcc.uma.es>
//
//  Copyright (c) 2011 Antonio J. Nebro, Juan J. Durillo
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
// 
//  You should have received a copy of the GNU Lesser General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

package jmetal.operators.crossover;

import jmetal.core.Solution;
import jmetal.encodings.solutionType.PermutationSolutionType;
import jmetal.encodings.variable.Permutation;
import jmetal.util.Configuration;
import jmetal.util.JMException;
import jmetal.util.PseudoRandom;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

/**
 * This class allows to apply a two points crossover operator using two parent
 * solutions. 
 * NOTE: the type of the solutions must be Permutation..
 */
public class TwoPointsCrossover extends Crossover {

  /**
   * Valid solution types to apply this operator 
   */
  private static final List VALID_TYPES = Arrays.asList(PermutationSolutionType.class) ;

  private Double crossoverProbability_ = null;

	/**
	 * Constructor
	 * Creates a new intance of the two point crossover operator
	 */
	public TwoPointsCrossover(HashMap<String, Object> parameters) {
		super(parameters) ;
		
  	if (parameters.get("probability") != null)
  		crossoverProbability_ = (Double) parameters.get("probability") ;  		
	} // TwoPointsCrossover


	/**
	 * Constructor
	 * @param A properties containing the Operator parameters
	 * Creates a new intance of the two point crossover operator
	 */
	//public TwoPointsCrossover(Properties properties) {
	//	this();
	//}


	/**
	 * Perform the crossover operation
	 * @param probability Crossover probability
	 * @param parent1 The first parent
	 * @param parent2 The second parent
	 * @return Two offspring solutions
	 * @throws jmetal.util.JMException
	 */
	public Solution[] doCrossover(double   probability, 
			Solution parent1, 
			Solution parent2) throws JMException {

		Solution [] offspring = new Solution[2];

		offspring[0] = new Solution(parent1);
		offspring[1] = new Solution(parent2);

		if (parent1.getType().getClass() == PermutationSolutionType.class) {
				if (PseudoRandom.randDouble() < probability) {
					int crosspoint1        ;
					int crosspoint2        ;
					int permutationLength  ;
					int parent1Vector[]    ;
					int parent2Vector[]    ;
					int offspring1Vector[] ;
					int offspring2Vector[] ;

					permutationLength = ((Permutation)parent1.getDecisionVariables()[0]).getLength() ;
					parent1Vector     = ((Permutation)parent1.getDecisionVariables()[0]).vector_ ;
					parent2Vector    = ((Permutation)parent2.getDecisionVariables()[0]).vector_ ;    
					offspring1Vector = ((Permutation)offspring[0].getDecisionVariables()[0]).vector_ ;
					offspring2Vector = ((Permutation)offspring[1].getDecisionVariables()[0]).vector_ ;

					// STEP 1: Get two cutting points
					crosspoint1 = PseudoRandom.randInt(0,permutationLength-1) ;
					crosspoint2 = PseudoRandom.randInt(0,permutationLength-1) ;

					while (crosspoint2 == crosspoint1)  
						crosspoint2 = PseudoRandom.randInt(0,permutationLength-1) ;

					if (crosspoint1 > crosspoint2) {
						int swap ;
						swap        = crosspoint1 ;
						crosspoint1 = crosspoint2 ;
						crosspoint2 = swap          ;
					} // if

					// STEP 2: Obtain the first child
					int m = 0;
					for(int j = 0; j < permutationLength; j++) {
						boolean exist = false;
						int temp = parent2Vector[j];
						for(int k = crosspoint1; k <= crosspoint2; k++) {
							if (temp == offspring1Vector[k]) {
								exist = true;
								break;
							} // if
						} // for
						if (!exist) {
							if (m == crosspoint1)
								m = crosspoint2 + 1;
							offspring1Vector[m++] = temp;
						} // if
					} // for

					// STEP 3: Obtain the second child
					m = 0;
					for(int j = 0; j < permutationLength; j++) {
						boolean exist = false;
						int temp = parent1Vector[j];
						for(int k = crosspoint1; k <= crosspoint2; k++) {
							if (temp == offspring2Vector[k]) {
								exist = true;
								break;
							} // if
						} // for
						if(!exist) {
							if (m == crosspoint1)
								m = crosspoint2 + 1;
							offspring2Vector[m++] = temp;
						} // if
					} // for
				} // if 
			} // if
			else
			{
				Configuration.logger_.severe("TwoPointsCrossover.doCrossover: invalid " +
						"type" + 
						parent1.getDecisionVariables()[0].getVariableType());
				Class cls = String.class;
				String name = cls.getName(); 
				throw new JMException("Exception in " + name + ".doCrossover()") ; 
			}

		return offspring;                                                                                      
	} // makeCrossover

	/**
	 * Executes the operation
	 * @param object An object containing an array of two solutions 
	 * @return An object containing an array with the offSprings
	 * @throws jmetal.util.JMException
	 */
	public Object execute(Object object) throws JMException {
		Solution [] parents = (Solution [])object;
		Double crossoverProbability ;

    if (!(VALID_TYPES.contains(parents[0].getType().getClass())  &&
        VALID_TYPES.contains(parents[1].getType().getClass())) ) {

			Configuration.logger_.severe("TwoPointsCrossover.execute: the solutions " +
					"are not of the right type. The type should be 'Permutation', but " +
					parents[0].getType() + " and " + 
					parents[1].getType() + " are obtained");
		} // if 

		crossoverProbability = (Double)getParameter("probability");

		if (parents.length < 2)
		{
			Configuration.logger_.severe("TwoPointsCrossover.execute: operator needs two " +
			"parents");
			Class cls = String.class;
			String name = cls.getName(); 
			throw new JMException("Exception in " + name + ".execute()") ;      
		}

		Solution [] offspring = doCrossover(crossoverProbability_,
				parents[0],
				parents[1]);

		return offspring; 
	} // execute

} // TwoPointsCrossover
