import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author Fuad Efendi (fuad@efendi.ca)
 */
public class ConcurrentLRUWeakCache<K, V> {

	protected ConcurrentHashMap<K, ValueWrapper<V>> map;
	protected int maxEntries;

	private static AtomicReference ref = new AtomicReference();
	
	public ConcurrentLRUWeakCache(int maxEntries) {
		map = new ConcurrentHashMap<K, ValueWrapper<V>>();
		this.maxEntries = maxEntries;
	}

	public V put(K key, V value) {
		ValueWrapper<V> wrapper = map.put(key, new ValueWrapper<V>(value));
		checkRemove(key);
		return value;
	}

	void checkRemove(K key) {
		if (map.size() <= maxEntries) return;
		Pointer pointer = (Pointer) ref.get();
		if (pointer == null) map.remove(key);
		else map.remove(pointer.key);
	}

	public V get(Object key) {
		ValueWrapper<V> wrapper = map.get(key);
		if (wrapper == null) return null;
		V value = wrapper.getValue();
		Pointer pointer = (Pointer) ref.get();
		if (pointer == null) { 
			ref.set(new Pointer(key, wrapper.lastAccessed));
		} else if (wrapper.lastAccessed < pointer.lastAccessed) {
			ref.set(new Pointer(key, wrapper.lastAccessed));
		}
		return value; 
	}

	public final static class ValueWrapper<V> {
		static volatile long counter;
		volatile long lastAccessed;
		V value;

		ValueWrapper(V value) {
			this.value = value;
			lastAccessed = counter++;
		}

		public boolean equals(Object o) {
			if (!(o instanceof ValueWrapper)) {
				return false;
			}
			return (value == null ? ((ValueWrapper) o).value == null : value.equals(((ValueWrapper) o).value));
		}

		public int hashCode() {
			return value.hashCode();
		}

		public V getValue() {
			lastAccessed = counter++;
			return value;
		}
	}
	
	public static final class Pointer<K> {
		K key;
		long lastAccessed;
		public Pointer(K key, long lastAccessed) {
			this.key=key;
			this.lastAccessed = lastAccessed;
		}
	}

}
