/*******************************************************************************
 * Copyright © 2005 VHA. All rights reserved
 ******************************************************************************/
package gov.va.med.fw.persistent.hibernate;

// Java Classes
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;

import org.hibernate.CallbackException;
import org.hibernate.type.Type;

import gov.va.med.esr.model.ServiceUser;
import gov.va.med.esr.webservices.jaxws.spring.interceptor.MessageContextHolder;
import gov.va.med.fw.model.AbstractVersionedEntity;
import gov.va.med.fw.model.AuditInfo;
import gov.va.med.fw.model.lookup.AbstractVersionedLookup;
import gov.va.med.fw.security.SecurityContextHelper;
import gov.va.med.fw.security.UserPrincipal;
import gov.va.med.fw.service.config.SingletonApplicationContext;
import gov.va.med.fw.service.transaction.TransactionTimestampManager;
import gov.va.med.fw.util.date.TimeZoneUtils;

/**
 * A Hibernate Interceptor to perform general interception during lifecycle
 * joinpoints. Currently implemented to do the following:<br>
 * <ul>
 * <li>Populate the "user" audit fields (eg, createdBy, modifiedBy) of an
 * Application for AbstractVersionedEntity entities.
 * <li>Synchronize all flushes in the same Transaction to use the same Timestamp
 * for the "date" audit fields (eg, createdOn, modifiedOn)
 * </ul>
 * 
 * @author Gregory Bohmer
 */
public class EntityInterceptor extends PersistentInterceptorAdapter implements Serializable {

	/**
	 * An instance of serialVersionUID
	 */
	private static final long serialVersionUID = 8642808474447403684L;

	/**
	 * An instance of CLASS_NAME
	 */
	// private static final String CLASS_NAME =
	// EntityInterceptor.class.getName();
	/**
	 * An instance of TRANSACTION_TIMESTAMP
	 */
	// private static final String TRANSACTION_TIMESTAMP = CLASS_NAME + ".ts";
	/**
	 * An instance of TRANSACTION_KEY
	 */
	// private static final String TRANSACTION_KEY = CLASS_NAME + ".tx";
	/**
	 * An instance of TIMESTAMP_NO_TX
	 */
	// private static final String TIMESTAMP_NO_TX = CLASS_NAME + ".noTxTs";
	/**
	 * An instance of PROPERTY_NAMES_CREATED_BY
	 */
	private static final Map PROPERTY_NAMES_CREATED_BY = new HashMap();

	/**
	 * An instance of PROPERTY_NAMES_MODIFIED_BY
	 */
	private static final Map PROPERTY_NAMES_MODIFIED_BY = new HashMap();

	/**
	 * An instance of PROPERTY_NAMES_CREATED_ON
	 */
	private static final Map PROPERTY_NAMES_CREATED_ON = new HashMap();

	private static final Map PROPERTY_NAMES_CREATED_IN_TIMEZONE = new HashMap();

	/**
	 * An instance of PROPERTY_NAMES_MODIFIED_ON
	 */
	private static final Map PROPERTY_NAMES_MODIFIED_ON = new HashMap();

	private static final Map PROPERTY_NAMES_MODIFIED_IN_TIMEZONE = new HashMap();

	// support for the following columns is provided
	static {
		PROPERTY_NAMES_CREATED_BY.put("createdBy", null);
		PROPERTY_NAMES_MODIFIED_BY.put("modifiedBy", null);
		PROPERTY_NAMES_CREATED_ON.put("created", null);
		PROPERTY_NAMES_CREATED_ON.put("createdOn", null);
		PROPERTY_NAMES_MODIFIED_ON.put("modified", null);
		PROPERTY_NAMES_MODIFIED_ON.put("modifiedOn", null);
		PROPERTY_NAMES_CREATED_IN_TIMEZONE.put("createdInTimeZone", null);
		PROPERTY_NAMES_MODIFIED_IN_TIMEZONE.put("modifiedInTimeZone", null);
	}

	/**
	 * An instance of defaultAuditName
	 */
	private String defaultAuditName = "anonymous";

	private TransactionTimestampManager timestampManager;
	private String TRANSACTION_TIMESTAMP_MANAGER_KEY = "common.timestampManager";

	/**
	 * Default Constructor
	 */
	public EntityInterceptor() {
		super();
	}

	/**
	 * @return Returns the defaultAuditName.
	 */
	public String getDefaultAuditName() {
		return defaultAuditName;
	}

	/**
	 * @param defaultAuditName
	 *            The defaultAuditName to set.
	 */
	public void setDefaultAuditName(String defaultAuditName) {
		this.defaultAuditName = defaultAuditName;
	}

	/**
	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
	 */
	public void afterPropertiesSet() {
		// This is not a bean in JPA
		// Validate.notNull( timestampManager);
	}

