package de.ugoe.cs.eventbench.data;

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.List;
import java.util.Map;
import java.util.Map.Entry;

import de.ugoe.cs.eventbench.models.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 (entry.getValue() instanceof Collection<?>) {
				Object listObj = ((Collection<?>) entry.getValue()).iterator()
						.next();
				if (listObj instanceof List<?>) {
					if (((List<?>) listObj).iterator().next() instanceof Event<?>) {
						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;
	}

}
