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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.jboss.annotation.ejb.Depends;
import org.jboss.aop.AspectManager;
import org.jboss.aop.DomainDefinition;
import org.jboss.ejb3.mdb.MDB;
import org.jboss.ejb3.mdb.MessageDrivenManager;
import org.jboss.ejb3.stateful.StatefulContainer;
import org.jboss.ejb3.stateful.StatefulManager;
import org.jboss.ejb3.stateless.StatelessContainer;
import org.jboss.ejb3.stateless.StatelessManager;
import org.jboss.logging.Logger;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.ArrayMemberValue;
import javassist.bytecode.annotation.MemberValue;
import javassist.bytecode.annotation.StringMemberValue;

public class Ejb3AnnotationHandler implements Ejb3Handler
{
   private static final Logger log = Logger.getLogger(Ejb3AnnotationHandler.class);

   protected static enum EJB_TYPE
   {
      STATELESS, STATEFUL, MESSAGE_DRIVEN, ENTITY
   };

   protected Ejb3Deployment.DeploymentUnit di;

   protected ClassFile cf;
   protected List<String> ejbNames = new ArrayList<String>();
   protected Class ejbClass;
   protected String className;
   protected EJB_TYPE ejbType;
   protected Annotation annotation;
   protected AnnotationsAttribute visible;
   protected Hashtable ctxProperties;
   protected String defaultSLSBDomain;
   protected String defaultSFSBDomain;
   protected String defaultMDBDomain;

