/*******************************************************************************
 * Copyright (c) 2012  DCA-FEEC-UNICAMP
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v3
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 * 
 * Contributors:
 *     K. Raizer, A. L. O. Paraense, R. R. Gudwin - initial API and implementation
 ******************************************************************************/

package br.unicamp.cst.core.entities;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

/**
 * A Memory Object is a generic information holder, acting as a sign or an
 * internal representation, which is responsible to store any auxiliary or
 * perennial information necessary for the cognitive architecture to perform its
 * behavior. Codelets and Memory Objects are intrinsically coupled to each
 * other, in the sense that Memory Objects hold the information necessary for
 * the Codelets to run, and are also the placeholders of any new information
 * generated by the codelet. The main property being hold by a Memory Object is
 * its Information (I). This information can be simply a number, or hold complex
 * structures like lists, trees, graphs or whole networks. In our computational
 * implementation, the information I is a generic Java Object, which can be used
 * to represent virtually anything. A Memory Object also has two extra kinds of
 * meta-information: a time stamp (T), which tags the Memory Object with a
 * marker indicating its last update, and an evaluation (E), which has many
 * different uses within CST. This evaluation is simply a number, which can be
 * used, e.g. as an appraisal factor in an emotional module, or simply as a
 * discriminator to differentiate among two or more Memory Objects of the same
 * type. These meta-information can be simply ignored by simpler codelets, or be
 * useful while implementing more sophisticated cognitive models.
 * 
 * @author A. L. O. Paraense
 * @author K. Raizer
 */
public class MemoryObject implements Memory, Serializable {

	private static final long serialVersionUID = 1L;

	private Long idmemoryobject;

	/**
	 * Date when the data was "created" in milliseconds
	 */
	private Long timestamp;

	/**
	 * An evaluation of this memory object based on inner references
	 */
	private volatile Double evaluation;

	/**
	 * Information contained in the memory object.
	 */
	private volatile Object I;

	/**
	 * Type of the memory object
	 */
	private String name;
	
	/**
	 * List of codetlets that observes memory
	 */
	private transient Set<MemoryObserver> memoryObservers;

	/**
	 * Creates a MemoryObject.
	 */
	public MemoryObject() {
		evaluation = 0.0d;
	}

	/**
	 * Gets the id of the Memory Object.
	 * 
	 * @return the id of the Memory Object.
	 */
	public synchronized Long getIdmemoryobject() {
		return this.idmemoryobject;
	}

	/**
	 * Sets the id of the Memory Object.
	 * 
	 * @param idmemoryobject
	 *            the id of the Memory Object to set.
	 */
	public synchronized void setIdmemoryobject(Long idmemoryobject) {
		this.idmemoryobject = idmemoryobject;
	}

	/**
	 * Gets the info of the Memory Object.
	 * 
	 * @return the info of the Memory Object.
	 */
	public synchronized Object getI() {
		return this.I;
	}

	/**
	 * Sets the info in memory object.
	 * 
	 * @param info
	 *            the info in memory object to set.
	 */
	public synchronized int setI(Object info) {
		this.I = info;
		setTimestamp(System.currentTimeMillis());
		notifyMemoryObservers();

		return -1;
	}
	
	private synchronized void notifyMemoryObservers() {
		if (memoryObservers != null && !memoryObservers.isEmpty()) {
			for (MemoryObserver memoryObserver : memoryObservers) {
				memoryObserver.notifyCodelet();
			}
		}
	}

	/**
	 * This method is deprecated after v0.1. For the time being, it has been
	 * kept only for backward compatibility. Use the {@link #setI(Object info)
	 * setI} method instead.
	 *
	 * @deprecated use {@link #setI(Object info)} instead.
	 *
	 * @param info
	 *            the info in memory object to set.
	 */
	@Deprecated
	public synchronized void updateI(Object info) {
		setI(info);
	}

	/**
	 * Gets the timestamp of this Memory Object.
	 * 
	 * @return the timestamp of this Memory Object.
	 */
	public synchronized Long getTimestamp() {
		return this.timestamp;
	}

	/**
	 * Sets the timestamp of this Memory Object.
	 * 
	 * @param timestamp
	 *            the timestamp of this Memory Object.
	 */
	public synchronized void setTimestamp(Long timestamp) {
		this.timestamp = timestamp;
	}

	/**
	 * Gets the type of the memory.
	 * 
	 * @return the type
	 */
	public synchronized String getName() {
		return name;
	}

	/**
	 * Sets the type of the memory.
	 * 
	 * @param name
	 *            the type to set.
	 */
        @Deprecated
	public synchronized void setType(String name) {
		this.name = name;
	}
        
        /**
	 * Sets the name of the memory.
	 * 
	 * @param name
	 *            the type to set.
	 */
	public synchronized void setName(String name) {
		this.name = name;
	}

	/**
	 * Gets the evaluation of the Memory Object.
	 * 
	 * @return the evaluation of the Memory Object.
	 */
	public synchronized Double getEvaluation() {
		return evaluation;
	}

	/**
	 * Sets the evaluation of the Memory Object.
	 * 
	 * @param evaluation
	 *            the evaluation to set.
	 */
	public synchronized void setEvaluation(Double evaluation) {
		this.evaluation = evaluation;
	}

	@Override
	public String toString() {
		return "MemoryObject [idmemoryobject=" + idmemoryobject + ", timestamp=" + timestamp + ", evaluation="
				+ evaluation + ", I=" + I + ", name=" + name + "]";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((I == null) ? 0 : I.hashCode());
		result = prime * result + ((evaluation == null) ? 0 : evaluation.hashCode());
		result = prime * result + ((idmemoryobject == null) ? 0 : idmemoryobject.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result + ((timestamp == null) ? 0 : timestamp.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		MemoryObject other = (MemoryObject) obj;
		if (I == null) {
			if (other.I != null)
				return false;
		} else if (!I.equals(other.I))
			return false;
		if (evaluation == null) {
			if (other.evaluation != null)
				return false;
		} else if (!evaluation.equals(other.evaluation))
			return false;
		if (idmemoryobject == null) {
			if (other.idmemoryobject != null)
				return false;
		} else if (!idmemoryobject.equals(other.idmemoryobject))
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (timestamp == null) {
			if (other.timestamp != null)
				return false;
		} else if (!timestamp.equals(other.timestamp))
			return false;
		return true;
	}

	/**
	 * Add a memory observer to its list
	 * @param memoryObserver the MemoryObserve to be added
	 */
	public void addMemoryObserver(MemoryObserver memoryObserver) {
		if (this.memoryObservers == null) {
			this.memoryObservers = new HashSet<MemoryObserver>(); 
		}
		this.memoryObservers.add(memoryObserver);
	}
        
        /**
	 * Remove a memory observer from its list
	 * @param memoryObserver the MemoryObserve to be removed
	 */
	public void removeMemoryObserver(MemoryObserver memoryObserver) {
		if (this.memoryObservers != null) {
		    this.memoryObservers.remove(memoryObserver);
                }
	}
        
        
}
