/*
 * Copyright 2015 Norbert
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the License. You may obtain a
 * copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package norbert.mynemo.core.selection;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import norbert.mynemo.core.evaluation.PersonnalRecommenderEvaluator;
import norbert.mynemo.core.recommendation.RecommenderFamily;
import norbert.mynemo.core.recommendation.RecommenderType;
import norbert.mynemo.core.recommendation.configuration.SvdBasedRecommenderConfiguration;
import norbert.mynemo.core.recommendation.recommender.SvdBasedRecommender;

import org.apache.commons.math3.analysis.MultivariateFunction;
import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.eval.DataModelBuilder;
import org.apache.mahout.cf.taste.model.DataModel;

/**
 * This function renders optimizable the numbers of features and iterations for a SVD based
 * recommender. Indeed, the {@link #value(double[])} method takes the two numbers as parameter, and
 * returns the value of a metric produced by an evaluator for these numbers. A list of all
 * evaluations performed are kept, and can be accessed via the {@link #getEvaluations()} method.
 *
 * <p>
 * A minimum coverage is taken in account. Indeed, an evaluation with a coverage below the given
 * minimum coverage produces a worst value than an evaluation with an acceptable coverage.
 *
 * <p>
 * Because this class implements the {@link MultivariateFunction}, it can be used by a
 * {@link org.apache.commons.math3.optim.nonlinear.scalar.MultivariateOptimizer
 * MultivariateOptimizer}.
 */
class SvdRecommenderEvalFunction implements MultivariateFunction {

  private final DataModel dataModel;
  private final DataModelBuilder dataModelBuilder;
  private final double evaluationPercentage;
  private final List<RecommenderEvaluation> evaluations;
  private final PersonnalRecommenderEvaluator evaluator;
  private final double minimumCoverage;
  private final boolean reuseIsAllowed;
  private final double trainingPercentage;
  private final RecommenderType type;

  /**
   * Creates a function for the given type of SVD based recommender.
   */
  public SvdRecommenderEvalFunction(SelectorConfiguration configuration, RecommenderType type,
      double minimumCoverage) {
    checkNotNull(configuration);
    checkArgument(type.getFamily() == RecommenderFamily.SVD_BASED);

    this.type = type;
    this.minimumCoverage = minimumCoverage;

    // extract the necessary data from the configuration
    dataModel = configuration.getDataModel();
    dataModelBuilder = configuration.getDataModelBuilder();
    evaluationPercentage = configuration.getEvaluationPercentage();
    evaluator = configuration.getEvaluator();
    reuseIsAllowed = configuration.reuseIsAllowed();
    trainingPercentage = configuration.getSpeed().getTrainingPercentage();

    evaluations = new ArrayList<>();
  }

  /**
   * Returns all evaluations performed.
   */
  public Collection<RecommenderEvaluation> getEvaluations() {
    return evaluations;
  }

  @Override
  public double value(double[] point) {
    int numFeatures = (int) Math.round(point[0]);
    int numIterations = (int) Math.round(point[1]);

    // initialize the data for the evaluation
    SvdBasedRecommenderConfiguration configuration =
        new SvdBasedRecommenderConfiguration(type, numFeatures, numIterations, dataModel,
            reuseIsAllowed);
    SvdBasedRecommender recommenderBuilder = new SvdBasedRecommender(configuration);

    // run the evaluation
    double result = 0;
    try {
      result =
          evaluator.evaluate(recommenderBuilder, dataModelBuilder, dataModel, trainingPercentage,
              evaluationPercentage);
    } catch (TasteException e) {
      throw new RuntimeException(e);
    }

    // save the evaluation and prepare the result
    evaluations.add(new RecommenderEvaluation(configuration, evaluator.getEvaluationReport()));

    if (evaluator.getEvaluationReport().getCoverage() < minimumCoverage) {
      // if the minimum coverage is not reached, the worst value is returned
      result = Double.MAX_VALUE;
    }

    return result;
  }
}

@@ -1058,20 +1058,68 @@
 va.util.
-List
+HashMap;%0Aimport java.util.List;%0Aimport java.util.Map
 ;%0A%0Aimpor
@@ -2555,16 +2555,336 @@
 tion %7B%0A%0A
+  /**%0A   * Returns a unique %3Ccode%3Elong%3C/code%3E from the two given integers. The same integers will always%0A   * return the same value.%0A   */%0A  private static Long generateUniqueKey(int numFeatures, int numIterations) %7B%0A    return (((long) numFeatures) %3C%3C 32) %7C numIterations;%0A  %7D%0A%0A  private final Map%3CLong, Double%3E cache;%0A
   privat
@@ -4104,16 +4104,45 @@
 st%3C%3E();%0A
+    cache = new HashMap%3C%3E();%0A
   %7D%0A%0A  /
@@ -4434,16 +4434,186 @@
 int%5B1%5D);
+%0A    Long cacheKey = generateUniqueKey(numFeatures, numIterations);%0A%0A    // check the cache%0A    if (cache.containsKey(cacheKey)) %7B%0A      return cache.get(cacheKey);%0A    %7D
 %0A%0A    //
@@ -5528,23 +5528,57 @@
 _VALUE;%0A
-
     %7D%0A%0A
+    cache.put(cacheKey, result);%0A%0A
     retu

improve a selector: speed up the SVD eval function with a cache