package org.mockejb;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

import net.sf.cglib.proxy.*;

import org.apache.commons.logging.*;

import org.mockejb.interceptor.InterceptableProxy;
import org.mockejb.interceptor.InterceptorInvoker;

/**
 * Provides mock subclass of the CMP entity abstract class.
 * The subclass gets created dynamically using CGLIB.
 * The behavior is the following: CMP field getters return null if the corresponding
 * set was not called, otherwise it returns the previously set value. 
 * 
 * Attempt to call ejbFind and ejbSelect will trigger exception. You need to intercept
 * the appropriate methods of the Home interface and provide your own implementation.
 *  
 * Intercept "create" method if you want to  change the behavior. 
 * 
 * @author Alexander Ananiev
 */
public class EntityBeanSubclass implements MethodInterceptor {
    
    // logger for this class
    private static Log logger = LogFactory.getLog( EntityBeanSubclass.class.getName() );
    
    
    /**
     * Creates an instance of EntityBeanSubclass. Current implementation creates
     * a new instance evry time. 
     * This method does not create the actual sublcass, for that you need to call "create". 
     * @return an instance of EntityBeanSubclass
     */
    public static EntityBeanSubclass newInstance( Class abstractEntityClass ){

        return (EntityBeanSubclass)InterceptableProxy.create( EntityBeanSubclass.class, 
                new EntityBeanSubclass( abstractEntityClass ) );
        
    }
    
    private Class abstractEntityClass;
    
    /**
     * Current values are stored here
     */
    private Map fieldVals = new HashMap();
    
    /**
     * Fake constructor for CGLIB. This is needed in order to 
     * be able to re-use InterceptableProxy class
     *
     */
    EntityBeanSubclass(){
    }
    
    private EntityBeanSubclass( Class abstractEntityClass ){
        this.abstractEntityClass = abstractEntityClass;
    }
    
    /**
     * Creates the subclass of the abstract entity class using CGLIB
     * enhancer. 
     * @return an instance of the subclass implementing abstract methods of the 
     * provided entity bean class 
     */
    public Object create( ){
        Enhancer e = new Enhancer();
        e.setSuperclass( abstractEntityClass );
        e.setCallback( this );
        
        return e.create();
        
    }

       
    public Object intercept(Object obj, Method method, Object[] paramVals,
            MethodProxy cglibMethodProxy) throws Throwable {
        
        String methodName = method.getName();
        Object returnVal = null;
        
        if ( isAbstractAndStartsWith( method, "set" ) ){
            
            String fieldName = deriveFieldName( methodName );
            if (paramVals.length != 1)
                throw new IllegalArgumentException("Attempt to call setter "+method+
                        " with incorrect number of  parameters");
            if (!method.getReturnType().getName().equals("void") ) {
                throw new IllegalArgumentException("Attempt to call setter "+method+
                    " which has the return type other than void");
            }
            
            logger.debug("Calling setter for "+fieldName+" with the value "+paramVals[0]);
            
            fieldVals.put(fieldName, paramVals[0]);
        }
        else if ( isAbstractAndStartsWith( method, "get" ) ) {
            
            String fieldName = deriveFieldName( methodName );
            if (paramVals.length > 0)
                throw new IllegalArgumentException("Attempt to call getter method "+method+
                        " with parameters. The method should not have any parameters");

            returnVal = fieldVals.get( fieldName );
            
            // check if it is CMR collection
            Class returnClass = method.getReturnType();
            if ( java.util.Collection.class.isAssignableFrom(returnClass ) && returnVal==null) {
                returnVal=new ArrayList();
            }
            else if ( java.util.Set.class.isAssignableFrom(returnClass ) && returnVal==null) {
                returnVal=new HashSet();
            }
            
            
            logger.debug("Calling getter for "+fieldName+" with return value "+returnVal);
            
        }
        // ejbSelect
        else if ( isAbstractAndStartsWith( method, "ejbSelect" ) ) {
            returnVal = invokeEjbSelect( obj, method, paramVals );
        }
        // business method or create or other method implemented in the abstract class
        else {
            returnVal = cglibMethodProxy.invokeSuper(obj, paramVals);
            //cglibMethodProxy.invokeSuper(obj, paramVals);
        }
        
        return returnVal;
    }
    
    private boolean isAbstractAndStartsWith( Method method, String prefix ){
        
        return ( method.getName().startsWith( prefix ) && Modifier.isAbstract( method.getModifiers()) );
    }
    
    private String deriveFieldName( String methodName ){
        
        return methodName.substring(3);
        
    }
    
    protected Object invokeEjbSelect( Object subclassObj, Method ejbSelectMethod,  
            Object[] paramVals ) throws Exception{
        
        Object returnObj = null;
        DummyCMPBean dummyCmpBean = new DummyCMPBean();
        
        InterceptorInvoker interceptorInvoker= new InterceptorInvoker();
        
        try {
            returnObj = interceptorInvoker.invoke( subclassObj, ejbSelectMethod, dummyCmpBean, 
                dummyCmpBean.getTargetMethod(), paramVals );
        }
        catch ( MustBeInterceptedException mbie ){
            // translate it into more meaningful error message
            throw new MustBeInterceptedException( ejbSelectMethod );
        }
        
        return returnObj;
        
    }
    
       
    
}