package eu.dnetlib.miscutils.cache;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.sf.ehcache.Element;

/**
 * This is a wrapper to a EhCache instance exposed as a miscutils Cache object offering statically typed java Map-like
 * behavior.
 * 
 * @author marko
 * 
 * @param <K>
 * @param <V>
 */
public class EhCache<K, V> implements Cache<K, V> { // NOPMD
	public class MapEntry implements java.util.Map.Entry<K, V> {

		transient K key;

		MapEntry(final K key) {
			this.key = key;
		}

		@Override
		public K getKey() {
			return key;
		}

		@Override
		public V getValue() {
			return get(key);
		}

		@Override
		public V setValue(final V value) {
			return put(key, value);
		}

	}

	/**
	 * underlying cache implementation.
	 */
	private net.sf.ehcache.Cache cache;

	public EhCache() {
		// no operation
	}

	public EhCache(final net.sf.ehcache.Cache ehCache) {
		basicSetCache(ehCache);
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.miscutils.cache.Cache#get(java.lang.Object) Java is really strange. Map<K,V> defines V
	 *      get(Object), so we have to implement this method using Object otherwise the compiler signals a name clash.
	 */
	@Override
	@SuppressWarnings("unchecked")
	public V get(final Object key) {
		final CacheElement<V> element = getElement((K) key);
		if (element == null)
			return null;
		return element.getValue();
	}

	@Override
	@SuppressWarnings("unchecked")
	public CacheElement<V> getElement(final K key) {
		final Element element = cache.get(key);
		if (element == null)
			return null;

		final CacheElement<V> res = new CacheElement<V>((V) element.getObjectValue());
		res.setTimeToLive(element.getTimeToLive());
		res.setTimeToLive(element.getTimeToIdle());
		res.setCreationTime(element.getCreationTime());
		res.setSpecificElement(element);
		return res;
	}

	@Override
	public V put(final K key, final CacheElement<V> element) {
		final boolean eternal = element.getTimeToLive() == 0;
		cache.put(new Element(key, element.getValue(), eternal, element.getTimeToLive(), element.getTimeToIdle()));
		return element.getValue();
	}

	@Override
	public V put(final K key, final V value) {
		return put(key, new CacheElement<V>(value));
	}

	@Override
	public void clear() {
		cache.removeAll();
	}

	@Override
	public boolean containsKey(final Object arg0) {
		return cache.isKeyInCache(arg0);
	}

	@Override
	public boolean containsValue(final Object arg0) {
		return cache.isValueInCache(arg0);
	}

	@Override
	public Set<java.util.Map.Entry<K, V>> entrySet() {
		final Set<java.util.Map.Entry<K, V>> res = new HashSet<java.util.Map.Entry<K, V>>();
		for (K key : keySet())
			res.add(new MapEntry(key)); // NOPMD
		return res;
	}

	@Override
	public boolean isEmpty() {
		return size() == 0;
	}

	@Override
	@SuppressWarnings("unchecked")
	public Set<K> keySet() {
		return new HashSet<K>(cache.getKeys());
	}

	@Override
	public void putAll(final Map<? extends K, ? extends V> arg0) {
		for (java.util.Map.Entry<? extends K, ? extends V> entry : arg0.entrySet())
			put(entry.getKey(), entry.getValue());
	}

	@Override
	public V remove(final Object arg0) {
		final V val = get(arg0);
		cache.remove(arg0);
		return val;
	}

	@Override
	public int size() {
		return cache.getSize();
	}

	@Override
	public Collection<V> values() {
		final List<V> res = new ArrayList<V>();
		for (K key : keySet())
			res.add(get(key));
		return res;
	}

	public net.sf.ehcache.Cache getCache() {
		return cache;
	}

	public void setCache(final net.sf.ehcache.Cache cache) {
		basicSetCache(cache);
	}
	
	private void basicSetCache(final net.sf.ehcache.Cache cache) {
		this.cache = cache;
	}

}
