/*
 * This file is part of Jstacs.
 *
 * Jstacs is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Jstacs 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Jstacs.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * For more information on Jstacs, visit http://www.jstacs.de
 */

package de.jstacs.utils;

import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;

/**
 * This class is a collection of methods which might be useful for the programmer.
 * 
 * @author Jens Keilwagen
 */
public class ToolBox {

	
	/**
	 * This method converts a {@link HashSet} in a {@link Hashtable} with unique indices starting at 0.
	 * The indices are derived from the order of the {@link Iterator} of the {@link HashSet}.
	 * 
	 * @param <K> the type of the keys
	 * 
	 * @param set the set of keys
	 * 
	 * @return a {@link Hashtable} with keys and unique indices starting at 0
	 */
	public static <K> Hashtable<K,Integer> parseHashSet2IndexHashtable( HashSet<K> set ){
		Hashtable<K, Integer> hash = new Hashtable<K, Integer>();
		if( set != null ) {
			Iterator<K> it = set.iterator();
			int i = 0;
			
			while( it.hasNext() ) {
				hash.put( it.next(), i );
				i++;
			}
		}
		return hash;
	}
	
	/**
	 * This method returns the maximum of the elements of an <code>array</code>.
	 * 
	 * @param array
	 *            the array of values
	 * 
	 * @return the maximum
	 */
	public static double max( double[] array ) {
		return max( 0, array.length, array );
	}
	
	/**
	 * This method returns the minimum of the elements of an <code>array</code>.
	 * 
	 * @param array
	 *            the array of values
	 * 
	 * @return the minimum
	 */
	public static double min( double[] array ) {
		return min( 0, array.length, array );
	}
	
	/**
	 * This method returns the maximum of the elements of an <code>array</code>
	 * between <code>start</code> and <code>end</code>.
	 * 
	 * @param start
	 *            start position (inclusive)
	 * @param end
	 *            end position (exclusive)
	 * @param array
	 *            the array of values
	 * 
	 * @return the maximum
	 */
	public static double max( int start, int end, double[] array ) {
		if( end <= start ) {
			throw new IllegalArgumentException();
		} else {
			double max = array[start];
			start++;
			while( start < end ) {
				if( max < array[start] ) {
					max = array[start];
				}
				start++;
			}
			return max;
		}
	}
	
	/**
	 * This method returns the minimum of the elements of an <code>array</code>
	 * between <code>start</code> and <code>end</code>.
	 * 
	 * @param start
	 *            start position (inclusive)
	 * @param end
	 *            end position (exclusive)
	 * @param array
	 *            the array of values
	 * 
	 * @return the minimum
	 */
	public static double min( int start, int end, double[] array ) {
		if( end <= start ) {
			throw new IllegalArgumentException();
		} else {
			double min = array[start];
			start++;
			while( start < end ) {
				if( min > array[start] ) {
					min = array[start];
				}
				start++;
			}
			return min;
		}
	}
	
	/**
	 * Returns the index with maximal value in a <code>double</code> array.
	 * 
	 * @param w
	 *            the given <code>double</code> array
	 * 
	 * @return the index
	 */
	public static final int getMaxIndex( double[] w ) {
		return getMaxIndex( 0, w.length, w );
	}

	/**
	 * Returns the index with maximal value in a <code>double</code> array.
	 * 
	 * @param w
	 *            the given <code>double</code> array
	 * @param start
	 *            start position (inclusive)
	 * @param end
	 *            end position (exclusive)
	 * 
	 * @return the index
	 */
	public static final int getMaxIndex( int start, int end, double[] w ) {
		int max = start, i = 1;
		for( ; i < end; i++ ) {
			if( w[i] > w[max] ) {
				max = i;
			}
		}
		return max;
	}
	
