/*
 * 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.UserBasedRecommenderConfiguration;
import norbert.mynemo.core.recommendation.recommender.UserSimilarityRecommender;

import org.apache.commons.math3.analysis.UnivariateFunction;
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 number of neighbors for a user-similarity based
 * recommender. Indeed, the {@link #value(double)} method takes a number of neighbors as parameter,
 * and returns the value of a metric produced by an evaluator for that number of neighbors. 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 the value {code Double.MAX_VALUE}.
 *
 * <p>
 * Because this class implements the {@link UnivariateFunction}, it can be used by a
 * {@link org.apache.commons.math3.optim.univariate.UnivariateOptimizer UnivariateOptimizer}.
 */
class UserBasedRecommenderEvaluationFunction implements UnivariateFunction {

  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 user-similarity based recommender.
   */
  public UserBasedRecommenderEvaluationFunction(SelectorConfiguration configuration,
      RecommenderType type, double minimumCoverage) {
    checkNotNull(configuration);
    checkArgument(type.getFamily() == RecommenderFamily.USER_SIMILARITY_BASED);
    checkArgument(0 <= minimumCoverage && minimumCoverage <= 1, "The minimum coverage must be"
        + "between 0 and 1.");

    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<>();
  }

  public Collection<RecommenderEvaluation> getEvaluations() {
    return evaluations;
  }

  @Override
  public double value(double numberOfUsers) {
    // initialize the data for the evaluation
    UserBasedRecommenderConfiguration configuration =
        new UserBasedRecommenderConfiguration(type, (int) Math.round(numberOfUsers), dataModel,
            reuseIsAllowed);
    UserSimilarityRecommender recommenderBuilder = new UserSimilarityRecommender(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
@@ -2543,16 +2543,68 @@
 tion %7B%0A%0A
+  private final Map%3CInteger, Double%3E cachedResults;%0A
   privat
@@ -3945,24 +3945,61 @@
 centage();%0A%0A
+    cachedResults = new HashMap%3C%3E();%0A
     evaluati
@@ -4164,25 +4164,197 @@
 ble 
-numberOfUse
+doubleNumNeighbors) %7B%0A%0A    int numNeighbors = (int) Math.round(doubleNumNeighbors);%0A%0A    if (cachedResults.containsKey(numNeighbo
 rs)
+)
  %7B%0A
+      return cachedResults.get(numNeighbors);%0A    %7D%0A%0A
     
@@ -4505,63 +4505,32 @@
 pe, 
-(int) Math.round(numberOfUsers), dataModel,%0A           
+numNeighbors, dataModel,
  reu
@@ -5262,24 +5262,70 @@
 LUE;%0A    %7D%0A%0A
+    cachedResults.put(numNeighbors, result);%0A%0A
     return r

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