public class CachedWAT2Annotator extends WAT2Annotator {
	private final static Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

	private static HashMap<String, byte[]> url2jsonCache = new HashMap<>();
	private static long flushCounter = 0;
	private static final int FLUSH_EVERY = 200;
	private static String resultsCacheFilename = null;

	public static class CachedWAT2AnnotatorBuilder extends WAT2Annotator.WAT2AnnotatorBuilder {
		public static CachedWAT2AnnotatorBuilder builder() {
			return new CachedWAT2AnnotatorBuilder();
		}

		public CachedWAT2Annotator build() {
			return new CachedWAT2Annotator(baseUri, tokenizer, method, debug, gcubeToken);
		}
	}

	protected CachedWAT2Annotator(String uri, String tokenizer, String method, int debug, String gcubeToken) {
		super(uri, tokenizer, method, debug, gcubeToken);
	}

	private static synchronized void increaseFlushCounter() throws FileNotFoundException, IOException {
		flushCounter++;
		if ((flushCounter % FLUSH_EVERY) == 0)
			flush();
	}

	public static synchronized void flush() throws FileNotFoundException, IOException {
		if (flushCounter > 0 && resultsCacheFilename != null) {
			LOG.info("Flushing WAT cache... ");
			new File(resultsCacheFilename).createNewFile();
			ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(resultsCacheFilename));
			oos.writeObject(url2jsonCache);
			oos.close();
			LOG.info("Flushing WAT cache Done.");
		}
	}

	public static void setCache(String cacheFilename) throws FileNotFoundException, IOException, ClassNotFoundException {
		if (resultsCacheFilename != null && resultsCacheFilename.equals(cacheFilename))
			return;
		LOG.info("Loading WAT2 cache...");
		resultsCacheFilename = cacheFilename;
		if (new File(resultsCacheFilename).exists()) {
			ObjectInputStream ois = new ObjectInputStream(new FileInputStream(resultsCacheFilename));
			url2jsonCache = (HashMap<String, byte[]>) ois.readObject();
			ois.close();
		}
	}

	public static void clearCache() {
		url2jsonCache = new HashMap<>();
		System.gc();
	}

	@Override
	protected JSONObject queryJson(String baseUrl, List<Pair<String, String>> getParameters) throws Exception {

		URI requestUri = getRequestUri(baseUrl, getParameters);

		String cacheKey = requestUri.toString();
		byte[] compressed = url2jsonCache.get(cacheKey);
		if (compressed != null) {
			try {
				String jsonString = SmaphUtils.decompress(compressed);
				return new JSONObject(jsonString);
			} catch (IOException e) {
				LOG.warn("Broken Gzip, re-downloading");
			}
		}

		increaseFlushCounter();
		JSONObject obj = super.queryJson(baseUrl, getParameters);
		url2jsonCache.put(cacheKey, SmaphUtils.compress(obj.toString()));
		return obj;

	}
} public class EntityToAnchors {
	public static final String DEFAULT_INPUT = "./data/anchors.tsv";
	public static final String DATASET_FILENAME = "./mapdb/e2a.db";

	private DB db;
	/**
	 * entity -> anchor-IDs
	 */
	private HTreeMap<Integer, int[]> entityToAnchorIDs;
	
	/**
	 * entity -> frequencies of anchor-ID for entity
	 */
	private HTreeMap<Integer,int[]> entityToFreqs;
	
	/**
	 * From anchor to anchor-ID
	 */
	private HTreeMap<String, Integer> anchorToAid;
	
	/**
	 * From anchor-ID to snchor
	 */
	private HTreeMap<Integer, String> aidToAnchor;
	
	/**
	 * anchor-ID -> how many times the anchor has been seen 
	 */
	private HTreeMap<Integer, Integer> anchorToOccurrences;	
	
	private static Logger logger = LoggerFactory.getLogger(EntityToAnchors.class.getName());

	public static EntityToAnchors fromDB(String datasetFilename) {
		logger.info("Opening E2A database.");
		DB db = DBMaker.fileDB(datasetFilename).fileMmapEnable().readOnly().closeOnJvmShutdown().make();
		EntityToAnchors e2a = new EntityToAnchors(db);
		logger.info("Loading E2A database done.");
		return e2a;
	}

	private EntityToAnchors(DB db) {
		this.db = db;
		entityToAnchorIDs = db.hashMap("entityToAnchorIDs", Serializer.INTEGER, Serializer.INT_ARRAY).createOrOpen();
		entityToFreqs = db.hashMap("entityToFreqs", Serializer.INTEGER, Serializer.INT_ARRAY).createOrOpen();
		anchorToAid = db.hashMap("anchorToAid", Serializer.STRING, Serializer.INTEGER).createOrOpen();
		aidToAnchor = db.hashMap("aidToAnchor", Serializer.INTEGER, Serializer.STRING).createOrOpen();
		anchorToOccurrences = db.hashMap("anchorToOccurrences", Serializer.INTEGER, Serializer.INTEGER).createOrOpen();
	}

	private static void createDB(String file) throws FileNotFoundException,
			IOException {
		
		InputStream inputStream = new FileInputStream(file);
		BufferedReader buffered = new BufferedReader(new InputStreamReader(
				inputStream, "UTF-8"));

		logger.info("Building database...");
		EntityToAnchors mdb = new EntityToAnchors(DBMaker.fileDB(DATASET_FILENAME).fileMmapEnable().closeOnJvmShutdown().make());

		long count = 0;
		long processedMbs = 0;
		Int2ObjectOpenHashMap<Int2IntArrayMap> entityToAnchorIDToFreqBuffer = new Int2ObjectOpenHashMap<Int2IntArrayMap>();
		
		int lastAnchorId = 0;
		String line = null;
		while ((line = buffered.readLine()) != null) {
			count += line.length();
			if (count > processedMbs * (10 * 1024 * 1024))
				logger.info(String.format("Read %d MiB.", 10*(processedMbs++)));
			
			String[] tokens = line.split("\t");
			if (tokens.length != 3){
				buffered.close();
				throw new RuntimeException("Read line: [" + line + "] should have three tokens.");
			}
			
			String anchor = tokens[0].toLowerCase();
			int freq = Integer.parseInt(tokens[2]);

			if (!mdb.anchorToAid.containsKey(anchor)){
				mdb.anchorToAid.put(anchor, lastAnchorId);
				mdb.aidToAnchor.put(lastAnchorId, anchor);
				lastAnchorId++;
			}
				
			Integer aId = mdb.anchorToAid.get(anchor);
			
			int pageId = Integer.parseInt(tokens[1]);

			Int2IntArrayMap aidToFreqMap = entityToAnchorIDToFreqBuffer.get(pageId);
			if (aidToFreqMap == null){
				aidToFreqMap = new Int2IntArrayMap();
				entityToAnchorIDToFreqBuffer.put(pageId, aidToFreqMap);
			}
			if (!aidToFreqMap.containsKey(aId))
				aidToFreqMap.put(aId.intValue(), freq);
			else
				aidToFreqMap.put(aId.intValue(), freq + aidToFreqMap.get(aId)); //increase the occurrence for anchor by freq (this should only happen for anchors with different case, same content.)
			
			if (!mdb.anchorToOccurrences.containsKey(aId))
				mdb.anchorToOccurrences.put(aId, 0);
			mdb.anchorToOccurrences.put(aId, mdb.anchorToOccurrences.get(aId) + freq);
		}
		buffered.close();
		logger.info(String.format("Finished reading %s (%.1f MBs)", file, count / 1024.0 / 1024.0));
		
		logger.info("Copying entity-to-anchor mappings in db...");
		int c = 0;
		for ( int id : entityToAnchorIDToFreqBuffer.keySet()){
			if (++c % 1000000 == 0)
				logger.info(String.format("Copied %d entity-to-anchor mappings.", c));
			Int2IntArrayMap anchorToFreq = entityToAnchorIDToFreqBuffer.get(id);
			int[] anchors = new int[anchorToFreq.size()];
			int[] frequencies = new int[anchorToFreq.size()];
			int idx=0;
			for (Entry<Integer, Integer> p : anchorToFreq.entrySet()){
				anchors[idx] = p.getKey();
				frequencies[idx] = p.getValue();
				idx++;
			}
			mdb.entityToAnchorIDs.put(id, anchors);
			mdb.entityToFreqs.put(id, frequencies);
		}
		
		logger.info("Committing changes...");
		mdb.db.commit();

		logger.info("Closing db...");
		mdb.db.close();
	}
	
	public String idToAnchor(int aId){
		String anchor = aidToAnchor.get(Integer.valueOf(aId));
		if (anchor == null)
			throw new RuntimeException("Anchor-ID "+aId+"not present.");
		return anchor;
	}
	
	public boolean containsId(int id){
		return entityToAnchorIDs.containsKey(id);
	}

	public List<Pair<String, Integer>> getAnchors(int id, double keepFreq) {
		if (!containsId(id))
			throw new RuntimeException("Anchors for page id=" + id
					+ " not found.");
		int[] anchors = entityToAnchorIDs.get(id);
		int[] freqs = entityToFreqs.get(id);

		int totalFreq = 0;
		List<Pair<Integer, Integer>> anchorsAndFreq = new Vector<>();
		for (int i=0; i<anchors.length; i++){
			totalFreq += freqs[i];
			anchorsAndFreq.add(new Pair<Integer, Integer>(anchors[i], freqs[i]));
		}

		Collections.sort(anchorsAndFreq, new SmaphUtils.ComparePairsBySecondElement<Integer, Integer>());

		int gathered = 0;
		List<Pair<String, Integer>> res = new Vector<Pair<String, Integer>>();
		for (int i = anchorsAndFreq.size() - 1; i >= 0; i--)
			if (gathered >= keepFreq * totalFreq)
				break;
			else {
				int aid = anchorsAndFreq.get(i).first;
				int freq = anchorsAndFreq.get(i).second;
				res.add(new Pair<String, Integer>(idToAnchor(aid), freq));
				gathered += freq;
			}
		return res;
	}

	public List<Pair<String, Integer>> getAnchors(int id) {
		if (!containsId(id))
			throw new RuntimeException("Anchors for page id=" + id
					+ " not found.");
		int[] anchors = entityToAnchorIDs.get(id);
		int[] freqs = entityToFreqs.get(id);
		
		List<Pair<String, Integer>> res = new Vector<Pair<String,Integer>>();
		for (int i=0; i<anchors.length; i++)
			res.add(new Pair<String, Integer>(idToAnchor(anchors[i]), freqs[i]));
		return res;
	}
	
	public int getAnchorGlobalOccurrences(String anchor){
		Integer aId = anchorToAid.get(anchor);
		if (aId == null)
			throw new RuntimeException("Anchor "+anchor+"not present.");
		return anchorToOccurrences.get(aId);
	}
	
	public double getCommonness(String anchor, int entity){
		if (!anchorToAid.containsKey(anchor))
			return 0.0;
		return ((double)getFrequency(anchor, entity)) / getAnchorGlobalOccurrences(anchor);
	}

	public double getCommonness(String anchor, int entity, int occurrences){
		return ((double) occurrences) / getAnchorGlobalOccurrences(anchor);
	}

	private int getFrequency(String anchor, int entity) {
		if (!containsId(entity))
			throw new RuntimeException("Anchors for page id=" + entity
					+ " not found.");
		if (!anchorToAid.containsKey(anchor))
			throw new RuntimeException("Anchors "+anchor+" not present.");
		int aid = anchorToAid.get(anchor);
		
		int[] anchors = entityToAnchorIDs.get(entity);
		int[] freqs = entityToFreqs.get(entity);
		
		for (int i=0; i<anchors.length; i++)
			if (anchors[i] == aid)
				return freqs[i];
		return 0;
	}

	public void dumpJson(String file) throws IOException, JSONException {
		FileWriter fw = new FileWriter(file);
		JSONWriter wr = new JSONWriter(fw);
		wr.object();
		for (int pageid : entityToAnchorIDs.getKeys()) {
			wr.key(Integer.toString(pageid)).array();
			List<Pair<String, Integer>> anchorAndFreqs = getAnchors(pageid);
			for (Pair<String, Integer> p: anchorAndFreqs)
				wr.object().key(p.first).value(p.second).endObject();
			wr.endArray();
		}
		wr.endObject();
		fw.close();
	}

	public static void main(String[] args) throws Exception{
		logger.info("Creating E2A database... ");
		createDB(args.length > 0 ? args[0] : DEFAULT_INPUT);
		logger.info("Done.");
	}
} public class WikipediaToFreebase {
	private Map<String, String> map;
	private Map<String, String> labels;

	public static WikipediaToFreebase open(String file) {
		return new WikipediaToFreebase(file);
	}

	private WikipediaToFreebase(String file) {
		DB db = DBMaker.fileDB(file).fileMmapEnable().readOnly().closeOnJvmShutdown().make();
		map = db.hashMap("index", Serializer.STRING, Serializer.STRING).createOrOpen();
		labels = db.hashMap("label", Serializer.STRING, Serializer.STRING).createOrOpen();
	}

	public String getLabel(String wikiid) {
		wikiid = wikiid.replaceAll(" ", "_");
		String freebase = map.get(wikiid);
		if (freebase == null)
			return null;

		String label = labels.get(freebase);
		return label;
	}

	public boolean hasEntity(String wikilabel) {
		wikilabel = wikilabel.replaceAll(" ", "_");
		return map.containsKey(wikilabel);
	}

	public String getFreebaseId(String wikilabel) {
		wikilabel = wikilabel.replaceAll(" ", "_");
		String freebase = map.get(wikilabel);
		return freebase;
	}

	public static void main(String[] args) {
		WikipediaToFreebase w2f = new WikipediaToFreebase("mapdb/freebase.db");
		System.out.println(w2f.getFreebaseId("Diego_Maradona"));
		System.out.println(w2f.getLabel("Diego_Maradona"));
		System.out.println(w2f.getFreebaseId("East Ridge High School (Minnesota)"));
		System.out.println(w2f.getLabel("East Ridge High School (Minnesota)"));
	}

} public class EntityFeaturePack extends FeaturePack<Tag> {
	private static final long serialVersionUID = 1L;

	public EntityFeaturePack(Tag candidate, String query, QueryInformation qi, WikipediaInterface wikiApi,
	        WikipediaToFreebase freeb) {
		super(getFeatures(candidate, query, qi, wikiApi, freeb));
	}

	public EntityFeaturePack() {
		super(null);
	}


	public static String[] ftrNames = new String[] {
		"found_s1",
		"found_s2",
		"found_s3",
		"a_editDistanceTitle",
		"a_webTotal",
		"a_wikiWebTotal",
		"a_editDistanceNoPar",
		"a_is_named_entity",
		"s1_rank",
		"s1_editDistanceBolds",
		"s1_capitalizedBolds",
		"s1_avgBoldsWords",
		"s2_rank",
		"s2_editDistanceBolds",
		"s2_capitalizedBolds",
		"s2_avgBoldsWords",
		"s3_freq",
		"s3_avgRank",
		"s3_pageRank",
		"s3_min_rho",
		"s3_max_rho",
		"s3_avg_rho",
		"s3_min_lp",
		"s3_max_lp",
		"s3_avg_lp",
		"s3_min_commonness",
		"s3_max_commonness",
		"s3_avg_commonness",
		"s3_min_ambig",
		"s3_max_ambig",
		"s3_avg_ambig",
		"s3_min_min_bold_ed",
		"s3_max_min_bold_ed",
		"s3_avg_min_bold_ed",
		"s3_min_min_mention_ed",
		"s3_max_min_mention_ed",
		"s3_avg_min_mention_ed",
		"s3_min_mention_bold_overlap",
		"s3_max_mention_bold_overlap",
		"s3_avg_mention_bold_overlap",
	};

	@Override
	public String[] getFeatureNames() {
		return getFeatureNamesStatic();
	}

	private static String[] getFeatureNamesStatic() {
		return ftrNames;
	}

	public static HashMap<String, Double> getFeatures(Tag candidate, String query, QueryInformation qi,
			WikipediaInterface wikiApi, WikipediaToFreebase freeb) {
		int wid = candidate.getConcept();
		boolean candidateIsNE;
		String title;
		try {
			candidateIsNE = ERDDatasetFilter.entityIsNE(wikiApi, freeb, wid);
			title = wikiApi.getTitlebyId(wid);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}

		HashMap<String, Double> res = new HashMap<>();

		// Source-independent features
		res.put("a_editDistanceTitle", SmaphUtils.getMinEditDist(query, title));
		res.put("a_webTotal", qi.webTotalNS);
		res.put("a_wikiWebTotal", qi.webTotalWS);
		res.put("a_editDistanceNoPar", SmaphUtils.getMinEditDist(query, SmaphUtils.removeTrailingParenthetical(title)));
		res.put("a_is_named_entity", candidateIsNE ? 1.0 : 0.0);

		// Normal search (S2) features
		if (qi.includeSourceNormalSearch) {
			res.put("found_s1", 0.0);
			/*res.put("s1_rank", qi.resultsCountNS + 1.0);
			res.put("s1_capitalizedBolds", 0.0);
			res.put("s1_editDistanceBolds", 1.0);
			res.put("s1_avgBoldsWords", 0.0);*/

			if (qi.candidatesNS.contains(candidate)) {
				int rank = qi.idToRankNS.get(wid);
				Triple<Double, Double, Double> EDCapitalizedWordcount = getBoldsEDCapitalizedWordcount(query, rank,
						qi.boldsAndRankNS);
				res.put("found_s1", 1.0);
				res.put("s1_rank", (double) rank);
				res.put("s1_capitalizedBolds", EDCapitalizedWordcount.getLeft());
				res.put("s1_editDistanceBolds", EDCapitalizedWordcount.getMiddle());
				res.put("s1_avgBoldsWords", EDCapitalizedWordcount.getLeft());
			}
		}

		// Wikipedia search (S3) features
		if (qi.includeSourceWikiSearch) {
			res.put("found_s2", 0.0);
/*			res.put("s2_rank", qi.resultsCountWS + 1.0);
			res.put("s2_capitalizedBolds", 0.0);
			res.put("s2_editDistanceBolds", 1.0);
			res.put("s2_avgBoldsWords", 0.0);
*/
			if (qi.candidatesWS.contains(candidate)) {
				int rank = qi.idToRankWS.get(wid);
				Triple<Double, Double, Double> EDCapitalizedWordcount = getBoldsEDCapitalizedWordcount(query, rank,
						qi.boldsAndRankWS);
				res.put("found_s2", 1.0);
				res.put("s2_rank", (double) rank);
				res.put("s2_capitalizedBolds", EDCapitalizedWordcount.getLeft());
				res.put("s2_editDistanceBolds", EDCapitalizedWordcount.getMiddle());
				res.put("s2_avgBoldsWords", EDCapitalizedWordcount.getLeft());
			}
		}

		// Snippet annotation (S3) features
		if (qi.includeSourceSnippets){
			res.put("found_s3", 0.0);
/*			res.put("s3_freq", 0.0);
			res.put("s3_avgRank", 1.0);
			res.put("s3_pageRank", 0.0);
			res.put("s3_min_rho", 0.0);
			res.put("s3_max_rho", 0.0);
			res.put("s3_avg_rho", 0.0);
			res.put("s3_min_lp", 0.0);
			res.put("s3_max_lp", 0.0);
			res.put("s3_avg_lp", 0.0);
			res.put("s3_min_commonness", 0.0);
			res.put("s3_max_commonness", 0.0);
			res.put("s3_avg_commonness", 0.0);
			res.put("s3_min_ambig", 0.0);
			res.put("s3_max_ambig", 0.0);
			res.put("s3_avg_ambig", 0.0);
			res.put("s3_min_min_bold_ed", 1.0);
			res.put("s3_max_min_bold_ed", 1.0);
			res.put("s3_avg_min_bold_ed", 1.0);
			res.put("s3_min_min_mention_ed", 1.0);
			res.put("s3_max_min_mention_ed", 1.0);
			res.put("s3_avg_min_mention_ed", 1.0);
			res.put("s3_min_mention_bold_overlap", 1.0);
			res.put("s3_max_mention_bold_overlap", 1.0);
			res.put("s3_avg_mention_bold_overlap", 1.0);
*/
			if (qi.candidatesSA.contains(candidate)) {
				res.put("found_s3", 1.0);
				List<HashMap<String, Double>> additionalInfos = qi.entityToAdditionalInfosSA.get(candidate);
				double pageRank = additionalInfos.get(0).get("pageRank");
				List<String> mentions = qi.entityToMentionsSA.get(candidate);
				List<String> bolds = qi.entityToBoldsSA.get(candidate);
				List<Integer> ranks = qi.entityToRanksSA.get(candidate);

				res.put("s3_freq", SmaphUtils.getFrequency(ranks.size(), qi.resultsCountNS));
				res.put("s3_avgRank", SmaphUtils.computeAvgRank(ranks, qi.resultsCountNS));
				res.put("s3_pageRank", pageRank);

				List<Double> rhoes = new Vector<>();
				List<Double> lps = new Vector<>();
				List<Double> commonness = new Vector<>();
				List<Double> ambiguities = new Vector<>();
				for (HashMap<String, Double> addInfo : additionalInfos){
					rhoes.add(addInfo.get("rho"));
					lps.add(addInfo.get("lp"));
					commonness.add(addInfo.get("commonness"));
					ambiguities.add(addInfo.get("ambiguity"));
				}
				List<Double> boldEDs = new Vector<>();
				for (String bold : bolds)
					boldEDs.add(SmaphUtils.getMinEditDist(query, bold));
				List<Double> mentionEDs = new Vector<>();
				for (String mention : mentions)
					mentionEDs.add(SmaphUtils.getMinEditDist(query, mention));
				List<Double> mentionBoldOverlap = new Vector<>();
				for (int i = 0; i < mentions.size(); i++) {
					List<String> boldTokens = SmaphUtils.tokenize(bolds.get(i));
					List<String> mentionTokens = SmaphUtils.tokenize(mentions.get(i));
					int overlap = 0;
					for (String boldToken : boldTokens)
						if (mentionTokens.contains(boldToken))
							overlap++;
					mentionBoldOverlap.add(((double) overlap)
							/ ((double) boldTokens.size() + mentionTokens.size()));
				}		

				Triple<Double, Double, Double> minMaxAvgRho = SmaphUtils.getMinMaxAvg(rhoes);
				res.put("s3_min_rho", minMaxAvgRho.getLeft());
				res.put("s3_max_rho", minMaxAvgRho.getMiddle());
				res.put("s3_avg_rho", minMaxAvgRho.getRight());
				Triple<Double, Double, Double> minMaxAvgLp = SmaphUtils.getMinMaxAvg(lps);
				res.put("s3_min_lp", minMaxAvgLp.getLeft());
				res.put("s3_max_lp", minMaxAvgLp.getMiddle());
				res.put("s3_avg_lp", minMaxAvgLp.getRight());
				Triple<Double, Double, Double> minMaxAvgComm = SmaphUtils.getMinMaxAvg(commonness);
				res.put("s3_min_commonness", minMaxAvgComm.getLeft());
				res.put("s3_max_commonness", minMaxAvgComm.getMiddle());
				res.put("s3_avg_commonness", minMaxAvgComm.getRight());
				Triple<Double, Double, Double> minMaxAvgAmbig = SmaphUtils.getMinMaxAvg(ambiguities);
				res.put("s3_min_ambig", minMaxAvgAmbig.getLeft());
				res.put("s3_max_ambig", minMaxAvgAmbig.getMiddle());
				res.put("s3_avg_ambig", minMaxAvgAmbig.getRight());
				Triple<Double, Double, Double> minMaxAvgBoldED = SmaphUtils.getMinMaxAvg(boldEDs);
				res.put("s3_min_min_bold_ed", minMaxAvgBoldED.getLeft());
				res.put("s3_max_min_bold_ed", minMaxAvgBoldED.getMiddle());
				res.put("s3_avg_min_bold_ed", minMaxAvgBoldED.getRight());
				Triple<Double, Double, Double> minMaxAvgMentionED = SmaphUtils.getMinMaxAvg(mentionEDs);
				res.put("s3_min_min_mention_ed", minMaxAvgMentionED.getLeft());
				res.put("s3_max_min_mention_ed", minMaxAvgMentionED.getMiddle());
				res.put("s3_avg_min_mention_ed", minMaxAvgMentionED.getRight());
				Triple<Double, Double, Double> minMaxAvgMentionBoldOverlap = SmaphUtils.getMinMaxAvg(mentionBoldOverlap);
				res.put("s3_min_mention_bold_overlap", minMaxAvgMentionBoldOverlap.getLeft());
				res.put("s3_max_mention_bold_overlap", minMaxAvgMentionBoldOverlap.getMiddle());
				res.put("s3_avg_mention_bold_overlap", minMaxAvgMentionBoldOverlap.getRight());

			}
		}
		if (res.isEmpty())
			throw new RuntimeException(wid + " is not a candidate for query " + query);
		return res;
	}

	@Override
	public void checkFeatures(HashMap<String, Double> features) {
		long ftrCount = 0;
		String[] sources = new String[] { "s1", "s2", "s3" };
		for (String source : sources)
			if (features.containsKey("found_" + source)) {
				long count = features.keySet().stream().filter(fn -> fn.startsWith(source + "_")).count();
				if ((source.equals("s1") || source.equals("s2")) && count != 4 && count != 0)
					throw new RuntimeException(String.format("Sources 1 and 2 must have 0 or 4 fetures set. %d found.", count));
				if (source.equals("s3") && count != 24 && count != 0)
					throw new RuntimeException(String.format("Source 3 must have 0 or 24 fetures set. %d found.", count));
				ftrCount += count;
				ftrCount ++;
			}

		long indipCount = features.keySet().stream().filter(fn -> fn.startsWith("a_")).count();
		if (indipCount != 5)
			throw new RuntimeException(String.format("Source-independent features must be 5. %d found.", indipCount));
		ftrCount += indipCount;

		if (ftrCount != features.size())
			throw new RuntimeException("Unrecognized additional features.");
	}

	private static Triple<Double, Double, Double> getBoldsEDCapitalizedWordcount(String query, int rank, List<Pair<String, Integer>> websearchBolds){
		double minEdDist = 1.0;
		double capitalized = 0;
		double avgNumWords = 0;
		int boldsCount = 0;
		for (Pair<String, Integer> p : websearchBolds)
			if (p.second == rank) {
				boldsCount++;
				minEdDist = Math.min(minEdDist,
						SmaphUtils.getMinEditDist(query, p.first));
				if (Character.isUpperCase(p.first.charAt(0)))
					capitalized++;
				avgNumWords += p.first.split("\\W+").length;
			}
		if (boldsCount != 0)
			avgNumWords /= boldsCount;
		return new ImmutableTriple<>(minEdDist, capitalized, avgNumWords);
	}
} public abstract class FeaturePack <T extends Object> implements Serializable {

	private static final long serialVersionUID = 1L;
	private double[] featuresArray;

	public FeaturePack(HashMap<String, Double> features){
		if (features == null)
			return;
		checkFeatures(features);
		featuresArray = new double[this.getFeatureCount()];
		
		for (String ftrName : getFeatureNames())
			featuresArray[ftrNameToArrayPosition(ftrName)] = features
					.containsKey(ftrName) ? features.get(ftrName) : Double.NaN;
	}
	
	public abstract void checkFeatures(HashMap<String, Double> features);
	
	/**
	 * @return the array of feature names, ordered by their ID. In name of the
	 *         feature with ID=i+1 is in position i.
	 */
	public abstract String[] getFeatureNames();

	/**
	 * @param ftrName
	 *            the feature name
	 * @return the feature index (an integer >=1) of a feature, or -1 if such a
	 *         feature does not exist.
	 */
	public int ftrNameToId(String ftrName) {
		int idx = Arrays.asList(getFeatureNames()).indexOf(ftrName);
		if (idx == -1)
			return -1;
		return idx + 1;
	}

	public int ftrNameToArrayPosition(String ftrName) {
		return ftrNameToId(ftrName) -1;
	}

	public double getFeature(String ftrName) {
		return featuresArray[ftrNameToArrayPosition(ftrName)];
	}

	public String ftrIdToName(int featureId) {
		return getFeatureNames()[featureId - 1];
	}
	public int getFeatureCount() {
		return getFeatureNames().length;
	}

	public boolean featureIsSet(String ftrName) {
		return !Double.isNaN(featuresArray[ftrNameToArrayPosition(ftrName)]);
	}
} public class GreedyFeaturePack extends FeaturePack<Annotation> {
	private static String[] ftrNames = null;
	
	private static final long serialVersionUID = 1L;

	public <A extends Annotation> GreedyFeaturePack(Annotation a, String query, QueryInformation qi, HashSet<A> partialSolution,
	        WikipediaInterface wikiApi, WikipediaToFreebase w2f, EntityToAnchors e2a) {
		super(getFeaturesStatic(a, query, qi, partialSolution, wikiApi, w2f, e2a));
	}
	
	public GreedyFeaturePack() {
		super(null);
	}
	
	public static <A extends Annotation> HashMap<String, Double> getFeaturesStatic(Annotation a, String query,
	        QueryInformation qi, HashSet<A> partialSolution, WikipediaInterface wikiApi, WikipediaToFreebase w2f,
	        EntityToAnchors e2a) {
		HashMap<String, Double> annotationFeatures = AnnotationFeaturePack.getFeaturesStatic(a, query, qi, wikiApi, w2f, e2a);

		int queryTokens = SmaphUtils.tokenize(query).size();

		int coveredTokensBefore = 0;
		for (Annotation aS : partialSolution)
			coveredTokensBefore += SmaphUtils.tokenize(query.substring(aS.getPosition(), aS.getPosition() + aS.getLength()))
			        .size();

		int coveredTokensAnnotation = SmaphUtils.tokenize(query.substring(a.getPosition(), a.getPosition() + a.getLength()))
		        .size();

		// Features: coverage
		annotationFeatures.put("covered_tokens_incr", (double) coveredTokensAnnotation);
		annotationFeatures.put("covered_tokens_incr_ratio", ((double) coveredTokensAnnotation) / queryTokens);
		annotationFeatures.put("covered_tokens_after", (double) coveredTokensAnnotation + coveredTokensBefore);

		Pair<Double, Double> lpSumAndAvg = BindingFeaturePack.getLpSumAvg(query);
		annotationFeatures.put("segments_lp_sum", lpSumAndAvg.first);
		annotationFeatures.put("segments_lp_avg", lpSumAndAvg.second);
		annotationFeatures.put("segments_lp_ratio", lpSumAndAvg.first / (partialSolution.size() + 1));
		
		if (partialSolution.size() >= 1) {
			// Features: Annotation -- partial solution
			Vector<Double> relatednessPairsJaccardBefore = new Vector<>();
			Vector<Double> relatednessPairsMWBefore = new Vector<>();
			for (Annotation a1 : partialSolution)
				for (Annotation a2 : partialSolution)
					if (a1 != a2) {
						relatednessPairsJaccardBefore
						.add(WATRelatednessComputer.getJaccardRelatedness(a1.getConcept(), a2.getConcept()));
						relatednessPairsMWBefore.add(WATRelatednessComputer.getMwRelatedness(a1.getConcept(), a2.getConcept()));
					}

			Triple<Double, Double, Double> minMaxAvgRelJaccardPS = SmaphUtils.getMinMaxAvg(relatednessPairsJaccardBefore);
			double minRelBefore = minMaxAvgRelJaccardPS.getLeft();
			double maxRelBefore = minMaxAvgRelJaccardPS.getMiddle();
			double avgRelBefore = minMaxAvgRelJaccardPS.getRight();
			annotationFeatures.put("max_relatedness_before", maxRelBefore);
			annotationFeatures.put("avg_relatedness_before", avgRelBefore);

			Triple<Double, Double, Double> minMaxAvgRelMWPS = SmaphUtils.getMinMaxAvg(relatednessPairsMWBefore);
			double minRelMwBefore = minMaxAvgRelMWPS.getLeft();
			double maxRelMwBefore = minMaxAvgRelMWPS.getMiddle();
			annotationFeatures.put("max_relatedness_mw_before", maxRelMwBefore);

			// Features: partial solution
			Vector<Double> relatednessPairsJaccardThisA = new Vector<>();
			Vector<Double> relatednessPairsMWThisA = new Vector<>();
			for (Annotation aS : partialSolution) {
				relatednessPairsJaccardThisA.add(WATRelatednessComputer.getJaccardRelatedness(a.getConcept(), aS.getConcept()));
				relatednessPairsMWThisA.add(WATRelatednessComputer.getMwRelatedness(a.getConcept(), aS.getConcept()));
			}

			Triple<Double, Double, Double> minMaxAvgRelJaccard = SmaphUtils.getMinMaxAvg(relatednessPairsJaccardThisA);
			double minRel = minMaxAvgRelJaccard.getLeft();
			double avgRel = minMaxAvgRelJaccard.getRight();

			Triple<Double, Double, Double> minMaxAvgRelMW = SmaphUtils.getMinMaxAvg(relatednessPairsMWThisA);
			double minRelMW = minMaxAvgRelMW.getLeft();
			double maxRelMW = minMaxAvgRelMW.getMiddle();
			annotationFeatures.put("min_relatedness_mw", minRelMW);
			annotationFeatures.put("max_relatedness_mw", maxRelMW);
			
			// Features: difference wrt partial solution
			double newMinRelatedness = Math.min(minRel, minRelBefore);
			double newMinRelatednessMw = Math.min(minRelMW, minRelMwBefore);
			double newMaxRelatednessMw = Math.max(maxRelMW, maxRelMwBefore);
			annotationFeatures.put("min_relatedness_diff", newMinRelatedness - minRelBefore);
			annotationFeatures.put("avg_relatedness_diff", avgRel - avgRelBefore);
			annotationFeatures.put("min_relatedness_mw_diff", newMinRelatednessMw - minRelMwBefore);
			annotationFeatures.put("max_relatedness_mw_diff", newMaxRelatednessMw - maxRelMwBefore);
		} else {
			// In case the partial solution is empty (first step), these features get the same values and will be discarded.
			annotationFeatures.put("max_relatedness_before", 0.0);
			annotationFeatures.put("avg_relatedness_before", 0.0);
			annotationFeatures.put("max_relatedness_mw_before", 0.0);
			annotationFeatures.put("min_relatedness_mw", 0.0);
			annotationFeatures.put("max_relatedness_mw", 0.0);
			annotationFeatures.put("min_relatedness_diff", 0.0);
			annotationFeatures.put("avg_relatedness_diff", 0.0);
			annotationFeatures.put("min_relatedness_mw_diff", 0.0);
			annotationFeatures.put("max_relatedness_mw_diff", 0.0);
		}

		return annotationFeatures;
	}

	@Override
	public String[] getFeatureNames() {
		return getFeatureNamesStatic();
	}

	public static String[] getFeatureNamesStatic() {
		if (ftrNames == null) {
			Vector<String> v = new Vector<String>();
			v.addAll(Arrays.asList(AnnotationFeaturePack.getFeatureNamesStatic()));
			v.add("covered_tokens_incr");
			v.add("covered_tokens_incr_ratio");
			v.add("covered_tokens_after");
			v.add("segments_lp_sum");
			v.add("segments_lp_avg");
			v.add("segments_lp_ratio");
			v.add("max_relatedness_before");
			v.add("avg_relatedness_before");
			v.add("max_relatedness_mw_before");
			v.add("min_relatedness_mw");
			v.add("max_relatedness_mw");
			v.add("min_relatedness_diff");
			v.add("avg_relatedness_diff");
			v.add("min_relatedness_mw_diff");
			v.add("max_relatedness_mw_diff");
			ftrNames = v.toArray(new String[] {});
		}
		return ftrNames;
	}

	@Override
	public void checkFeatures(HashMap<String, Double> features) {
		String[] ftrNames = getFeatureNames();
		for (String ftrName : features.keySet())
			if (!Arrays.asList(ftrNames).contains(ftrName))
				throw new RuntimeException("Feature " + ftrName
						+ " does not exist!");
	}
} public interface EntityFilter {
	/**
	 * @param fp
	 *            not-yet-normalized features of the entity.
	 * @return true iff the entity should be kept.
	 */
	public boolean filterEntity(FeaturePack<Tag> fp, FeatureNormalizer fn);
} public class LibSvmEntityFilter extends LibSvmModel<Tag> implements EntityFilter, Serializable {
	private static final long serialVersionUID = 1L;

	private LibSvmEntityFilter(URL modelFile) throws IOException {
		super(modelFile);
	}

	private LibSvmEntityFilter(svm_model model) {
		super(model);
	}

	public static LibSvmEntityFilter fromUrl(URL modelUrl) throws IOException {
		LibSvmEntityFilter obj;
		try {
			ObjectInputStream in = new ObjectInputStream(modelUrl.openStream());
			obj = (LibSvmEntityFilter) in.readObject();
			in.close();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return obj;
	}

	public static LibSvmEntityFilter fromModel(svm_model model) {
		return new LibSvmEntityFilter(model);
	}

	@Override
	public boolean filterEntity(FeaturePack<Tag> fp, FeatureNormalizer fn) {
		boolean result = predict(fp, fn);
		return result;
	}

	public void toFile(File modelFile) {
		try {
			ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(modelFile));
			out.writeObject(this);
			out.close();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

} public class NoEntityFilter implements EntityFilter {

	@Override
	public boolean filterEntity(FeaturePack<Tag> fp, FeatureNormalizer fn) {
		return true;
	}

} public interface AnnotationRegressor {
	public <A extends Annotation> double predictScore(FeaturePack<A> fp, FeatureNormalizer fn);	
	public double threshold();	
} public class LibSvmAnnotationRegressor extends LibSvmModel<Annotation> implements AnnotationRegressor, Serializable {
    private static final long serialVersionUID = 1L;
	private double threshold;
	
	public static LibSvmAnnotationRegressor fromUrl(URL uri) {
		LibSvmAnnotationRegressor obj;
		try {
			ObjectInputStream in = new ObjectInputStream(uri.openStream());
			obj = (LibSvmAnnotationRegressor) in.readObject();
			in.close();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return obj;
	}
	
	public void toFile(File file){
        try {
			ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
			out.writeObject(this);
			out.close();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	public LibSvmAnnotationRegressor(svm_model model, double threshold) {
		super(model);
		this.threshold = threshold;
	}

	@Override
	public double threshold() {
		return threshold;
	}
} public interface BindingRegressor {
	public double[] getScores(List<FeaturePack<HashSet<Annotation>>> features, FeatureNormalizer fn);
} public class RankLibBindingRegressor extends RankLibModel<HashSet<Annotation>> implements BindingRegressor {

	private RankLibBindingRegressor(URL modelUrl) throws IOException {
		super(modelUrl);
	}

	public static RankLibBindingRegressor fromUrl(URL modelUrl) throws IOException {
		return new RankLibBindingRegressor(modelUrl);
	}

	@Override
	public double[] getScores(List<FeaturePack<HashSet<Annotation>>> features, FeatureNormalizer fn) {
		double[] scores = new double[features.size()];
		for (int i = 0; i < features.size(); i++) {
			scores[i] = super.predictScore(features.get(i), fn);
		}
		return scores;
	}
} public abstract class FeatureNormalizer {

	/**
	 * @param fp
	 *            a feature pack
	 * @param ftrName
	 *            a feature name
	 * @return the normalized value for this feature.
	 */
	public abstract double normalizeFeature(FeaturePack<?> fp, String ftrName);

	/**
	 * @param features
	 *            the hashmap of non-normalized features.
	 * @return a feature vector of the given features (normalized), 0.0 in case
	 *         a feature is not provided.
	 */
	public double[] ftrToNormalizedFtrArray(FeaturePack<?> fp) {
		double[] res = new double[fp.getFeatureCount()];
		for (String featureName : fp.getFeatureNames()) {
			int ftrPos = fp.ftrNameToArrayPosition(featureName);
			res[ftrPos] = normalizeFeature(fp, featureName);
		}
		return res;
	}
} public class NoFeatureNormalizer extends FeatureNormalizer {

	@Override
	public double normalizeFeature(FeaturePack fp, String ftrName) {
		if (!fp.featureIsSet(ftrName))
			return 0.0;
		return fp.getFeature(ftrName);
	}

} public class ZScoreFeatureNormalizer extends FeatureNormalizer implements Serializable {

	private HashMap<String, Double> avgs = new HashMap<>();
	private HashMap<String, Double> stdDevs = new HashMap<>();
	private static Mean meanComputer = new Mean();
	private static StandardDeviation stdDevComputer = new StandardDeviation();
	private double defaultValue = 0.0; // Default value for missing features. 0.0 is the average value.
	private boolean strict;

	private ZScoreFeatureNormalizer() {
	}

	/**
	 * @param zscore
	 * @param fp
	 * @param strict decides behavior in case a call tries to normalize a value that has never been observed.
	 * if true, throws an exception, if false, returns a default value.
	 * @return
	 */
	public static ZScoreFeatureNormalizer fromUrl(URL zscore, FeaturePack<?> fp, boolean strict) {
		ZScoreFeatureNormalizer fn = new ZScoreFeatureNormalizer();
		fn.strict = strict;
		fn.load(zscore, fp);
		return fn;
	}

	public static ZScoreFeatureNormalizer fromUrl(URL zscore, FeaturePack<?> fp) {
		return fromUrl(zscore, fp, true);
	}

	public static <E extends Serializable, G extends Serializable> ZScoreFeatureNormalizer fromGatherer(ExampleGatherer<E, G> exampleGatherer, boolean strict) {
		ZScoreFeatureNormalizer fn = new ZScoreFeatureNormalizer();
		fn.strict = strict;
		List<FeaturePack<E>> ftrPacks = exampleGatherer.getAllFeaturePacks();
		for (String ftrName : ftrPacks.get(0).getFeatureNames()) {
			Vector<Double> ftrValues = new Vector<>();
			for (FeaturePack<E> fp : ftrPacks)
				if (fp.featureIsSet(ftrName))
					ftrValues.add(fp.getFeature(ftrName));
			double[] ftrValArray = ArrayUtils.toPrimitive(ftrValues.toArray(new Double[] {}));

			double valMean = meanComputer.evaluate(ftrValArray);
			double stdDev = stdDevComputer.evaluate(ftrValArray, valMean);
			fn.avgs.put(ftrName, valMean);
			fn.stdDevs.put(ftrName, stdDev);
		}
		return fn;
	}

	public static <E extends Serializable, G extends Serializable> ZScoreFeatureNormalizer fromGatherer(ExampleGatherer<E, G> exampleGatherer) {
		return fromGatherer(exampleGatherer, true);
	}

	@Override
	public double normalizeFeature(FeaturePack<?> fp, String ftrName) {
		if (!fp.featureIsSet(ftrName))
			return defaultValue;
		if (!avgs.containsKey(ftrName))
			return fp.getFeature(ftrName);
		double avg = avgs.get(ftrName);
		double stdDev = stdDevs.get(ftrName);
		if (Double.isNaN(avg) || Double.isNaN(stdDev))
			if (strict)
				throw new RuntimeException(
						"You are trying to normalize feature " + ftrName + " that has never been observed before.");
			else return defaultValue;
		if (stdDev == 0.0)
			return fp.getFeature(ftrName) - avg;
		return (fp.getFeature(ftrName) - avg) / stdDev;
	}

	private void load(URL zScoreFile, FeaturePack<?> fp) {
		Vector<String[]> tokensVect = new Vector<>();
		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(zScoreFile.openStream()));
			String line;
			while ((line = reader.readLine()) != null) {
				String[] tokens = line.split(" ");
				if (tokens.length != 3) {
					reader.close();
					throw new RuntimeException("Can't read line: [" + line + "]");
				}
				tokensVect.add(tokens);
			}
			reader.close();
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
		for (String[] tokens : tokensVect) {
			String ftrName = tokens[0];
			if (fp.ftrNameToId(ftrName) < 0)
				throw new RuntimeException("Unrecognized feature " + tokens[0]);
			double avg = Double.parseDouble(tokens[1]);
			double stdDev = Double.parseDouble(tokens[2]);
			avgs.put(ftrName, avg);
			stdDevs.put(ftrName, stdDev);
		}
	}

	public void dump(File normFile) throws IOException {
		BufferedWriter br = new BufferedWriter(new FileWriter(normFile));
		for (String ftrName : SmaphUtils.sorted(avgs.keySet())) {
			br.write(String.format("%s %.16f %.16f%n", ftrName, avgs.get(ftrName), stdDevs.get(ftrName)));
		}

		br.close();
	}
} public class CollectiveLinkBack implements LinkBack {
	private final static Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
	private BindingRegressor bindingRegressorModel;
	private WikipediaInterface wikiApi;
	private BindingGenerator bg;
	private FeatureNormalizer brFn;
	private SmaphDebugger debugger;
	private WikipediaToFreebase w2f;
	private EntityToAnchors e2a;

	public CollectiveLinkBack(WikipediaInterface wikiApi, WikipediaToFreebase w2f, EntityToAnchors e2a, BindingGenerator bg,
	        BindingRegressor lbReg, FeatureNormalizer brFn) throws IOException {
		this.bindingRegressorModel = lbReg;
		this.wikiApi = wikiApi;
		this.bg = bg;
		this.brFn = brFn;
		this.w2f = w2f;
		this.e2a = e2a;
	}
	
	public static List<Pair<HashSet<Annotation>, BindingFeaturePack>> getBindingFeaturePacks(String query,
	        Set<Tag> acceptedEntities, QueryInformation qi, BindingGenerator bg, WikipediaInterface wikiApi,
	        WikipediaToFreebase w2f, EntityToAnchors e2a, SmaphDebugger debugger) {
		List<Pair<HashSet<Annotation>,BindingFeaturePack>> featurePacks = new Vector<>();
		//TODO: don't like.
		acceptedEntities = acceptedEntities.stream().filter(e -> e2a.containsId(e.getConcept())).collect(Collectors.toCollection(HashSet::new));
		
		// Generate all possible bindings
		LOG.info("Generating bindings.");
		List<HashSet<Annotation>> bindings = bg.getBindings(query, qi,
				acceptedEntities, wikiApi);
		LOG.info("Generated {} bindings.", bindings.size());

		LOG.info("Generating Binding Features.");
		for (HashSet<Annotation> binding : bindings) {
			//Discard bindings that have entities w/o anchors
			/*boolean bad = false;
			for (Annotation a : binding)
				if (!EntityToAnchors.e2a().containsId(a.getConcept())){
					bad = true;
					throw new RuntimeException();
				}
			if (bad) continue;*/

			HashMap<Annotation, HashMap<String, Double>> debugAnnotationFeatures = new HashMap<>();
			HashMap<String, Double> debugBindingFeatures = new HashMap<>();
			BindingFeaturePack features = new BindingFeaturePack(binding, query,
					qi, wikiApi, w2f, e2a, debugAnnotationFeatures, debugBindingFeatures);
			featurePacks.add(new Pair<HashSet<Annotation>, BindingFeaturePack>(binding, features));
			if (debugger != null)
				debugger.addLinkbackBindingFeatures(query, binding, debugAnnotationFeatures, debugBindingFeatures);

		}
		LOG.info("Generated Binding Features.");

		return featurePacks;
	}

	@Override
	public HashSet<ScoredAnnotation> linkBack(String query,
			HashSet<Tag> acceptedEntities, QueryInformation qi) {

		// Predict a score and pick the best-performing
		HashSet<Annotation> bestBinding = null;
		double bestScore = Double.NEGATIVE_INFINITY;
		
		List<Pair<HashSet<Annotation>, BindingFeaturePack>> bindingFeaturePacks = getBindingFeaturePacks(query, acceptedEntities,
		        qi, bg, wikiApi, w2f, e2a, debugger);
		List<FeaturePack<HashSet<Annotation>>> packs = new Vector<>();
		for (Pair<HashSet<Annotation>, BindingFeaturePack> bindingAndFeatures: bindingFeaturePacks)
			packs.add(bindingAndFeatures.second);
		
		double[] scores = bindingRegressorModel.getScores(packs, brFn);
		
		for (int i=0; i<scores.length; i++){
			if (scores[i] > bestScore) {
				bestBinding = bindingFeaturePacks.get(i).first;
				bestScore = scores[i];
			}
			if (debugger != null)
				debugger.addLinkbackBindingScore(query, bindingFeaturePacks.get(i).first, scores[i]);
		}

		HashSet<ScoredAnnotation> scoredBestBinding = new HashSet<>();
		for (Annotation ann : bestBinding) {
			scoredBestBinding.add(new ScoredAnnotation(ann.getPosition(), ann
					.getLength(), ann.getConcept(), 1.0f));
		}
		return SmaphUtils.collapseBinding(scoredBestBinding);
	}




/*
	@Override
	public HashSet<ScoredAnnotation> linkBack(String query,
			HashSet<Tag> acceptedEntities, QueryInformation qi) {
		// Generate all possible bindings
		List<HashSet<Annotation>> bindings = bg.getBindings(query, qi,
				acceptedEntities, wikiApi);

		// Predict a score and pick the best-performing
		HashSet<Annotation> bestBinding = null;
		double bestScore = Double.NEGATIVE_INFINITY;
		for (HashSet<Annotation> binding : bindings) {
			
			//Discard bindings that have entities w/o anchors
			boolean bad = false;
			for (Annotation a : binding)
				if (!EntityToAnchors.e2a().containsId(a.getConcept()))
					bad = true;
			if (bad) continue;
			
			HashMap<Annotation, HashMap<String, Double>> debugAnnotationFeatures = new HashMap<>();
			HashMap<String, Double> debugBindingFeatures = new HashMap<>();
			BindingFeaturePack features = new BindingFeaturePack(binding, query,
					qi, acceptedEntities, wikiApi, debugAnnotationFeatures, debugBindingFeatures);
			
			double predictedScore = bindingRegressorModel
					.predictScore(features,brFn);
			
			if (predictedScore > bestScore) {
				bestBinding = binding;
				bestScore = predictedScore;
			}

			if (debugger != null){
				debugger.addLinkbackBindingFeatures(query, binding, debugAnnotationFeatures, debugBindingFeatures);
				debugger.addLinkbackBindingScore(query, binding, predictedScore);
			}
		}

		HashSet<ScoredAnnotation> scoredBestBinding = new HashSet<>();
		for (Annotation ann : bestBinding) {
			scoredBestBinding.add(new ScoredAnnotation(ann.getPosition(), ann
					.getLength(), ann.getConcept(), 1.0f));
		}
		return SmaphUtils.collapseBinding(scoredBestBinding);
	}
*/
	@Override
	public void setDebugger(SmaphDebugger debugger) {
		this.debugger = debugger;
	}
} public class DummyLinkBack implements LinkBack {
	@Override
	public HashSet<ScoredAnnotation> linkBack(String query,
			HashSet<Tag> acceptedEntities,
			QueryInformation qi) {
		HashSet<ScoredAnnotation> res = new HashSet<>();
		for (Tag entity : acceptedEntities)
			res.add(new ScoredAnnotation(0, 1, entity.getConcept(), 1));
		return res;
	}

	@Override
	public void setDebugger(SmaphDebugger debugger) {
	}
} public class GreedyLinkback implements LinkBack {
	private List<AnnotationRegressor> ar;
	private List<FeatureNormalizer> annFn;
	private WikipediaInterface wikiApi;
	private double edthreshold;
	private WikipediaToFreebase w2f;
	private EntityToAnchors e2a;
	
	public GreedyLinkback(List<AnnotationRegressor> annRegs, List<FeatureNormalizer> fns, WikipediaInterface wikiApi,
	        WikipediaToFreebase w2f, EntityToAnchors e2a, double edthreshold) throws FileNotFoundException, IOException {
		if (annRegs.size() != fns.size())
			throw new IllegalArgumentException();
		this.ar = annRegs;
		this.annFn = fns;
		this.wikiApi = wikiApi;
		this.w2f = w2f;
		this.edthreshold = edthreshold;
		this.e2a = e2a;
	}

	public static List<Annotation> getAnnotations(String query, Set<Tag> acceptedEntities, double anchorMaxED, EntityToAnchors e2a, WikipediaInterface wikiApi) {
		return IndividualLinkback.getAnnotations(query, acceptedEntities, anchorMaxED, e2a, wikiApi);
	}

	@Override
	public HashSet<ScoredAnnotation> linkBack(String query, HashSet<Tag> acceptedEntities, QueryInformation qi) {
		List<Annotation> annotations = getAnnotations(query, acceptedEntities, edthreshold, e2a, wikiApi);
		
		HashSet<ScoredAnnotation> solution = new HashSet<>();
		for (int i = 0; i < ar.size(); i++) {
			List<Pair<GreedyFeaturePack, Annotation>> ftrsAndAnnotations = annotations.stream().map(
			        a -> new Pair<GreedyFeaturePack, Annotation>(
			        		new GreedyFeaturePack(a, query, qi, solution, wikiApi, w2f, e2a),
			                a))
			        .collect(Collectors.toList());
			ScoredAnnotation annToAdd = getStepAnnotation(ftrsAndAnnotations, ar.get(i), annFn.get(i));
			if (annToAdd == null)
				break;
			solution.add(annToAdd);
			annotations = annotations.stream().filter(a -> solution.stream().allMatch(as -> !as.overlaps(a)))
			        .collect(Collectors.toList());
			if (annotations.isEmpty())
				break;
		}
		return solution;
	}

	public static <A extends Annotation, F extends FeaturePack<A>> ScoredAnnotation getStepAnnotation(
	        List<Pair<F, Annotation>> ftrsAndAnnotations, AnnotationRegressor annotationRegressor,
	        FeatureNormalizer featureNormalizer) {
		double highestScore = Double.NEGATIVE_INFINITY;
		Annotation bestAnn = null;
		for (Pair<F, Annotation> ftrsAndAnnotation : ftrsAndAnnotations) {
			double score = annotationRegressor.predictScore(ftrsAndAnnotation.first, featureNormalizer);
			if (score > annotationRegressor.threshold() && score > highestScore) {
				bestAnn = ftrsAndAnnotation.second;
				highestScore = score;
			}
		}
		if (bestAnn == null)
			return null;

		return new ScoredAnnotation(bestAnn.getPosition(), bestAnn.getLength(), bestAnn.getConcept(), (float) highestScore);
	}

	@Override
	public void setDebugger(SmaphDebugger debugger) {
	}

} public class IndividualLinkback implements LinkBack {
	private AnnotationRegressor ar;
	private FeatureNormalizer annFn;
	private WikipediaInterface wikiApi;
	private double edthreshold;
	private WikipediaToFreebase w2f;
	private EntityToAnchors e2a;

	public IndividualLinkback(AnnotationRegressor ar, FeatureNormalizer annFn, WikipediaInterface wikiApi,
	        WikipediaToFreebase w2f, EntityToAnchors e2a, double edthreshold) throws FileNotFoundException, IOException {
		this.ar = ar;
		this.annFn = annFn;
		this.wikiApi = wikiApi;
		this.w2f = w2f;
		this.edthreshold = edthreshold;
		this.e2a = e2a;
	}

	public static List<Annotation> getAnnotations(String query, Set<Tag> acceptedEntities, double anchorMaxED, EntityToAnchors e2a, WikipediaInterface wikiApi) {
		List<Pair<Integer, Integer>> segments = SmaphUtils.findSegments(query);
		List<Annotation> annotations = new Vector<>();
		for (Tag t : acceptedEntities) {
			//if (!e2a.containsId(t.getConcept()))
			//	continue;
			List<Pair<String, Integer>> entityAnchors;
			try {
				if (e2a.containsId(t.getConcept()))
					entityAnchors = e2a.getAnchors(t.getConcept());
				else if (wikiApi.getTitlebyId(t.getConcept()) != null)
					entityAnchors = AnnotationFeaturePack.getFakeAnchors(wikiApi.getTitlebyId(t.getConcept()));
				else continue;
			} catch (IOException e) {
				e.printStackTrace();
				throw new RuntimeException(e);
			}
			for (Pair<Integer, Integer> segment : segments) {
				String segmentStr = query.substring(segment.first, segment.second);
				if (entityAnchors.stream().anyMatch(anchor -> SmaphUtils.getNormEditDistance(anchor.first, segmentStr) < anchorMaxED))
					annotations.add(new Annotation(segment.first, segment.second - segment.first, t.getConcept()));
			}
		}
		return annotations;
	}

	@Override
	public HashSet<ScoredAnnotation> linkBack(String query, HashSet<Tag> acceptedEntities, QueryInformation qi) {

		List<Pair<Annotation, Double>> scoreAndAnnotations = new Vector<>();
		for (Annotation a : getAnnotations(query, acceptedEntities, edthreshold, e2a, wikiApi)) {
			double score = ar.predictScore(new AnnotationFeaturePack(a, query, qi, wikiApi, w2f, e2a),
					annFn);
			scoreAndAnnotations.add(new Pair<Annotation, Double>(a, score));
		}

		return getResult(scoreAndAnnotations, ar.threshold());
		
	}
	
	public static HashSet<ScoredAnnotation> getResult(List<Pair<Annotation, Double>> annotationsAndScore, double threshold){
		Collections.sort(annotationsAndScore, new SmaphUtils.ComparePairsBySecondElement<Annotation, Double>());
		Collections.reverse(annotationsAndScore);

		HashSet<ScoredAnnotation> res = new HashSet<>();
		
		for (Pair<Annotation, Double> pair : annotationsAndScore){
			Annotation annI = pair.first;
			double score = pair.second;
			
			if (score < threshold) break;
			
			boolean overlap = false;
			for (ScoredAnnotation ann : res)
				if (annI.overlaps(ann)) {
					overlap = true;
					break;
				}
			if (!overlap){
				res.add(new ScoredAnnotation(annI.getPosition(), annI.getLength(), annI.getConcept(), (float)score));
			}
		}

		//TODO: shall we collapse bindings?
		//return SmaphUtils.collapseBinding(res);
		return res;
	}


	@Override
	public void setDebugger(SmaphDebugger debugger) {
	}

} public interface LinkBack {
	public HashSet<ScoredAnnotation> linkBack(String query,
			HashSet<Tag> acceptedEntities, QueryInformation qi);

	public void setDebugger(SmaphDebugger debugger);
} public interface BindingGenerator {
	public List<HashSet<Annotation>> getBindings(String query, QueryInformation qi, Set<Tag> acceptedEntities, WikipediaInterface wikiApi);
} public class DefaultBindingGenerator implements BindingGenerator {
	private final static Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
	private static final int MAX_SEGMENTATIONS = 1000;
	private static final int MAX_SEGMENTATIONS_AFTER_FILTER = 150;//150;
	private static final int MAX_BINDINGS_PER_SEGMENTATION = 50;//50;

	private List<HashSet<Annotation>> getAllBindings(String query,
			HashMap<Tag, String[]> entityToBolds, HashMap<Tag, String> entitiesToTitles, Set<Tag> acceptedEntities) {
		HashSet<HashSet<Annotation>> insertedAnnotationSets = new HashSet<>();
		List<HashSet<Annotation>> annotationSets = new Vector<>();
		List<List<Pair<Integer, Integer>>> segmentations = pruneSegmentations(query, entityToBolds, entitiesToTitles);
		
		for (List<Pair<Integer, Integer>> segmentation : segmentations) {
			HashSet<Tag> assignedTags = new HashSet<>();
			List<List<Tag>> candidatesForSegmentation = new Vector<>();
			for (Pair<Integer, Integer> segment : segmentation) {
				List<Tag> candidatesForSegment = new Vector<>();
				candidatesForSegmentation.add(candidatesForSegment);
				// This segment may not be linked to any entity.
				candidatesForSegment.add(new Tag(-1));
				for (Tag tag : entityToBolds.keySet())
					if (acceptedEntities.contains(tag)) {
						double minEditDistance = 1.0;
						for (String tagText : entityToBolds.get(tag))
							minEditDistance = Math.min(
							        SmaphUtils.getNormEditDistanceLC(query.substring(segment.first, segment.second), tagText),
							        minEditDistance);
						if (minEditDistance <= 0.4) {
							candidatesForSegment.add(tag);
							assignedTags.add(tag);
						}
					}
				/*for (Tag tag : entityToBoldS2S3.keySet()) {
					if (candidatesForSegment.contains(tag))
						continue;
					String[] tagTexts = entityToBoldS2S3.get(tag);
					double minEditDistance = 1.0;
					for (String tagText : tagTexts)
						minEditDistance = Math.min(
								SmaphUtils.getNormEditDistanceLC(query
										.substring(segment.first,
												segment.second), tagText),
								minEditDistance);
					if (minEditDistance <= -1) {
						candidatesForSegment.add(tag);
						assignedTags.add(tag);
					}
				}*/
				for (Tag tag : entitiesToTitles.keySet())
					if (acceptedEntities.contains(tag)) {
						if (candidatesForSegment.contains(tag))
							continue;
						if (SmaphUtils.getMinEditDist(entitiesToTitles.get(tag), query.substring(segment.first, segment.second)) <= 0.7) {
							candidatesForSegment.add(tag);
							assignedTags.add(tag);
						}
					}
			}
			
			//add orphan tags to closest segment TODO: don't like at all!!!!
			/*for (Tag tag : entitiesToTitles.keySet())
				if (acceptedEntities.contains(tag)) {
					if (assignedTags.contains(tag))
						continue;

					double minEditDistance = Double.POSITIVE_INFINITY;
					List<Tag> candidatesForBestSegment = null;

					for (int i = 0; i < segmentation.size(); i++) {
						Pair<Integer, Integer> segment = segmentation.get(i);
						String segmentStr = query.substring(segment.first, segment.second);
						double editDistance = SmaphUtils.getMinEditDist(entitiesToTitles.get(tag), segmentStr);
						if (editDistance < minEditDistance) {
							minEditDistance = editDistance;
							candidatesForBestSegment = candidatesForSegmentation.get(i);
						}
					}
					if (minEditDistance < 1.0 && candidatesForBestSegment != null) {
						candidatesForBestSegment.add(tag);
						assignedTags.add(tag);
					}
				}*/
			
			//add tags that were not candidates to any segment to all segments
			for (Tag tag : entitiesToTitles.keySet())
				if (acceptedEntities.contains(tag)) {
					if (assignedTags.contains(tag))
						continue;
					for (List<Tag> candidatesForSegment : candidatesForSegmentation)
						candidatesForSegment.add(tag);
				}

			List<List<Tag>> bindingsForSegmentation = SmaphUtils.getBindings(candidatesForSegmentation, MAX_BINDINGS_PER_SEGMENTATION);

			for (List<Tag> tags : bindingsForSegmentation) {
				HashSet<Annotation> annotations = new HashSet<>();
				for (int i = 0; i < tags.size(); i++)
					if (tags.get(i).getConcept() != -1)
						annotations.add(new Annotation(
								segmentation.get(i).first,
								segmentation.get(i).second
										- segmentation.get(i).first, tags
										.get(i).getConcept()));
				if (!insertedAnnotationSets.contains(annotations)) {
					annotationSets.add(annotations);
					insertedAnnotationSets.add(annotations);
				}
			}
		}
		
		if (!insertedAnnotationSets.contains(new HashSet<Annotation>())) {
			annotationSets.add(new HashSet<Annotation>());
			insertedAnnotationSets.add(new HashSet<Annotation>());
		}
		
		LOG.info("Segmentations: {} Bindings: {}",
				segmentations.size(), annotationSets.size());
		return annotationSets;
	}
	

	private List<List<Pair<Integer, Integer>>> pruneSegmentations(String query,
			HashMap<Tag, String[]> entityToBoldsS1, HashMap<Tag, String> entitiesToTitles){
		//Do an initial pruning of segmentations and entities: if the bold (or title) of an entity found with source1 matches a substring in the query, fix it 
				List<Pair<Integer, Integer>> segments = SmaphUtils.findSegments(query);
				
				HashMap<Pair<Integer, Integer>, List<Tag>> segmentFixedCandidates = new HashMap<>();
				HashSet<Tag> fixedCandidates =  new HashSet<>();
				// link all entities to segments with edit-distance <0.1
				for (Pair<Integer, Integer> segment : segments) {
/*					for (Tag tag : entityToBoldsS1.keySet()) {
						double minEditDistance = 1.0;
						for (String tagText : entityToBoldsS1.get(tag))
							minEditDistance = Math.min(SmaphUtils
									.getNormEditDistanceLC(query.substring(
											segment.first, segment.second), tagText),
									minEditDistance);
						if (minEditDistance <= 0.1) {
							if (!segmentFixedCandidates.containsKey(segment))
								segmentFixedCandidates.put(segment, new Vector<Tag>());
							segmentFixedCandidates.get(segment).add(tag);
							fixedCandidates.add(tag);
						}
					}
*/					for (Tag tag : entitiesToTitles.keySet()) {
						if (segmentFixedCandidates.containsKey(segment)
								&& segmentFixedCandidates.get(segment).contains(tag))
							continue;
						if (SmaphUtils.getMinEditDist(entitiesToTitles.get(tag),
								query.substring(segment.first, segment.second)) <= 0.1) {
							if (!segmentFixedCandidates.containsKey(segment))
								segmentFixedCandidates.put(segment, new Vector<Tag>());
							segmentFixedCandidates.get(segment).add(tag);
							fixedCandidates.add(tag);
						}
					}
				}
				
					
				
				List<List<Pair<Integer, Integer>>> allSegmentations = SmaphUtils
						.getSegmentations(query, MAX_SEGMENTATIONS);
				
				//If a fixed segment is included into another and they point to the same set of entities, delete segmentations that include the inner one (this is safe).
				HashSet<Pair<Integer, Integer>> fixedSegmentsToRemove = new HashSet<>();
				for (Pair<Integer, Integer> segment : segmentFixedCandidates.keySet()) {
					boolean included = false;
					for (Pair<Integer, Integer> segment2 : segmentFixedCandidates
							.keySet())
						if (!segment.equals(segment2)
								&& segment2.first <= segment.first
								&& segment.second <= segment2.second
								&& new HashSet<Tag>(segmentFixedCandidates.get(segment)).equals(
										new HashSet<Tag>(segmentFixedCandidates.get(segment2)))) {
							included = true;
							break;
						}
					if (included) {
						fixedSegmentsToRemove.add(segment);
						List<Integer> segmentationsToRemove = new Vector<>();
						for (int i = 0; i < allSegmentations.size(); i++) {
							List<Pair<Integer, Integer>> segmentation = allSegmentations
									.get(i);
							if (segmentation.contains(segment))
								segmentationsToRemove.add(i);
						}
						for (int i = segmentationsToRemove.size() - 1; i >= 0; i--)
							allSegmentations.remove(segmentationsToRemove.get(i)
									.intValue());
					}
				}
				for (Pair<Integer, Integer> segment : fixedSegmentsToRemove)
					segmentFixedCandidates.remove(segment);
				
				// Throw away bindings that conflict with fixed ones.
				/*for (Pair<Integer, Integer> fixedSegment : segmentFixedCandidates.keySet()){
					//If this segment overlaps with another fixed one, do not perform any deletion.
					boolean foundOverlappingSegment = false;
					for (Pair<Integer, Integer> fixedSegment2 : segmentFixedCandidates.keySet())
						if (!fixedSegment2.equals(fixedSegment) && segmentsOverlap(fixedSegment, fixedSegment2)){
							foundOverlappingSegment = true;
							break;
						}
					LOG.info(query.substring(fixedSegment.first, fixedSegment.second)+" "+(foundOverlappingSegment?"overlap":"no-overlap"));
					if (foundOverlappingSegment)
						continue;
					//There are no overlapping fixed segments, delete all segmentations that contain segments conflicting with segment.
					List<Integer> segmentationsToRemove = new Vector<>();
					for (int i=0; i<allSegmentations.size(); i++){
						List<Pair<Integer, Integer>> segmentation= allSegmentations.get(i);
						for (Pair<Integer, Integer> segment : segmentation)
							if (!fixedSegment.equals(segment) && segmentsOverlap(fixedSegment, segment)){
								segmentationsToRemove.add(i);
								break;
							}
					}
					for (int i = segmentationsToRemove.size()-1; i>=0; i--)
						allSegmentations.remove(segmentationsToRemove.get(i).intValue());
				}
				
				LOG.info(allSegmentations.size());
				*/
				
				if (allSegmentations.size() > MAX_SEGMENTATIONS_AFTER_FILTER)
					allSegmentations.subList(MAX_SEGMENTATIONS_AFTER_FILTER, allSegmentations.size()).clear();
				
				return allSegmentations;
	}
	
	@Override
	public List<HashSet<Annotation>> getBindings(String query,
			QueryInformation qi, Set<Tag> acceptedEntities,
			WikipediaInterface wikiApi) {
		HashMap<Tag, String[]> entityToBolds;
		entityToBolds = SmaphUtils.getEntitiesToBoldsList(qi.entityToBoldsSA,
				qi.allCandidates());
		HashMap<Tag, String> entitiesToTitles = SmaphUtils.getEntitiesToTitles(acceptedEntities, wikiApi);
		return getAllBindings(query, entityToBolds, entitiesToTitles, acceptedEntities);
		
	}
} public class FrequencyAnnotationFilter implements SnippetAnnotationFilter {
	private double threshold;

	public FrequencyAnnotationFilter(double threshold) {
		this.threshold = threshold;
	}

	@Override
	public HashSet<Tag> filterAnnotations(
			HashMap<Tag, List<Integer>> tagToRanks, double resCount) {
		HashSet<Tag> filteredTags = new HashSet<>();
		for (Tag t : tagToRanks.keySet())
			if (((double) tagToRanks.get(t).size()) / (double) resCount >= threshold)
				filteredTags.add(t);
		return filteredTags;
	}

}
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.math3.util.Pair;
import it.unipi.di.acube.batframework.systemPlugins.CachedWAT2Annotator;
import it.unipi.di.acube.batframework.systemPlugins.WAT2Annotator;
import it.unipi.di.acube.batframework.utils.WikipediaInterface;
import it.unipi.di.acube.searchapi.CachedWebsearchApi;
import it.unipi.di.acube.searchapi.WebsearchApi;
import it.unipi.di.acube.searchapi.callers.BingSearchApiCaller;
import it.unipi.di.acube.searchapi.callers.GoogleSearchApiCaller;
import it.unipi.di.acube.smaph.datasets.wikiAnchors.EntityToAnchors;
import it.unipi.di.acube.smaph.datasets.wikitofreebase.WikipediaToFreebase;
import it.unipi.di.acube.smaph.learn.featurePacks.EntityFeaturePack;
import it.unipi.di.acube.smaph.learn.featurePacks.FeaturePack;
import it.unipi.di.acube.smaph.learn.featurePacks.GreedyFeaturePack;
import it.unipi.di.acube.smaph.learn.models.entityfilters.EntityFilter;
import it.unipi.di.acube.smaph.learn.models.entityfilters.LibSvmEntityFilter;
import it.unipi.di.acube.smaph.learn.models.entityfilters.NoEntityFilter;
import it.unipi.di.acube.smaph.learn.models.linkback.annotationRegressor.AnnotationRegressor;
import it.unipi.di.acube.smaph.learn.models.linkback.annotationRegressor.LibSvmAnnotationRegressor;
import it.unipi.di.acube.smaph.learn.models.linkback.bindingRegressor.BindingRegressor;
import it.unipi.di.acube.smaph.learn.models.linkback.bindingRegressor.RankLibBindingRegressor;
import it.unipi.di.acube.smaph.learn.normalizer.FeatureNormalizer;
import it.unipi.di.acube.smaph.learn.normalizer.NoFeatureNormalizer;
import it.unipi.di.acube.smaph.learn.normalizer.ZScoreFeatureNormalizer;
import it.unipi.di.acube.smaph.linkback.CollectiveLinkBack;
import it.unipi.di.acube.smaph.linkback.DummyLinkBack;
import it.unipi.di.acube.smaph.linkback.GreedyLinkback;
import it.unipi.di.acube.smaph.linkback.IndividualLinkback;
import it.unipi.di.acube.smaph.linkback.LinkBack;
import it.unipi.di.acube.smaph.linkback.bindingGenerator.BindingGenerator;
import it.unipi.di.acube.smaph.linkback.bindingGenerator.DefaultBindingGenerator;
import it.unipi.di.acube.smaph.snippetannotationfilters.FrequencyAnnotationFilter;
package it.unipi.di.acube.smaph;




public class SmaphBuilder {

	public static final BindingGenerator DEFAULT_BINDING_GENERATOR = new DefaultBindingGenerator();
	public static WAT2Annotator DEFAULT_AUX_ANNOTATOR = WAT2Annotator.WAT2AnnotatorBuilder.builder().baseUri("http://localhost:8080/wat-servlet").enableAdditionalInfo().tokenizer("lucene").build();
	public static WAT2Annotator DEFAULT_CACHED_AUX_ANNOTATOR = CachedWAT2Annotator.CachedWAT2AnnotatorBuilder.builder().baseUri("http://localhost:8080/wat-servlet").enableAdditionalInfo().tokenizer("lucene").build();
	public static WebsearchApi BING_WEBSEARCH_API = null;
	public static WebsearchApi GOOGLE_WEBSEARCH_API = null;
	private static Map<URL, FeatureNormalizer> urlToNormalizer = new HashMap<>();
	private static Map<URL, EntityFilter> urlToEntityFilter = new HashMap<>();
	private static Map<URL, AnnotationRegressor> urlToAnnotationRegressor = new HashMap<>();
	private static Map<URL, BindingRegressor> urlToBindingRegressor = new HashMap<>();
	public static final int DEFAULT_NORMALSEARCH_RESULTS = 5;
	public static final int DEFAULT_WIKISEARCH_RESULTS = 10;
	public static final int DEFAULT_ANNOTATED_SNIPPETS = 15;
	public static final double DEFAULT_ANNOTATIONFILTER_RATIO = 0.03;
	public static final double DEFAULT_ANCHOR_MENTION_ED = 0.7;
	public static final Websearch DEFAULT_WEBSEARCH = Websearch.GOOGLE_CSE;
	
	public static void initialize(String watGcubeToken){
		DEFAULT_AUX_ANNOTATOR = WAT2Annotator.WAT2AnnotatorBuilder.builder().gcubeToken(watGcubeToken).enableAdditionalInfo().tokenizer("lucene").build();
		DEFAULT_CACHED_AUX_ANNOTATOR = CachedWAT2Annotator.CachedWAT2AnnotatorBuilder.builder().gcubeToken(watGcubeToken).enableAdditionalInfo().tokenizer("lucene").build();
	}

	public enum SmaphVersion {
		ENTITY_FILTER("ef"), ANNOTATION_REGRESSOR("ar"), COLLECTIVE("coll"), GREEDY("greedy");

		private String label;

		private SmaphVersion(String label) {
			this.label = label;
		}

		@Override
		public String toString() {
			return label;
		}
	}

	public enum Websearch {
		BING, GOOGLE_CSE
	}

	public static Websearch websearchFromString(String wsStr) {
		switch (wsStr) {
		case "bing":
			return SmaphBuilder.Websearch.BING;
		case "google":
			return SmaphBuilder.Websearch.GOOGLE_CSE;
		}
		return null;
	}

	public static WebsearchApi getWebsearch(Websearch ws, SmaphConfig c)
	        throws FileNotFoundException, ClassNotFoundException, IOException {

		if (c.getDefaultWebsearchCache() == null)
			switch (ws) {
			case GOOGLE_CSE:
				return new WebsearchApi(new GoogleSearchApiCaller(c.getDefaultGoogleCseId(), c.getDefaultGoogleApiKey()));
			case BING:
				return new WebsearchApi(new BingSearchApiCaller(c.getDefaultBingKey()));
			}
		else
			switch (ws) {
			case GOOGLE_CSE:
				if (GOOGLE_WEBSEARCH_API == null)
					GOOGLE_WEBSEARCH_API = CachedWebsearchApi.builder()
					        .api(new GoogleSearchApiCaller(c.getDefaultGoogleCseId(), c.getDefaultGoogleApiKey()))
					        .dbFrom((CachedWebsearchApi) BING_WEBSEARCH_API).path(c.getDefaultWebsearchCache()).create();
				return GOOGLE_WEBSEARCH_API;
			case BING:
				if (BING_WEBSEARCH_API == null)
					BING_WEBSEARCH_API = CachedWebsearchApi.builder().api(new BingSearchApiCaller(c.getDefaultBingKey()))
					        .dbFrom((CachedWebsearchApi) GOOGLE_WEBSEARCH_API).path(c.getDefaultWebsearchCache()).create();
				return BING_WEBSEARCH_API;
			}
		return null;
	}

	public static SmaphAnnotator getDefaultSmaphParamTopk(WikipediaInterface wikiApi, WikipediaToFreebase wikiToFreeb,
	        WAT2Annotator auxAnnotator, EntityToAnchors e2a, EntityFilter entityFilter, FeatureNormalizer efNorm, LinkBack lb,
	        boolean s1, int topkS1, boolean s2, int topkS2, boolean s3, int topkS3, Websearch ws, SmaphConfig c)
	        throws FileNotFoundException, ClassNotFoundException, IOException {
		return new SmaphAnnotator(s1, topkS1, s2, topkS2, s3, topkS3, DEFAULT_ANCHOR_MENTION_ED, false, lb, entityFilter, efNorm,
		        DEFAULT_BINDING_GENERATOR, auxAnnotator, new FrequencyAnnotationFilter(DEFAULT_ANNOTATIONFILTER_RATIO), wikiApi,
		        wikiToFreeb, getWebsearch(ws, c), e2a);
	}

	private static SmaphAnnotator getDefaultSmaphParam(WikipediaInterface wikiApi, WikipediaToFreebase wikiToFreeb,
	        WAT2Annotator auxAnnotator, EntityToAnchors e2a, EntityFilter entityFilter, FeatureNormalizer efNorm, LinkBack lb,
	        boolean s1, boolean s2, boolean s3, Websearch ws, SmaphConfig c)
	        throws FileNotFoundException, ClassNotFoundException, IOException {
		return getDefaultSmaphParamTopk(wikiApi, wikiToFreeb, auxAnnotator, e2a, entityFilter, efNorm, lb, s1,
		        DEFAULT_NORMALSEARCH_RESULTS, s2, DEFAULT_WIKISEARCH_RESULTS, s3, DEFAULT_ANNOTATED_SNIPPETS, ws, c);
	}

	public static SmaphAnnotator getSmaphGatherer(WikipediaInterface wikiApi, WikipediaToFreebase wikiToFreeb,
	        EntityToAnchors e2a, boolean s1, boolean s2, boolean s3, Websearch ws, SmaphConfig c)
	        throws FileNotFoundException, ClassNotFoundException, IOException {
		return getDefaultSmaphParam(wikiApi, wikiToFreeb, DEFAULT_CACHED_AUX_ANNOTATOR, e2a, new NoEntityFilter(), null,
		        new DummyLinkBack(), s1, s2, s3, ws, c);
	}

	public static SmaphAnnotator getSmaphGatherer(WikipediaInterface wikiApi, WikipediaToFreebase wikiToFreeb,
	        EntityToAnchors e2a, boolean s1, int topkS1, boolean s2, int topkS2, boolean s3, int topkS3, Websearch ws,
	        SmaphConfig c) throws FileNotFoundException, ClassNotFoundException, IOException {
		return getDefaultSmaphParamTopk(wikiApi, wikiToFreeb, DEFAULT_CACHED_AUX_ANNOTATOR, e2a, new NoEntityFilter(), null,
		        new DummyLinkBack(), s1, topkS1, s2, topkS2, s3, topkS3, ws, c);
	}

	public static SmaphAnnotator getSmaph(SmaphVersion v, WikipediaInterface wikiApi, WikipediaToFreebase wikiToFreeb,
	        WAT2Annotator auxAnnotator, EntityToAnchors e2a, boolean includeS2, SmaphConfig c)
	        throws FileNotFoundException, ClassNotFoundException, IOException {
		return getSmaph(v, wikiApi, wikiToFreeb, auxAnnotator, e2a, includeS2, DEFAULT_WEBSEARCH, c, -1);
	}

	public static SmaphAnnotator getSmaph(SmaphVersion v, WikipediaInterface wikiApi, WikipediaToFreebase wikiToFreeb,
	        WAT2Annotator auxAnnotator, EntityToAnchors e2a, boolean includeS2, Websearch ws, SmaphConfig c, int greedyStepLimit)
	        throws FileNotFoundException, ClassNotFoundException, IOException {
		URL model = getDefaultModel(v, ws, true, includeS2, true, -1);
		URL zscore = getDefaultZscoreNormalizer(v, ws, true, includeS2, true, -1);

		SmaphAnnotator a = null;
		switch (v) {
		case ANNOTATION_REGRESSOR: {
			AnnotationRegressor ar = getCachedAnnotationRegressor(model);
			FeatureNormalizer fn = getCachedFeatureNormalizer(zscore, new GreedyFeaturePack());
			a = getDefaultSmaphParam(wikiApi, wikiToFreeb, auxAnnotator, e2a, new NoEntityFilter(), null,
			        new IndividualLinkback(ar, fn, wikiApi, wikiToFreeb, e2a, DEFAULT_ANCHOR_MENTION_ED), true, includeS2,
			        true, ws, c);
		}
			break;
		case ENTITY_FILTER: {
			EntityFilter ef = getCachedSvmEntityFilter(model);
			FeatureNormalizer norm = getCachedFeatureNormalizer(zscore, new EntityFeaturePack());