/**
  * Computes the mean rating that a user has given to movies.
  *
  * @param ratings Map of ratings for a particular user.
  * @return Returns a double indicating the average rating.
  */
 public static double calculateMean(Map<Integer, Rating> ratings) {
   double sum = 0;
   for (Rating r : ratings.values()) {
     sum += r.getRating();
   }
   return sum / ratings.size();
 }
  public static void main(String[] args) {
    // Maps that maps a user id to a subset of ratings given by that user.
    Map<Integer, User> trainRatings = new HashMap<Integer, User>();
    Map<Integer, User> testRatings = new HashMap<Integer, User>();

    // Parse training and test data into maps.
    System.out.println("1. Parsing DataSets");
    parseRatings(trainRatings, "./DataSet/TrainingRatings.txt");
    parseRatings(testRatings, "./DataSet/TestingRatings.txt");

    // Fill Map of means.
    System.out.println("2. Calculating Mean Values");
    for (Integer uid : trainRatings.keySet()) {
      trainRatings.get(uid).setMean(calculateMean(trainRatings.get(uid).getRatings()));
    }

    // Predict ratings for users.
    System.out.println("3. Predicting Ratings");
    int total = 0;
    double errorAbsSum = 0;
    double errorRootSum = 0;
    for (Integer uid : testRatings.keySet()) {
      // Predict movie ratings and determine error summation
      for (Integer mid : testRatings.get(uid).getRatings().keySet()) {
        Rating r = testRatings.get(uid).getRatings().get(mid);
        double error =
            r.getRating() - calculateWeightedSum(trainRatings, trainRatings.get(uid), mid);
        errorAbsSum += Math.abs(error);
        errorRootSum += Math.pow(error, 2);
        total++;
      }
    }

    // Compute accuracy of algorithm.
    System.out.printf("Mean Absolute Error: %.2f\n", (errorAbsSum / total));
    System.out.printf("Root Mean Squared Error: %.2f\n", (Math.sqrt(errorRootSum / total)));
  }
  /**
   * Calculates the weight for two users over all items they share recorded ratings for.
   *
   * @param train User object for particular user in training data.
   * @param test User object for active user.
   * @return Returns a Double representing the computed weight.
   */
  public static double calculateWeight(User train, User test) {
    double numSum = 0;
    double denTestSum = 0;
    double denTrainSum = 0;
    Map<Integer, Rating> ratings = train.getRatings();

    for (Rating r : test.getRatings().values()) {
      if (ratings.containsKey(r.getMovieId())) {
        numSum +=
            (r.getRating() - test.getMean())
                * (ratings.get(r.getMovieId()).getRating() - train.getMean());
        denTestSum += Math.pow(r.getRating() - test.getMean(), 2);
        denTrainSum += Math.pow(ratings.get(r.getMovieId()).getRating() - train.getMean(), 2);
      }
    }
    return (denTestSum == 0 || denTrainSum == 0) ? 0 : numSum / Math.sqrt(denTestSum * denTrainSum);
  }