package de.ugoe.cs.quest.ui;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;

import de.ugoe.cs.quest.SequenceInstanceOf;
import de.ugoe.cs.quest.eventcore.guimodel.GUIModel;
import de.ugoe.cs.quest.usageprofiles.IStochasticProcess;

/**
 * <p>
 * This data structure can be used by the commands to store any {@link Object}.
 * The data is stored in a key-value map, with strings as keys.
 * </p>
 * <p>
 * This class is implemented as a singleton, as more than one data container
 * does not serves no purpose.
 * </p>
 * 
 * @author Steffen Herbold
 * @version 1.0
 */
public class GlobalDataContainer implements Serializable {

	/**
	 * <p>
	 * Id for object serialization.
	 * </p>
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * <p>
	 * Instance of the {@link GlobalDataContainer} (implemented as singleton).
	 * </p>
	 */
	transient private static GlobalDataContainer theInstance = null;

	/**
	 * <p>
	 * Internal storage of the data.
	 * </p>
	 */
	private Map<String, Object> dataObjects;

	/**
	 * <p>
	 * Returns the instance of the container. If it does not yet exist, the data
	 * container is created.
	 * </p>
	 * 
	 * @return instance of the container
	 */
	public static GlobalDataContainer getInstance() {
		if (theInstance == null) {
			theInstance = new GlobalDataContainer();
		}
		return theInstance;
	}

	/**
	 * <p>
	 * Manual serialization of the object. Necessary to guarantee the singleton
	 * property.
	 * </p>
	 * 
	 * @param s
	 *            output stream for the serialization
	 * @throws IOException
	 *             thrown if there is problem writing to the output stream
	 */
	private void writeObject(ObjectOutputStream s) throws IOException {
		s.defaultWriteObject();
		s.writeObject(dataObjects);
	}

	/**
	 * <p>
	 * Manual de-serialization of the object. Necessary to guarantee the
	 * singleton property.
	 * 
	 * @param s
	 *            input stream for the de-serialization
	 * @throws IOException
	 *             thrown if there is problem reading from the input stream
	 * @throws ClassNotFoundException
	 *             thrown if there is a problem reading from the input stream
	 */
	@SuppressWarnings("unchecked")
	private void readObject(ObjectInputStream s) throws IOException,
			ClassNotFoundException {
		s.defaultReadObject();
		if (theInstance == null) {
			theInstance = new GlobalDataContainer();
		}
		theInstance.dataObjects = (Map<String, Object>) s.readObject();
	}

	/**
	 * <p>
	 * Manual de-serialization to guarantee the singleton property.
	 * </p>
	 * 
	 * @return instance of the container
	 */
	private Object readResolve() {
		return theInstance;
	}

	/**
	 * <p>
	 * Constructor. Creates a new GlobalDataContainer. Private to guarantee the
	 * singleton property.
	 * </p>
	 */
	private GlobalDataContainer() {
		dataObjects = new HashMap<String, Object>();
	}

	/**
	 * <p>
	 * Adds data to the container.
	 * </p>
	 * 
	 * @param key
	 *            key that identifies the data
	 * @param data
	 *            data that is stored
	 * @return true, if an old entry was overwritten; false otherwise
	 */
	public boolean addData(String key, Object data) {
		Object previousEntry = dataObjects.put(key, data);
		return previousEntry != null;
	}

	/**
	 * <p>
	 * Removes data from the container.
	 * </p>
	 * 
	 * @param key
	 *            key of the data to be removed
	 * @return true, if the object was removed; false if it was not present
	 */
	public boolean removeData(String key) {
		Object previousEntry = dataObjects.remove(key);
		return previousEntry != null;
	}

	/**
	 * <p>
	 * Returns the data associated with a key or {@code null} if no data is
	 * stored for the key.
	 * </p>
	 * 
	 * @param key
	 *            key whose data is returned
	 * @return data associated with the key; {@code null} if no data is
	 *         available
	 */
	public Object getData(String key) {
		return dataObjects.get(key);
	}

	/**
	 * <p>
	 * Resets the data container, i.e., deletes all its contents.
	 * </p>
	 */
	public void reset() {
		dataObjects = new HashMap<String, Object>();
	}

	/**
	 * <p>
	 * Returns all keys of collections of sequences contained in the storage.
	 * </p>
	 * 
	 * @return keys of all collections of sequences contained in the storage
	 */
	public Collection<String> getAllSequencesNames() {
		Collection<String> allSequencesNames = new LinkedList<String>();
		for (Entry<String, Object> entry : dataObjects.entrySet()) {
			if( SequenceInstanceOf.isCollectionOfSequences(entry.getValue())) {
				allSequencesNames.add(entry.getKey());
			}
		}
		return allSequencesNames;
	}

	/**
	 * <p>
	 * Returns the keys of all {@link IStochasticProcess}s contained in the
	 * storage.
	 * </p>
	 * 
	 * @return keys of all {@link IStochasticProcess}s contained in the storage
	 */
	public Collection<String> getAllModelNames() {
		Collection<String> modelNames = new LinkedList<String>();
		for (Entry<String, Object> entry : dataObjects.entrySet()) {
			if (entry.getValue() instanceof IStochasticProcess) {
				modelNames.add(entry.getKey());
			}
		}
		return modelNames;
	}
	
	/**
	 * <p>
	 * Returns the keys of all {@link GUIModel}s contained in the storage.
	 * </p>
	 *
	 * @return keys of all {@link GUIModel}s contained in the storage
	 */
	public Collection<String> getAllGUIModelNames() {
	    Collection<String> modelNames = new LinkedList<String>();
	    for(Entry<String, Object> entry : dataObjects.entrySet()) {
	        if( entry.getValue() instanceof GUIModel ) {
	            modelNames.add(entry.getKey());
	        }
	    }
	    return modelNames;
	}

	/**
	 * <p>
	 * Returns the keys of all objects contained in the storage.
	 * </p>
	 * 
	 * @return keys of all objects in the storage
	 */
	public Collection<String> getAllKeys() {
		return dataObjects.keySet();
	}

}
