//  Hypervolume.java
//
//  Author:
//       Antonio J. Nebro <antonio@lcc.uma.es>
//       Juan J. Durillo <juanjo.durillo@gmail.com>
//
//  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.qualityIndicator;

import jmetal.core.SolutionSet;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.StringTokenizer;

public class R2 {

  public jmetal.qualityIndicator.util.MetricsUtil utils_;
  double [][] matrix_ = null;
  double [][] lambda_ = null;
  int    nObj_         = 0;
  
  /**
  * Constructor
  * Creates a new instance of the R2 indicator for a problem with two objectives
  * and 100 lambda vectors
  */
  public R2() {
    utils_ = new jmetal.qualityIndicator.util.MetricsUtil();
    
    // by default it creates an R2 indicator for a two dimensions probllem and
    // uses only 100 weight vectors for the R2 computation
    nObj_ = 2;
    // generating the weights
    lambda_ = new double[100][2];
    for (int n = 0; n < 100; n++) {
        double a = 1.0 * n / (100 - 1); 
        lambda_[n][0] = a;
        lambda_[n][1] = 1 - a;
    } // for
    
    
  } // R2
  
  
  
  /**
  * Constructor
  * Creates a new instance of the R2 indicator for a problem with two objectives
  * and N lambda vectors
  */
  public R2(int nVectors) {
    utils_ = new jmetal.qualityIndicator.util.MetricsUtil();
    
    // by default it creates an R2 indicator for a two dimensions probllem and
    // uses only <code>nVectors</code> weight vectors for the R2 computation
    nObj_ = 2;
    // generating the weights
    lambda_ = new double[nVectors][2];
    for (int n = 0; n < nVectors; n++) {
        double a = 1.0 * n / (nVectors - 1);
        lambda_[n][0] = a;
        lambda_[n][1] = 1 - a;
    } // for
    
    
  } // R2
  
   
  
  /**
  * Constructor
  * Creates a new instance of the R2 indicator for nDimensiosn
  * It loads the weight vectors from the file fileName
  */
  public R2(int nObj, String file) {
    utils_ = new jmetal.qualityIndicator.util.MetricsUtil();
    // A file is indicated, the weights are taken from there
    
    // by default it creates an R2 indicator for a two dimensions probllem and
    // uses only <code>nVectors</code> weight vectors for the R2 computation
    nObj_ = nObj;
    // generating the weights
    
    // reading weights
    try {
        // Open the file
        FileInputStream fis = new FileInputStream(file);
        InputStreamReader isr = new InputStreamReader(fis);
        BufferedReader br = new BufferedReader(isr);

        int numberOfObjectives = 0;
        int i = 0;
        int j = 0;
        String aux = br.readLine();
        LinkedList<double[]> list = new LinkedList<double[]>();
        while (aux != null) {
            StringTokenizer st = new StringTokenizer(aux);
            j = 0;
            numberOfObjectives = st.countTokens();
            double [] vector = new double[nObj];
            while (st.hasMoreTokens()) {
                double value = new Double(st.nextToken());
                vector[j++] = value;                                        
            }
            list.add(vector);
            aux = br.readLine();                
        }
        br.close();
            
        // convert the LinkedList into a vector
        lambda_ = new double[list.size()][];            
        int index = 0;
        for (double[] aList : list) lambda_[index++] = aList;
            
            
    } catch (Exception e) {
        System.out.println("initUniformWeight: failed when reading for file: " + file);
        e.printStackTrace();
    }                
  } // R2
  
  
  
   /**
   * Returns the R2 indicator value of a given front
   * @param front The front 
   * @param trueParetoFront The true Pareto front
   * @param numberOfObjectives The number of objectives
   * @param lambda A vector containing the lambda vectors for R2
   */
  private double 
  R2Withouth(double [][] approximation,double [][] paretoFront, int index) 
  {
        
    /**
     * Stores the maximum values of true Pareto front.
     */
    double [] maximumValue ;
    
    /**
     * Stores the minimum values of the true Pareto front.
     */
    double [] minimumValue ;
    
    /**
     * Stores the normalized front.
     */
    double [][] normalizedApproximation ;
    
    /**
     * Stores the normalized true Pareto front.
     */ 
    double [][] normalizedParetoFront ; 
    
    // STEP 1. Obtain the maximum and minimum values of the Pareto front
    maximumValue = utils_.getMaximumValues(paretoFront, nObj_);
    minimumValue = utils_.getMinimumValues(paretoFront, nObj_);
    
    // STEP 2. Get the normalized front and true Pareto fronts
    normalizedApproximation       = utils_.getNormalizedFront(approximation, 
                                                              maximumValue, 
                                                              minimumValue);
    normalizedParetoFront = utils_.getNormalizedFront(paretoFront, 
    		                                          maximumValue,
    		                                          minimumValue);
    
    // STEP 3. compute all the matrix of tchebicheff values if it is null
    //if (matrix_ == null) {
        matrix_ = new double[approximation.length][lambda_.length];
        for (int i = 0; i < approximation.length; i++) {
            for (int j = 0; j < lambda_.length; j++) {
                matrix_[i][j] = lambda_[j][0] * Math.abs(normalizedApproximation[i][0]); 
                for (int n = 1; n < nObj_; n++) {
                    matrix_[i][j] = Math.max(matrix_[i][j], lambda_[j][n] * Math.abs(normalizedApproximation[i][n]));
                }
            }
        }
    //}
    
        
    // STEP45. Compute the R2 value withouth the point
    double sumWithout = 0.0;     
    for (int i = 0; i < lambda_.length; i++) {
        
        double tmp;
        if (index != 0) {
            tmp = matrix_[0][i];
        } else {
            tmp = matrix_[1][i];
        }
        for (int j = 0; j < approximation.length; j++) {
            if ( j != index) {
                tmp = Math.min(tmp, matrix_[j][i]);
            }
        }
        sumWithout += tmp;
    }
    
    // STEP 5. Return the R2 value
    return sumWithout / (double) lambda_.length;
            
  } // R2
  