	/**
	 * Ranks the values in <code>values</code>, where the greatest value obtains the lowest rank.
	 * The boolean <code>sameRank</code> allows to decide whether tied values should obtain the same rank.
	 * 
	 * @param values the values
	 * @param sameRank a switch whether tied values obtain the same rank.
	 * @return the ranks
	 */
	public static final int[] rank( double[] values, boolean sameRank ){
		
		int n = values.length;
		double[][] help= new double[n][2];
		for(int i=0;i<n;i++){
			help[i][0] = values[i];
			help[i][1] = i;
		}
		Arrays.sort( help, new DoubleArrayComparator() );
		int[] ranks = new int[n];
		n--;
		int rank = 0;
		for(int i=n;i>=0;i--){
			ranks[(int)Math.round(help[i][1])] = rank; 
			if( !sameRank || (i > 0 && help[i][0] != help[i-1][0]) ){
				rank++;
			}
		}
		/*
		double[] v = values.clone();
		int[] ranks = new int[v.length];
		int rank = -1;
		double last = Double.NEGATIVE_INFINITY;
		int maxIdx = 0;
		for(int i=0;i<v.length;i++){
			maxIdx = getMaxIndex( v );
			if(v[maxIdx] != last){
				rank++;
			}
			ranks[maxIdx] = rank;
			last = v[maxIdx];
			v[maxIdx] = Double.NEGATIVE_INFINITY;
		}
		*/
		return ranks;
	}
	
	
	private static class DoubleArrayComparator implements Comparator<double[]> {
		public int compare(double[] o1, double[] o2) {
			return (int) Math.signum( o1[0] - o2[0] );
		}
		
	}
	
	/**
	 * The method computes the Spearman correlation of two vectors.
	 * 	
	 * @param v1 the first vector
	 * @param v2 the second vector
	 * 
	 * @return the Spearman correlation of the two vectors
	 * 
	 * @throws Exception if the vectors have different length
	 */
	public static double spearmanCorrelation(double[] v1, double[] v2) throws Exception{
		if(v1.length != v2.length){
			throw new Exception("Number of values in both vector differ.");
		}
		
		int[] rankTruth = ToolBox.rank( v2, true );
		int[] rankPred = ToolBox.rank( v1, true );
		
		double sumTruth = 0;
		double sumPred = 0;
		double sqTruth = 0;
		double sqPred = 0;
		double cross = 0;
		double n = rankTruth.length;
		
		for(int i=0;i<rankTruth.length;i++){
			sumTruth += rankTruth[i];
			sumPred += rankPred[i];
			sqTruth += rankTruth[i]*rankTruth[i];
			sqPred += rankPred[i]*rankPred[i];
			cross += rankTruth[i]*rankPred[i];
		}
		
		return (cross - sumTruth*sumPred/n)/( Math.sqrt( sqTruth - sumTruth*sumTruth/n )*Math.sqrt( sqPred - sumPred*sumPred/n) );
	}
	
	/**
	 * The method computes the Pearson correlation of two vectors.
	 * 	
	 * @param v1 the first vector
	 * @param v2 the second vector
	 * 
	 * @return the Pearson correlation of the two vectors
	 * 
	 * @throws Exception if the vectors have different length
	 */
	public static double pearsonCorrelation(double[] v1, double[] v2) throws Exception{
		if(v1.length != v2.length){
			throw new Exception("Number of values in both vector differ.");
		}
		
		double sumTruth = 0;
		double sumPred = 0;
		double sqTruth = 0;
		double sqPred = 0;
		double cross = 0;
		double n = v2.length;
		
		for(int i=0;i<v2.length;i++){
			sumTruth += v2[i];
			sumPred += v1[i];
			sqTruth += v2[i]*v2[i];
			sqPred += v1[i]*v1[i];
			cross += v2[i]*v1[i];
		}
		
		return (cross - sumTruth*sumPred/n)/( Math.sqrt( sqTruth - sumTruth*sumTruth/n )*Math.sqrt( sqPred - sumPred*sumPred/n) );
	}
	
}
