package org.mockejb;

import java.lang.reflect.*;

import javax.ejb.*;

/**
 * Supports invocations of the home methods specific for entity beans, such as 
 * finders and ejbHome.  
 * 
 * @author Alexander Ananiev
 */
class EntityBeanHome extends BasicEjbHome {

    // bean and method that will be used as the target for methods that MockEJB does not implement itself
    private DummyCMPBean dummyCmpBean = new DummyCMPBean();
    
    private EntityDatabase entityDatabase;
    
    EntityBeanHome( BasicEjbDescriptor descriptor, final EntityDatabase entityDatabase ){
        super( descriptor );
        this.entityDatabase = entityDatabase;
    }

    /**
     * Creates entity bean. It involves instantiating it, setting the context and 
     * calling "create".
     */
    public Object create( BasicEjbDescriptor basicDescriptor, MockEjbObject ejbObject, 
            Method createMethod, Object[] paramVals ) throws Exception  {
        
        EntityBeanDescriptor descriptor = (EntityBeanDescriptor)basicDescriptor;
        
        MockEjbContext ejbContext = new MockEjbContext( getHomeProxy() );
        
        Object bean = createBeanInstance(descriptor, createMethod, ejbContext );         
                   
        Object pk = null;
        if ( ! createMethod.getDeclaringClass().equals( GenericHome.class  )) {
            
            pk = invokeBeanCreateMethod( bean, createMethod, paramVals );
            ejbContext.setPrimaryKey( pk );
      
            // ejbPostCreate
            invokeBeanMethodWithPrefix( "ejbPost", bean, createMethod, paramVals );
        }
        
        Object ejbObjectProxy = ejbObject.createProxy( bean, ejbContext );
        
        // TODO: refactor - FindByPKHandler should handle create as well
        if (pk != null ) {
            entityDatabase.add( descriptor.getHomeClass(), pk, ejbObjectProxy);
        }
            
            
        return ejbObjectProxy;        
    }
    
    /**
     * Creates the bean without calling its create method and creating proxy.
     * @param basicDescriptor
     * @return new bean instance
     */
    private Object createBeanInstance( EntityBeanDescriptor descriptor, 
            Method method, MockEjbContext ejbContext ) throws Exception  {

        // if the bean class is an abstract class - use the helper class to create the subclass
        Class beanClass = descriptor.getBeanClass();
        
        Object bean;
        
        if (beanClass != null && descriptor.isCMP() ) {
            EntityBeanSubclass subclassFactory = EntityBeanSubclass.newInstance(beanClass);
            bean = subclassFactory.create();
        }
        else {
            bean = createBean( descriptor );
        }

        if ( bean instanceof EntityBean ) {

            Class paramTypes[]={ EntityContext.class };
            Object args[]={ ejbContext };
            
            invokeBeanMethod(bean, null, 
                    "setEntityContext", paramTypes, args );
        }
        
        return bean;
    }
    
    
    public Object invokeHomeMethod( BasicEjbDescriptor basicDescriptor,
            Method homeMethod, Object[] paramVals ) throws Exception{
        
        Object returnObj = null;
        
        EntityBeanDescriptor descriptor = (EntityBeanDescriptor)basicDescriptor;
        if ( homeMethod.getName().startsWith("find") )
            returnObj = invokeFinder(descriptor, homeMethod, paramVals);
        else  {
            // consider everything else a home method 
            
            // create a bean that we can use to call a home method on
            Object bean = createBeanInstance( descriptor, homeMethod, 
                    getMockEjbContext( ) );
            
            // try to find this method on the target bean
            returnObj = invokeBeanMethodWithPrefix( "ejbHome", bean, homeMethod, paramVals );
            
        }
        
        return returnObj;

    }
    
    protected Object invokeFinder(EntityBeanDescriptor descriptor, Method finderMethod, 
            Object[] paramVals ) throws Exception{
        
        Object returnObj = null;
        
        if ( descriptor.isCMP() ) {
            
            // this is to make sure that there is a context
            getMockEjbContext(); 
            
            try {
                returnObj = interceptorInvoker.invoke( getHomeProxy(), finderMethod, 
                        dummyCmpBean, dummyCmpBean.getTargetMethod(), paramVals );

            }
            catch ( MustBeInterceptedException mbie ){
                // translate it into more meaningful error message
                throw new MustBeInterceptedException( finderMethod );
            }
                
        }
        else {
            // create a bean that we can use to call a finder method on
            Object bean = createBeanInstance( descriptor, finderMethod, 
                    getMockEjbContext() );
            
            returnObj = invokeBeanMethodWithPrefix("ejb", bean, finderMethod,  
                paramVals );
        }
        
        return returnObj;
        
    }

    
    private MockEjbContext getMockEjbContext( ) {
        MockEjbContext context = new MockEjbContext( getHomeProxy() );
        interceptorInvoker.setContext( MockEjbContext.class.getName(), context );
        return context;
    }
}