package org.jboss.resteasy.integration.deployers;

import org.jboss.deployers.spi.DeploymentException;
import org.jboss.deployers.spi.deployer.DeploymentStages;
import org.jboss.deployers.spi.deployer.helpers.AbstractRealDeployer;
import org.jboss.deployers.structure.spi.DeploymentUnit;
import org.jboss.logging.Logger;
import org.jboss.mcann.AnnotationRepository;
import org.jboss.mcann.Element;
import org.jboss.metadata.javaee.spec.ParamValueMetaData;
import org.jboss.metadata.web.jboss.JBossWebMetaData;
import org.jboss.metadata.web.spec.FilterMetaData;
import org.jboss.metadata.web.spec.ServletMetaData;
import org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher;
import org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrapClasses;
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;

import javax.ws.rs.Path;
import javax.ws.rs.core.Application;
import javax.ws.rs.ext.Provider;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
 * @version $Revision: 1 $
 */
public class ResteasyScannerDeployer extends AbstractRealDeployer
{
   private static final Logger LOGGER = Logger.getLogger(ResteasyScannerDeployer.class);
   public static final Set<String> BOOT_CLASSES = new HashSet<String>();

   static
   {
      for (String clazz : ResteasyBootstrapClasses.BOOTSTRAP_CLASSES)
      {
         BOOT_CLASSES.add(clazz);
      }
   }

   public ResteasyScannerDeployer()
   {
      super();

      addRequiredInput(JBossWebMetaData.class);
      addInput(AnnotationRepository.class);
      addOutput(JBossWebMetaData.class);
      addOutput(ResteasyDeploymentData.class);
      setStage(DeploymentStages.PRE_REAL); // TODO -- right stage?
   }

   protected void internalDeploy(DeploymentUnit du) throws DeploymentException
   {
      JBossWebMetaData webdata = du.getAttachment(JBossWebMetaData.class);
      boolean applicationClassDeployed = deployApplicationClass(du, webdata);

      // If an Application class is present, we assume the user wants to totally control deployment
      if (applicationClassDeployed) return;

      scan(du, webdata);

   }

   /**
    * If any servlet/filter classes are declared, then we probably don't want to scan.
    *
    * @param du
    * @param webdata
    * @return
    * @throws DeploymentException
    */
   protected boolean scannable(DeploymentUnit du, JBossWebMetaData webdata) throws DeploymentException
   {
      ClassLoader loader = du.getClassLoader();
      if (webdata.getServlets() != null)
      {
         for (ServletMetaData servlet : webdata.getServlets())
         {
            String servletClass = servlet.getServletClass();
            if (BOOT_CLASSES.contains(servletClass)) return false;
         }
      }
      if (webdata.getFilters() != null)
      {
         for (FilterMetaData filter : webdata.getFilters())
         {
            if (BOOT_CLASSES.contains(filter.getFilterClass())) return false;
         }
      }
      return true;

   }

   protected boolean deployApplicationClass(DeploymentUnit du, JBossWebMetaData webdata) throws DeploymentException
   {
      ClassLoader loader = du.getClassLoader();
      if (webdata.getServlets() == null) return false;

      for (ServletMetaData servlet : webdata.getServlets())
      {
         String servletClass = servlet.getServletClass();
         if (servletClass == null) continue;
         Class clazz = null;
         try
         {
            clazz = loader.loadClass(servletClass);
         }
         catch (ClassNotFoundException e)
         {
            throw new DeploymentException(e);
         }
         if (Application.class.isAssignableFrom(clazz))
         {
            servlet.setServletClass(HttpServletDispatcher.class.getName());
            ParamValueMetaData param = new ParamValueMetaData();
            param.setParamName("javax.ws.rs.Application");
            param.setParamValue(servletClass);
            List<ParamValueMetaData> params = servlet.getInitParam();
            if (params == null)
            {
               params = new ArrayList<ParamValueMetaData>();
               servlet.setInitParam(params);
            }
            params.add(param);
            return true;
         }
      }
      return false;

   }

   protected void scan(DeploymentUnit du, JBossWebMetaData webdata)
           throws DeploymentException
   {
      ResteasyDeploymentData scanningData = new ResteasyDeploymentData();

      // If there is a resteasy boot class in web.xml, then the default should not be to scan
      if (scannable(du, webdata) && !webdata.isMetadataComplete())
      {
         scanningData.setScanAll(true);
         scanningData.setScanProviders(true);
         scanningData.setScanResources(true);
      }

      // check resteasy configuration flags


      List<ParamValueMetaData> contextParams = webdata.getContextParams();
      boolean hasResourceParam = false;
      boolean hasProviderParam = false;
      boolean hasJndiParam = false;
      if (contextParams != null)
      {
         for (ParamValueMetaData param : contextParams)
         {
            if (param.getParamName().equals(ResteasyContextParameters.RESTEASY_SCAN))
            {
               scanningData.setScanAll(Boolean.valueOf(param.getParamValue()));
            }
            else if (param.getParamName().equals(ResteasyContextParameters.RESTEASY_SCAN_PROVIDERS))
            {
               scanningData.setScanProviders(Boolean.valueOf(param.getParamValue()));
            }
            else if (param.getParamName().equals(ResteasyContextParameters.RESTEASY_SCAN_RESOURCES))
            {
               scanningData.setScanResources(Boolean.valueOf(param.getParamValue()));
            }
            else if (param.getParamName().equals(ResteasyContextParameters.RESTEASY_RESOURCES))
            {
               hasResourceParam = true;
            }
            else if (param.getParamName().equals(ResteasyContextParameters.RESTEASY_PROVIDERS))
            {
               hasProviderParam = true;
            }
            else if (param.getParamName().equals(ResteasyContextParameters.RESTEASY_JNDI_RESOURCES))
            {
               hasJndiParam = true;
            }
         }
      }

      if (!scanningData.shouldScan())
      {
         if (hasResourceParam || hasProviderParam || hasJndiParam)
         {
            scanningData.createDispatcher();
            du.addAttachment(ResteasyDeploymentData.class, scanningData);
         }
         return;
      }

      du.addAttachment(ResteasyDeploymentData.class, scanningData);

      AnnotationRepository env = du.getAttachment(AnnotationRepository.class);
      if (env == null)
      {
         LOGGER.debug("Expecting AnnotationRepository class for scanning WAR for JAX-RS classes");
         return;
      }

      Set<Element<Path, Class<?>>> resources = null;
      Set<Element<Provider, Class<?>>> providers = null;
      if (scanningData.isScanResources())
      {
         resources = env.classIsAnnotatedWith(Path.class);
      }
      if (scanningData.isScanProviders())
      {
         providers = env.classIsAnnotatedWith(Provider.class);
      }

      if ((resources == null || resources.isEmpty()) && (providers == null || providers.isEmpty())) return;

      if (resources != null)
      {
         for (Element e : resources)
         {
            if (e.getOwner().isInterface()) continue;
            scanningData.getResources().add(e.getOwnerClassName());
         }
      }
      if (providers != null)
      {
         for (Element e : providers)
         {
            if (e.getOwner().isInterface()) continue;
            scanningData.getResources().add(e.getOwnerClassName());
         }
      }
   }

}