   /**
   * Returns the element contributing the most to the R2 indicator
   * @param front The front 
   * @param trueParetoFront The true Pareto front
   * @param numberOfObjectives The number of objectives
   * @param lambda A vector containing the lambda vectors for R2
   */
  public int 
  getBest(double [][] approximation,double [][] paretoFront) 
  {
        int     index_best = -1;
        double  value = Double.NEGATIVE_INFINITY;
        
        for (int i = 0; i < approximation.length; i++) {
            double aux = this.R2Withouth(approximation, paretoFront, i);
            if (aux > value) {
                index_best = i;
                value = aux;
            }
        }  
            
        return index_best;
  } // getBest 
  

   /**
   * Returns the element contributing the less to the R2
   * @param front The front 
   * @param trueParetoFront The true Pareto front
   * @param numberOfObjectives The number of objectives
   * @param lambda A vector containing the lambda vectors for R2
   */
  public int 
  getWorst(double [][] approximation,double [][] paretoFront) 
  {
        int     index_worst = -1;
        double  value = Double.POSITIVE_INFINITY;
        
        for (int i = 0; i < approximation.length; i++) {
            double aux = this.R2Withouth(approximation, paretoFront, i);
            if (aux < value) {
                index_worst = i;
                value = aux;
            }
        }  
            
        return index_worst;
  } // getWorst
  

   /**
   * Returns the element contributing the most to the R2
   * @param front A solution set     
   */
  public int 
  getBest(SolutionSet set) 
  {
      double [][] approximationFront = set.writeObjectivesToMatrix();
      double [][] trueFront          = set.writeObjectivesToMatrix();

      return this.getBest(approximationFront, trueFront);
  } // getWorst
  
  
   /**
   * Returns the element contributing the less to the R2
   * @param front The front 
   * @param trueParetoFront The true Pareto front      
   */
  public int 
  getWorst(SolutionSet set) 
  {
      double [][] approximationFront = set.writeObjectivesToMatrix();
      double [][] trueFront          = set.writeObjectivesToMatrix();

      return this.getWorst(approximationFront, trueFront);
  } // getWorst
  
  
  
  
   /**
   * Returns the element contributing the most to the R2 indicator
   * @param front The front 
   * @param trueParetoFront The true Pareto front
   * @param numberOfObjectives The number of objectives
   * @param lambda A vector containing the lambda vectors for R2
   */
  public int []
  getNBest(double [][] approximation,double [][] paretoFront, int N) 
  {
        int  []    index_bests = new int[approximation.length];
        double [] values       = new double[approximation.length];
                                
        
        for (int i = 0; i < approximation.length; i++) {
            values[i]      = this.R2Withouth(approximation, paretoFront, i);
            index_bests[i] = i;
        } // for
        
        
        // sorting the values and index_bests
        for (int i  = 0; i < approximation.length; i++) {
            for (int j = i; j < approximation.length; j++) {
                if (values[j] < values[i]) {
                    double aux = values[j];
                    values[j]  = values[i];
                    values[i]  = aux;
                    
                    int aux_index = index_bests[j];
                    index_bests[j]   = index_bests[i];
                    index_bests[i]   = aux_index;
                }
            }
        }
            
        int [] res = new int[N];
        for (int i = 0; i< N; i++) {
            res[i] = index_bests[i];
        }
        
        return res;
  } // getBest
            
       
  /**
   * Returns the indexes of the N best solutions according to this indicator
   * @param SolutionSet the solution set for which the best solutions are computed
   */
  public int []
  getNBest(SolutionSet set, int N) {
      double [][] approximationFront = set.writeObjectivesToMatrix();
      double [][] trueFront          = set.writeObjectivesToMatrix();
      
      return this.getNBest(approximationFront,trueFront,N);

  } // getNBest
  
  
  
