package org.mockejb;

import java.io.Serializable;
import java.lang.reflect.*;
import java.rmi.RemoteException;

import javax.ejb.*;

import org.mockejb.interceptor.*;

/**
 * Base class for concrete Home implementations.
 * Creates proxy for home interface and calls "create" method.
 * "create" must be defined by the subclasses.
 * 
 * @author Alexander Ananiev
 */
abstract class BasicEjbHome implements InvocationHandler, Serializable {

    
    private static MethodContainer standardMethods;

    /**
     * Standard methods required by EJBHome and EJBLocalHome implemented by
     * this class. 
     */
    static {
        standardMethods = new MethodContainer( SessionBeanHome.class );
        standardMethods.add("getEJBMetaData");
        standardMethods.add("getHomeHandle");

        Class objectArg[] = {Object.class};
        standardMethods.add("remove", objectArg );
        
        // Object methods
        standardMethods.add("toString" );

        // equals is a convenience method
        standardMethods.add("equals", objectArg );
        standardMethods.add("hashCode" );
        
        // another convenience method
        standardMethods.add("toString" );
        
    }

    private BasicEjbDescriptor descriptor;
    //private MockEjbObject ejbObject;
    
    private Object proxy;
    
    private MockEjbMetaData ejbMetaData;
    

    protected InterceptorInvoker interceptorInvoker= new InterceptorInvoker();
    
    BasicEjbHome( BasicEjbDescriptor descriptor ){
        this.descriptor = descriptor;
        interceptorInvoker.setContext("descriptor", descriptor );
        ejbMetaData = new MockEjbMetaData( descriptor );
        
    }


    /** Creates a new instance */
    public Object createProxy( ) {

        Class homeClass = descriptor.getHomeClass();
        
        proxy = Proxy.newProxyInstance( homeClass.getClassLoader(), 
            new Class[] { homeClass, GenericHome.class }, this );        
        
        ejbMetaData.setHomeProxy( proxy );
        
        return proxy;            
            
    }

    protected Object getHomeProxy(){
        return proxy;
    }
    
    /**
     * Handles calls to Home methods
     */
    public Object invoke( Object thisProxy, Method homeMethod, Object[] paramVals ) 
            throws Throwable {

        Object returnValue = null;
        // if this is one of the standard methods
        Method method = standardMethods.find( homeMethod );
        if ( method != null ){
            returnValue = interceptorInvoker.invoke( proxy, homeMethod, this,  method, paramVals );
        }
        // Handle create methods, this also includes genericCreate from the GenericHome iface
        else if ( homeMethod.getName().startsWith("create") || 
                homeMethod.getDeclaringClass().equals( GenericHome.class  ) ) {
            // Create MockEjbObject
            MockEjbObject ejbObject = new MockEjbObject( descriptor.getIfaceClass() );
            ejbObject.setHomeImpl(this);
            ejbObject.setHomeProxy(proxy);
             // create returns the proxy to the newly created object
            returnValue = create( descriptor, ejbObject, homeMethod, paramVals );
        }
        else
            returnValue = invokeHomeMethod( descriptor, homeMethod, paramVals );
        
        return returnValue;
        
    }

    /**
     * Implementors of this method must create an instance of a bean (if needed)
     * and call the required EJB lifecycle methods.
     * @param descriptor
     * @param ejbObject
     * @param homeMethod
     * @param paramVals
     * @return Proxy created by the EjbObject ready to be returned to the client.
     */
    public abstract Object create( BasicEjbDescriptor descriptor, MockEjbObject ejbObject, 
            Method homeMethod, Object[] paramVals ) throws Exception;
    
    /**
     * Implementors of this method can invoke home methods other than create and remove
     * suitable for their EJB type, such as ejbFind for entity bean.
     * If the EJB type only supports create and remove methods, this method must
     * throw exception. 
     * @param descriptor
     * @param ejbObject
     * @param homeMethod
     * @param paramVals
     * @return return value of the home method
     */
    public abstract Object invokeHomeMethod( BasicEjbDescriptor descriptor, 
            Method homeMethod, Object[] paramVals ) throws Exception;

    /**
     * Helper method to create a bean instance. If the bean object is passed in the 
     * descriptor simply returns it, otherwise calls newInstance.
     * @param descriptor EJB descriptor
     * @return bean object
     */
    protected Object createBean(BasicEjbDescriptor descriptor) {
        
        Object bean;
        if ( descriptor.getBean() == null ) {
            bean = invokeNewInstance( descriptor.getBeanClass() );
        }
        else {
            bean = descriptor.getBean();
        }
        
        return bean;
        
    }
    
