/*
    StatCvs - CVS statistics generation 
    Copyright (C) 2002  Lukasz Pekacki <lukasz@pekacki.de>
    http://statcvs.sf.net/
    
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    
	$Name:  $ 
	Created on $Date: 2004/10/12 07:22:42 $ 
*/
package net.sf.statcvs.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * Utility class for storing a map from <code>Object</code>s to
 * <code>int</code>s.
 * This class makes it easy to sort by key or value, and provides
 * useful features like {@link #sum()}, {@link #max()}, and
 * percent calculation.
 * <p>
 * The keys must be comparable, for example <code>String</code>s.
 * <p>
 * Behaviour for <code>null</code> keys is unspecified.
 * 
 * @author Richard Cyganiak
 * @version $Id: IntegerMap.java,v 1.14 2004/10/12 07:22:42 cyganiak Exp $
 */
public class IntegerMap {

	private Map map = new TreeMap();
	private Comparator comparator = new SortByValueComparator(map);
	private int sum = 0;
	private int max = 0;

	/**
	 * Puts a value into the map, overwriting any previous value
	 * for the same key.
	 * 
	 * @param key an <code>Object</code> which is used as key.
	 * @param value the <code>int</code> value to be stored at this key.
	 */
	public void put(Object key, int value) {
		max = Math.max(max, value);
		sum -= get(key);
		sum += value;
		map.put(key, new Integer(value));
	}
	
	/**
	 * Gets a value from the map. Returns the value which was
	 * stored in the map at the same key before. If no value was
	 * stored for this key, 0 will be returned.
	 * 
	 * @param key an <code>Object</code> which is used as key.
	 * @return the value for this key
	 */
	public int get(Object key) {
		Integer result = (Integer) map.get(key);
		if (result == null) {
			return 0;
		}
		return result.intValue();
	}
	
	/**
	 * Same as {@link #get(Object)}, but returns an <code>Integer</code>,
	 * not an <code>int</code>.
	 * 
	 * @param key the key to get the value for
	 * @return the value wrapped in an <code>Integer</code> object
	 */
	public Integer getInteger(Object key) {
		return (Integer) map.get(key);
	}
	
	/**
	 * Gets the value stored at a key as a percentage of all values
	 * in the map.
	 * 
	 * @param key the key to get the value for
	 * @return the value as a percentage of the sum of all values
	 */
	public double getPercent(Object key) {
		return (double) get(key) * 100 / sum;
	}

	/**
	 * Gets the value stored at a key as a percentage of the maximum
	 * value in the map. For the maximum value, this will return
	 * 100.0. For a value half as large as the maximum value, this
	 * will return 50.0.
	 * 
	 * @param key the key to get the value for
	 * @return the value as a percentage of largest value in the map
	 */
	public double getPercentOfMaximum(Object key) {
		return get(key) * 100 / max;
	}

	/**
	 * Adds an <code>int</code> to the value stored at a key.
	 * If no value was stored before at this key, the <code>int</code>
	 * will be stored there.
	 * 
	 * @param key the key to whose value <code>addValue</code> should be added
	 * @param addValue the <code>int</code> to be added
	 */
	public void addInt(Object key, int addValue) {
		put(key, addValue + get(key));
	}
	
	/**
	 * Same as <code>addInt(key, 1)</code>
	 * 
	 * @param key the key whose value should be increased
	 */
	public void inc(Object key) {
		addInt(key, 1);
	}
	
	/**
	 * Same as <code>addInt(key, -1)</code>
	 * 
	 * @param key the key whose value should be decreased
	 */
	public void dec(Object key) {
		addInt(key, -1);
	}
	
	/**
	 * Deletes a value from the map. This is different from
	 * <code>put(key, 0)</code>. Removing will reduce
	 * the size of the map, putting 0 will not.
	 * 
	 * @param key the key that should be removed
	 */
	public void remove(Object key) {
		sum -= get(key);
		map.remove(key);
	}

	/**
	 * Returns <code>true</code> if the map contains a value
	 * for this key.
	 * 
	 * @param key the key to check for
	 * @return <code>true</code> if the key is in the map
	 */
	public boolean contains(Object key) {
		return map.containsKey(key);
	}

	/**
	 * Returns the number of key-value pairs stored in the map.
	 * 
	 * @return the number of key-value pairs stored in the map
	 */
	public int size() {
		return map.size();
	}
	
	/**
	 * Returns a set view of the keys. The set will be in
	 * ascending key order.
	 * 
	 * @return a <code>Set</code> view of all keys
	 */
	public Set keySet() {
		return map.keySet();
	}

	/**
	 * Returns an iterator on the keys, sorted by key ascending.
	 * 
	 * @return an iterator on the keys
	 */
	public Iterator iteratorSortedByKey() {
		return map.keySet().iterator();
	}
	
	/**
	 * Returns an iterator on the keys, sorted by values ascending.
	 * 
	 * @return an iterator on the keys
	 */
	public Iterator iteratorSortedByValue() {
		List keys = new ArrayList(map.keySet());
		Collections.sort(keys, comparator);
		return keys.iterator();
	}

	/**
	 * Returns an iterator on the keys, sorted by values descending.
	 * 
	 * @return an iterator on the keys
	 */
	public Iterator iteratorSortedByValueReverse() {
		List keys = new ArrayList(map.keySet());
		Collections.sort(keys, comparator);
		Collections.reverse(keys);
		return keys.iterator();
	}

	/**
	 * Returns the sum of all values in the map.
	 * 
	 * @return the sum of all values in the map
	 */
	public int sum() {
		return sum;
	}

	/**
	 * Returns the average of all values in the map.
	 * 
	 * @return the average of all values in the map
	 */
	public double average() {
		return (double) sum() / size();
	}

	/**
	 * Returns the maximum value in the map.
	 * 
	 * @return the maximum value in the map.
	 */
	public int max() {
		return max;
	}

	/**
	 * Private utility class for comparing of map entries by value.
	 */
	private class SortByValueComparator implements Comparator {
		
		private Map mapToBeSorted;

		public SortByValueComparator(Map map) {
			this.mapToBeSorted = map;
		}

		public int compare(Object o1, Object o2) {
			int i1 = ((Integer) this.mapToBeSorted.get(o1)).intValue();
			int i2 = ((Integer) this.mapToBeSorted.get(o2)).intValue();
			if (i1 < i2) {
				return -1;
			} else if (i1 > i2) {
				return 1;
			}
			return 0;
		}
	}
}