//  SBXSinglePointCrossover.java
//
//  Author:
//       Antonio J. Nebro <antonio@lcc.uma.es>
// 
//  Copyright (c) 2011 Antonio J. Nebro
//
//  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.ArrayRealAndBinarySolutionType;
import jmetal.encodings.variable.Binary;
import jmetal.util.Configuration;
import jmetal.util.JMException;
import jmetal.util.PseudoRandom;
import jmetal.util.wrapper.XReal;

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

public class SBXSinglePointCrossover extends Crossover {
	/**
	 * EPS defines the minimum difference allowed between real values
	 */
	private static final double EPS= 1.0e-14;

	private static final double ETA_C_DEFAULT_ = 20.0;
	private Double realCrossoverProbability_ = null;
	private Double binaryCrossoverProbability_ = null;
	private double distributionIndex_ = ETA_C_DEFAULT_;

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

	/**
	 * Constructor
	 */
	public SBXSinglePointCrossover(HashMap<String, Object> parameters) {
		super (parameters) ;

		if (parameters.get("realCrossoverProbability") != null)
			realCrossoverProbability_ = (Double) parameters.get("realCrossoverProbability") ;  		
		if (parameters.get("binaryrossoverProbability") != null)
			binaryCrossoverProbability_ = (Double) parameters.get("binaryrossoverProbability") ;  		
		if (parameters.get("distributionIndex") != null)
			distributionIndex_ = (Double) parameters.get("distributionIndex") ;  		
	} // Constructor


	/**
	 * Perform the crossover operation. 
	 * @param realProbability Crossover probability
	 * @param parent1 The first parent
	 * @param parent2 The second parent
	 * @return An array containing the two offsprings
	 */
	public Solution[] doCrossover(Double realProbability,
		Double binaryProbability,
		Solution parent1, 
		Solution parent2) throws JMException {

		Solution [] offSpring = new Solution[2];

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

		// SBX crossover
		double rand;
		double y1, y2, yL, yu;
		double c1, c2;
		double alpha, beta, betaq;
		double valueX1,valueX2;
		XReal x1 = new XReal(parent1) ;		
		XReal x2 = new XReal(parent2) ;		
		XReal offs1 = new XReal(offSpring[0]) ;
		XReal offs2 = new XReal(offSpring[1]) ;

		int numberOfVariables = x1.size() ;

		if (PseudoRandom.randDouble() <= realProbability) {
			for (int i=0; i<x1.size(); i++){
				valueX1 = x1.getValue(i);
				valueX2 = x2.getValue(i);
				if (PseudoRandom.randDouble()<=0.5 ){
					if (Math.abs(valueX1- valueX2) > EPS) {

						if (valueX1 < valueX2){
							y1 = valueX1;
							y2 = valueX2;
						} else {
							y1 = valueX2;
							y2 = valueX1;
						} // if                       

						yL = x1.getLowerBound(i) ;
						yu = x1.getUpperBound(i) ;
						rand = PseudoRandom.randDouble();
						beta = 1.0 + (2.0*(y1-yL)/(y2-y1));
						alpha = 2.0 - Math.pow(beta,-(distributionIndex_+1.0));

						if (rand <= (1.0/alpha)){
							betaq = Math.pow ((rand*alpha),(1.0/(distributionIndex_+1.0)));
						} else {
							betaq = Math.pow ((1.0/(2.0 - rand*alpha)),(1.0/(distributionIndex_+1.0)));
						} // if

						c1 = 0.5*((y1+y2)-betaq*(y2-y1));
						beta = 1.0 + (2.0*(yu-y2)/(y2-y1));
						alpha = 2.0 - Math.pow(beta,-(distributionIndex_+1.0));

						if (rand <= (1.0/alpha)){
							betaq = Math.pow ((rand*alpha),(1.0/(distributionIndex_+1.0)));
						} else {
							betaq = Math.pow ((1.0/(2.0 - rand*alpha)),(1.0/(distributionIndex_+1.0)));
						} // if

						c2 = 0.5*((y1+y2)+betaq*(y2-y1));

						if (c1<yL)
							c1=yL;

						if (c2<yL)
							c2=yL;

						if (c1>yu)
							c1=yu;

						if (c2>yu)
							c2=yu;                        

						if (PseudoRandom.randDouble()<=0.5) {
							offs1.setValue(i, c2) ;
							offs2.setValue(i, c1) ;
						} else {
							offs1.setValue(i, c1) ;
							offs2.setValue(i, c2) ;
						} // if
					} // if 
					else {
						offs1.setValue(i,valueX1) ;
						offs2.setValue(i, valueX2) ;
					} // if
				} // if
				else {
					offs1.setValue(i, valueX2) ;
					offs2.setValue(i, valueX1) ;
				} // else
			} // for
		} // if

		// Single point crossover

		if (PseudoRandom.randDouble()<=binaryProbability) {
			Binary binaryChild0 = (Binary)offSpring[0].getDecisionVariables()[1] ;
			Binary binaryChild1 = (Binary)offSpring[1].getDecisionVariables()[1] ;

			int totalNumberOfBits = binaryChild0.getNumberOfBits() ;

			//2. Calcule the point to make the crossover
			int crossoverPoint = PseudoRandom.randInt(0, totalNumberOfBits - 1);

			//5. Make the crossover;
			for (int i = crossoverPoint; i < totalNumberOfBits; i++) {
				boolean swap = binaryChild0.bits_.get(i);
				binaryChild0.bits_.set(i, binaryChild1.bits_.get(i));
				binaryChild1.bits_.set(i, swap);
			} // for
		} // if

		return offSpring;      
	} // doCrossover

	@Override
	public Object execute(Object object) throws JMException {
		Solution [] parents = (Solution [])object;

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

		if (!(VALID_TYPES.contains(parents[0].getType().getClass())  &&
				VALID_TYPES.contains(parents[1].getType().getClass())) ) {
			Configuration.logger_.severe("SBXSinglePointCrossover.execute: the solutions " +
					"type " + parents[0].getType() + " is not allowed with this operator");

			Class cls = String.class;
			String name = cls.getName(); 
			throw new JMException("Exception in " + name + ".execute()") ;
		} // if 
		Solution [] offSpring;
		offSpring = doCrossover(realCrossoverProbability_, 
				binaryCrossoverProbability_, parents[0], parents[1]);

		return offSpring ;
	} // execute

} // SBXSinglePointCrossover