   /**
   * Returns the R2 indicator value of a given front
   * @param front The front 
   * @param trueParetoFront The true Pareto front
   * @param numberOfObjectives The number of objectives
   * @param lambda A vector containing the lambda vectors for R2
   */
  public double 
  R2(double [][] approximation,double [][] paretoFront) 
  {
        
    /**
     * Stores the maximum values of true pareto front.
     */
    double [] maximumValue ;
    
    /**
     * Stores the minimum values of the true pareto front.
     */
    double [] minimumValue ;
    
    /**
     * Stores the normalized front.
     */
    double [][] normalizedApproximation ;
    
    /**
     * Stores the normalized true Pareto front.
     */ 
    double [][] normalizedParetoFront ; 
    
    // STEP 1. Obtain the maximum and minimum values of the Pareto front
    maximumValue = utils_.getMaximumValues(paretoFront, nObj_);
    minimumValue = utils_.getMinimumValues(paretoFront, nObj_);
    
    // STEP 2. Get the normalized front and true Pareto fronts
    normalizedApproximation       = utils_.getNormalizedFront(approximation, 
                                                              maximumValue, 
                                                              minimumValue);
    normalizedParetoFront = utils_.getNormalizedFront(paretoFront, 
    		                                          maximumValue,
    		                                          minimumValue);
    
    // STEP 3. compute all the matrix of tchebicheff values if it is null
    //if (matrix_ == null) {
        matrix_ = new double[approximation.length][lambda_.length];
        for (int i = 0; i < approximation.length; i++) {
            for (int j = 0; j < lambda_.length; j++) {
                matrix_[i][j] = lambda_[j][0] * Math.abs(normalizedApproximation[i][0]); 
                for (int n = 1; n < nObj_; n++) {
                    matrix_[i][j] = Math.max(matrix_[i][j], lambda_[j][n] * Math.abs(normalizedApproximation[i][n]));
                }
            }
        }
    //}
    
    // STEP 4. The matrix is not null. Compute the R2 value
    double sum = 0.0;
    for (int i = 0; i < lambda_.length; i++) {
        double tmp = matrix_[0][i];
        for (int j = 1; j < approximation.length; j++) {
            tmp = Math.min(tmp, matrix_[j][i]);
        }
        sum += tmp;
    }
    
    // STEP 5. Return the R2 value
    return sum / (double) lambda_.length;
            
  } // R2

  /**
   * Returns the R2 indicator of a given population, using as a reference
   * point 0, 0. Normalization is using taking into account the population itself
   * @param set
   * @return 
   */
  public double
  R2(SolutionSet set) {
      double [][] approximationFront = set.writeObjectivesToMatrix();
      double [][] trueFront          = set.writeObjectivesToMatrix();
      
      return this.R2(approximationFront, trueFront);
      
  } // R2
  
  
  
     /**
   * Returns the R2 indicator value of a given front
   * @param front The front 
   * @param trueParetoFront The true Pareto front
   * @param numberOfObjectives The number of objectives
   * @param lambda A vector containing the lambda vectors for R2
   */
  public double 
  R2Without(SolutionSet set, int index) 
  {
      double [][] approximationFront = set.writeObjectivesToMatrix();
      double [][] trueFront          = set.writeObjectivesToMatrix();      
      return this.R2(approximationFront, trueFront);                    
  } // R2ContributionWithout

  
  
  
  /**
   * This class can be call from the command line. At least three parameters 
   * are required:
   * 1) the name of the file containing the front,  
   * 2) the number of objectives
   * 2) a file containing the reference point / the Optimal Pareto front for normalizing
   * 3) the file containing the weight vector
   */
  public static void main(String args[]) {
    if (args.length < 3) {
      System.err.println("Error using R2. Usage: \n java jmetal.qualityIndicator.Hypervolume " +
                         "<SolutionFrontFile> " +
                         "<TrueFrontFile> " + "<numberOfObjectives>");
      System.exit(1);
    }
    
    //Create a new instance of the metric
    R2 qualityIndicator;
    //Read the front from the files
    
    
    int nObj                            = new Integer(args[2]);
    
    

    
    if (nObj==2 && args.length==3) {
      qualityIndicator = new R2();
    
    } else {
      qualityIndicator = new R2(nObj,args[3]);        
    }
    
    
    double [][] approximationFront      = qualityIndicator.utils_.readFront(args[0]);
    
    double [][] paretoFront             = qualityIndicator.utils_.readFront(args[1]);
    
    
    //Obtain delta value
    double value = qualityIndicator.R2(approximationFront,paretoFront); 
                                  
    
    System.out.println(value);  
    
    System.out.println(qualityIndicator.R2Withouth(approximationFront,paretoFront,1)); 
    System.out.println(qualityIndicator.R2Withouth(approximationFront,paretoFront,15)); 
    System.out.println(qualityIndicator.R2Withouth(approximationFront,paretoFront,25)); 
    System.out.println(qualityIndicator.R2Withouth(approximationFront,paretoFront,75)); 
    
  } // main
} // R2
