/*
 * JBoss, Home of Professional Open Source
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.seam.intercept;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.List;

import org.jboss.seam.log.LogProvider;
import org.jboss.seam.log.Logging;
import org.jboss.seam.Component;
import org.jboss.seam.Seam;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.annotations.intercept.InterceptorType;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.contexts.Lifecycle;
import org.jboss.seam.core.Mutable;
import org.jboss.seam.util.EJB;

/**
 * Abstract superclass of all controller interceptors
 * 
 * @author Gavin King
 */
public class RootInterceptor implements Serializable
{
   private static final long serialVersionUID = 8041533870186694663L;
   private static final LogProvider log = Logging.getLogProvider(RootInterceptor.class);
   
   private final InterceptorType type;
   private boolean isSeamComponent;
   private String componentName;
   private List<Object> userInterceptors;
   
   private transient Component component; //a cache of the Component reference for performance

   protected RootInterceptor(InterceptorType type)
   {
      this.type = type;
   }
   
   protected void init(Component component)
   {
      isSeamComponent = true;
      componentName = component.getName();
      userInterceptors = component.createUserInterceptors(type);
      this.component = component;
   }
   
   protected void initNonSeamComponent()
   {
      isSeamComponent = false;
   }
   
   protected void postConstruct(Object bean)
   {
      // initialize the bean instance
      if (isSeamComponent)
      {
         try
         {
            getComponent().initialize(bean);
         }
         catch (RuntimeException e)
         {
            throw e;
         }
         catch (Exception e)
         {
            throw new RuntimeException("exception initializing EJB component", e);
         }
      }      
   }

   protected void invokeAndHandle(InvocationContext invocation, EventType invocationType)
   {
      try
      {
         invoke(invocation, invocationType);
      }
      catch (RuntimeException e)
      {
         throw e;
      }
      catch (Exception e)
      {
         throw new RuntimeException("exception in EJB lifecycle callback", e);
      }
   }
   
   protected Object invoke(InvocationContext invocation, EventType invocationType) throws Exception
   {
      if ( !isSeamComponent || !Lifecycle.isApplicationInitialized())
      {
         //not a Seam component
         return invocation.proceed();
      }
      else if ( Contexts.isEventContextActive() || Contexts.isApplicationContextActive() ) //not sure about the second bit (only needed at init time!)
      {

         // a Seam component, and Seam contexts exist
         return createInvocationContext(invocation, invocationType).proceed();
      }
      else
      {            
         //if invoked outside of a set of Seam contexts,
         //set up temporary Seam EVENT and APPLICATION
         //contexts just for this call
         
         Lifecycle.beginCall();         
         try
         {
            return createInvocationContext(invocation, invocationType).proceed();
         }
         finally
         {
            Lifecycle.endCall();
         }
      }
   }

   private InvocationContext createInvocationContext(InvocationContext invocation, EventType eventType) throws Exception
   {
      if ( isProcessInterceptors( invocation.getMethod(), invocation.getTarget() ) )
      {
         if ( log.isTraceEnabled() ) 
         {
            log.trace( "intercepted: " + getInterceptionMessage(invocation, eventType) );
         }
         return createSeamInvocationContext(invocation, eventType);
      }
      else 
      {
         if ( log.isTraceEnabled() ) 
         {
            log.trace( "not intercepted: " + getInterceptionMessage(invocation, eventType) );
         }
         return invocation;
      }
   }

   private SeamInvocationContext createSeamInvocationContext(InvocationContext invocation, EventType eventType) throws Exception
   {
      return EJB.INVOCATION_CONTEXT_AVAILABLE ?
            createEE5SeamInvocationContext(invocation, eventType) :
            createNonEE5SeamInvocationContext(invocation, eventType);
   }

   private SeamInvocationContext createNonEE5SeamInvocationContext(InvocationContext invocation, EventType eventType)
   {
      return new SeamInvocationContext( invocation, eventType, userInterceptors, getComponent().getInterceptors(type) );
   }
   
   private static final Constructor CONSTRUCTOR;
   static
   {
      if ( EJB.INVOCATION_CONTEXT_AVAILABLE )
      {
         try
         {
            Class[] paramTypes = {InvocationContext.class, EventType.class, List.class, List.class};
            CONSTRUCTOR = Class.forName("org.jboss.seam.intercept.EE5SeamInvocationContext").getConstructor(paramTypes);
         }
         catch (Exception e)
         {
            throw new RuntimeException(e);
         }
      }
      else
      {
         CONSTRUCTOR = null;
      }
      
   }

   private SeamInvocationContext createEE5SeamInvocationContext(InvocationContext invocation, EventType eventType)
   {
      try
      {
         return (SeamInvocationContext) CONSTRUCTOR.newInstance( invocation, eventType, userInterceptors, getComponent().getInterceptors(type) );
      }
      catch (Exception e)
      {
         throw new RuntimeException(e);
      }
   }

   private String getInterceptionMessage(InvocationContext invocation, EventType eventType)
   {
      return getComponent().getName() + '.' + 
            ( eventType==EventType.AROUND_INVOKE ? invocation.getMethod().getName() : eventType );
   }

   private boolean isProcessInterceptors(Method method, Object bean)
   {
      return isSeamComponent && 
            getComponent().isInterceptionEnabled() &&
            !isBypassed(method) &&
            !isClearDirtyMethod(method, bean);
   }

   private boolean isBypassed(Method method)
   {
      return method!=null && method.isAnnotationPresent(BypassInterceptors.class);
   }

   private boolean isClearDirtyMethod(Method method, Object bean)
   {
      return bean instanceof Mutable &&
            method!=null &&
            method.getName().equals("clearDirty") &&
            method.getParameterTypes().length==0;
   }
   
   protected Component getComponent()
   {
      if (isSeamComponent && component==null) 
      {
         component = Seam.componentForName(componentName);
      }
      return component;
   }
   
   protected boolean isSeamComponent()
   {
      return isSeamComponent;
   }

   protected String getComponentName()
   {
      return componentName;
   }
   
}
