/*
  * JBoss, Home of Professional Open Source
  * Copyright 2005, JBoss Inc., and individual contributors as indicated
  * by the @authors tag. See the copyright.txt in the distribution for a
  * full listing of individual contributors.
  *
  * This is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation; either version 2.1 of
  * the License, or (at your option) any later version.
  *
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this software; if not, write to the Free
  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
  */
package org.jboss.ejb3;

import org.jboss.annotation.ejb.LocalBinding;
import org.jboss.annotation.ejb.RemoteBinding;
import org.jboss.annotation.ejb.RemoteBindings;
import org.jboss.aop.Advisor;
import org.jboss.ejb.LocalImpl;
import org.jboss.ejb.RemoteImpl;
import org.jboss.logging.Logger;

import javax.ejb.Local;
import javax.ejb.LocalHome;
import javax.ejb.Remote;
import javax.ejb.RemoteHome;
import javax.jws.WebService;
import javax.naming.Context;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;

/**
 * Comment
 *
 * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
 * @version $Revision$
 */
public class ProxyFactoryHelper
{
   private static final Logger log = Logger.getLogger(ProxyFactoryHelper.class);

   public static Context getProxyFactoryContext(Context ctx)
           throws NamingException
   {

      try
      {
         return (Context) ctx.lookup("proxyFactory");
      }
      catch (NameNotFoundException e)
      {
         return ctx.createSubcontext("proxyFactory");
      }
   }

   public static String getEndpointInterface(Container container)
   {
      WebService ws = (javax.jws.WebService) ((EJBContainer) container).resolveAnnotation(javax.jws.WebService.class);
      if (ws != null)
      {
         return ws.endpointInterface();
      }
      return null;

   }

   public static Class[] getLocalInterfaces(Container container)
   {
      Local li = (javax.ejb.Local) ((EJBContainer) container).resolveAnnotation(javax.ejb.Local.class);

      if (li != null)
      {
         if (li.value().length > 0) return li.value();

         // We have an emtpy @Local annotated bean class

         ArrayList list = getBusinessInterfaces(container.getBeanClass());
         if (list.size() == 0)
            throw new RuntimeException("Use of empty @Local on bean class and there are no valid business interfaces");
         if (list.size() > 1)
            throw new RuntimeException("Use of empty @Local on bean class and there are more than one default interface");
         Class[] rtn = {(Class) list.get(0)};
         li = new LocalImpl(rtn);
         ((EJBContainer) container).getAnnotations().addClassAnnotation(javax.ejb.Local.class, li);
         return rtn;
      }

      Class beanClass = container.getBeanClass();
      String endpoint = getEndpointInterface(container);
      Class[] ri = getRemoteInterfaces(container);

      if (li == null && ri == null && endpoint == null && (beanClass.getInterfaces() == null || beanClass.getInterfaces().length == 0))
         throw new RuntimeException("bean class has no local, webservice, or remote interfaces defined and does not implement at least one business interface");

      // introspect implemented interfaces.
      if (li == null)
      {
         Class[] intfs = beanClass.getInterfaces();
         ArrayList<Class> locals = new ArrayList<Class>();
         for (Class clazz : intfs)
         {
            if (clazz.isAnnotationPresent(javax.ejb.Local.class))
            {
               locals.add(clazz);
            }
         }
         if (locals.size() > 0)
         {
            intfs = locals.toArray(new Class[locals.size()]);
            li = new LocalImpl(intfs);
            ((Advisor) container).getAnnotations().addClassAnnotation(javax.ejb.Local.class, li);
            return li.value();
         }
      }
      // no @Local interfaces implemented
      if (li == null)
      {
         // search for default
         ArrayList<Class> interfaces = getBusinessInterfaces(beanClass);
         if (interfaces.size() != 1) return null; // indeterminate

         Class intf = interfaces.get(0);
         if (ri != null)
         {
            for (Class rintf : ri)
            {
               if (intf.getName().equals(rintf.getName()))
               {
                  return null;
               }
            }
         }
         if (intf.getName().equals(endpoint)) return null;

         Class[] rtn = {intf};
         li = new LocalImpl(rtn);
         ((EJBContainer) container).getAnnotations().addClassAnnotation(javax.ejb.Local.class, li);
         return rtn;
      }
      return li.value();
   }

