package org.uma.jmetal.component.densityestimator.impl;

import org.uma.jmetal.component.densityestimator.DensityEstimator;
import org.uma.jmetal.solution.Solution;
import org.uma.jmetal.solution.util.attribute.util.attributecomparator.AttributeComparator;
import org.uma.jmetal.solution.util.attribute.util.attributecomparator.impl.DoubleValueAttributeComparator;
import org.uma.jmetal.util.comparator.ObjectiveComparator;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * This class implements the crowding distance
 *
 * @author Antonio J. Nebro <antonio@lcc.uma.es>
 */
@SuppressWarnings("serial")
public class CrowdingDistanceDensityEstimator<S extends Solution<?>> implements DensityEstimator<S> {

  private String attributeId = getClass().getName();
  private Comparator<S> solutionComparator ;

  public CrowdingDistanceDensityEstimator() {
    solutionComparator = new DoubleValueAttributeComparator<>(attributeId, AttributeComparator.Ordering.DESCENDING) ;
  }
  /**
   * Assigns crowding distances to all population in a <code>SolutionSet</code>.
   *
   * @param solutionList The <code>SolutionSet</code>.
   */

  @Override
  public void computeDensityEstimator(List<S> solutionList) {
    int size = solutionList.size();

    if (size == 0) {
      return;
    }

    if (size == 1) {
      solutionList.get(0).setAttribute(attributeId, Double.POSITIVE_INFINITY);
      return;
    }

    if (size == 2) {
      solutionList.get(0).setAttribute(attributeId, Double.POSITIVE_INFINITY);
      solutionList.get(1).setAttribute(attributeId, Double.POSITIVE_INFINITY);

      return;
    }

    // Use a new SolutionSet to avoid altering the original solutionSet
    List<S> front = new ArrayList<>(size);
    for (S solution : solutionList) {
      front.add(solution);
    }

    for (int i = 0; i < size; i++) {
      front.get(i).setAttribute(attributeId, 0.0);
    }

    double objetiveMaxn;
    double objetiveMinn;
    double distance;

    int numberOfObjectives = solutionList.get(0).getNumberOfObjectives() ;

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

      // Set de crowding distance
      front.get(0).setAttribute(attributeId, Double.POSITIVE_INFINITY);
      front.get(size - 1).setAttribute(attributeId, 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 += (double)front.get(j).getAttribute(attributeId);
        front.get(j).setAttribute(attributeId, distance);
      }
    }
  }

  @Override
  public String getAttributeId() {
    return attributeId ;
  }

  @Override
  public Comparator<S> getSolutionComparator() {
    return solutionComparator ;
  }

  @Override
  public List<S> sort(List<S> solutionList) {
    Collections.sort(solutionList, getSolutionComparator());

    return solutionList ;
  }
}


@@ -450,38 +450,8 @@
 st;%0A
-import java.util.Collections;%0A
 impo
@@ -1607,25 +1607,24 @@
 _INFINITY);%0A
-%0A
       return
@@ -1745,84 +1745,21 @@
 %3C%3E(s
-ize);%0A    for (S solution : solutionList) %7B%0A      front.add(solution);%0A    %7D
+olutionList);
 %0A%0A  
@@ -1855,80 +1855,8 @@
  %7D%0A%0A
-    double objetiveMaxn;%0A    double objetiveMinn;%0A    double distance;%0A%0A
     
@@ -2026,32 +2026,19 @@
     
-Collections
+front
 .sort(
-front, 
 new 
@@ -2061,35 +2061,440 @@
 tor%3C
-S
 %3E(i))
- ;%0A     
+;%0A%0A      // It may be beneficial to change this according to https://dl.acm.org/citation.cfm?doid=2463372.2463456.%0A      // The additional change that may be beneficial is that if we have only two distinct
  obje
+c
 tive
-Minn
+ values,%0A      //   we also don't update the crowding distance, as they all will %22go to eleven%22,%0A      //   which makes no sense as this objective just appears to be non-discriminating.%0A%0A      double minObjective
  = f
@@ -2528,28 +2528,35 @@
 ;%0A      
-o
+double maxO
 bje
+c
 tive
-Maxn
  = front
@@ -2594,16 +2594,159 @@
 tive(i);
+%0A      if (minObjective == maxObjective) %7B%0A        continue; // otherwise all crowding distances will be NaN = 0.0 / 0.0 except for two%0A      %7D
 %0A%0A      
@@ -2752,17 +2752,18 @@
  // Set 
-d
+th
 e crowdi
@@ -2773,16 +2773,39 @@
 distance
+ for the extreme points
 %0A      f
@@ -2945,24 +2945,97 @@
 INFINITY);%0A%0A
+      // Increase the crowding distances for all the intermediate points%0A
       for (i
@@ -3065,32 +3065,39 @@
  j++) %7B%0A        
+double 
 distance = front
@@ -3195,35 +3195,35 @@
  / (
-o
+maxO
 bje
+c
 tive
-Maxn - o
+ - minO
 bje
+c
 tive
-Minn
 );%0A 
@@ -3249,16 +3249,17 @@
 (double)
+ 
 front.ge
@@ -3608,39 +3608,26 @@
     
-Collec
+solu
 tion
-s
+List
 .sort(
-solutionList, 
 getS
@@ -3644,25 +3644,24 @@
 parator());%0A
-%0A
     return s
@@ -3680,9 +3680,8 @@
 ;%0A  %7D%0A%7D%0A
-%0A

Refactor crowding distance slightly.