package userrecommender;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import Jama.Matrix;



/**
 * Implements the Spear Algorithm. This implementation is hidden therefore package
 * scope. Please use the appropriate wrapper class which transforms the given
 * data set into an appropriate input for this algorithm.
 * 
 * @author ilire.mavriqi
 *
 */
class SpearAlgorithm {

	private static final int DFEAULT_NUMBER_OF_ITERATIONS = 250;
	private Matrix matrix;
	private HashMap<Integer, TreeSet<UserActivity>> activities = null;

	private LinkedList<Integer> uniqueUsers = null;
	private List<Integer> uniqueResources = null;

	private Map<Integer, Double> expertiseResult = null;
	private Map<Integer, Double> qualityResult = null;

	/**
	 * creates a new Object.
	 * 
	 * @param activities
	 *            triples (resourceID, time stamp, userID). Presumption: the
	 *            given set is already sorted by time stamp whereby the
	 *            first entry is the earliest time stamp for the given resource.
	 * @param uniqueUsers
	 *            list of unique userIDs
	 * @param uniqueResources
	 *            list of unique resouceIDs
	 */
	SpearAlgorithm(HashMap<Integer, TreeSet<UserActivity>> activities,
			LinkedList<Integer> uniqueUsers, List<Integer> uniqueResources) {
		this.activities = activities;
		this.uniqueUsers = uniqueUsers;
		this.uniqueResources = uniqueResources;
	}

	/**
	 * Executes spear algorithm on the given data set by initializing the sparse
	 * matrix and calculating the credit scores.
	 * 
	 * @param numberOfIterations
	 *            number of iterations. Minimum number is 250.
	 */
	SpearAlgorithmResult execute(int numberOfIterations) {
		if (activities == null || activities.size() == 0
				|| uniqueUsers.size() == 0 || uniqueResources.size() == 0) {
			throw new IllegalArgumentException(
					"Invalid input data. Plase make sure that entries, unique users and unique resources do not equall zero");
		}

		int finalNumberOfIterations = numberOfIterations > DFEAULT_NUMBER_OF_ITERATIONS ? numberOfIterations
				: DFEAULT_NUMBER_OF_ITERATIONS;

		populateMatrix();

		applyCreditScores();

		generateScoreVectors(finalNumberOfIterations);
		
		SpearAlgorithmResult result = new SpearAlgorithmResult(expertiseResult,
				qualityResult);

		return result;
	}

	private void generateScoreVectors(int finalNumberOfIterations) {
		Matrix userExpertiseVector = getInitSparseVector(uniqueUsers.size());
		Matrix resourceQualityVector = getInitSparseVector ( uniqueResources.size());

		for (int i = 0; i < finalNumberOfIterations; i++) {
			
			userExpertiseVector = resourceQualityVector.times((matrix).transpose());
			resourceQualityVector = userExpertiseVector.times(matrix);
			userExpertiseVector.timesEquals(1. / getColumnSum (userExpertiseVector));   	// normalise
			resourceQualityVector.timesEquals(1. / getColumnSum(resourceQualityVector));  	// normalise
			System.out.println("iteration:  " + i +  "  time:  " + Calendar.getInstance().getTime() );
		}

		expertiseResult = new HashMap<Integer, Double>();
		qualityResult = new HashMap<Integer, Double>();

		for (int j = 0; j < userExpertiseVector.getColumnDimension(); j++) {
			expertiseResult.put( uniqueUsers.get(j),userExpertiseVector.get(0,j));
		}
		
		for (int j = 0; j < resourceQualityVector.getColumnDimension(); j++) {
			qualityResult.put(uniqueResources.get(j),resourceQualityVector.get(0,j));
		}
		
	}

	private Matrix getInitSparseVector(int size) {
		Matrix spVec = new Matrix(1, size);
		for (int i = 0; i < spVec.getColumnDimension(); i++) {
			spVec.set(0, i, 1d);
		}
		return spVec;
	}
	
	private double getColumnSum (Matrix matrix){
		double sum = 0;
		for(int j=0;j<matrix.getColumnDimension();j++) {
			sum += matrix.get(0, j);
		}
		return sum;
	}

	private void applyCreditScores() {
		
		for (int i = 0; i < matrix.getRowDimension(); i++) {
			for (int j = 0; j < matrix.getColumnDimension(); j++) {
				double creditScore = matrix.get(i, j);
					matrix.set(i, j, Math.sqrt(creditScore));
			}
		}
	}

	private void populateMatrix() {
		matrix = new Matrix(uniqueUsers.size(),
				uniqueResources.size());

		Iterator<Integer> resourceIterator = activities.keySet().iterator();

		while (resourceIterator.hasNext()) {

			long lastTimeStamp = 0;
			double lastScore = 0d;
			int currentProcessedUsers = 0;

			Integer resourceID = resourceIterator.next();
			TreeSet<UserActivity> timeUserTuple = activities.get(resourceID);

			for (UserActivity activity : timeUserTuple) {
				if (activity.getTimeStamp() == 0) {
					throw new IllegalArgumentException(
							"Invalid time stamp found for resource with id: "
									+ resourceID + " and user id:"
									+ activity.getUserId());
				}
				if (activity.getTimeStamp() == lastTimeStamp) {
					matrix.set(uniqueUsers.indexOf(activity.getUserId()),
							uniqueResources.indexOf(resourceID), lastScore);

				} else {
					lastScore =   activities.get(resourceID).size() - currentProcessedUsers;
					lastTimeStamp = activity.getTimeStamp();
					matrix.set(uniqueUsers.indexOf(activity.getUserId()),
							uniqueResources.indexOf(resourceID), lastScore);
				}
				currentProcessedUsers += 1;
			}
		}
	}
}
