package eu.dnetlib.enabling.aas.retrievers.cache;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.log4j.Logger;

import edu.emory.mathcs.backport.java.util.Collections;
import eu.dnetlib.enabling.aas.utils.dump.IDumpable;
import eu.dnetlib.enabling.aas.utils.invalidate.IInvalidatable;

/**
 * Synchronized map based profiles cache.
 * Old profiles are being dropped periodically.
 * 
 * @author mhorst
 *
 */
@SuppressWarnings("unchecked")
public class SynchronizedMapProfilesCache implements IProfilesCache, 
	IInvalidatable, IDumpable {

	class InsertionTimeAwareEntry<Entry> {
		
		long insterionTime;
		Entry entry;
		
		public InsertionTimeAwareEntry(Entry entry) {
			this.insterionTime = System.currentTimeMillis();
			this.entry = entry;
		}

	}
	
	private Logger log = Logger.getLogger(this.getClass());
	
	/**
	 * ResourceId to profile content cache.
	 * Preserves the order of added elements.
	 */
	private Map<String, InsertionTimeAwareEntry<String>> cacheMap = Collections.synchronizedMap(
			new LinkedHashMap<String, InsertionTimeAwareEntry<String>>()); 
	
	
	private static final long profilesTTLUnset = -1;
	
	/**
	 * Profiles max TTL in milliseconds.
	 */
	private long profilesTTL = profilesTTLUnset;
	
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.aas.retrievers.cache.IProfilesCache#getProfile(java.lang.String)
	 */
	@Override
	public String getProfile(String id) {
		InsertionTimeAwareEntry<String> entry = cacheMap.get(id); 
		return entry!=null?entry.entry:null;
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.aas.retrievers.cache.IProfilesCache#setProfile(java.lang.String, java.lang.String)
	 */
	@Override
	public void setProfile(String id, String content) {
		cacheMap.put(id, new InsertionTimeAwareEntry<String>(content));
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.aas.retrievers.cache.IProfilesCache#removeProfile(java.lang.String)
	 */
	@Override
	public String removeProfile(String id) {
		InsertionTimeAwareEntry<String> result = cacheMap.remove(id); 
		return result!=null?result.entry:null;
	}

	/**
	 * Cleans stored entries by removing all outdated objects.
	 */
	public void cleanup() {
		if (profilesTTL!=profilesTTLUnset) {
			synchronized(cacheMap) {
				Iterator<String> it = cacheMap.keySet().iterator();
				while (it.hasNext()) {
					String key = it.next();
					if (System.currentTimeMillis() > 
						cacheMap.get(key).insterionTime + profilesTTL) {
						log.debug("removing: " + key);
						it.remove();
					} else {
//						elements are ordered by time of insertion therefore 
//						there is no point to verify subsequent entries
						return;
					}
				}
			}
		} else {
			log.warn("cleanup is currently disabled, profiles TTL is set to " + profilesTTL);
		}
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.aas.utils.invalidate.IInvalidatable#invalidate()
	 */
	@Override
	public void invalidate() {
		this.cacheMap.clear();
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.aas.utils.dump.IDumpable#dump(java.io.OutputStream, java.lang.Object[])
	 */
	@Override
	public void dump(OutputStream out, Object... params) throws IOException {
		synchronized (this.cacheMap) {
			for (String key : this.cacheMap.keySet()) {
				out.write(this.cacheMap.get(key).entry.getBytes());
				out.write('\n');
			}
		}
	}
	
	/**
	 * Sets profiles TTL expressed in seconds.
	 * @param profilesTTLSeconds
	 */
	public void setProfilesTTLSeconds(int profilesTTLSeconds) {
		this.profilesTTL = profilesTTLSeconds * 1000;
	}

}
