/*******************************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ******************************************************************************/

// Package
package gov.va.med.fw.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;

import javax.persistence.MappedSuperclass;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlTransient;

import org.apache.commons.lang.builder.ToStringBuilder;

import gov.va.med.fw.model.lookup.TransactionType;
import gov.va.med.fw.util.date.DateWithTimeZone;

/**
 * An abstract class that can be used as a base class for modifiable (versioned)
 * entity classes.
 * 
 * @author Vu Le
 * @version 1.0
 */
@MappedSuperclass
@XmlAccessorType(XmlAccessType.FIELD)
public abstract class AbstractVersionedEntity<T> extends AbstractEntity {
	private static final long serialVersionUID = -7758974447945480434L;

	// The principal that created the object.
	private AuditInfo createdBy = new AuditInfo();

	// The date/time when the object was created.
	private Date createdOn = null;

	// The principal that modified the object last.
	private AuditInfo modifiedBy = new AuditInfo();

	// The date/time when the object was last modified.
	private Date modifiedOn = null;

	// The version of the object's data.
	private Integer version = null;

	// Transaction type Insert/delete/update
	@XmlTransient
	private TransactionType transactionType = null;

	// if need to track original (user) TimeZone information
	private TimeZone createdInTimeZone;
	private TimeZone modifiedInTimeZone;

	/**
	 * The previous version of this versioned entity
	 */
	@XmlTransient
	private AbstractVersionedEntity<T> previousVersion;

	/**
	 * id of the history table
	 */
	@XmlTransient
	private Serializable historyId;

	/**
	 * A default constructor
	 */
	protected AbstractVersionedEntity() {
		super();
	}

	/**
	 * A constructor initialized with the specific values
	 * 
	 * @param createdOn
	 *            The date/time when the object was created.
	 * @param modifiedOn
	 *            The date/time when the object was last modified.
	 * @param version
	 *            The version of the object's data.
	 */
	public AbstractVersionedEntity(Date createdOn, Date modifiedOn, Integer version) {
		super();
		this.createdOn = createdOn;
		this.modifiedOn = modifiedOn;
		this.version = version;
	}

	/**
	 * Returns the principal that created the object.
	 * 
	 * @return The principal that created the object.
	 */
	public AuditInfo getCreatedBy() {
		return this.createdBy;
	}

	/**
	 * Returns the date/time when the object was created.
	 * 
	 * @return The date/time when the object was created.
	 */
	public Date getCreatedOn() {
		return this.createdOn;
	}

	/**
	 * Sets the date/time when the object was created.
	 * 
	 * @param createdOn
	 *            The date/time when the object was created.
	 */
	private void setCreatedOn(Date createdOn) {
		this.createdOn = createdOn;
	}

	/**
	 * Returns the principal that modified the object last.
	 * 
	 * @return The principal that modified the object last.
	 */
	public AuditInfo getModifiedBy() {
		return this.modifiedBy;
	}

	/**
	 * Returns the date/time when the object was last modified.
	 * 
	 * @return The date/time when the object was last modified.
	 */
	public Date getModifiedOn() {
		return this.modifiedOn;
	}

	/**
	 * Sets the date/time when the object was last modified.
	 * <p/>
	 * NOTE: This method should not be called directly since Hibernate will
	 * automatically call it when loading an object. However, it is public
	 * because there are certain cases when we want to build an entity manually
	 * (e.g. Building a Person with only PSIM traits for Identity Traits
	 * History).
	 * 
	 * @param modifiedOn
	 *            The date/time when the object was last modified.
	 */
	public void setModifiedOn(Date modifiedOn) {
		this.modifiedOn = modifiedOn;
	}

	/**
	 * Returns the version of the object's data.
	 * 
	 * @return The version of the object's data.
	 */
	public Integer getVersion() {
		return this.version;
	}

	/**
	 * Sets the version of the object's data.
	 * 
	 * @param version
	 *            The version of the object's data.
	 */
	private void setVersion(Integer version) {
		this.version = version;
	}

