package org.jboss.seam.ioc.spring;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpSessionActivationListener;

import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.contexts.Lifecycle;
import org.jboss.seam.core.Expressions;
import org.jboss.seam.core.Mutable;
import org.jboss.seam.core.Expressions.ValueExpression;
import org.jboss.seam.intercept.Proxy;
import org.springframework.aop.TargetSource;

/**
 * A TargetSource for a seam component instance. Will obtain an instance given a
 * name and optionally a scope and create. Used by the SeamFactoryBean to create
 * a proxy for a requested seam component instance.
 * 
 * @author youngm
 */
@SuppressWarnings("serial")
public class SeamTargetSource implements TargetSource, Serializable
{
   private ScopeType scope;

   private String name;

   private Boolean create;

   private ValueExpression valueExpression;

   private Class type;

   public SeamTargetSource(String name, ScopeType scope, Boolean create, Class type)
   {
      this(name, scope, create);
      this.type = type;
   }

   /**
    * @param name
    *           Name of the component: required
    * @param scope
    *           Name of the scope the component is in: optional
    * @param create
    *           Whether to create a new instance if one doesn't already exist:
    *           optional
    */
   public SeamTargetSource(String name, ScopeType scope, Boolean create)
   {
      if (name == null || "".equals(name))
      {
         throw new IllegalArgumentException("Name is required.");
      }
      this.name = name;
      this.scope = scope;
      this.create = create;

      if (name.startsWith("#"))
      {
         this.valueExpression = Expressions.instance().createValueExpression(name);
      }
   }

   /**
    * Returns a component instance for this TargetSource.
    * 
    * @see org.springframework.aop.TargetSource#getTarget()
    */
   public Object getTarget() throws Exception
   {
      if (valueExpression != null)
      {
         return valueExpression.getValue();
      }
      else
      {
         if (scope == null && create == null)
         {
            return Component.getInstance(name);
         }
         else if (scope == null)
         {
            return Component.getInstance(name, create);
         }
         else if (create == null)
         {
            return Component.getInstance(name, scope);
         }
         else
         {
            return Component.getInstance(name, scope, create);
         }
      }
   }

   /**
    * Obtains the seam component beanClass or the defined type for this
    * TargetSource.
    * 
    * @see org.springframework.aop.TargetSource#getTargetClass()
    */
   public Class getTargetClass()
   {
      if (type != null)
      {
         return type;
      }
      Component component = getComponent();
      if (component == null)
      {
         return null;
      }
      if (component.hasUnwrapMethod())
      {
         return component.getUnwrapMethod().getReturnType();
      }
      return component.getBeanClass();
   }

   public List<Class> getSeamInterfaces()
   {
      List<Class> interfaces = new ArrayList<Class>();
      Component component = getComponent();
      // Attempt to piece together all of the possible interfaces to apply
      // to our proxy.
      if ( component != null && component.isInterceptionEnabled() )
      {
         if (component.getType().isSessionBean())
         {
            interfaces.addAll(component.getBusinessInterfaces());
         }
         else
         {
            interfaces.add(HttpSessionActivationListener.class);
            interfaces.add(Mutable.class);
         }
         interfaces.add(Proxy.class);
      }
      return interfaces;
   }

   /**
    * Get the component for this TargetSource
    * 
    * @return component
    */
   public Component getComponent()
   {
      if (valueExpression != null)
      {
         return null;
      }
      else
      {
         // TODO reuse
         boolean unmockApplication = false;
         if (!Contexts.isApplicationContextActive())
         {
            Lifecycle.mockApplication();
            unmockApplication = true;
         }
         try
         {
            Component component = Component.forName(name);
            if (component == null)
            {
               throw new IllegalStateException("Cannot find targetClass for seam component: "
                        + name + ".  Make sure Seam is being configured before Spring.");
            }
            return component;
         }
         finally
         {
            if (unmockApplication)
            {
               Lifecycle.unmockApplication();
            }
         }
      }
   }

   /**
    * @see org.springframework.aop.TargetSource#isStatic()
    */
   public boolean isStatic()
   {
      return false;
   }

   /**
    * Don't think we need to do anything here.
    * 
    * @see org.springframework.aop.TargetSource#releaseTarget(java.lang.Object)
    */
   public void releaseTarget(Object target) throws Exception
   {
      // Do Nothing
   }
}