	/**
	 * @see org.hibernate.Interceptor#onFlushDirty(java.lang.Object,
	 *      java.io.Serializable, java.lang.Object[], java.lang.Object[],
	 *      java.lang.String[], org.hibernate.type.Type[])
	 */
	public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState,
			Object[] previousState, String[] propertyNames, Type[] types) throws CallbackException {
		boolean wasStateModified = false;

		if ((entity instanceof AbstractVersionedEntity) || (entity instanceof AbstractVersionedLookup)) {
			Timestamp ts = getCurrentTimestamp();
			ServiceUser user = MessageContextHolder.getServiceUser();
			
			
			for (int index = 0; index < propertyNames.length; index++) {
				if (PROPERTY_NAMES_MODIFIED_BY.containsKey(propertyNames[index])) {
					currentState[index] = getCurrentPrincipal(user);
					wasStateModified = true;
				} else if (PROPERTY_NAMES_MODIFIED_ON.containsKey(propertyNames[index])) {
					currentState[index] = ts;
					wasStateModified = true;
				} else if (PROPERTY_NAMES_MODIFIED_IN_TIMEZONE.containsKey(propertyNames[index])) {
					currentState[index] = getCurrentUserTimezone(user);
					wasStateModified = true;
				}
			}
		}

		return wasStateModified;
	}

	/**
	 * @see org.hibernate.Interceptor#onSave(java.lang.Object,
	 *      java.io.Serializable, java.lang.Object[], java.lang.String[],
	 *      org.hibernate.type.Type[])
	 */
	public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames,
			Type[] types) throws CallbackException {
		boolean wasStateModified = false;

		if ((entity instanceof AbstractVersionedEntity) || (entity instanceof AbstractVersionedLookup)) {
			Timestamp ts = getCurrentTimestamp();
			ServiceUser user = MessageContextHolder.getServiceUser();
			for (int index = 0; index < propertyNames.length; index++) {
				if (PROPERTY_NAMES_CREATED_BY.containsKey(propertyNames[index])
						|| PROPERTY_NAMES_MODIFIED_BY.containsKey(propertyNames[index])) {
					state[index] = getCurrentPrincipal(user);
					wasStateModified = true;
				} else if (PROPERTY_NAMES_CREATED_ON.containsKey(propertyNames[index])
						|| PROPERTY_NAMES_MODIFIED_ON.containsKey(propertyNames[index])) {
					state[index] = ts;
					wasStateModified = true;
				} else if (PROPERTY_NAMES_CREATED_IN_TIMEZONE.containsKey(propertyNames[index])
						|| PROPERTY_NAMES_MODIFIED_IN_TIMEZONE.containsKey(propertyNames[index])) {
					state[index] = getCurrentUserTimezone(user);
					wasStateModified = true;
				}
			}
		}

		return wasStateModified;
	}

	/**
	 * @see org.hibernate.Interceptor#onDelete(java.lang.Object,
	 *      java.io.Serializable, java.lang.Object[], java.lang.String[],
	 *      org.hibernate.type.Type[])
	 */
	public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames,
			Type[] types) throws CallbackException {

		if ((entity instanceof AbstractVersionedEntity) || (entity instanceof AbstractVersionedLookup)) {
			Timestamp ts = getCurrentTimestamp();
			ServiceUser user = MessageContextHolder.getServiceUser();
			
			for (int index = 0; index < propertyNames.length; index++) {
				if (PROPERTY_NAMES_MODIFIED_BY.containsKey(propertyNames[index])) {
					state[index] = getCurrentPrincipal(user);
				} else if (PROPERTY_NAMES_MODIFIED_ON.containsKey(propertyNames[index])) {
					state[index] = ts;
				} else if (PROPERTY_NAMES_MODIFIED_IN_TIMEZONE.containsKey(propertyNames[index])) {
					state[index] = getCurrentUserTimezone(user);
				}
			}
		}

	}

	/**
	 * @see org.hibernate.Interceptor#onPrepareStatement(java.lang.String)
	 */
	public String onPrepareStatement(String arg0) {
		return arg0;
	}

	/**
	 * Get the loaded Timestamp from Thread-bound storage
	 * 
	 * @return
	 */
	private Timestamp getCurrentTimestamp() {
		return (getTimestampManager() == null) ? new Timestamp(System.currentTimeMillis())
				: getTimestampManager().getTransactionTimestamp();
	}

	/**
	 * Get the logged in Principal that is bound to the Thread
	 * 
	 * @return
	 */
	private AuditInfo getCurrentPrincipal(ServiceUser user) {
		try {
			return (user == null) ? new AuditInfo(getDefaultAuditName()) : new AuditInfo(user);
		} catch (Exception e) {
			return new AuditInfo(getDefaultAuditName());
		}
	}

	private TimeZone getCurrentUserTimezone(ServiceUser user) {
		try {
			
			return TimeZoneUtils.getTimeZone();
			/**
			if (user == null || user.getCurrentTimeZone() == null) {
				return TimeZoneUtils.getTimeZone();
			} else {
				return user.getCurrentTimeZone();
			}
			***/
		} catch (Exception e) {
			return TimeZoneUtils.getTimeZone();
		}
	}

	/**
	 * @return Returns the timestampManager.
	 */
	public TransactionTimestampManager getTimestampManager() {
		// use static application context to get the timestampmanager
		if (this.timestampManager == null) {
			SingletonApplicationContext locator = SingletonApplicationContext.getInstance();

			// ApplicationContext ctx = locator.getSingletonContext();
			this.timestampManager = (TransactionTimestampManager) locator.getBean(
					TRANSACTION_TIMESTAMP_MANAGER_KEY, TransactionTimestampManager.class);
			
			
		}
		return timestampManager;
	}

	/**
	 * @param timestampManager
	 *            The timestampManager to set.
	 */
	public void setTimestampManager(TransactionTimestampManager timestampManager) {
		this.timestampManager = timestampManager;
	}
}