/*
 *                    BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 *
 */
package org.biojava.nbio.structure.ecod;

import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import org.biojava.nbio.structure.align.util.UserConfiguration;
import org.biojava.nbio.structure.cath.CathFactory;
import org.biojava.nbio.structure.scop.ScopFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Controls global {@link EcodDatabase EcodDatabases} being used.
 * Implements a multiton pattern through {@link #getEcodDatabase(String)},
 * and a singleton pattern through {@link #getEcodDatabase()}.
 * @author Spencer Bliven
 * @see ScopFactory
 * @see CathFactory
 * @see EcodInstallation
 */
public class EcodFactory {

	private static final Logger logger = LoggerFactory.getLogger(EcodFactory.class);

	public static final String DEFAULT_VERSION = EcodInstallation.DEFAULT_VERSION;

	private static Map<String, SoftReference<EcodDatabase>> versionedEcodDBs =
			Collections.synchronizedMap(new HashMap<String, SoftReference<EcodDatabase>>());
	private static String defaultVersion = EcodInstallation.DEFAULT_VERSION;

	/**
	 * Returns the (singleton) database for the current default version
	 */
	public static EcodDatabase getEcodDatabase() {
		return getEcodDatabase(defaultVersion);
	}

	public static EcodDatabase getEcodDatabase(String version) {
		if( version == null )
			version = defaultVersion;

		logger.trace("Waiting for EcodFactory lock to get version "+version);
		synchronized(versionedEcodDBs) {
			logger.trace("Got EcodFactory lock to get version "+version);

			releaseReferences();

			SoftReference<EcodDatabase> ecodRef = versionedEcodDBs.get(version.toLowerCase());
			EcodDatabase ecod = null;
			if(ecodRef != null) {
				ecod = ecodRef.get();
			}
			if( ecod == null ) {
				logger.debug("Creating new {}, version {}",EcodInstallation.class.getSimpleName(),version);
				String cacheDir = new UserConfiguration().getCacheFilePath();
				ecod = new EcodInstallation(cacheDir, version);
				versionedEcodDBs.put(version.toLowerCase(), new SoftReference<EcodDatabase>(ecod));

				// If the parsed version differed from that requested, add that too
				// Note that getVersion() may trigger file parsing
				try {
					if( ! versionedEcodDBs.containsKey(ecod.getVersion().toLowerCase()) ) {
						versionedEcodDBs.put(ecod.getVersion().toLowerCase(),new SoftReference<EcodDatabase>(ecod));
					}
				} catch (IOException e) {
					// For parsing errors, just use the requested version
				}
			}
			logger.trace("Releasing EcodFactory lock after getting version "+version);

			return ecod;
		}
	}

	/**
	 * removes SoftReferences which have already been garbage collected
	 */
	private static void releaseReferences() {
		synchronized(versionedEcodDBs) {
			Iterator<Entry<String, SoftReference<EcodDatabase>>> it = versionedEcodDBs.entrySet().iterator();
			while(it.hasNext()) {
				Entry<String, SoftReference<EcodDatabase>> entry = it.next();
				SoftReference<EcodDatabase> ref = entry.getValue();
				if(ref.get() == null) {
					logger.debug("Removed version {} from EcodFactory to save memory.",entry.getKey());
					it.remove();
				}
			}
		}
	}

	/**
	 * Updates the default version
	 * @param version
	 */
	public static void setEcodDatabase(String version) {
		getEcodDatabase(version);
		defaultVersion = version;
	}

	/** Can't instantiate */
	private EcodFactory() {}

}