	/**
	 * @param createdBy
	 *            The createdBy to set.
	 */
	public void setCreatedBy(AuditInfo createdBy) {
		this.createdBy = createdBy;
	}

	/**
	 * @param modifiedBy
	 *            The modifiedBy to set.
	 */
	public void setModifiedBy(AuditInfo modifiedBy) {
		this.modifiedBy = modifiedBy;
	}

	/**
	 * @return Returns the previousVersion.
	 */
	public AbstractVersionedEntity<T> getPreviousVersion() {
		return previousVersion;
	}

	/**
	 * @param previousVersion
	 *            The previousVersion to set.
	 */
	public void setPreviousVersion(AbstractVersionedEntity<T> previousVersion) {
		this.previousVersion = previousVersion;
	}

	/**
	 * get Transaction type Insert/Update/Delete
	 * 
	 * @return the transaction type
	 */
	public TransactionType getTransactionType() {
		return transactionType;
	}

	/**
	 * @param transactionType
	 *            the transaction type
	 */
	private void setTransactionType(TransactionType transactionType) {
		this.transactionType = transactionType;
	}

	public Serializable getHistoryId() {
		return historyId;
	}

	private void setHistoryId(Serializable historyId) {
		this.historyId = historyId;
	}

	public Boolean isDeleted() {
		return (transactionType == null ? Boolean.FALSE : transactionType.isDeleted());
	}

	/**
	 * Returns an ordered iterator of all loaded versions where the first
	 * element is current object.
	 * 
	 * @return the iterator of versions
	 */
	public Iterator<AbstractVersionedEntity<T>> versions() {
		List<AbstractVersionedEntity<T>> orderedVersions = new ArrayList<AbstractVersionedEntity<T>>();
		AbstractVersionedEntity<T> current = this;
		while (current != null) {
			orderedVersions.add(current);
			current = current.getPreviousVersion();
		}
		return orderedVersions.iterator();
	}

	/**
	 * @see java.lang.Object#finalize()
	 */
	protected void finalize() throws Throwable {
		super.finalize();

		this.createdBy = null;
		this.modifiedBy = null;
		this.modifiedOn = null;

		this.setVersion(null);
		this.setTransactionType(null);
		this.setHistoryId(null);
		this.setCreatedOn(null);
	}

	/**
	 * @see gov.va.med.fw.model.AbstractEntity#buildToString(org.apache.commons.lang.builder.ToStringBuilder)
	 */
	protected void buildToString(ToStringBuilder builder) {
		builder.append("createdBy", this.createdBy);
		builder.append("createdOn", this.createdOn);
		builder.append("modifiedBy", this.modifiedBy);
		builder.append("modifiedOn", this.modifiedOn);
		builder.append("version", this.version);
		builder.append("historyId", this.historyId);
	}

	/**
	 * @return the createdInTimeZone
	 */
	public TimeZone getCreatedInTimeZone() {
		return createdInTimeZone;
	}

	/**
	 * @param createdInTimeZone
	 *            the createdInTimeZone to set
	 */
	public void setCreatedInTimeZone(TimeZone createdInTimeZone) {
		this.createdInTimeZone = createdInTimeZone;
	}

	/**
	 * @return the modifiedInTimeZone
	 */
	public TimeZone getModifiedInTimeZone() {
		return modifiedInTimeZone;
	}

	/**
	 * @param modifiedInTimeZone
	 *            the modifiedInTimeZone to set
	 */
	public void setModifiedInTimeZone(TimeZone modifiedInTimeZone) {
		this.modifiedInTimeZone = modifiedInTimeZone;
	}

	public DateWithTimeZone getCreatedOnWithTimeZone() {
		return createdOn != null ? new DateWithTimeZone(createdOn, getCreatedInTimeZone()) : null;
	}

	public DateWithTimeZone getModifiedOnWithTimeZone() {
		return modifiedOn != null ? new DateWithTimeZone(modifiedOn, getModifiedInTimeZone())
				: null;
	}
}