   public static ArrayList<Class> getBusinessInterfaces(Class beanClass)
   {
      ArrayList<Class> interfaces = new ArrayList<Class>(Arrays.asList(beanClass.getInterfaces()));
      interfaces.remove(java.io.Serializable.class);
      interfaces.remove(java.io.Externalizable.class);
      interfaces.remove(javax.ejb.SessionSynchronization.class);
      interfaces.remove(javax.ejb.TimedObject.class);
      Iterator<Class> it = interfaces.iterator();
      while (it.hasNext())
      {
         if (it.next().getName().startsWith("javax.ejb")) it.remove();
      }
      return interfaces;
   }

   public static Class getLocalHomeInterface(Container container)
   {
      Class beanClass = container.getBeanClass();
      LocalHome li = (javax.ejb.LocalHome) ((EJBContainer) container).resolveAnnotation(javax.ejb.LocalHome.class);
      if (li != null) return li.value();
      return null;
   }

   public static Class getRemoteHomeInterface(Container container)
   {
      Class beanClass = container.getBeanClass();
      RemoteHome li = (javax.ejb.RemoteHome) ((EJBContainer) container).resolveAnnotation(javax.ejb.RemoteHome.class);
      if (li != null) return li.value();
      return null;
   }

   public static boolean publishesInterface(Container container, Class businessInterface)
   {
      if (!(container instanceof SessionContainer)) return false;
      Class[] remotes = getRemoteInterfaces(container);
      if (remotes != null)
      {
         for (Class intf : remotes)
         {
            if (intf.getName().equals(businessInterface.getName())) return true;
         }
      }

      Class remoteHome = getRemoteHomeInterface(container);
      if (remoteHome != null)
      {
         if (businessInterface.getName().equals(remoteHome.getName()))
         {
            return true;
         }
      }
      Class[] locals = getLocalInterfaces(container);
      if (locals != null)
      {
         for (Class clazz : locals)
         {
            if (clazz.getName().equals(businessInterface.getName()))
            {
               return true;
            }
         }
      }
      Class localHome = getLocalHomeInterface(container);
      if (localHome != null)
      {
         if (businessInterface.getName().equals(localHome.getName()))
         {
            return true;
         }
      }

      return false;
   }

   public static String getJndiName(Container container, Class businessInterface)
   {
      if (!(container instanceof SessionContainer)) return null;
      Advisor advisor = (Advisor) container;
      Class[] remotes = getRemoteInterfaces(container);
      if (remotes != null)
      {
         for (Class clazz : remotes)
         {
            if (clazz.getName().equals(businessInterface.getName()))
            {
               RemoteBindings bindings = (RemoteBindings) advisor.resolveAnnotation(RemoteBindings.class);
               if (bindings == null)
               {
                  RemoteBinding binding = (RemoteBinding) advisor.resolveAnnotation(RemoteBinding.class);
                  if (binding == null)
                     throw new RuntimeException("RemoteBindings should not be null: " + container.getEjbName());

                  return getRemoteJndiName(container, binding);
               }
               return getRemoteJndiName(container, bindings.value()[0]);
            }
         }
      }
      Class remoteHome = getRemoteHomeInterface(container);
      if (remoteHome != null)
      {
         if (businessInterface.getName().equals(remoteHome.getName()))
         {
            return getRemoteJndiName(container) + "Home";
         }
      }
      Class[] locals = getLocalInterfaces(container);
      if (locals != null)
      {
         for (Class clazz : locals)
         {
            if (clazz.getName().equals(businessInterface.getName()))
            {
               return getLocalJndiName(container);
            }
         }
      }
      Class localHome = getLocalHomeInterface(container);
      if (localHome != null)
      {
         if (businessInterface.getName().equals(localHome.getName()))
         {
            return getLocalJndiName(container) + "Home";
         }
      }

      return null;
   }

   public static String getLocalJndiName(Container container)
   {
      return getLocalJndiName(container, true);
   }

   public static String getLocalJndiName(Container container, boolean conflictCheck)
   {
      Advisor advisor = (Advisor) container;
      LocalBinding localBinding = (LocalBinding) advisor
              .resolveAnnotation(LocalBinding.class);
      if (localBinding == null)
      {
         String name = container.getEjbName() + "/local";
         EAR ear = ((EJBContainer) container).getDeployment().getEar();
         if (ear != null) return ear.getBaseName() + "/" + name;

         if (conflictCheck)
            checkForRemoteJndiConflict(container);

         return name;
      }
      else
      {
         return localBinding.jndiBinding();
      }
   }

