//  NonUniformMutation.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.mutation;

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 implements a non-uniform mutation operator.
  */
public class NonUniformMutation extends Mutation{
  /**
   * Valid solution types to apply this operator 
   */
  private static final List VALID_TYPES = Arrays.asList(RealSolutionType.class,
  		                                            ArrayRealSolutionType.class) ;
  /**
   * perturbation_ stores the perturbation value used in the Non Uniform 
   * mutation operator
   */
  private Double perturbation_ = null;
  
  /**
   * maxIterations_ stores the maximun number of iterations. 
   */
  private Integer maxIterations_ = null;    
  
  /**
   * currentIteration_ stores the iteration in which the operator is going to be
   * applied
   */
  private Integer currentIteration_ = null;
           
  private Double mutationProbability_ = null;

  /** 
  * Constructor
  * Creates a new instance of the non uniform mutation
  */
  public NonUniformMutation(HashMap<String, Object> parameters) {
  	super(parameters) ;
  	if (parameters.get("probability") != null)
  		mutationProbability_ = (Double) parameters.get("probability") ;  		
  	if (parameters.get("perturbation") != null)
  		perturbation_ = (Double) parameters.get("perturbation") ;  		
  	if (parameters.get("maxIterations") != null)
  		maxIterations_ = (Integer) parameters.get("maxIterations") ;  		
  } // NonUniformMutation
            

  /**
  * Constructor
  * Creates a new instance of the non uniform mutation
  */
  //public NonUniformMutation(Properties properties){
  //   this();
  //} // NonUniformMutation


  /**
  * Perform the mutation operation
  * @param probability Mutation probability
  * @param solution The solution to mutate
   * @throws jmetal.util.JMException
  */
  public void doMutation(double probability, Solution solution) throws JMException {                
  	XReal x = new XReal(solution) ; 
    for (int var = 0; var < solution.getDecisionVariables().length; var++) {         
      if (PseudoRandom.randDouble() < probability) {
        double rand = PseudoRandom.randDouble();
        double tmp;
                
        if (rand <= 0.5) {
          tmp = delta(x.getUpperBound(var) - x.getValue(var),
                      perturbation_.doubleValue());
          tmp += x.getValue(var);
        }
        else {
          tmp = delta(x.getLowerBound(var) - x.getValue(var),
                      perturbation_.doubleValue());
          tmp += x.getValue(var);
        }
                
        if (tmp < x.getLowerBound(var))
          tmp = x.getLowerBound(var);
        else if (tmp > x.getUpperBound(var))
          tmp = x.getUpperBound(var);
                
        x.setValue(var, tmp) ;
      }
    }
  } // doMutation
    

  /**
   * Calculates the delta value used in NonUniform mutation operator
   */
  private double delta(double y, double bMutationParameter) {
    double rand = PseudoRandom.randDouble();
    int it,maxIt;
    it    = currentIteration_.intValue();
    maxIt = maxIterations_.intValue();
        
    return (y * (1.0 - 
                Math.pow(rand,
                         Math.pow((1.0 - it /(double) maxIt),bMutationParameter)
                         )));
  } // delta

  /**
  * Executes the operation
  * @param object An object containing a solution
  * @return An object containing the mutated solution
   * @throws jmetal.util.JMException
  */
  public Object execute(Object object) throws JMException {
    Solution solution = (Solution)object;
    
		if (!VALID_TYPES.contains(solution.getType().getClass())) {
      Configuration.logger_.severe("NonUniformMutation.execute: the solution " +
      		solution.getType() + "is not of the right type");

      Class cls = String.class;
      String name = cls.getName(); 
      throw new JMException("Exception in " + name + ".execute()") ;
    } // if  
    
  	if (getParameter("currentIteration") != null)
  		currentIteration_ = (Integer) getParameter("currentIteration") ;  		

    doMutation(mutationProbability_,solution);
        
    return solution;    
  } // execute
} // NonUniformMutation
