package org.jboss.resteasy.plugins.server.undertow;

import io.undertow.Undertow;
import io.undertow.server.handlers.PathHandler;
import io.undertow.server.handlers.resource.ResourceHandler;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.DeploymentManager;
import io.undertow.servlet.api.ServletContainer;
import io.undertow.servlet.api.ServletInfo;

import org.jboss.resteasy.util.PortProvider;
import org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher;
import org.jboss.resteasy.spi.ResteasyDeployment;

import javax.servlet.ServletException;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

import static io.undertow.servlet.Servlets.servlet;

import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;


/**
 * Wrapper around Undertow to make resteasy deployments easier
 * Each ResteasyDeployment or jaxrs Application is deployed under its own web deployment (WAR)
 *
 * You may also deploy after the server has started.
 *
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
 * @version $Revision: 1 $
 */
public class UndertowJaxrsServer
{
   final PathHandler root = new PathHandler();
   final ServletContainer container = ServletContainer.Factory.newInstance();
   protected Undertow server;

   /**
    * Creates a web deployment for your ResteasyDeployent so you can set up things like security constraints
    * You'd call this method, add your servlet security constraints, then call deploy(DeploymentInfo)
    *
    * Note, only one ResteasyDeployment can be applied per DeploymentInfo
    * ResteasyServlet is mapped to mapping + "/*"
    *
    * Example:
    *
    * DeploymentInfo di = server.undertowDeployment(resteasyDeployment, "rest");
    * di.setDeploymentName("MyDeployment")
    * di.setContextRoot("root");
    * server.deploy(di);
    *
    * @param deployment
    * @param mapping resteasy.servlet.mapping.prefix
    * @return must be deployed by calling deploy(DeploymentInfo), also does not set context path or deployment name
    */
   public DeploymentInfo undertowDeployment(ResteasyDeployment deployment, String mapping)
   {
      if (mapping == null) mapping = "/";
      if (!mapping.startsWith("/")) mapping = "/" + mapping;
      if (!mapping.endsWith("/")) mapping += "/";
      mapping = mapping + "*";
      String prefix = null;
      if (!mapping.equals("/*")) prefix = mapping.substring(0, mapping.length() - 2);
      ServletInfo resteasyServlet = servlet("ResteasyServlet", HttpServlet30Dispatcher.class)
              .setAsyncSupported(true)
              .setLoadOnStartup(1)
              .addMapping(mapping);
      if (prefix != null) resteasyServlet.addInitParam("resteasy.servlet.mapping.prefix", prefix);

      return  new DeploymentInfo()
              .addServletContextAttribute(ResteasyDeployment.class.getName(), deployment)
              .addServlet(
                      resteasyServlet
                         );
   }

   /**
    * Creates a web deployment for your ResteasyDeployent so you can set up things like security constraints
    * You'd call this method, add your servlet security constraints, then call deploy(DeploymentInfo)
    *
    * Note, only one ResteasyDeployment can be applied per DeploymentInfo.  Resteasy servlet is mapped to "/*"
    *
    * @param deployment
    * @return
    */
   public DeploymentInfo undertowDeployment(ResteasyDeployment deployment)
   {
      return undertowDeployment(deployment, "/");
   }

   /**
    * Creates a web deployment for the jaxrs Application.  Will ignore any @ApplicationPath annotation.
    *
    * @param application
    * @param mapping resteasy.servlet.mapping.prefix
    * @return
    */
   public DeploymentInfo undertowDeployment(Class<? extends Application> application, String mapping)
   {
      ResteasyDeployment deployment = new ResteasyDeployment();
      deployment.setApplicationClass(application.getName());
      DeploymentInfo di = undertowDeployment(deployment, mapping);
      di.setClassLoader(application.getClassLoader());
      return di;
   }

   /**
    * Creates a web deployment for the jaxrs Application.  Will bind the resteasy.servlet.mapping.prefix
    * to @ApplicationPath if it exists, otherwise "/".
    *
    * @param application
    * @return
    */
   public DeploymentInfo undertowDeployment(Class<? extends Application> application)
   {
      ApplicationPath appPath = application.getAnnotation(ApplicationPath.class);
      String path = "/";
      if (appPath != null) path = appPath.value();
      return undertowDeployment(application, path);
   }
   
   /**
    * Maps a path prefix to a resource handler to allow serving resources other than the JAX-RS endpoints.
    * For example, this can be used for serving static resources like web pages or API documentation that might 
    * be deployed with the REST application server. 
    * 
    * @param path 
    * @param handler
    */
   public void addResourcePrefixPath(String path, ResourceHandler handler) 
   {
      root.addPrefixPath(path, handler);
   }

