/**
 * Copyright (C) 2015 Stratio (http://stratio.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.stratio.explorer.server;

import java.io.File;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;

import javax.servlet.DispatcherType;
import javax.ws.rs.core.Application;

import com.stratio.explorer.conf.ConstantsFolder;
import org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.bio.SocketConnector;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.webapp.WebAppContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.stratio.explorer.conf.ExplorerConfiguration;
import com.stratio.explorer.interpreter.InterpreterFactory;
import com.stratio.explorer.notebook.Notebook;
import com.stratio.explorer.rest.InterpreterRestApi;

import com.stratio.explorer.scheduler.SchedulerFactory;
import com.stratio.explorer.socket.ExplorerWebSocketServer;

public class ExplorerServer extends Application {
    private static final Logger LOG = LoggerFactory.getLogger(ExplorerServer.class);

    private SchedulerFactory schedulerFactory;
    public static Notebook notebook;
    static ExplorerWebSocketServer websocket;

    private InterpreterFactory replFactory;

    public static void main(String[] args) throws Exception {
        ExplorerConfiguration conf = ExplorerConfiguration.create(ConstantsFolder.CT_NAME_FILE_INTERPRETERS_CONFIGURE);
        conf.setProperty("args", args);

        int port = conf.getInt(ExplorerConfiguration.ConfVars.EXPLORER_PORT);
        final Server server = setupJettyServer(port);
        websocket = new ExplorerWebSocketServer(port + 1);

        //REST api
        final ServletContextHandler restApi = setupRestApiContextHandler();
        /** NOTE: Swagger-core is included via the web.xml in web
         * But the rest of swagger is configured here
         */
        final ServletContextHandler swagger = setupSwaggerContextHandler(port);
        //Web UI
        final WebAppContext webApp = setupWebAppContext(conf);
        final WebAppContext webAppSwagg = setupWebAppSwagger(conf);

        //Add all handlers
        ContextHandlerCollection contexts = new ContextHandlerCollection();
        contexts.setHandlers(new Handler[] { swagger, restApi, webApp, webAppSwagg });
        server.setHandler(contexts);

        websocket.start();
        LOG.info("Start explorer server");
        server.start();
        LOG.info("Started");

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override public void run() {
                LOG.info("Shutting down explorer Server ... ");
                try {
                    server.stop();
                    websocket.stop();
                } catch (Exception e) {
                    LOG.error("Error while stopping servlet container", e);
                }
                LOG.info("Bye");
            }
        });
        server.join();
    }

    private static Server setupJettyServer(int port) {
        int timeout = 1000 * 30;
        final Server server = new Server();
        SocketConnector connector = new SocketConnector();

        // Set some timeout options to make debugging easier.
        connector.setMaxIdleTime(timeout);
        connector.setSoLingerTime(-1);
        connector.setPort(port);
        server.addConnector(connector);
        return server;
    }

    private static ServletContextHandler setupRestApiContextHandler() {
        final ServletHolder cxfServletHolder = new ServletHolder(new CXFNonSpringJaxrsServlet());
        cxfServletHolder.setInitParameter("javax.ws.rs.Application", ExplorerServer.class.getName());
        cxfServletHolder.setName("rest");
        cxfServletHolder.setForcedPath("rest");

        final ServletContextHandler cxfContext = new ServletContextHandler();
        cxfContext.setSessionHandler(new SessionHandler());
        cxfContext.setContextPath("/api");
        cxfContext.addServlet(cxfServletHolder, "/*");
        cxfContext.addFilter(new FilterHolder(CorsFilter.class), "/*",
                EnumSet.allOf(DispatcherType.class));
        return cxfContext;
    }

    /**
     * Swagger core handler - Needed for the RestFul api documentation
     *
     * @return ServletContextHandler of Swagger
     */
    private static ServletContextHandler setupSwaggerContextHandler(int port) {
        // Configure Swagger-core
        final ServletHolder SwaggerServlet = new ServletHolder(
                new com.wordnik.swagger.jersey.config.JerseyJaxrsConfig());
        SwaggerServlet.setName("JerseyJaxrsConfig");
        SwaggerServlet.setInitParameter("api.version", "1.0.0");
        SwaggerServlet.setInitParameter("swagger.api.basepath", "http://localhost:" + port + "/api");
        SwaggerServlet.setInitOrder(2);

        // Setup the handler
        final ServletContextHandler handler = new ServletContextHandler();
        handler.setSessionHandler(new SessionHandler());
        handler.addServlet(SwaggerServlet, "/api-docs/*");
        return handler;
    }

    private static WebAppContext setupWebAppContext(ExplorerConfiguration conf) {
        WebAppContext webApp = new WebAppContext();
        File webapp = new File(conf.getString(ExplorerConfiguration.ConfVars.EXPLORER_WAR));
        if (webapp.isDirectory()) { // Development mode, read from FS
            webApp.setDescriptor(webapp+"/WEB-INF/web.xml");
            webApp.setResourceBase(webapp.getPath());
            webApp.setContextPath("/");
            webApp.setParentLoaderPriority(true);
        } else { //use packaged WAR
            webApp.setWar(webapp.getAbsolutePath());
        }

        ServletHolder servletHolder = new ServletHolder(new DefaultServlet());
        servletHolder.setInitParameter("cacheControl","private, max-age=0, must-revalidate");
        webApp.addServlet(servletHolder, "/*");
        return webApp;
    }

    /**
     * Handles the WebApplication for Swagger-ui
     *
     * @return WebAppContext with swagger ui context
     */
    private static WebAppContext setupWebAppSwagger(ExplorerConfiguration conf) {
        WebAppContext webApp = new WebAppContext();
        File webapp = new File(conf.getString(ExplorerConfiguration.ConfVars.EXPLORER_API_WAR));

        if (webapp.isDirectory()) {
            webApp.setResourceBase(webapp.getPath());
        } else {
            webApp.setWar(webapp.getAbsolutePath());
        }
        webApp.setContextPath("/docs");
        webApp.setParentLoaderPriority(true);
        // Bind swagger-ui to the path HOST/docs
        webApp.addServlet(new ServletHolder(new DefaultServlet()), "/docs/*");
        return webApp;
    }

    public ExplorerServer() throws Exception {
        ExplorerConfiguration conf = ExplorerConfiguration.create(ConstantsFolder.CT_NAME_FILE_INTERPRETERS_CONFIGURE);

        this.schedulerFactory = new SchedulerFactory();

        this.replFactory = new InterpreterFactory(conf);
        notebook = new Notebook(conf, schedulerFactory, replFactory, websocket);
    }

    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> classes = new HashSet<Class<?>>();
        return classes;
    }

    @Override
    public java.util.Set<java.lang.Object> getSingletons() {
        Set<Object> singletons = new HashSet<Object>();

        InterpreterRestApi interpreterApi = new InterpreterRestApi(replFactory);
        singletons.add(interpreterApi);

        return singletons;
    }

    public static ExplorerWebSocketServer getWebsocket(){
        return websocket;
    }

}