package com.proofpoint.jetty;

import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.servlet.GuiceFilter;
import org.eclipse.jetty.http.security.Constraint;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterMapping;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlets.GzipFilter;
import org.eclipse.jetty.util.thread.QueuedThreadPool;

import javax.annotation.Nullable;
import javax.management.MBeanServer;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;

/**
 * Provides an instance of a Jetty server ready to be configured with
 * com.google.inject.servlet.ServletModule
 */
public class JettyProvider
        implements Provider<Server>
{
    private MBeanServer mbeanServer;
    private JettyConfig config;
    private LoginService loginService;

    @Inject
    public JettyProvider(JettyConfig config)
    {
        this.config = config;
    }

    @Inject(optional = true)
    public void setMBeanServer(MBeanServer server)
    {
        mbeanServer = server;
    }

    @Inject(optional = true)
    public void setLoginService(@Nullable LoginService loginService)
    {
        this.loginService = loginService;
    }

    public Server get()
    {
        String ip = config.getServerIp();
        
        Server server = new Server();

        if (mbeanServer != null) {
            // export jmx mbeans if a server was provided
            MBeanContainer mbeanContainer = new MBeanContainer(mbeanServer);
            server.getContainer().addEventListener(mbeanContainer);
        }

        // set up NIO-based HTTP connector
        SelectChannelConnector connector = new SelectChannelConnector();
        connector.setPort(config.getHttpPort());
        connector.setStatsOn(true);
        if (ip != null) {
            connector.setHost(ip);
        }
        
        server.addConnector(connector);

        if (config.isHttpsEnabled()) {
            SslSelectChannelConnector sslConnector = new SslSelectChannelConnector();
            sslConnector.setPort(config.getHttpsPort());
            sslConnector.setStatsOn(true);
            sslConnector.setKeystore(config.getKeystorePath());
            sslConnector.setPassword(config.getKeystorePassword());
            if (ip != null) {
                sslConnector.setHost(ip);
            }

            server.addConnector(sslConnector);
        }

        QueuedThreadPool threadpool = new QueuedThreadPool(config.getMaxThreads());
        threadpool.setMinThreads(config.getMinThreads());
        threadpool.setMaxIdleTimeMs(config.getThreadMaxIdleTime());
        server.setThreadPool(threadpool);

        /**
         * structure is:
         *
         * server
         *    |--- statistics handler
         *           |--- context handler
         *           |       |--- gzip response filter
         *           |       |--- gzip request filter
         *           |       |--- security handler
         *           |       |--- guice filter
         *           |       |--- default servlet (no op, as all requests are handled by filter)
         *           |--- log handler
         */
        HandlerCollection handlers = new HandlerCollection();
        handlers.addHandler(getContextHandler());
        try {
            handlers.addHandler(getLogHandler());
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }

        StatisticsHandler statsHandler = new StatisticsHandler();
        statsHandler.setHandler(handlers);
        server.setHandler(statsHandler);

        return server;
    }

    private ServletContextHandler getContextHandler()
    {
        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
        context.addFilter(GzipFilter.class, "/*", FilterMapping.DEFAULT);
        context.addFilter(GZipRequestFilter.class, "/*", FilterMapping.DEFAULT);
        context.addFilter(GuiceFilter.class, "/*", FilterMapping.DEFAULT);
        context.addServlet(DefaultServlet.class, "/");

        if (loginService != null) {
            context.setSecurityHandler(getSecurityHandler());
        }

        return context;
    }

    private SecurityHandler getSecurityHandler()
    {
        Constraint constraint = new Constraint();
        constraint.setAuthenticate(false);

        ConstraintMapping constraintMapping = new ConstraintMapping();
        constraintMapping.setConstraint(constraint);
        constraintMapping.setPathSpec("/*");

        ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
        securityHandler.setLoginService(loginService);

        // TODO: support for other auth schemes (digest, etc)
        securityHandler.setAuthenticator(new BasicAuthenticator());
        securityHandler.setConstraintMappings(Arrays.asList(constraintMapping));
        return securityHandler;
    }

    private RequestLogHandler getLogHandler()
            throws IOException
    {
        // TODO: use custom (more easily-parseable) format
        // TODO: make retention & rotation configurable
        RequestLogHandler logHandler = new RequestLogHandler();

        File logPath = new File(config.getLogPath()).getParentFile();
        if (!logPath.mkdirs() && (!logPath.exists() || logPath.exists() && !logPath.isDirectory())) {
            throw new IOException(String.format("Cannot create %s or path exists but is not a directory", logPath.getAbsolutePath()));
        }

        RequestLog requestLog = new DelimitedRequestLog(config.getLogPath(), config.getLogRetainDays());
        logHandler.setRequestLog(requestLog);

        return logHandler;
    }
}

@@ -1301,16 +1301,56 @@
 rrays;%0A%0A
+import static java.lang.String.format;%0A%0A
 /**%0A * P
@@ -5988,20 +5988,20 @@
 File log
-Path
+File
  = new F
@@ -6028,89 +6028,253 @@
 h())
-.getParentFile();%0A        if (!logPath.mkdirs() && (!logPath.exists() %7C%7C 
+;%0A        if (logFile.exists() && !logFile.isFile()) %7B%0A            throw new IOException(format(%22Log path %25s exists but is not a file%22, logFile.getAbsolutePath()));%0A        %7D%0A%0A        File logPath = logFile.getParentFile();%0A        if (!
 logPath.
 exis
@@ -6269,21 +6269,21 @@
 logPath.
-exist
+mkdir
 s() && !
@@ -6294,21 +6294,15 @@
 ath.
-isDirectory()
+exists(
 )) %7B
@@ -6336,23 +6336,16 @@
 ception(
-String.
 format(%22
@@ -6365,45 +6365,39 @@
  %25s 
-or
+and
  path 
-exists but is not a directory
+does not already exist
 %22, l
@@ -6427,32 +6427,33 @@
 )));%0A        %7D%0A%0A
+%0A
         RequestL

make sure log config path is a file and that parent dir can be created if the file does not exist