package Tools;

import java.util.*;

/**
 * A HashMap that roughly bounds the size of storage consumed, and
 * kicks out keys whenever they haven't been accessed in a long time.
 *
 * @todo A clock algorithm (as in OS buffer cache pages) might be
 * faster in some situations.
 */
public class LRUHashMap
	extends HashMap {

	int maxOccupancy;	// always store at least this many guys
		// Not really true -- this is actually the amount of "time"
		// a slot has to have been accessed within to not get reaped
		// when we pass the tripSize. So accessing only a couple slots
		// frequently will cause reaping to drop the size down
		// below maxOccupancy.
	int tripSize;		// don't kick guys out until we're over this size
						// (to avoid walking the list too often)
	int now;			// current "date" -- new guys (or visited guys)
						// get this as their "birthday" to keep them from
						// getting reaped.

	public LRUHashMap() {
		this(10, 10, (float) 0.75);
	}

	/**
	 * @param maxOccupancy - how many keys can live in the LRUHashMap
	 * at once. Eventually this might be in terms of a size() parameter
	 * called on the keys.
	 */
	public LRUHashMap(int initialCapacity, int maxOccupancy, float loadFactor) {
		super(initialCapacity, loadFactor);
		this.maxOccupancy = maxOccupancy;
		this.tripSize = (int) (((float) maxOccupancy)/loadFactor);
	}

// Unsupported because we need to init before super does.
// Would have to reimplement. Silly constructors.
//	public LRUHashMap(java.util.Map p0) {
//		super(p0);
//		init();
//	}

	public Object clone() {
		throw new RuntimeException("no I'm really not cloneable");
	}

	public Object get(Object key) {
		AgeSlot as = (AgeSlot) super.get(key);
		if (as==null) {
			return null;
		} else {
			as.age = now++;
			return as.value;
		}
	}

	/**
	 * Sometimes put() will eject other key(s) from the hashtable.
	 * (at most one key until I implement a notion of per-entry size)
	 *
	 * Note that this class currently does not allow you to hash null
	 * values.
	 */
	public Object put(Object key, Object value) {
		AgeSlot old = (AgeSlot) super.put(key, new AgeSlot(now++, value));
		if (size()>tripSize) {
// System.out.println("time "+now+"; size "+size()+"; tripSize "+tripSize);
			reap();
		}
		return (old!=null) ? old.value : null;
	}

// super implements this in terms of put(), so we don't have to override it.
//	public void putAll(java.util.Map p0)

	public Object remove(Object key) {
		 AgeSlot as = (AgeSlot) super.remove(key);
		 if (as!=null) {
		 	return as.value;
		 } else {
			return null;
		 }
	}

	public Collection values() {
		throw new RuntimeException("Not supported!");
	}

	protected void reap() {
		Iterator iter = keySet().iterator();
		int threshold = now - maxOccupancy;
	// System.out.println("Reaping at time "+now+"; threshold = "+threshold);
		while (iter.hasNext()) {
			Object key = iter.next();
			AgeSlot as = (AgeSlot) super.get(key);
			if (as.age<=threshold) {
				iter.remove();
				// System.out.println("Reaped a guy of age "+as.age);
			} else {
				// System.out.println("Left   a guy of age "+as.age);
			}
		}
	}
}

/**
 * Some extra data to store alongside any given hash value. This is
 * where we keep track of access times; perhaps sizes, too, later.
 */
class AgeSlot {
	int age;
	Object value;

	public AgeSlot(int age, Object value) {
		this.age = age;
		this.value = value;
	}

	public boolean equals(Object as) {
		return (as instanceof AgeSlot)
			&& value.equals(((AgeSlot) as).value);
	}
}

