//  Distance.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.util;

import jmetal.core.Solution;
import jmetal.core.SolutionSet;
import jmetal.util.comparators.ObjectiveComparator;
import jmetal.util.wrapper.XReal;

/**
 * This class implements some utilities for calculating distances
 */
public class Distance {

  /**
   * Constructor.
   */
  public Distance() {
    //do nothing.
  } // Distance


  /**
   * Returns a matrix with distances between solutions in a
   * <code>SolutionSet</code>.
   * @param solutionSet The <code>SolutionSet</code>.
   * @return a matrix with distances.
   */
  public double [][] distanceMatrix(SolutionSet solutionSet) {
    Solution solutionI, solutionJ;

    //The matrix of distances
    double [][] distance = new double [solutionSet.size()][solutionSet.size()];
    //-> Calculate the distances
    for (int i = 0; i < solutionSet.size(); i++){
      distance[i][i] = 0.0;
      solutionI = solutionSet.get(i);
      for (int j = i + 1; j < solutionSet.size(); j++){
        solutionJ = solutionSet.get(j);
        distance[i][j] = this.distanceBetweenObjectives(solutionI,solutionJ);
        distance[j][i] = distance[i][j];
      } // for
    } // for        

    //->Return the matrix of distances
    return distance;
  } // distanceMatrix

  /** Returns the minimum distance from a <code>Solution</code> to a
   * <code>SolutionSet according to the objective values</code>.
   * @param solution The <code>Solution</code>.
   * @param solutionSet The <code>SolutionSet</code>.
   * @return The minimum distance between solution and the set.
   * @throws JMException
   */
  public double distanceToSolutionSetInObjectiveSpace(Solution    solution,
                                                      SolutionSet solutionSet) throws JMException{
    //At start point the distance is the max
    double distance = Double.MAX_VALUE;

    // found the min distance respect to population
    for (int i = 0; i < solutionSet.size();i++){
      double aux = this.distanceBetweenObjectives(solution,solutionSet.get(i));
      if (aux < distance)
        distance = aux;
    } // for

    //->Return the best distance
    return distance;
  } // distanceToSolutionSetinObjectiveSpace

  /** Returns the minimum distance from a <code>Solution</code> to a 
   * <code>SolutionSet according to the encodings.variable values</code>.
   * @param solution The <code>Solution</code>.
   * @param solutionSet The <code>SolutionSet</code>.
   * @return The minimum distance between solution and the set.
   * @throws JMException
   */
  public double distanceToSolutionSetInSolutionSpace(Solution    solution,
                                                     SolutionSet solutionSet) throws JMException{
    //At start point the distance is the max
    double distance = Double.MAX_VALUE;

    // found the min distance respect to population
    for (int i = 0; i < solutionSet.size();i++){
      double aux = this.distanceBetweenSolutions(solution,solutionSet.get(i));
      if (aux < distance)
        distance = aux;
    } // for

    //->Return the best distance
    return distance;
  } // distanceToSolutionSetInSolutionSpace

  /** Returns the distance between two solutions in the search space.
   *  @param solutionI The first <code>Solution</code>.
   *  @param solutionJ The second <code>Solution</code>.
   *  @return the distance between solutions.
   * @throws JMException
   */
  public double distanceBetweenSolutions(Solution solutionI, Solution solutionJ)
          throws JMException{
    /*
    double distance = 0.0;
    if ((solutionI.getDecisionVariables() != null) &&
            (solutionJ.getDecisionVariables() != null)) {
      Variable[] decisionVariableI = solutionI.getDecisionVariables();
      Variable[] decisionVariableJ = solutionJ.getDecisionVariables();

      double diff;    //Auxiliar var
      //-> Calculate the Euclidean distance
      for (int i = 0; i < decisionVariableI.length; i++){
        diff = decisionVariableI[i].getValue() -
                decisionVariableJ[i].getValue();
        distance += Math.pow(diff,2.0);
      } // for    
    }
    //-> Return the euclidean distance
    return Math.sqrt(distance);
    */
    double distance = 0.0;
    XReal solI = new XReal(solutionI) ;
    XReal solJ = new XReal(solutionJ) ;

      double diff;    //Auxiliar var
      //-> Calculate the Euclidean distance
      for (int i = 0; i < solI.getNumberOfDecisionVariables(); i++){
        diff = solI.getValue(i) - solJ.getValue(i);
        distance += Math.pow(diff,2.0);
      } // for
    //-> Return the euclidean distance
    return Math.sqrt(distance);
  } // distanceBetweenSolutions

