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

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

import java.lang.reflect.Constructor;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.EntityMode;
import org.hibernate.HibernateException;
import org.hibernate.engine.EntityKey;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.event.LoadEvent;
import org.hibernate.event.LoadEventListener;
import org.hibernate.event.def.DefaultLoadEventListener;
import org.hibernate.persister.entity.EntityPersister;
import org.springframework.beans.BeanUtils;

/**
 * Created by IntelliJ IDEA.
 * User: zzy9v4
 * Date: 12-feb-2007
 * Time: 16.59.32
 * To change this template use File | Settings | File Templates.
 */
public class LoadListener extends DefaultLoadEventListener {
    private static Log logger = LogFactory.getLog(LoadListener.class);
	/** 
	 * Handle the given load event.
	 *
	 * @param event The load event to be handled.
	 * @throws HibernateException
	 */
	@SuppressWarnings("rawtypes")
	public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType) throws HibernateException {
		final SessionImplementor source = event.getSession();

		EntityPersister persister;
		if ( event.getInstanceToLoad() != null ) {
			persister = source.getEntityPersister( null, event.getInstanceToLoad() ); //the load() which takes an entity does not pass an entityName
			event.setEntityClassName( event.getInstanceToLoad().getClass().getName() );
		}
		else {
			persister = source.getFactory().getEntityPersister( event.getEntityClassName() );
		}

		if ( persister == null ) {
			throw new HibernateException( 
					"Unable to locate persister: " + 
					event.getEntityClassName() 
				);
		}

		if ( persister.getIdentifierType().isComponentType() && EntityMode.DOM4J == event.getSession().getEntityMode() ) {
			// skip this check for composite-ids relating to dom4j entity-mode;
			// alternatively, we could add a check to make sure the incoming id value is
			// an instance of Element...
		} else {
			Class idClass = persister.getIdentifierType().getReturnedClass();
			if ( idClass != null && !idClass.isInstance( event.getEntityId()) && EntityGeneric.class.isInstance(event.getEntityId())) {
				Entity theEntity = (Entity)event.getEntityId();
				event.setEntityId(theEntity.getEntityId());
				event.setInstanceToLoad(theEntity);
			}
		}

		super.onLoad(event, loadType);
	}

    protected Object proxyOrLoad(final LoadEvent event, final EntityPersister persister, final EntityKey keyToLoad, final LoadType options) throws HibernateException {
        if (options == INTERNAL_LOAD_LAZY &&
            options.isAllowProxyCreation() &&
            event.getSession().getPersistenceContext().getEntity(keyToLoad) == null)
        {
			Entity entity;
        	Object id = event.getEntityId();
        	if (id instanceof Entity) {
				entity = (Entity) id;
        	} else {
        		entity = (Entity)event.getInstanceToLoad();
        		if (entity == null) {
        			entity = createNewEntity(event, id);
        		}
        	}
            if (logger.isDebugEnabled()) {
                logger.debug("proxyOrLoad " + INTERNAL_LOAD_LAZY + " intercepted and isAllowProxyCreation = true. on entity:" + entity + ". Avoiding prozy creation...");
            }
            entity.setInternalEntityStateToLazy();
            return entity; 
        } else {
        	Object result = event.getSession().getPersistenceContext().getEntity(keyToLoad);
        	if (result != null && result != event.getInstanceToLoad()) {
        		return result;
        	}
            return super.proxyOrLoad(event, persister, keyToLoad, options);
        }
    }

	private Entity createNewEntity(LoadEvent event, Object id) {
		try {
			Class entClass = Class.forName(event.getEntityClassName());
			Constructor idConstructor = entClass.getDeclaredConstructor(new Class[] {id.getClass()});
			Entity ent = (Entity)BeanUtils.instantiateClass(idConstructor, new Object[] {id});
			return ent;
		} catch (Exception e) {
			logger.error("Unable to instantiate class: " + event.getEntityClassName(), e);
		}
		return null;
	}
}
