package de.lmu.ifi.dbs.elki.preprocessing;

import de.lmu.ifi.dbs.elki.data.RealVector;
import de.lmu.ifi.dbs.elki.database.AssociationID;
import de.lmu.ifi.dbs.elki.database.Database;
import de.lmu.ifi.dbs.elki.distance.DoubleDistance;
import de.lmu.ifi.dbs.elki.distance.distancefunction.DistanceFunction;
import de.lmu.ifi.dbs.elki.distance.distancefunction.EuclideanDistanceFunction;
import de.lmu.ifi.dbs.elki.properties.Properties;
import de.lmu.ifi.dbs.elki.utilities.Progress;
import de.lmu.ifi.dbs.elki.utilities.QueryResult;
import de.lmu.ifi.dbs.elki.utilities.UnableToComplyException;
import de.lmu.ifi.dbs.elki.utilities.Util;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.*;
import de.lmu.ifi.dbs.elki.varianceanalysis.PCAFilteredResult;
import de.lmu.ifi.dbs.elki.varianceanalysis.PCAFilteredRunner;

import java.util.Iterator;
import java.util.List;

/**
 * Abstract superclass for preprocessors for HiCO correlation dimension
 * assignment to objects of a certain database.
 * 
 * @author Elke Achtert
 */
public abstract class HiCOPreprocessor<V extends RealVector<V, ?>> extends AbstractParameterizable implements Preprocessor<V> {
  /**
   * The default distance function for the PCA.
   */
  public static final String DEFAULT_PCA_DISTANCE_FUNCTION = EuclideanDistanceFunction.class.getName();

  /**
   * Parameter for pca distance function.
   */
  public static final String PCA_DISTANCE_FUNCTION_P = "pcaDistancefunction";

  /**
   * Description for parameter pca distance function.
   */
  public static final String PCA_DISTANCE_FUNCTION_D = "the distance function for the PCA to determine the distance between database objects " + Properties.KDD_FRAMEWORK_PROPERTIES.restrictionString(DistanceFunction.class) + ". Default: " + DEFAULT_PCA_DISTANCE_FUNCTION;

  /**
   * The parameter settings for the PCA.
   */
  private String[] pcaParameters;

  /**
   * The distance function for the PCA.
   */
  protected DistanceFunction<V, DoubleDistance> pcaDistanceFunction;

  /**
   * PCA utility object
   */
  private PCAFilteredRunner<V> pca = new PCAFilteredRunner<V>();
  
  /**
   * Provides a new Preprocessor that computes the correlation dimension of
   * objects of a certain database.
   */
  public HiCOPreprocessor() {
    super();
    // parameter pca distance function
    ClassParameter<DistanceFunction<V, DoubleDistance>> pcaDist = new ClassParameter(PCA_DISTANCE_FUNCTION_P, PCA_DISTANCE_FUNCTION_D, DistanceFunction.class);
    pcaDist.setDefaultValue(DEFAULT_PCA_DISTANCE_FUNCTION);
    optionHandler.put(pcaDist);
  }

  /**
   * This method determines the correlation dimensions of the objects stored in
   * the specified database and sets the necessary associations in the database.
   * 
   * @param database the database for which the preprocessing is performed
   * @param verbose flag to allow verbose messages while performing the
   *        algorithm
   * @param time flag to request output of performance time
   */
  public void run(Database<V> database, boolean verbose, boolean time) {
    if(database == null) {
      throw new IllegalArgumentException("Database must not be null!");
    }

    long start = System.currentTimeMillis();
    Progress progress = new Progress("Preprocessing correlation dimension", database.size());
    if(verbose) {
      verbose("Preprocessing:");
    }

    int processed = 1;
    for(Iterator<Integer> it = database.iterator(); it.hasNext();) {
      Integer id = it.next();
      List<QueryResult<DoubleDistance>> objs = resultsForPCA(id, database, verbose, false);

      PCAFilteredResult pcares = pca.processQueryResult(objs, database);

      database.associate(AssociationID.LOCAL_PCA, id, pcares);
      database.associate(AssociationID.LOCALLY_WEIGHTED_MATRIX, id, pcares.similarityMatrix());
      progress.setProcessed(processed++);

      if(verbose) {
        progress(progress);
      }
    }

    long end = System.currentTimeMillis();
    if(time) {
      long elapsedTime = end - start;
      verbose(this.getClass().getName() + " runtime: " + elapsedTime + " milliseconds.");
    }
  }

  /**
   * Sets the values for the parameters alpha, pca and pcaDistancefunction if
   * specified. If the parameters are not specified default values are set.
   * 
   * @see de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable#setParameters(String[])
   */
  @Override
  public String[] setParameters(String[] args) throws ParameterException {
    String[] remainingParameters = super.setParameters(args);

    // pca distance function
    String pcaDistanceFunctionClassName = (String) optionHandler.getOptionValue(PCA_DISTANCE_FUNCTION_P);
    try {
      // noinspection unchecked
      // todo
      pcaDistanceFunction = Util.instantiate(DistanceFunction.class, pcaDistanceFunctionClassName);
    }
    catch(UnableToComplyException e) {
      throw new WrongParameterValueException(PCA_DISTANCE_FUNCTION_P, pcaDistanceFunctionClassName, PCA_DISTANCE_FUNCTION_D);
    }
    remainingParameters = pcaDistanceFunction.setParameters(remainingParameters);

    remainingParameters = pca.setParameters(remainingParameters);

    setParameters(args, remainingParameters);

    return remainingParameters;
  }

  /**
   * Returns the parameter setting of the attributes.
   * 
   * @return the parameter setting of the attributes
   */
  @Override
  public List<AttributeSettings> getAttributeSettings() {
    List<AttributeSettings> attributeSettings = super.getAttributeSettings();

    attributeSettings.addAll(pca.getAttributeSettings());
    return attributeSettings;
  }

  /**
   * Returns the ids of the objects stored in the specified database to be
   * considerd within the PCA for the specified object id.
   * 
   * @param id the id of the object for which a PCA should be performed
   * @param database the database holding the objects
   * @param verbose flag to allow verbose messages while performing the
   *        algorithm
   * @param time flag to request output of performance time
   * @return the list of the object ids to be considerd within the PCA
   */
  protected abstract List<Integer> objectIDsForPCA(Integer id, Database<V> database, boolean verbose, boolean time);

  /**
   * Returns the ids of the objects and distances stored in the specified
   * database to be considerd within the PCA for the specified object id.
   * 
   * @param id the id of the object for which a PCA should be performed
   * @param database the database holding the objects
   * @param verbose flag to allow verbose messages while performing the
   *        algorithm
   * @param time flag to request output of performance time
   * @return the list of the object ids to be considerd within the PCA
   */
  protected abstract List<QueryResult<DoubleDistance>> resultsForPCA(Integer id, Database<V> database, boolean verbose, boolean time);
}

@@ -1791,96 +1791,8 @@
 N;%0A%0A
-  /**%0A   * The parameter settings for the PCA.%0A   */%0A  private String%5B%5D pcaParameters;%0A%0A
   /*

remove unused pcaParameters string; obsoleted by PCA rewrite