//  BLXAlphaCrossover.java
//
//  Author:
//       Antonio J. Nebro <antonio@lcc.uma.es>
//
//  Copyright (c) 2012 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.ArrayRealSolutionType;
import jmetal.encodings.solutionType.RealSolutionType;
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;

/**
 * This class allows to apply a SBX crossover operator using two parent
 * solutions.
 */
public class BLXAlphaCrossover extends Crossover {
	/**
	 * EPS defines the minimum difference allowed between real values
	 */
	private static final double DEFAULT_ALPHA = 0.5;

	private double alpha_ = DEFAULT_ALPHA ;
	private Double crossoverProbability_ = null;

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

	/** 
	 * Constructor
	 * Create a new SBX crossover operator whit a default
	 * index given by <code>DEFAULT_INDEX_CROSSOVER</code>
	 */
	public BLXAlphaCrossover(HashMap<String, Object> parameters) {
		super (parameters) ;

		if (parameters.get("probability") != null)
			crossoverProbability_ = (Double) parameters.get("probability") ;  		
		if (parameters.get("alpha") != null)
			alpha_  = (Double) parameters.get("alpha") ;  		
	} // SBXCrossover

	/**
	 * Perform the crossover operation. 
	 * @param probability Crossover probability
	 * @param parent1 The first parent
	 * @param parent2 The second parent
	 * @return An array containing the two offsprings
	 */
	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);

		int i;
		double random;
		double valueY1 ;
		double valueY2 ;
		double valueX1 ;
		double valueX2 ;
		double upperValue ;
		double lowerValue ;

		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.getNumberOfDecisionVariables() ;

		if (PseudoRandom.randDouble() <= probability){
			for (i=0; i<numberOfVariables; i++){
				upperValue = x1.getUpperBound(i);
				lowerValue = x1.getLowerBound(i);
				valueX1 = x1.getValue(i) ;
				valueX2 = x2.getValue(i) ;

				double max ;
				double min ;
				double range ;

        if (valueX2 > valueX1) {
          max = valueX2 ;
          min = valueX1 ;
        } // if
        else {
          max = valueX1 ;
          min = valueX2 ;
        } // else

        range = max - min ;
        // Ranges of the new alleles ;
        double minRange ;
        double maxRange ;

        minRange = min - range*alpha_;
        maxRange = max + range*alpha_;

        random = PseudoRandom.randDouble();
        valueY1 =  minRange + random * (maxRange - minRange) ;

        random = PseudoRandom.randDouble() ;
        valueY2 =  minRange + random * (maxRange - minRange) ;

        if (valueY1 < lowerValue)
        	offs1.setValue(i, lowerValue) ;
        else if (valueY1 > upperValue)
        	offs1.setValue(i, upperValue);
        else
        	offs1.setValue(i, valueY1);

        if (valueY2 < lowerValue)
        	offs2.setValue(i, lowerValue) ;
        else if (valueY2 > upperValue)
        	offs2.setValue(i, upperValue) ;
        else
        	offs2.setValue(i, valueY2) ;
			} // if
		} // if

		return offSpring;                                                                                      
	} // doCrossover


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

		if (parents.length != 2) {
			Configuration.logger_.severe("BLXAlphaCrossover.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("BLXAlphaCrossover.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(crossoverProbability_,
				parents[0],
				parents[1]);

		return offSpring;
	} // execute 
} // BLXAlphaCrossover