   private static void checkForRemoteJndiConflict(Container container)
   {
      if (((Advisor) container).resolveAnnotation(Remote.class) != null)
      {
         String remoteJndiName = getRemoteJndiName(container, false);
         String ejbName = container.getEjbName();
         if ((remoteJndiName.equals(ejbName) || remoteJndiName.startsWith(ejbName + "/")) && (!remoteJndiName.equals(ejbName + "/remote")))
            throw new javax.ejb.EJBException("Conflict between default local jndi name " + ejbName + "/local and remote jndi name " + remoteJndiName + " for ejb-name:" + ejbName + ", bean class=" + container.getBeanClass());
      }
   }

   public static Class[] getRemoteInterfaces(Container container)
   {
      Remote ri = (Remote) ((Advisor) container).resolveAnnotation(Remote.class);
      if (ri == null)
      {
         Class beanClass = container.getBeanClass();
         Class[] intfs = beanClass.getInterfaces();
         ArrayList<Class> remotes = new ArrayList<Class>();
         for (Class clazz : intfs)
         {
            if (clazz.isAnnotationPresent(Remote.class))
            {
               remotes.add(clazz);
            }
         }
         if (remotes.size() > 0)
         {
            intfs = remotes.toArray(new Class[remotes.size()]);
            ri = new RemoteImpl(intfs);
            ((Advisor) container).getAnnotations().addClassAnnotation(Remote.class, ri);
            return ri.value();
         }

         return null;
      }

      if (ri.value().length > 0) return ri.value();

      // We have an emtpy @Remote annotated bean class

      ArrayList list = getBusinessInterfaces(container.getBeanClass());
      if (list.size() == 0)
         throw new RuntimeException("Use of empty @Remote on bean class and there are no valid business interfaces");
      if (list.size() > 1)
         throw new RuntimeException("Use of empty @Remote on bean class and there are more than one default interface");
      Class[] rtn = {(Class) list.get(0)};
      ri = new RemoteImpl(rtn);
      ((EJBContainer) container).getAnnotations().addClassAnnotation(javax.ejb.Remote.class, ri);
      return rtn;
   }

   public static String getRemoteJndiName(Container container)
   {
      return getRemoteJndiName(container, true);
   }

   public static String getRemoteJndiName(Container container, boolean check)
   {
      Advisor advisor = (Advisor) container;
      RemoteBinding binding = (RemoteBinding) advisor
              .resolveAnnotation(RemoteBinding.class);

      return getRemoteJndiName(container, binding);
   }

   private static void checkForLocalJndiConflict(Container container)
   {
      if (((Advisor) container).resolveAnnotation(Local.class) != null)
      {
         String localJndiName = getLocalJndiName(container, false);
         String ejbName = container.getEjbName();
         if ((localJndiName.equals(ejbName) || localJndiName.startsWith(ejbName + "/")) && (!localJndiName.equals(ejbName + "/local")))
            throw new javax.ejb.EJBException("Conflict between default remote jndi name " + ejbName + "/remote and local jndi name " + localJndiName + " for ejb-name:" + ejbName + ", bean class=" + container.getBeanClass());

      }
   }

   public static String getRemoteJndiName(Container container, RemoteBinding binding)
   {
      return getRemoteJndiName(container, binding, true);
   }

   public static String getRemoteJndiName(Container container, RemoteBinding binding, boolean conflictCheck)
   {
      String jndiName = null;
      if (binding == null || binding.jndiBinding() == null || binding.jndiBinding().equals(""))
      {
         jndiName = getDefaultRemoteJndiName(container);

         if (conflictCheck)
            checkForLocalJndiConflict(container);
      }
      else
      {
         jndiName = binding.jndiBinding();
      }

      return jndiName;
   }

   public static String getDefaultRemoteJndiName(Container container)
   {
      String name = container.getEjbName() + "/remote";
      EAR ear = ((EJBContainer) container).getDeployment().getEar();
      if (ear != null) return ear.getBaseName() + "/" + name;
      return name;
   }

}
