package it.mice.voila.runtime.hibernate.listeners;

import it.mice.voila.runtime.entity.Entity;
import it.mice.voila.runtime.entity.TrackAttributeAwareEntity;

import java.io.Serializable;

import javax.persistence.PreUpdate;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.engine.internal.ForeignKeys;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.event.internal.DefaultSaveOrUpdateEventListener;
import org.hibernate.event.internal.EntityState;
import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.SaveOrUpdateEvent;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.HibernateProxy;

/**
 * Created by IntelliJ IDEA. User: zzy9v4 Date: 22-feb-2007 Time: 14.02.38 To
 * change this template use File | Settings | File Templates.
 */
public class SaveOrUpdateEventListener extends DefaultSaveOrUpdateEventListener {
	private static final Log log = LogFactory.getLog(SaveOrUpdateEventListener.class);

	/**
	 * Determine whether the entity is persistent, detached, or transient
	 *
	 * @param entity     The entity to check
	 * @param entityName The name of the entity
	 * @param entry      The entity's entry in the persistence context
	 * @param source     The originating session.
	 * @return The state.
	 */
	protected EntityState getEntityState(Object entity, String entityName, EntityEntry entry, // pass this as an
																								// argument only to
																								// avoid double looking
			SessionImplementor source) {

		if (entry != null) { // the object is persistent
			return EntityState.getEntityState(entity, entityName, entry, source, getAssumedUnsavedForEntity(entity));
		} else { // The object is not in the session cache !!!
			return getInternalEntityState(entityName, entity, source);
		}
	}

	private EntityState getInternalEntityState(String entityName, Object entity, SessionImplementor source) {
		// the object is transient or detached
		// the entity is not associated with the session, so
		// try interceptor and unsaved-value
		if (entity instanceof Entity) {
			Entity wrappedEntity = (Entity) entity;
			if (wrappedEntity.isNewEntity()) {
				return EntityState.TRANSIENT;
			} else {
				return EntityState.DETACHED;
			}
		}
		Boolean assumedUnsaved = getAssumedUnsavedForEntity(entity);
		EntityState entityState = EntityState.getEntityState(entity, entityName, null, source, assumedUnsaved);

		if (entityState == EntityState.TRANSIENT) {
			if (log.isTraceEnabled()) {
				log.trace("transient instance of: " + getLoggableName(entityName, entity));
			}
		} else {
			if (log.isTraceEnabled()) {
				log.trace("detached instance of: " + getLoggableName(entityName, entity));
			}
		}
		return entityState;
	}

	private Boolean getAssumedUnsavedForEntity(Object entity) {
		if (entity instanceof Entity) {
			if (((Entity) entity).isNewEntity()) {
				return Boolean.TRUE;
			} else {
				return Boolean.FALSE;
			}
		}
		return null;
	}

	@Override
	public void onSaveOrUpdate(SaveOrUpdateEvent event) {
		Entity entity = (Entity) event.getObject();
		if (entity.isNewEntity()) {
			if (entity instanceof TrackAttributeAwareEntity) {
				((TrackAttributeAwareEntity) entity).trackInsertAttribute();
			}
		}
		if (entity.isNewEntity() || entity.isChangedEntity()) {
			if (entity instanceof TrackAttributeAwareEntity) {
				((TrackAttributeAwareEntity) entity).trackUpdateAttribute();
			}
		}

		final SessionImplementor source = event.getSession();
		final Object object = event.getObject();
		final Serializable requestedId = event.getRequestedId();

		if ( requestedId != null ) {
			//assign the requested id to the proxy, *before*
			//reassociating the proxy
			if ( object instanceof HibernateProxy ) {
				( (HibernateProxy) object ).getHibernateLazyInitializer().setIdentifier( requestedId );
			}
		}

		// For an uninitialized proxy, noop, don't even need to return an id, since it is never a save()
		if ( reassociateIfUninitializedProxy( object, source ) ) {
			//LOG.trace( "Reassociated uninitialized proxy" );
		}
		else {
			//initialize properties of the event:
			final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
			final Object entity2 = persistenceContext.unproxyAndReassociate( object );
			event.setEntity( entity2 );
			event.setEntry( persistenceContext.getEntry( entity2 ) );
			//return the id in the event object
			event.setResultId( performSaveOrUpdate( event ) );
		}

//		super.onSaveOrUpdate(event);
	}

	protected Serializable performSaveOrUpdate(SaveOrUpdateEvent event) {
		EntityState entityState = EntityState.getEntityState(
				event.getEntity(),
				event.getEntityName(),
				event.getEntry(),
				event.getSession(),
				null
		);

		Entity entity = (Entity) event.getObject();
		if (entity.isNewEntity() && entityState != EntityState.PERSISTENT) {
			return entityIsTransient( event );
		} 
		if (entityState == EntityState.PERSISTENT) {
			return entityIsPersistent( event );
		}
		if (entityState == EntityState.DETACHED) {
			entityIsDetached( event );
			return null;
		}
		
		if (entity.isDeletedEntity()) {
			return entityIsTransient( event );
		}
		if (entity.isChangedEntity()) {
			entityIsDetached( event );
			return null;
		}
		return entityIsTransient( event );
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.hibernate.event.def.DefaultSaveOrUpdateEventListener#performUpdate(org.
	 * hibernate.event.SaveOrUpdateEvent, java.lang.Object,
	 * org.hibernate.persister.entity.EntityPersister)
	 */
	@Override
	protected void performUpdate(SaveOrUpdateEvent event, Object entity, EntityPersister persister)
			throws HibernateException {

		final EventSource source = event.getSession();
		EntityKey key = new EntityKey(event.getRequestedId(), persister);
		if (source.getPersistenceContext().getEntity(key) != null) {
			source.getPersistenceContext().removeEntity(key);
		}

		super.performUpdate(event, entity, persister);
	}

	protected String getLoggableName(String entityName, Object entity) {
		return entityName == null ? entity.getClass().getName() : entityName;
	}
}
