/*
 TagRecommender:
 A framework to implement and evaluate algorithms for the recommendation
 of tags.
 Copyright (C) 2013 Dominik Kowald
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero 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 Affero General Public License for more details.
 
 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package common;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Calculator for the Pearson similarity between two user ratings
 * @author elacic
 *
 */
public class PearsonSimilarityCalculator {
	
	/**
	 * Calculates the Pearson-similarity based on the resource ratings of a user and the resource ratings of his neighbor
	 * @param userRatings resource ratings of the user
	 * @param neighborRatings resource ratings of the user's neighbor
	 * @return the Pearson similarity
	 */
	public static double getPearsonSim(List<Bookmark> userRatings, List<Bookmark> neighborRatings) {
		double differenceSum = 0.0;
		double userSquareDifferenceSum = 0.0;
		double neighboorSquareDifferenceSum = 0.0;
		
		Map<Integer, List<Double>> coratedItems = new HashMap<Integer, List<Double>>();
		
		double userRatingAverage = calculateAverageAndFillCoratedItems(userRatings, coratedItems);
		double neighboorRatingAverage = calculateAverageAndFillCoratedItems(neighborRatings, coratedItems);
		
		for (Integer resourceId : coratedItems.keySet()) {
			List<Double> resourceRatings = coratedItems.get(resourceId);
			
			if (resourceRatings.size() < 2) {
				continue;
			}
			
			Double userResourceRating = resourceRatings.get(0);
			Double neighboorResourceRating = resourceRatings.get(1);
			
			differenceSum += (userResourceRating - userRatingAverage) * (neighboorResourceRating - neighboorRatingAverage);
			
			userSquareDifferenceSum += Math.pow( (userResourceRating - userRatingAverage) , 2);
			neighboorSquareDifferenceSum += Math.pow( (neighboorResourceRating - neighboorRatingAverage) , 2);
		}
		
		return differenceSum / (Math.sqrt(userSquareDifferenceSum) * Math.sqrt(neighboorSquareDifferenceSum));
	}

	/**
	 * Calculates the average of a user's ratings and fills the coratedItems map
	 * @param userRatings
	 * @param coratedItems
	 * @return
	 */
	private static double calculateAverageAndFillCoratedItems(List<Bookmark> userRatings, Map<Integer, List<Double>> coratedItems) {
		double ratingSum = 0.0;
		for (Bookmark userRating : userRatings) {
			double filteredRating = filterRating(userRating.getRating());
			ratingSum += filteredRating;
			
			fillCoratedItems(coratedItems, userRating.getResourceID(), filteredRating);
		}
		return ratingSum / userRatings.size();
	}

	/**
	 * Fills the mapping on the rating for a resource
	 * @param coratedItems mapping for resource rating which will be filled
	 * @param resource resource that is rated
	 * @param filteredRating rating for the resource
	 */
	private static void fillCoratedItems(Map<Integer, List<Double>> coratedItems, Integer resource, double filteredRating) {
		List<Double> resourceRating = null;
		
		if (coratedItems.containsKey(resource)) {
			resourceRating = coratedItems.get(resource);
		} else {
			resourceRating = new ArrayList<Double>();
		}
		
		resourceRating.add(filteredRating);
		coratedItems.put(resource, resourceRating);
	}
	
	/**
	 * For read only ratings which were set to -1 by convention in the CUL dataset
	 * @param rating rating to be filtered
	 * @return filtered rating
	 */
	private static double filterRating(double rating) {
		if (rating == -1) {
			return 3;
		}
		
		return rating;
	}
}