  /** Returns the distance between two solutions in objective space.
   *  @param solutionI The first <code>Solution</code>.
   *  @param solutionJ The second <code>Solution</code>.
   *  @return the distance between solutions in objective space.
   */
  public double distanceBetweenObjectives(Solution solutionI, Solution solutionJ){
    double diff;    //Auxiliar var
    double distance = 0.0;
    //-> Calculate the euclidean distance
    for (int nObj = 0; nObj < solutionI.getNumberOfObjectives();nObj++){
      diff = solutionI.getObjective(nObj) - solutionJ.getObjective(nObj);
      distance += Math.pow(diff,2.0);
    } // for   

    //Return the euclidean distance
    return Math.sqrt(distance);
  } // distanceBetweenObjectives.

  /**
   * Return the index of the nearest solution in the solution set to a given solution
   * @param solution
   * @param solutionSet
   * @return  The index of the nearest solution; -1 if the solutionSet is empty
   */
  public int indexToNearestSolutionInSolutionSpace(Solution solution, SolutionSet solutionSet) {
    int index = -1 ;
    double minimumDistance = Double.MAX_VALUE ;
    try {
      for (int i = 0 ; i < solutionSet.size(); i++) {
        double distance = 0;
          distance = distanceBetweenSolutions(solution, solutionSet.get(i));
          if (distance < minimumDistance) {
            minimumDistance = distance ;
            index = i ;
        }
      }
    }
    catch (Exception e) {
      e.printStackTrace();
    }
    return index ;
  }

  /** Assigns crowding distances to all solutions in a <code>SolutionSet</code>.
   * @param solutionSet The <code>SolutionSet</code>.
   * @param nObjs Number of objectives.
   */
  public void crowdingDistanceAssignment(SolutionSet solutionSet, int nObjs) {
    int size = solutionSet.size();

    if (size == 0)
      return;

    if (size == 1) {
      solutionSet.get(0).setCrowdingDistance(Double.POSITIVE_INFINITY);
      return;
    } // if

    if (size == 2) {
      solutionSet.get(0).setCrowdingDistance(Double.POSITIVE_INFINITY);
      solutionSet.get(1).setCrowdingDistance(Double.POSITIVE_INFINITY);
      return;
    } // if       

    //Use a new SolutionSet to evite alter original solutionSet
    SolutionSet front = new SolutionSet(size);
    for (int i = 0; i < size; i++){
      front.add(solutionSet.get(i));
    }

    for (int i = 0; i < size; i++)
      front.get(i).setCrowdingDistance(0.0);

    double objetiveMaxn;
    double objetiveMinn;
    double distance;

    for (int i = 0; i<nObjs; i++) {
      // Sort the population by Obj n            
      front.sort(new ObjectiveComparator(i));
      objetiveMinn = front.get(0).getObjective(i);
      objetiveMaxn = front.get(front.size()-1).getObjective(i);

      //Set de crowding distance            
      front.get(0).setCrowdingDistance(Double.POSITIVE_INFINITY);
      front.get(size-1).setCrowdingDistance(Double.POSITIVE_INFINITY);

      for (int j = 1; j < size-1; j++) {
        distance = front.get(j+1).getObjective(i) - front.get(j-1).getObjective(i);
        distance = distance / (objetiveMaxn - objetiveMinn);
        distance += front.get(j).getCrowdingDistance();
        front.get(j).setCrowdingDistance(distance);
      } // for
    } // for        
  } // crowdingDistanceAssing            
} // Distance

