package org.mockejb;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import javax.ejb.*;

import org.apache.commons.logging.*;

import org.mockejb.interceptor.*;

/**
 * Intercepts the calls to the BMP find methods, 
 * checks if the returned PK (or elements of the pk collection) is in the EntityDatabase, 
 * if not, creates a new entity and calls "ejbLoad", and adds it to the EntityDatabase.
 * Returns the entity or entity collection to the client 
 *  
 * @author Alexander Ananiev
 */
public class BMPFinderHandler  implements Aspect, Serializable {

    // logger for this class
    private static Log logger = LogFactory.getLog( BMPFinderHandler.class.getName() );

    protected EntityDatabase entityDatabase;
    
    public BMPFinderHandler( final EntityDatabase entityDatabase ){
        this.entityDatabase = entityDatabase;
    }
    
    public Pointcut getPointcut(){
        // we're only interested in EntityBeans and ejbFind... methods
        return PointcutPair.and(  new MethodPatternPointcut( "ejbFind" ), 
                new ClassPointcut( EntityBean.class, true) ); 
    }
   

    /**
     * Intercepts and handles finders.
     */
    public void intercept( InvocationContext invocationContext ) throws Exception {
        
        // we wnat to proceed in any case
        invocationContext.proceed();
        
        // get the descriptor
        BasicEjbDescriptor ejbDescriptor= (
                BasicEjbDescriptor) invocationContext.getPropertyValue("descriptor");
        MockEjbContext ejbContext = (
                MockEjbContext) invocationContext.getPropertyValue( MockEjbContext.class.getName() );
        
        // handle only CMP entity beans
        if ( ejbDescriptor instanceof EntityBeanDescriptor && 
                !((EntityBeanDescriptor) ejbDescriptor).isCMP() ) {
            
            EntityBeanDescriptor descriptor = (EntityBeanDescriptor) ejbDescriptor;
            
            // call finder
            invocationContext.proceed();
            
            logger.debug("Intercepted "+invocationContext.getProxyMethod());

            Object pkOrPkCollection = invocationContext.getReturnObject();
            
            // if it's a simple PK
            if ( ! (pkOrPkCollection instanceof Collection) && 
                    !(pkOrPkCollection instanceof EJBObject) &&
                    !(pkOrPkCollection instanceof EJBLocalObject)) {
                
                Object entity = findInCacheOrCreate( descriptor, ejbContext,pkOrPkCollection);
                invocationContext.setReturnObject( entity );
                
            } else if ( pkOrPkCollection instanceof Collection) { // this is a collection of PKs
                
                Collection pks = (Collection)pkOrPkCollection;
                Iterator i = pks.iterator();
                Collection resultingCollection = new ArrayList();
                while( i.hasNext() ) {
                    Object pk = i.next();
                    if ( !(pk instanceof EJBObject) &&
                        !(pk instanceof EJBLocalObject)) {
                        
                        Object entity = findInCacheOrCreate( descriptor, ejbContext, pk);
                        resultingCollection.add(entity);
                    }
                }
                invocationContext.setReturnObject( resultingCollection );
            }   // end of if collection
        }   
    }
    
    protected Object findInCacheOrCreate( EntityBeanDescriptor descriptor, 
            MockEjbContext ejbContext, Object pk  ) throws Exception {
        
        // check in the cache
        // TODO: we can use context instead
        Object newEntity = entityDatabase.find(descriptor.getHomeClass(), pk);

        if (newEntity == null) {
            logger.debug( "Entity "+descriptor.getIfaceClass().getName()+
                    " for PK "+pk+" was not found in the EntityDatabase. Will try to create the new entity and call ejbLoad");
            
            // not in cache -- create
            // get our home
            GenericHome home = (GenericHome) ejbContext.getEJBLocalHome();
            newEntity = home.genericCreate();
            // call ejbLoad
            EjbBeanAccess beanAccess = (EjbBeanAccess) newEntity;
            Object bean = beanAccess.getBean();
            if (! (bean instanceof EntityBean)) {
                throw new EJBException("Can't call ejbLoad on the "+bean.getClass().getName()+
                        " because it does not implement EntityBean interface. You can avod this error by"+
                        " adding the entity with PK "+pk+" to the EntityDatabase." );
            }
            // set the PK on the context of this entity
            MockEjbContext newEntityContext = beanAccess.getEjbContext();
            newEntityContext.setPrimaryKey( pk );
            EntityBean entityBean = (EntityBean)bean;
            entityBean.ejbLoad();
        }
        
        return newEntity;
    }
    
    
    
    /**
     * This class does not have state, so all instances of this class
     * are considered equal
     */
    public boolean equals( Object obj ){
        
        if ( obj instanceof BMPFinderHandler && 
                entityDatabase.equals( ((BMPFinderHandler)obj).entityDatabase ) ) 
            return true;
        else
            return false;
        
    }

    public int hashCode() { 
        return this.getClass().hashCode()+entityDatabase.hashCode(); 
    }    
   

}