package tudo.streamingrec.algorithms;

import java.util.List;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import tudo.streamingrec.data.ClickData;
import tudo.streamingrec.data.Item;
import tudo.streamingrec.data.Transaction;

/**
 * Post-filters the results of another algorithm based on the popularity of the candidate items.
 * @author MK, MJ
 *
 */
public class PopularityPostFiltering extends Algorithm {
	//the minimum click count each item must have to not be filtered from the recommendation list
	private int minClickCount = 100;// default = 100 clicks
	//the underlying algorithm (set via JSON config)
	protected Algorithm mainStrategy;
	//should we fill append the items that were filtered from the result list again at the end of the list?
	private boolean fallback = false;
	
	//a map with click counts for each item
	private Long2IntOpenHashMap itemClickCount = new Long2IntOpenHashMap();

	@Override
	protected void trainInternal(List<Item> items, List<ClickData> clickData) {
		//if new click arrived, update the click counts for each item
		for (ClickData c : clickData) {
			increaseClickCount(c.click);
		}
		//let the underlying strategy train
		mainStrategy.train(items, clickData);
	}

	@Override
	public LongArrayList recommendInternal(ClickData clickData) {
		//filter out items with low overall click counts
		//first, retrieve the recommendation results of the underlying algorithm
		LongArrayList rec = mainStrategy.recommendInternal(clickData);
		
		//create lists of filtered items and retained items
		LongArrayList filteredRec = new LongArrayList();
		LongArrayList filteredRecNotMatch = new LongArrayList();
		//iterate over the recommendation list of the underlying strategy
		for (int j = 0; j < rec.size(); j++) {
			long i = rec.getLong(j);
			//filter items if they do not have enough clicks
			if ((itemClickCount.containsKey(i)) && (itemClickCount.get(i) >= minClickCount)) {
				filteredRec.add(i);
			} else if (fallback) {
				//if we have a fallback, add the filtered item to the fallback list
				filteredRecNotMatch.add(i);
			}
		}
		//merge the filtered list with the fallback list (empty in case fallback==false)
		filteredRec.addAll(filteredRecNotMatch);
		//return the filtered list
		return filteredRec;
	}

	/**
	 * the minimum click count each item must have to not be filtered from the recommendation list
	 * @param minClickCount -
	 */
	public void setMinClickCount(int minClickCount) {
		this.minClickCount = minClickCount;
	}

	/**
	 * Set the underlying strategy (done via JSON config)
	 * @param algorithm -
	 */
	public void setMainStrategy(Algorithm algorithm) {
		mainStrategy = algorithm;
	}

	/**
	 * should we fill append the items that were filtered from the result list again at the end of the list?
	 * @param fallback -
	 */
	public void setFallback(boolean fallback) {
		this.fallback = fallback;
	}

	/**
	 * increases the click count of an item that has just been clicked on by 1
	 * @param transaction -
	 */
	private void increaseClickCount(Transaction transaction) {
		if (itemClickCount.containsKey(transaction.item.id)) {
			itemClickCount.put(transaction.item.id, itemClickCount.get(transaction.item.id) + 1);
		} else {
			itemClickCount.put(transaction.item.id, 1);
		}
	}

}