package de.psychometrica.norming;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

/**
 * Calculates relative ranks, based on different ranking algorithms. *
 * Precompiled binaries and Javadoc can be found at
 * http://www.psychometrica.de/norming.html <br>
 * The code is licensed under the Lesser General Public License (LGPL 2.0)
 * 
 * @author PD Dr. Wolfgang Lenhard
 * 
 */
public class Rank {

	/**
	 * Option for using the Blom (1959) ranking algorithm: (relativeRank – 3/8)
	 * / (sampleSize + 1/4)
	 */
	public static final int BLOM = 0;

	/**
	 * Option for using the ranking algorithm from Chambers, Cleveland, Kleiner,
	 * and Tukey (1983): (relativeRank – .5) / sampleSize
	 */
	public static final int RANKIT = 1;

	/**
	 * Option for using the transformation of Tukey /1962): (relativeRank – 1/3)
	 * / (sampleSize + 1/3)
	 */
	public static final int TUKEY = 2;

	/**
	 * Option for using the Van der Warden transformation (Lehmann, 1975):
	 * relativeRank / (sampleSize + 1)
	 */
	public static final int VAN_DER_WARDEN = 3;

	/**
	 * Get the relative rank of a value by its index
	 * 
	 * @param valueList
	 *            a list of integer values
	 * @param index
	 *            the index, for which the value should be retrieved
	 * @param mode
	 *            the ranking algorithm
	 * @return the relative rank
	 */
	public static double getRankByIndex(List<Integer> valueList, int index,
			int mode) {
		return getRank(valueList, valueList.get(index), mode);
	}

	/**
	 * Get the relative rank of a value by its index
	 * 
	 * @param valueList
	 *            a list of integer values
	 * @param index
	 *            the index, for which the value should be retrieved
	 * @return the relative rank
	 */
	public static double getRankByIndex(List<Integer> valueList, int index) {
		return getRank(valueList, valueList.get(index), Rank.RANKIT);
	}

	/**
	 * Get the relative rank for a specific target value when comparing it to a
	 * list of values
	 * 
	 * @param valueList
	 *            a list of integer values
	 * @param targetValue
	 *            the raw value for which the relative rank is computed
	 * @return the relative rank
	 */
	public static double getRank(List<Integer> valueList, int targetValue) {
		return getRank(valueList, targetValue, Rank.RANKIT);
	}

	/**
	 * Get the relative rank for a specific target value when comparing it to a
	 * list of values
	 * 
	 * @param valueList
	 *            a list of integer values
	 * @param targetValue
	 *            the raw value for which the relative rank is computed
	 * @param mode
	 *            the ranking algorithm
	 * @return the relative rank
	 */
	public static double getRank(List<Integer> valueList, int targetValue,
			int mode) {
		if (valueList.size() == 0 || !valueList.contains(targetValue))
			return Double.NaN;

		double relativeRank = 1.0f;
		ArrayList<Integer> indexList = new ArrayList<Integer>();
		Collections.sort(valueList);
		HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();

		for (int i = 0; i < valueList.size(); i++) {
			if (map.containsKey(valueList.get(i))) {
				map.put(valueList.get(i), map.get(valueList.get(i)) + 1);
			} else {
				map.put(valueList.get(i), 1);
				indexList.add(valueList.get(i));
			}
		}

		// get mean rank
		Collections.sort(indexList);
		int i = 0;
		int index = indexList.get(i);
		while (index < targetValue) {
			relativeRank = relativeRank + 2 * map.get(index);
			i = i + 1;
			index = indexList.get(i);
		}

		relativeRank = (relativeRank + ((double) map.get(index))) / 2.0f;

		if (mode == Rank.RANKIT)
			return rankit(relativeRank, valueList.size());
		else if (mode == Rank.BLOM)
			return blom(relativeRank, valueList.size());
		else if (mode == Rank.TUKEY)
			return tukey(relativeRank, valueList.size());
		else if (mode == Rank.VAN_DER_WARDEN)
			return vanderwarden(relativeRank, valueList.size());
		else
			return rankit(relativeRank, valueList.size());
	}

	/**
	 * Calculate the van der warden ranking algorithm
	 * 
	 * @param relativeRank
	 *            the relative rank of the target value
	 * @param weight
	 *            the size of the list of values
	 * @return the weighted rank
	 */
	private static double vanderwarden(double relativeRank, double weight) {
		return relativeRank / (weight + 1f);
	}

	/**
	 * Calculate the RANKIT ranking algorithm
	 * 
	 * @param relativeRank
	 *            the relative rank of the target value
	 * @param weight
	 *            the size of the list of values
	 * @return the weighted rank
	 */
	private static double rankit(double relativeRank, double weight) {
		return (relativeRank - .5f) / weight;
	}

	/**
	 * Calculate the Blom (1951) ranking algorithm
	 * 
	 * @param relativeRank
	 *            the relative rank of the target value
	 * @param weight
	 *            the size of the list of values
	 * @return the weighted rank
	 */
	private static double blom(double relativeRank, double weight) {
		return (relativeRank - .375f) / (weight + .25f);
	}

	/**
	 * Calculate the Tukey ranking algorithm
	 * 
	 * @param relativeRank
	 *            the relativ rank of the target value
	 * @param weight
	 *            the size of the list of values
	 * @return the weighted rank
	 */
	private static double tukey(double relativeRank, double weight) {
		return (relativeRank - .33333333334f) / (weight + .33333333334f);
	}

	public static void main(String[] args) {
		int targetValue = 5;
		ArrayList<Integer> list = new ArrayList<Integer>();
		list.add(1);
		list.add(1);
		list.add(1);
		list.add(2);
		list.add(2);
		list.add(2);
		list.add(2);
		list.add(3);
		list.add(3);
		list.add(3);
		list.add(3);
		list.add(3);
		list.add(4);
		list.add(4);
		list.add(3);
		list.add(5);
		System.out.println("Target-Value: " + targetValue);
		System.out.println("List size: " + list.size());
		System.out
				.println("\n--------------------------\n----- Relative Ranks -----\n--------------------------");
		System.out.println("Blom: " + getRank(list, targetValue, Rank.BLOM));
		System.out
				.println("RANKIT: " + getRank(list, targetValue, Rank.RANKIT));
		System.out.println("Tukey: " + getRank(list, targetValue, Rank.TUKEY));
		System.out.println("Van der Warden: "
				+ getRank(list, targetValue, Rank.VAN_DER_WARDEN));
	}
}