   public Ejb3AnnotationHandler(Ejb3Deployment.DeploymentUnit di, ClassFile cf, String defaultSLSB, String defaultSFSB, String defaultMDB)
   {
      this.di = di;
      this.cf = cf;
      className = cf.getName();
      visible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.visibleTag);
      defaultSLSBDomain = defaultSLSB;
      defaultSFSBDomain = defaultSFSB;
      defaultMDBDomain = defaultMDB;
   }

   public void setCtxProperties(Hashtable ctxProperties)
   {
      this.ctxProperties = ctxProperties;
   }

   private String getJacccontextId()
   {
      return di.getShortName();
   }

   public boolean isEjb()
   {
      if (visible == null) return false;

      if (EJB3Util.isStateless(visible)) return true;
      if (EJB3Util.isMessageDriven(visible)) return true;
      if (EJB3Util.isStatefulSession(visible)) return true;
      return false;
   }

   public List getContainers(ClassFile cf, Ejb3Deployment deployment) throws Exception
   {
      List containers = new ArrayList();

      Container manager = null;
      populateBaseInfo();

      for (int ejbIndex = 0; ejbIndex < ejbNames.size(); ++ejbIndex)
      {
         String ejbName = ejbNames.get(ejbIndex);
         if (ejbType == EJB_TYPE.STATELESS)
         {
            StatelessContainer container = getStatelessContainer(ejbIndex);
            container.setDefaultClientBinding(deployment.getDefaultClientBinding());
            manager = new StatelessManager(container, getJacccontextId());
            containers.add(manager);
         }
         else if (ejbType == EJB_TYPE.STATEFUL)
         {
            StatefulContainer container = getStatefulContainer(ejbIndex);
            container.setDefaultClientBinding(deployment.getDefaultClientBinding());
            manager = new StatefulManager(container, getJacccontextId());
            containers.add(manager);
         }
         else if (ejbType == EJB_TYPE.MESSAGE_DRIVEN)
         {
            MDB container = getMDB(ejbIndex);
            manager = new MessageDrivenManager(container, getJacccontextId());
            containers.add(manager);
         }
         log.info("found EJB3: ejbName=" + ejbName + ", class=" + className + ", type=" + ejbType);
      }

      return containers;
   }

   protected StatefulContainer getStatefulContainer(int ejbIndex) throws Exception
   {
      String containerName = EJB3Util.getAspectDomain(visible, defaultSFSBDomain);
      DomainDefinition domain = AspectManager.instance().getContainer(containerName);

      if (domain == null)
         throw new RuntimeException("No container configured with name '"
                                    + containerName + "''");

      //TODO Use AOPManager interface once that has stabilized
      return new StatefulContainer(di.getClassLoader(), cf.getName(),
                                                          ejbNames.get(ejbIndex), (AspectManager)domain.getManager(), ctxProperties);

   }

   protected StatelessContainer getStatelessContainer(int ejbIndex) throws Exception
   {
      String containerName = EJB3Util.getAspectDomain(visible, defaultSLSBDomain);
      DomainDefinition domain = AspectManager.instance().getContainer(containerName);

      if (domain == null)
         throw new RuntimeException("No container configured with name '"
                                    + containerName + "''");

      //TODO Use AOPManager interface once that has stabilized
      return new StatelessContainer(di.getClassLoader(), cf.getName(),
                                                            ejbNames.get(ejbIndex), (AspectManager)domain.getManager(), ctxProperties);
   }

   protected MDB getMDB(int ejbIndex) throws Exception
   {
      String containerName = EJB3Util.getAspectDomain(visible, defaultMDBDomain);
      DomainDefinition domain = AspectManager.instance().getContainer(containerName);

      if (domain == null)
         throw new RuntimeException("No container configured with name '"
                                    + containerName + "''");

      //TODO Use AOPManager interface once that has stabilized
      return new MDB(ejbNames.get(ejbIndex), (AspectManager)domain.getManager(), di.getClassLoader(), cf.getName(), ctxProperties);
   }

   protected void populateBaseInfo() throws Exception
   {
      String ejbName = null;
      ejbClass = di.getClassLoader().loadClass(className);

      visible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.visibleTag);

      if (visible != null)
      {
         annotation = visible.getAnnotation(javax.ejb.Stateless.class.getName());
         if (annotation != null)
         {
            ejbType = EJB_TYPE.STATELESS;
         }
         else
         {
            annotation = visible.getAnnotation(javax.ejb.Stateful.class.getName());
            if (annotation != null)
            {
               ejbType = EJB_TYPE.STATEFUL;
            }
            else
            {
               annotation = visible.getAnnotation(javax.persistence.Entity.class.getName());
               if (annotation != null)
               {
                  ejbType = EJB_TYPE.ENTITY;
               }
               else
               {
                  annotation = visible.getAnnotation(javax.ejb.MessageDriven.class.getName());
                  if (annotation != null)
                     ejbType = EJB_TYPE.MESSAGE_DRIVEN;
               }
            }
         }

         if (annotation != null)
         {
            StringMemberValue mv = (StringMemberValue) annotation.getMemberValue("name");
            if (mv != null)
               ejbName = mv.getValue();
            else
               ejbName = cf.getName();
         }
      }

      if (ejbName != null)
      {
         ejbNames.add(ejbName);
      }
   }

   public List<Collection> getAllDepends()
   {
      ArrayList<Collection> dependsList = new ArrayList<Collection>();
      ArrayList depends = new ArrayList();

      //    Get the class annotation
      if (visible != null)
      {
         Annotation annotation = visible.getAnnotation(org.jboss.annotation.ejb.Depends.class.getName());
         depends = getDependsValues(depends, annotation);

         //Get the method annotations
         for (Iterator methods = cf.getMethods().iterator(); methods.hasNext();)
         {
            javassist.bytecode.MethodInfo minfo = (javassist.bytecode.MethodInfo) methods.next();
            AnnotationsAttribute mgroup = (AnnotationsAttribute) minfo.getAttribute(AnnotationsAttribute.visibleTag);
            if (mgroup == null) continue;
            javassist.bytecode.annotation.Annotation binfo = mgroup.getAnnotation(Depends.class.getName());
            depends = getDependsValues(depends, binfo);
         }

         for (Iterator fields = cf.getFields().iterator(); fields.hasNext();)
         {
            javassist.bytecode.FieldInfo finfo = (javassist.bytecode.FieldInfo) fields.next();
            AnnotationsAttribute mgroup = (AnnotationsAttribute) finfo.getAttribute(AnnotationsAttribute.visibleTag);
            if (mgroup == null) continue;
            javassist.bytecode.annotation.Annotation binfo = mgroup.getAnnotation(Depends.class.getName());
            depends = getDependsValues(depends, binfo);
         }

         dependsList.add(depends);
      }
      return dependsList;
   }

   private ArrayList getDependsValues(ArrayList depends, Annotation info)
   {
      if (info == null) return depends;

      ArrayMemberValue amv = (ArrayMemberValue) info.getMemberValue("value");
      MemberValue[] values = amv.getValue();

      for (int i = 0; i < values.length; i++)
      {
         StringMemberValue sv = (StringMemberValue) values[i];
         try
         {
            ObjectName on = new ObjectName(sv.getValue());
            if (depends == null) depends = new ArrayList();
            depends.add(on);
         }
         catch (MalformedObjectNameException e)
         {
            throw new RuntimeException("'" + sv.getValue() + "' is not a valid object name");
         }
      }
      return depends;
   }
}