    // *** Helper methods to call bean's methods

    /**
     * Finds a method of the bean using reflection and calls InterceptorInvoker to
     * find interceptors and call the target.
     * 
     * @param bean target bean object
     * @param homeMethod home method being callese
     * @param methodName name of the method of the target bean to call
     * @param paramTypes types of the parameters of the target method 
     * @param paramVals parameters of the target method
     * @return
     */
    protected Object invokeBeanMethod( Object bean, Method homeMethod, String methodName, 
            Class[] paramTypes, Object[] paramVals ) throws Exception {

        Method method;

        try {
            
            method = bean.getClass().getMethod( methodName, paramTypes);
        
        }
        catch ( NoSuchMethodException noSuchMethodEx ) {
            throw new MockEjbSystemException( "EJB "+bean.getClass().getName()+
                " does not implement method "+
                methodName, noSuchMethodEx );     
        }
        return interceptorInvoker.invoke( getHomeProxy(),homeMethod, bean, method, paramVals );
        
    }

    Object invokeBeanMethod( Object bean, Method homeMethod, 
            String methodName ) throws Exception {
        
        Class paramTypes[]={};
        Object args[]={};

        return invokeBeanMethod( bean, homeMethod, methodName, paramTypes, args);
    }
    
    /**
     * Invokes "ejbCreate" method of a bean. Translate the name of the home 
     * create method to the appropriate "ejbCreate". 
     * @param bean target bean object
     * @param createMethod create method of the home interface
     * @param paramVals parameters of the create method (for entity and stateful beans)
     *  
     */
    protected Object invokeBeanCreateMethod( Object bean, Method createMethod, 
            Object[] paramVals ) throws Exception {
        
        Object returnVal = null;
        if ( createMethod.getName().startsWith("create")) {
            returnVal = invokeBeanMethodWithPrefix( "ejb", bean, createMethod, paramVals );
        }
        else {
            // genericCreate
            returnVal = invokeBeanMethod( bean, createMethod, "ejbCreate", createMethod.getParameterTypes(), 
                    paramVals );
        }
        
        return returnVal;
    }
    
    
    protected Object invokeBeanMethodWithPrefix( String prefix, Object bean, Method homeMethod, 
            Object[] paramVals ) throws Exception  {
        
        String methodName = prefix+homeMethod.getName().substring(0,1).toUpperCase()+
            homeMethod.getName().substring(1);
       
        return invokeBeanMethod( bean, homeMethod, methodName, homeMethod.getParameterTypes(), 
                paramVals );
       
    }
    
    
    /**
     * Create an instance of a bean
     */
    protected Object invokeNewInstance( Class beanClass ) {
    
        Object bean = null;
        try {
            bean = beanClass.newInstance();
        }
        catch ( IllegalAccessException iae ) {
            throw new MockEjbSystemException( "Error instantiating a bean "+
                beanClass.getName(), iae );     
        }
        catch ( InstantiationException ie ) {
            throw new MockEjbSystemException( "Error instantiating a bean "+
                beanClass.getName(), ie );      
        }
        
        return bean;
        
    }
    
    
    

    // Implementation of methods required by the EJBHome and EJBLocalHome interfaces

    /**
     * This method is not implemented by this class. 
     */
    public void remove( Object obj) throws RemoveException {
        throwMethodNotImplemented("remove( obj )");
    }

    /**
     * 
     */
    public EJBMetaData getEJBMetaData() throws RemoteException {
        return ejbMetaData;
    }


    /**
     * 
     */
    public HomeHandle getHomeHandle() throws RemoteException {
        throwMethodNotImplemented("getHomeHandle()");
        return null;
    }

    /**
     * If the object is a proxy, the pointer equality is used.
     * Otherwise, super.equals is called.
     * 
     */
    public boolean equals( Object obj ){
        // if the object is proxy
        if (descriptor.getHomeClass().isInstance( obj ) )
            return (proxy==obj);
        else
            return super.equals( obj );
    }
    

    public int hashCode() {
        return descriptor.getHomeClass().hashCode();
    }
    

    public String toString(){
        return "Home object: HomeIface: "+descriptor.getHomeClass().getName()+
            " BusinessIface: "+descriptor.getIfaceClass().getName();
             
    }
    
    /**
     * Helper method to throw NotImplementedException for this class
     * @param methodName
     */
    protected void throwMethodNotImplemented( String methodName ){

        throw new MethodNotImplementedException( methodName, 
            this.getClass().getName() );

    }


}