/*
 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.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.apache.commons.lang3.StringEscapeUtils;

import com.google.common.primitives.Ints;

import file.BookmarkReader;

public class Utilities {

	public static int REC_LIMIT = 20;
	public static boolean FILTER_OWN = false;
	
    private final static String REV_START = "<rev xml:space=\"preserve\">";
    private final static String REV_END = "</rev>";

    public static Bookmark getLastBookmarkOfUser(List<List<Bookmark>> userBookmarks, int userID) {
    	List<Bookmark> bookmarks = userBookmarks.get(userID);
    	return bookmarks.get(bookmarks.size() - 1);
    }
    
    public static boolean isEntityEvaluated(BookmarkReader reader, int id, Integer minBookmarks, Integer maxBookmarks,
            boolean resource) {
        if (reader == null || id == -1) {
            return true;
        }
        List<Integer> entityCounts = (resource ? reader.getResourceCounts() : reader.getUserCounts());
        if (id < entityCounts.size()) {
            int count = entityCounts.get(id);
            if (minBookmarks != null) {
                if (count < minBookmarks.intValue()) {
                    return false;
                }
            } else if (maxBookmarks != null) {
                if (count > maxBookmarks.intValue()) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    public static Map<Integer, Integer> getPopMap(BookmarkReader reader) {
		Map<Integer, Integer> countMap = new LinkedHashMap<Integer, Integer>();
		for (int i = 0; i < reader.getTagCounts().size(); i++) {
			countMap.put(i, reader.getTagCounts().get(i));
		}
		return countMap;
    }
    
    public static List<Map<Integer, Integer>> getUserMaps(List<Bookmark> userLines) {
        List<Map<Integer, Integer>> userMaps = new ArrayList<Map<Integer, Integer>>();
        for (Bookmark data : userLines) {
            int userID = data.getUserID();
            if (userID >= userMaps.size()) {
                userMaps.add(Utilities.mergeListWithMap(data.getTags(), new LinkedHashMap<Integer, Integer>()));
            } else {
                Utilities.mergeListWithMap(data.getTags(), userMaps.get(userID));
            }
        }
        return userMaps;
    }
    
    public static List<Map<Integer, Double>> getFloatUserMaps(List<Bookmark> userLines) {
        List<Map<Integer, Double>> userMaps = new ArrayList<Map<Integer, Double>>();
        for (Bookmark data : userLines) {
            int userID = data.getUserID();
            if (userID >= userMaps.size()) {
                userMaps.add(Utilities.mergeFloatListWithMap(data.getTags(), new LinkedHashMap<Integer, Double>()));
            } else {
                Utilities.mergeFloatListWithMap(data.getTags(), userMaps.get(userID));
            }
        }
        return userMaps;
    }

    public static List<Map<Integer, Integer>> getResMaps(List<Bookmark> userLines) {
        List<Map<Integer, Integer>> resMaps = new ArrayList<Map<Integer, Integer>>();
        for (Bookmark data : userLines) {
            int resID = data.getResourceID();
            if (resID >= resMaps.size()) {
                resMaps.add(Utilities.mergeListWithMap(data.getTags(), new LinkedHashMap<Integer, Integer>()));
            } else {
                Utilities.mergeListWithMap(data.getTags(), resMaps.get(resID));
            }
        }
        return resMaps;
    }

    public static List<Map<Integer, Integer>> getUserTopics(List<Bookmark> userLines) {
        List<Map<Integer, Integer>> userMaps = new ArrayList<Map<Integer, Integer>>();
        for (Bookmark data : userLines) {
            int userID = data.getUserID();
            if (userID >= userMaps.size()) {
                userMaps.add(Utilities.mergeListWithMap(data.getCategories(), new LinkedHashMap<Integer, Integer>()));
            } else {
                Utilities.mergeListWithMap(data.getCategories(), userMaps.get(userID));
            }
        }
        return userMaps;
    }

    public static List<Map<Integer, Integer>> getResTopics(List<Bookmark> userLines) {
        List<Map<Integer, Integer>> resMaps = new ArrayList<Map<Integer, Integer>>();
        for (Bookmark data : userLines) {
            int resID = data.getResourceID();
            if (resID >= resMaps.size()) {
                resMaps.add(Utilities.mergeListWithMap(data.getCategories(), new LinkedHashMap<Integer, Integer>()));
            } else {
                Utilities.mergeListWithMap(data.getCategories(), resMaps.get(resID));
            }
        }
        return resMaps;
    }

    public static List<Map<Integer, Double>> getUniqueTopicMaps(List<Bookmark> userLines, boolean resource) {
        List<Map<Integer, Double>> resMaps = new ArrayList<Map<Integer, Double>>();
        for (Bookmark data : userLines) {
            int resID = resource ? data.getResourceID() : data.getUserID();
            Map<Integer, Double> rMap = null;
            if (resID >= resMaps.size()) {
                rMap = new LinkedHashMap<Integer, Double>();
                resMaps.add(rMap);
            } /*
               * else { rMap = resMaps.get(resID); }
               */
            if (rMap != null) {
                for (int cat : data.getCategories()) {
                    rMap.put(cat, 1.0);
                }
            }
        }
        return resMaps;
    }

    // returns for each tag the list of resources this tag was used for
    public static List<Map<Integer, Double>> getResourceMapsForTags(List<Bookmark> userLines) {
        List<Map<Integer, Double>> tagMaps = new ArrayList<Map<Integer, Double>>();
        for (Bookmark data : userLines) {
            for (int tagID : data.getTags()) {
                Map<Integer, Double> tagMap = null;
                if (tagID >= tagMaps.size()) {
                    tagMap = new LinkedHashMap<Integer, Double>();
                    tagMaps.add(tagMap);
                } else {
                    tagMap = tagMaps.get(tagID);
                }
                if (tagMap != null) {
                    Double count = tagMap.get(data.getResourceID());
                    tagMap.put(data.getResourceID(), count == null ? 1.0 : count.doubleValue() + 1.0);
                }
            }
        }
        return tagMaps;
    }

    public static List<int[]> createRandomBaseline(int from, int to, int count) {
        List<int[]> baseline = new ArrayList<int[]>();

        for (int i = 0; i < count; i++) {
            baseline.add(Ints.toArray(getRandomIndices(from, to)));
        }

        return baseline;
    }

    public static List<Integer> getRandomIndices(int from, int to) {
        List<Integer> indices = new ArrayList<Integer>();
        for (int j = from; j <= to; j++) {
            indices.add(j);
        }
        Collections.shuffle(indices);
        return indices;
    }

    public static boolean writeStringToFile(String filename, String stringToWrite) {
        try {
            FileWriter writer = new FileWriter(new File(filename));
            BufferedWriter bw = new BufferedWriter(writer);
            bw.write(stringToWrite);
            bw.flush();
            bw.close();
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    public static List<String> readFileToStringList(String filename) {
        List<String> returnList = new ArrayList<String>();

        try {
            // FileReader reader = new FileReader(new File(filename));
            InputStreamReader reader = new InputStreamReader(new FileInputStream(new File(filename)), "UTF8");
            BufferedReader br = new BufferedReader(reader);
            String line = "";
            while ((line = br.readLine()) != null) {
                returnList.add(line);
            }
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return returnList;
    }

    public static Map<Integer, Double> getRelativeMapFromList(List<Integer> from) {
        Map<Integer, Double> to = new LinkedHashMap<Integer, Double>();
        for (Integer value : from) {
            Double count = to.get(value);
            to.put(value, (count != null ? count + 1.0 : 1.0));
        }
        for (Map.Entry<Integer, Double> entry : to.entrySet()) {
            entry.setValue(entry.getValue() / (double) from.size());
        }
        return to;
    }

    public static Map<Integer, Integer> mergeListWithMap(List<Integer> from, Map<Integer, Integer> to) {
        for (Integer value : from) {
            Integer count = to.get(value);
            to.put(value, (count != null ? count + 1 : 1));
        }
        return to;
    }
    
    public static Map<Integer, Double> mergeFloatListWithMap(List<Integer> from, Map<Integer, Double> to) {
        for (Integer value : from) {
            Double count = to.get(value);
            to.put(value, (count != null ? count + 1 : 1));
        }
        return to;
    }

    public static Map<Integer, Double> mergeProbMaps(BookmarkReader reader, Map<Integer, Double> from,
            Map<Integer, Double> to, double lambda) {
        Map<Integer, Double> resultMap = new LinkedHashMap<Integer, Double>();
        for (int i = 0; i < reader.getTags().size(); i++) {
            Double fromVal = from.get(i);
            if (fromVal == null) {
                fromVal = 0.0;
            }
            Double toVal = to.get(i);
            if (toVal == null) {
                toVal = 0.0;
            }
            if (fromVal > 0.0 || toVal > 0.0) {
                resultMap.put(i, lambda * fromVal + (1.0 - lambda) * toVal);
            }
        }
        return resultMap;
        /*
         * for (Map.Entry<Integer, Double> entry : from.entrySet()) { Double
         * prob = to.get(entry.getKey()); if (prob == null) { prob = 0.0;
         * System.out.println("Merge maps: value not found"); }
         * to.put(entry.getKey(), lambda * entry.getValue() + (1.0 - lambda) *
         * prob); } return to;
         */
    }

    public static String getWikiContent(String xmlString) {
        int startIndex = xmlString.indexOf(REV_START);
        int endIndex = xmlString.indexOf(REV_END);
        if (startIndex != -1 && endIndex != -1) {
            return StringEscapeUtils.unescapeHtml4(xmlString.substring(startIndex + REV_START.length(), endIndex));
        }
        return null;
    }

    public static List<String> getTagNames(List<Integer> tagIDs, BookmarkReader reader) {
        List<String> tagNames = new ArrayList<String>();
        for (int id : tagIDs) {
            tagNames.add(reader.getTags().get(id));
        }
        return tagNames;
    }

    public static Set<Integer> getUsersByResource(List<Bookmark> userLines, int resID) {
        Set<Integer> userList = new HashSet<Integer>();
        for (Bookmark data : userLines) {
            if (data.getResourceID() == resID) {
                userList.add(data.getUserID());
            }
        }
        return userList;
    }

    public static List<Set<Integer>> getUserResourceLists(List<Bookmark> userLines) {
        List<Set<Integer>> userLists = new ArrayList<Set<Integer>>();
        for (Bookmark data : userLines) {
            int userID = data.getUserID();
            Set<Integer> resList = null;
            if (userID >= userLists.size()) {
                resList = new HashSet<Integer>();
                userLists.add(resList);
            } else {
                resList = userLists.get(userID);
            }
            resList.add(data.getResourceID());
        }
        return userLists;
    }
    
    public static List<Map<Integer, Integer>> getUserResourceMaps(List<Bookmark> userLines) {
        List<Map<Integer, Integer>> userLists = new ArrayList<Map<Integer, Integer>>();
        for (Bookmark data : userLines) {
            int userID = data.getUserID();
            Map<Integer, Integer> resList = null;
            if (userID >= userLists.size()) {
                resList = new LinkedHashMap<Integer, Integer>();
                userLists.add(resList);
            } else {
                resList = userLists.get(userID);
            }
            Integer resVal = resList.get(data.getResourceID());
            if (resVal == null) {
            	resList.put(data.getResourceID(), 1);
            } else {
            	resList.put(data.getResourceID(), resVal + 1);
            }
        }
        return userLists;
    }

    public static List<Map<Integer, Double>> getRelativeTagMaps(List<Bookmark> userLines, boolean resource) {
        List<Map<Integer, Integer>> maps = (resource ? getResMaps(userLines) : getUserMaps(userLines));
        List<Map<Integer, Double>> relMaps = new ArrayList<Map<Integer, Double>>();
        for (Map<Integer, Integer> m : maps) {
            double count = getMapCount(m);
            Map<Integer, Double> relM = new LinkedHashMap<Integer, Double>();
            for (Map.Entry<Integer, Integer> entry : m.entrySet()) {
                relM.put(entry.getKey(), (double) entry.getValue().intValue() / count);
            }
            relMaps.add(relM);
        }
        return relMaps;
    }

    public static List<Map<Integer, Double>> getDoubleTagMaps(List<Bookmark> userLines, boolean resource) {
        List<Map<Integer, Integer>> maps = (resource ? getResMaps(userLines) : getUserMaps(userLines));
        List<Map<Integer, Double>> relMaps = new ArrayList<Map<Integer, Double>>();
        for (Map<Integer, Integer> m : maps) {
            Map<Integer, Double> relM = new LinkedHashMap<Integer, Double>();
            for (Map.Entry<Integer, Integer> entry : m.entrySet()) {
                relM.put(entry.getKey(), entry.getValue().doubleValue());
            }
            relMaps.add(relM);
        }
        return relMaps;
    }

    public static List<Map<Integer, Double>> getRelativeTopicMaps(List<Bookmark> userLines, boolean resource) {
        List<Map<Integer, Integer>> maps = (resource ? getResTopics(userLines) : getUserTopics(userLines));
        List<Map<Integer, Double>> relMaps = new ArrayList<Map<Integer, Double>>();
        for (Map<Integer, Integer> m : maps) {
            double count = getMapCount(m);
            Map<Integer, Double> relM = new LinkedHashMap<Integer, Double>();
            for (Map.Entry<Integer, Integer> entry : m.entrySet()) {
                relM.put(entry.getKey(), (double) entry.getValue().intValue() / count);
            }
            relMaps.add(relM);
        }
        return relMaps;
    }

    public static List<Map<Integer, Double>> getNormalizedMaps(List<Bookmark> userLines, boolean resource) {
        List<Map<Integer, Integer>> maps = (resource ? getResMaps(userLines) : getUserMaps(userLines));
        List<Map<Integer, Double>> relMaps = new ArrayList<Map<Integer, Double>>();
        for (Map<Integer, Integer> m : maps) {
            double denom = getMapDenom(m);
            Map<Integer, Double> relM = new LinkedHashMap<Integer, Double>();
            for (Map.Entry<Integer, Integer> entry : m.entrySet()) {
                relM.put(entry.getKey(), Math.exp((double) entry.getValue().intValue()) / denom);
            }
            relMaps.add(relM);
        }
        return relMaps;
    }

    public static List<List<Bookmark>> getBookmarks(List<Bookmark> lines, boolean resource) {
        List<List<Bookmark>> bookmarks = new ArrayList<List<Bookmark>>();
        for (Bookmark data : lines) {
            int id = (resource ? data.getResourceID() : data.getUserID());
            List<Bookmark> b = null;
            if (id >= bookmarks.size()) {
                b = new ArrayList<Bookmark>();
                bookmarks.add(b);
            } else {
                b = bookmarks.get(id);
            }
            b.add(data);
        }
        return bookmarks;
    }

    public static List<Map<Integer, Double>> getUsedEntities(List<Bookmark> lines, boolean resource,
            List<Map<Integer, Double>> valueMaps) {
        List<Map<Integer, Double>> entities = new ArrayList<Map<Integer, Double>>();
        for (Bookmark data : lines) {
            int id = (resource ? data.getResourceID() : data.getUserID());
            int entityID = (resource ? data.getUserID() : data.getResourceID());
            Map<Integer, Double> values = null;
            if (valueMaps != null && id < valueMaps.size()) {
                values = valueMaps.get(id);
            }

            Map<Integer, Double> e = null;
            if (id >= entities.size()) {
                e = new LinkedHashMap<Integer, Double>();
                entities.add(e);
            } else {
                e = entities.get(id);
            }
            double val = 1.0;
            if (values != null) {
                val = 0.0;
                for (int t : data.getTags()) {
                    Double v = values.get(t);
                    if (v != null) {
                        val += v.doubleValue();
                    }
                }
            }
            Double oldVal = e.get(entityID);
            e.put(entityID, oldVal != null ? oldVal.doubleValue() + val : val);
        }
        return entities;
    }

    // returns a Map<TagId, frequency> for all lines in userLines
    public static Map<Integer, Integer> getTags(List<Bookmark> userLines) {
        Map<Integer, Integer> resTags = new LinkedHashMap<Integer, Integer>();
        for (Bookmark data : userLines) {
            for (Integer tag : data.getTags()) {
                int count = resTags.containsKey(tag) ? resTags.get(tag) : 0;
                resTags.put(tag, count + 1);
            }
        }
        return resTags;
    }

    public static double getMapCount(Map<Integer, Integer> map) {
        double sum = 0.0;
        if (map != null) {
            for (Integer val : map.values()) {
                sum += (double) val;
            }
        }
        return sum;
    }

    public static double getMapDenom(Map<Integer, Integer> map) {
        double sum = 0.0;
        if (map != null) {
            for (Integer val : map.values()) {
                sum += Math.exp((double) val);
            }
        }
        return sum;
    }

    public static double getSmoothedTagValue(double userVal, double userTagCount, double resVal, double resTagCount,
            double pt) {
        userVal = Math.log(userTagCount + 1.0) / Math.log(2.0) * userVal
                + Math.log(resTagCount + 1.0) / Math.log(2.0) * pt;
        resVal = Math.log(resTagCount + 1.0) / Math.log(2.0) * resVal
                + Math.log(userTagCount + 1.0) / Math.log(2.0) * pt;
        return userVal * resVal / pt;
    }

    public static double getJaccardSim(Map<Integer, Integer> targetMap, Map<Integer, Integer> nMap) {
        Set<Integer> unionSet = new HashSet<Integer>(targetMap.keySet());
        Set<Integer> intersectSet = new HashSet<Integer>(targetMap.keySet());
        unionSet.addAll(nMap.keySet());
        intersectSet.retainAll(nMap.keySet());
        if (intersectSet.size() == 0 || unionSet.size() == 0)
            return 0.0;

        return (double) intersectSet.size() / (double) unionSet.size();
    }

    public static double getJaccardSimLists(List<Integer> targetMap, List<Integer> nMap) {
        Set<Integer> unionSet = new HashSet<Integer>(targetMap);
        Set<Integer> intersectSet = new HashSet<Integer>(targetMap);
        unionSet.addAll(nMap);
        intersectSet.retainAll(nMap);
        if (intersectSet.size() == 0 || unionSet.size() == 0)
            return 0.0;
        return (double) intersectSet.size() / (double) unionSet.size();
    }

    public static double getJaccardFloatSim(Map<Integer, Double> targetMap, Map<Integer, Double> nMap) {
        Set<Integer> unionSet = new HashSet<Integer>(targetMap.keySet());
        Set<Integer> intersectSet = new HashSet<Integer>(targetMap.keySet());
        unionSet.addAll(nMap.keySet());
        intersectSet.retainAll(nMap.keySet());
        if (intersectSet.size() == 0 || unionSet.size() == 0)
            return 0.0;
        return (double) intersectSet.size() / (double) unionSet.size();
    }

    public static double getCosineSim(Map<Integer, Integer> targetMap, Map<Integer, Integer> nMap) {
        Set<Integer> both = new HashSet<Integer>(targetMap.keySet());
        both.retainAll(nMap.keySet());
        if (both.size() == 0) {
        	return 0.0;
        }
        double scalar = 0.0, norm1 = 0.0, norm2 = 0.0;
        for (int k : both)
            scalar += (targetMap.get(k) * nMap.get(k));
        for (int k : targetMap.keySet())
            norm1 += (targetMap.get(k) * targetMap.get(k));
        for (int k : nMap.keySet())
            norm2 += (nMap.get(k) * nMap.get(k));
        if (Math.sqrt(norm1 * norm2) == 0.0) {       	
            return 0.0;
        }
        return scalar / Math.sqrt(norm1 * norm2);
    }

    public static double getCosineSimList(List<Integer> targetList, List<Integer> nList) {
        Map<Integer, Integer> targetMap = getMapForList(targetList);
        Map<Integer, Integer> nMap = getMapForList(nList);
        Set<Integer> both = new HashSet<Integer>(targetMap.keySet());
        both.retainAll(nMap.keySet());
        double scalar = 0.0, norm1 = 0.0, norm2 = 0.0;
        for (int k : both)
            scalar += (targetMap.get(k) * nMap.get(k));
        for (int k : targetMap.keySet())
            norm1 += (targetMap.get(k) * targetMap.get(k));
        for (int k : nMap.keySet())
            norm2 += (nMap.get(k) * nMap.get(k));
        if (Math.sqrt(norm1 * norm2) == 0.0)
            return 0.0;
        return scalar / Math.sqrt(norm1 * norm2);
    }

    public static double getCosineFloatSim(Map<Integer, Double> targetMap, Map<Integer, Double> nMap) {
        Set<Integer> both = new HashSet<Integer>(targetMap.keySet());
        both.retainAll(nMap.keySet());
        double scalar = 0.0, norm1 = 0.0, norm2 = 0.0;
        for (int k : both) {
            /*
            System.out.println(" k >> " + k);
            System.out.println(" nMap >> " + nMap);
            System.out.println(" targetMap >> " + targetMap);
            System.out.println(" nMap.get(k) >> " + nMap.get(k));
            System.out.println(" targetMap.get(k) >> " + targetMap.get(k));
            */
            scalar += (targetMap.get(k) * nMap.get(k));
        }
        for (int k : targetMap.keySet())
            norm1 += (targetMap.get(k) * targetMap.get(k));
        for (int k : nMap.keySet())
            norm2 += (nMap.get(k) * nMap.get(k));
        if (Math.sqrt(norm1 * norm2) == 0.0)
            return 0.0;
        return scalar / Math.sqrt(norm1 * norm2);
    }

    public static double getPearsonSim(List<Bookmark> userRatings, List<Bookmark> neighborRatings) {
        return PearsonSimilarityCalculator.getPearsonSim(userRatings, neighborRatings);
    }

    public static long getBaselineTimestamp(List<Bookmark> testLines, int refID, boolean resource) {
        if (testLines != null) {
            long maxTimestamp = -1;
            for (Bookmark data : testLines) {
                int id = resource ? data.getResourceID() : data.getUserID();
                if (id == refID) {
                    long timestamp = Long.parseLong(data.getTimestamp());
                    if (timestamp > maxTimestamp) {
                        maxTimestamp = timestamp;
                    }
                }
            }
            return maxTimestamp;
        }
        return System.currentTimeMillis() / 1000;
    }

    public static long getBaselineTimestampEff(List<Bookmark> testLines, int refID) {
        if (testLines != null) {
            for (int i = testLines.size() - 1; i >= 0; i--) {
                Bookmark data = testLines.get(i);
                if (data.getUserID() == refID) {
                    return Long.parseLong(data.getTimestamp());
                }
            }
            return -1;
        }
        return System.currentTimeMillis() / 1000;
    }

    public static Map<Integer, Integer> getMapForList(List<Integer> list) {
        Map<Integer, Integer> map = new LinkedHashMap<Integer, Integer>();
        for (Integer val : list) {
            map.put(val, 1);
        }
        return map;
    }

    public static Map<Integer, Double> getAllEntities(List<Bookmark> trainList, boolean resource) {
        Map<Integer, Double> allEntities = new LinkedHashMap<Integer, Double>();
        for (Bookmark data : trainList) {
            if (resource) {
                allEntities.put(data.getResourceID(), 0.0);
            } else {
                allEntities.put(data.getUserID(), 0.0);
            }
        }
        return allEntities;
    }

    public static Map<Integer, Double> getNeighbors(int userID, int resID, Map<Integer, Double> allNeighbors,
            List<Map<Integer, Double>> userMaps, List<Bookmark> trainList, Similarity sim, boolean sorting) {

        Map<Integer, Double> neighbors = new LinkedHashMap<Integer, Double>();
        // List<Map<Integer, Integer>> neighborMaps = new ArrayList<Map<Integer,
        // Integer>>();
        Map<Integer, Double> targetMap = null;

        // get all users that have tagged the resource
        if (resID != -1) {
            for (Bookmark data : trainList) {
                if (data.getUserID() != userID) {
                    if (data.getResourceID() == resID) {
                        neighbors.put(data.getUserID(), 0.0);
                    }
                }
            }
        }
        // if list is empty, use all users
        if (neighbors.size() == 0) {
            // for (Bookmark data : trainList) {
            // if (data.getUserID() != userID) {
            // neighbors.put(data.getUserID(), 0.0);
            // }
            // }
            neighbors.putAll(allNeighbors);
        }

        if (userID < userMaps.size()) {
            targetMap = userMaps.get(userID);
        } else {
            return neighbors;
        }
        // for (int id : neighbors.keySet()) {
        // neighborMaps.add(this.userMaps.get(id));
        // }
        // double lAverage = getLAverage(neighborMaps);
        for (Map.Entry<Integer, Double> entry : neighbors.entrySet()) {
            Map<Integer, Double> nMap = userMaps.get(entry.getKey());
            if (userID != entry.getKey()/* && !nMap.isEmpty() */) {
                Double bm25Value = (sim == Similarity.JACCARD ? Utilities.getJaccardFloatSim(targetMap, nMap)
                        : Utilities.getCosineFloatSim(targetMap, nMap));
                // if (resID == -1) {
                // bm25Value = Math.pow(bm25Value.doubleValue(), 3);
                // }
                // getBM25Value(neighborMaps, lAverage, targetMap,
                // this.userMaps.get(entry.getKey()));
                if (!bm25Value.isInfinite() && !bm25Value.isNaN()) {
                    entry.setValue(bm25Value);
                }
            }
        }

        if (sorting) {
            // return the sorted neighbors
            Map<Integer, Double> sortedNeighbors = new TreeMap<Integer, Double>(new DoubleMapComparator(neighbors));
            sortedNeighbors.putAll(neighbors);
            return sortedNeighbors;
        } else {
            return neighbors;
        }
    }

    public static Map<Integer, Double> getSimResources(int userID, int resID, List<Integer> userResources,
            Map<Integer, Double> allResources, List<Map<Integer, Double>> resMaps, List<Bookmark> trainList,
            Similarity sim, boolean sorting) {
        Map<Integer, Double> resources = new LinkedHashMap<Integer, Double>();
        Map<Integer, Double> targetMap = null;
        if (resID < resMaps.size()) {
            targetMap = resMaps.get(resID);
        }
        if (targetMap == null || targetMap.isEmpty()) {
            resources.putAll(allResources);
            return resources;
        }

        // get all resources that have been tagged by the user
        if (userID != -1) {
            for (Bookmark data : trainList) {
                if (data.getResourceID() != resID) {
                    if (data.getUserID() == userID) {
                        resources.put(data.getResourceID(), 0.0);
                    }
                }
            }
        }
        // if list is empty, use all resources
        if (resources.size() == 0) {
            resources.putAll(allResources);
        }

        for (Map.Entry<Integer, Double> entry : resources.entrySet()) {
            Map<Integer, Double> rMap = resMaps.get(entry.getKey());
            if ((userResources == null || !userResources.contains(entry.getKey())) && !rMap.isEmpty()
                    && entry.getKey() != resID) {
                double bm25Value = (sim == Similarity.JACCARD ? Utilities.getJaccardFloatSim(targetMap, rMap)
                        : Utilities.getCosineFloatSim(targetMap, rMap));
                entry.setValue(bm25Value);
            }
        }

        if (sorting) {
            // return the sorted resources
            Map<Integer, Double> sortedResources = new TreeMap<Integer, Double>(new DoubleMapComparator(resources));
            sortedResources.putAll(resources);
            return sortedResources;
        } else {
            return resources;
        }
    }

    public static Map<Integer, Double> getSimResourcesForUser(int userID, Map<Integer, Double> allResources,
            List<Map<Integer, Double>> userMaps, List<Map<Integer, Double>> resMaps, List<Integer> userResources,
            Similarity sim, boolean sorting) {
        Map<Integer, Double> resources = new LinkedHashMap<Integer, Double>();
        Map<Integer, Double> targetMap = null;
        if (userID < userMaps.size()) {
            targetMap = userMaps.get(userID);
        }
        if (targetMap == null || targetMap.isEmpty()) {
            return resources;
        }
        resources.putAll(allResources);

        for (Map.Entry<Integer, Double> entry : resources.entrySet()) {
            Map<Integer, Double> rMap = resMaps.get(entry.getKey());
            if (!rMap.isEmpty() && !userResources.contains(entry.getKey())) {
                double bm25Value = (sim == Similarity.JACCARD ? Utilities.getJaccardFloatSim(targetMap, rMap)
                        : Utilities.getCosineFloatSim(targetMap, rMap));
                entry.setValue(bm25Value);
            }
        }

        // return the sorted resources
        if (sorting) {
            Map<Integer, Double> sortedResources = new TreeMap<Integer, Double>(new DoubleMapComparator(resources));
            sortedResources.putAll(resources);
            return sortedResources;
        } else {
            return resources;
        }
    }

    public static Map<Integer, Double> getSimUsersForResource(int resID, Map<Integer, Double> allUsers,
            List<Map<Integer, Double>> userMaps, List<Map<Integer, Double>> resMaps, List<Integer> resourceUsers,
            Similarity sim, boolean sorting) {
        Map<Integer, Double> users = new LinkedHashMap<Integer, Double>();
        Map<Integer, Double> targetMap = null;
        if (resID < resMaps.size()) {
            targetMap = resMaps.get(resID);
        }
        if (targetMap == null || targetMap.isEmpty()) {
            return users;
        }
        users.putAll(allUsers);

        for (Map.Entry<Integer, Double> entry : users.entrySet()) {
            Map<Integer, Double> uMap = userMaps.get(entry.getKey());
            if (!uMap.isEmpty() && !resourceUsers.contains(entry.getKey())) {
                double simValue = (sim == Similarity.JACCARD ? Utilities.getJaccardFloatSim(targetMap, uMap)
                        : Utilities.getCosineFloatSim(targetMap, uMap));
                entry.setValue(simValue);
            }
        }

        // return the sorted users
        if (sorting) {
            Map<Integer, Double> sortedUsers = new TreeMap<Integer, Double>(new DoubleMapComparator(users));
            sortedUsers.putAll(users);
            return sortedUsers;
        } else {
            return users;
        }
    }
}