   /**
    * Creates a web deployment under "/"
    *
    * @param deployment
    * @return
    */
   public UndertowJaxrsServer deploy(ResteasyDeployment deployment)
   {
      return deploy(deployment, "/");
   }

   /**
    * Creates a web deployment under contextPath
    *
    * @param deployment
    * @param contextPath
    * @return
    */
   public UndertowJaxrsServer deploy(ResteasyDeployment deployment, String contextPath)
   {
      return deploy(deployment, contextPath, null, null);
   }
   
   public UndertowJaxrsServer deploy(ResteasyDeployment deployment, String contextPath, Map<String, String> contextParams, Map<String, String> initParams)
   {
      if (contextPath == null) contextPath = "/";
      if (!contextPath.startsWith("/")) contextPath = "/" + contextPath;
      DeploymentInfo builder = undertowDeployment(deployment);
      builder.setContextPath(contextPath);
      builder.setDeploymentName("Resteasy" + contextPath);
      builder.setClassLoader(deployment.getApplication().getClass().getClassLoader());
      if (contextParams != null)
      {
         for (Entry<String, String> e : contextParams.entrySet())
         {
            builder.addInitParameter(e.getKey(), e.getValue());
         }
      }  
      if (initParams != null)
      {
         ServletInfo servletInfo = builder.getServlets().get("ResteasyServlet");
         for (Entry<String, String> e : initParams.entrySet())
         {
            servletInfo.addInitParam(e.getKey(), e.getValue());
         }
      }
      return deploy(builder);
   }

   /**
    * Creates a web deployment for the jaxrs Application.  Will bind the contextPath
    * to @ApplicationPath if it exists, otherwise "/".
    *
    * @param application
    * @return
    */
   public UndertowJaxrsServer deploy(Class<? extends Application> application)
   {
      ApplicationPath appPath = application.getAnnotation(ApplicationPath.class);
      String path = "/";
      if (appPath != null) path = appPath.value();
      return deploy(application, path);
   }

   /**
    * Creates a web deployment for the jaxrs Application.  Will ignore any @ApplicationPath annotation.
    *
    * @param application
    * @param contextPath
    * @return
    */
   public UndertowJaxrsServer deploy(Class<? extends Application> application, String contextPath)
   {
      if (contextPath == null) contextPath = "/";
      if (!contextPath.startsWith("/")) contextPath = "/" + contextPath;
      ResteasyDeployment deployment = new ResteasyDeployment();
      deployment.setApplicationClass(application.getName());
      DeploymentInfo di = undertowDeployment(deployment);
      di.setClassLoader(application.getClassLoader());
      di.setContextPath(contextPath);
      di.setDeploymentName("Resteasy" + contextPath);
      return deploy(di);
   }

   /**
    * Creates a web deployment for the jaxrs Application.  Will bind the contextPath
    * to @ApplicationPath if it exists, otherwise "/".
    *
    * @param application
    * @return
    */
   public UndertowJaxrsServer deploy(Application application)
   {
      ApplicationPath appPath = application.getClass().getAnnotation(ApplicationPath.class);
      String path = "/";
      if (appPath != null) path = appPath.value();
      return deploy(application, path);
   }

   /**
    * Creates a web deployment for the jaxrs Application.  Will ignore any @ApplicationPath annotation.
    *
    * @param application
    * @param contextPath
    * @return
    */
   public UndertowJaxrsServer deploy(Application application, String contextPath)
   {
      if (contextPath == null) contextPath = "/";
      if (!contextPath.startsWith("/")) contextPath = "/" + contextPath;
      ResteasyDeployment deployment = new ResteasyDeployment();
      deployment.setApplication(application);
      DeploymentInfo di = undertowDeployment(deployment);
      di.setClassLoader(application.getClass().getClassLoader());
      di.setContextPath(contextPath);
      di.setDeploymentName("Resteasy" + contextPath);
      return deploy(di);
   }


   /**
    * Adds an arbitrary web deployment to underlying Undertow server.  This is for your own deployments
    *
    * @param builder
    * @return
    */
   public UndertowJaxrsServer deploy(DeploymentInfo builder)
   {
      DeploymentManager manager = container.addDeployment(builder);
      manager.deploy();
      try
      {
         root.addPrefixPath(builder.getContextPath(), manager.start());
      }
      catch (ServletException e)
      {
         throw new RuntimeException(e);
      }
      return this;
   }

   public UndertowJaxrsServer start(Undertow.Builder builder)
   {
      server = builder.setHandler(root).build();
      server.start();
      return this;
   }

   public UndertowJaxrsServer start()
   {
      server = Undertow.builder()
              .addHttpListener(PortProvider.getPort(), "localhost")
              .setHandler(root)
              .build();
      server.start();
      return this;
   }

   public void stop()
   {
      server.stop();
   }

}
