package de.visone.nlpext.util;

import static java.lang.Math.log;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

import au.com.bytecode.opencsv.CSVWriter;

/**
 * Calculates the entropy of a series of measurement.
 * @author <a href="mailto:endele@inf.uni-konstanz.de">Simon Endele</a>
 */
public class EntropyComputer {
	
	/** Natural logarithm of 2. */
	public static final double LN2 = log(2.0);
	
	public EntropyComputer() { }
	
	
	/** Returns the base 2 logarithm of a <code>double</code> value. */
	public static double log2(double a) {
		return log(a)/LN2;
	}
	
	
	public static double getEntropy(Map<String,Double>[] hashMaps, String key) {
		return getEntropy(Arrays.asList(hashMaps), key);
	}
	public static double getEntropy(List<? extends Map<String,Double>> hashMaps, String key) {
		List<Double> list = new LinkedList<Double>();
		for (Map<String,Double> hashMap : hashMaps) {
			Double value = hashMap.get(key);
			if (value!=null) {
				list.add(value);
			}
		}
		return getEntropy(list);
	}
	
	
	
	public static double getEntropy(Double[] values) {
		return getEntropy(Arrays.asList(values));
	}
	public static double getEntropy(Collection<Double> values) {
		double sum = sum(values);
		if (sum==0.0) return 0.0;
		double entropy = 0.0;
		for (Double value : values) {
			//System.out.println(value);
			if (value!=null) {
				double val = value.doubleValue() / sum; //normalization:  val = val/sum(all vals)
				if (val>0.0) {
					entropy -= val*log2(val);
					//System.out.println("    entr.: "+val*log2(val));
				}
			}
		}
		//if (entropy==0.0) System.out.println("    entropy = 0");
		return entropy;
	}
	
	
	public static double sum(Collection<Double> values) {
		double sum = 0.0;
		for (Double val : values) {
			sum += (val!=null) ? val.doubleValue() : 0.0;
		}
		return sum;
	}
	
	
	/** Calculates the entropy of all words occuring in all csv-centrality-files
	 * and saves the words with entropy greater 0 and their entropy in descending 
	 * order to a csv-file. */
	public static void main(String[] args) throws IOException {
		if (args==null || args.length!=3) {
			System.out.println("Usage is:: EntropyComputer <cvsFilesDirectory> <centralityName> <output>");
			return;
		}
		File cvsDirectory = new File(args[0]);
		String centralityName = args[1];
		File output = new File(args[2]);
		
		File[] files = cvsDirectory.listFiles(new GraphMLFileFilter());
		Arrays.sort(files);
		System.out.println("Found "+files.length+" files.");
		
		FileWriter fileWriter = new FileWriter(output);
		CSVWriter writer = new CSVWriter(fileWriter, ',', '"');
		String[] header = {"Word","Entropy("+centralityName+")"};
		writer.writeNext(header);
		
		//read in all centrality-lists
		List<LinkedHashMap<String, Double>> centralitiesList = HashMapReader.readFiles(files, centralityName);
		TreeSet<String> words = HashMapReader.getAllKeys(centralitiesList);
		
		HashMap<String, Double> entropies = new HashMap<String, Double>();
		for (String word : words) {
			double entropy = getEntropy(centralitiesList, word);
			if (entropy!=0.0) {
				entropies.put(word, entropy);
			}
		}
		List<String> list = new LinkedList<String>();
		list.addAll(entropies.keySet());
		Collections.sort(list, new ValueComparator<String,Double>(entropies));
		for (String word : list) {
			String[] line = {word, ""+entropies.get(word)};
			writer.writeNext(line);
		}
		writer.close();
		System.out.println("Data successfully written to "+output.getAbsolutePath());
	}
	
	/** Compares two keys according their values stored in a map in descending order. */
	public static class ValueComparator<K,V extends Comparable<? super V>> implements Comparator<K> {
		private Map<K,V> map;
		public ValueComparator(Map<K,V> map) {
			this.map = map;
		}
		public int compare(K k1, K k2) {
			V v1 = map.get(k1);
			V v2 = map.get(k2);
			if (v1==null || v2==null) return 0;
			else return - v1.compareTo(v2); //minus for descending order
		}
		public boolean equals(Object obj) {
			return false;
		}
	}
}