package io.insideout.stanbol.enhancer.nlp.freeling.server;

import io.insideout.stanbol.enhancer.nlp.freeling.Freeling;
import io.insideout.stanbol.enhancer.nlp.freeling.web.Constants;
import io.insideout.stanbol.enhancer.nlp.freeling.web.FreelingApplication;

import java.io.File;
import java.util.Iterator;
import java.util.ServiceLoader;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.io.FilenameUtils;
import org.apache.stanbol.enhancer.servicesapi.ContentItemFactory;
import org.apache.wink.server.internal.servlet.RestServlet;
import org.mortbay.jetty.Connector;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.nio.SelectChannelConnector;
import org.mortbay.jetty.servlet.Context;
import org.mortbay.jetty.servlet.ServletHolder;


public class Main {

    private static final String ENV_FREELING_SHARED_FOLDER = "FREELINGSHARED";
    private static final String PROPERTY_FREELING_SHARED_FOLDER = "freeling.shared";
    private static final String PROPERTY_FREELING_CONFIG_FOLDER = "freeling.config";
    
    private static final int DEFAULT_PORT = 8080;
    private static final int DEFAULT_MAX_POOL_SIZE = 10;
    private static final int DEFAULT_MIN_QUEUE_SIZE = 1;
    private static final int DEFAULT_INIT_THREADS = 1;
    
    private static final Options options;
    static {
        options = new Options();
        options.addOption("h", "help", false, "display this help and exit");
        options.addOption("p","port",true, 
            "The port for the server (default: "+DEFAULT_PORT+")");
        options.addOption("s", "shared", true,
            "The Freeling shared folder. If the `$FREELINGSHARE` nor " +
            "`freeling.shared` is present this parameter is required");
        options.addOption("c","config",true,
            "The Freeling config folder (default: `{freeling-shared}/config`");
        options.addOption("l","native-lib",true,
            "The native library file (defaults: `" + Freeling.DEFAULT_FREELING_LIB_PATH
            + "` with fallback `{freeling-shared}/" + Freeling.DEFAULT_FREELING_LIB_PATH+"`)");
        options.addOption("w","max-wait-time",true,
            "The maximum time in ms to wait for a Freeling Resource to become available"
            + "(default: "+Constants.DEFAULT_RESOURCE_WAIT_TIME+").");
        options.addOption("m","max-pool-size",true,
            "The maximum number of Analyzers created for a supported language. "
            + "This defines how manny texts of a single language can be processed "
            + "concurrently (default: "+DEFAULT_MAX_POOL_SIZE+")");
        options.addOption("q","min-queue-size",true,
            "If the pool of available Analyzers for a language becomes less that "
            + "the configured value a new Analyzer is created. The initial size "
            + "of the Analyzers pools is `{min-queue-size}+1` (default : " 
            + DEFAULT_MIN_QUEUE_SIZE+")");
        options.addOption("i","init-threads",true,
            "The size of the thread-pool used to initialize Freeling Analyzers. "
            + "Increasing this number allows to faster create additional Analyzers. "
            + "Note that concurrent creating of Analyzers may cause JVM crashes "
            + "on some systems (default : "+DEFAULT_INIT_THREADS+")");
    }
    /**
     * @param args
     * @throws Exception 
     */
    public static void main(String[] args) throws Exception {
        CommandLineParser parser = new PosixParser();
        CommandLine line = parser.parse(options, args);
        args = line.getArgs();
        if(line.hasOption('h')){
            printHelp();
            System.exit(0);
        }
        //Parse the Freeling parameter and init the Freeling instance
        String sharedFolder = System.getenv(ENV_FREELING_SHARED_FOLDER);
        sharedFolder = System.getProperty(PROPERTY_FREELING_SHARED_FOLDER, sharedFolder);
        sharedFolder = line.getOptionValue('s', sharedFolder);
        if(sharedFolder == null){
            System.err.println("The Freeling shared Folder MUST BE set! \n");
            printHelp();
            System.exit(0);
        }
        File shared = new File(sharedFolder);
        if(!shared.isDirectory()){
            System.err.println("The configured Freeling shared folder '"
                + sharedFolder + "' is not a directory!\n");
            System.exit(1);
        }
        String configFolder = FilenameUtils.concat(sharedFolder, "config");
        configFolder = System.getProperty(PROPERTY_FREELING_CONFIG_FOLDER, configFolder);
        configFolder = line.getOptionValue('c', configFolder);
        File config = new File(configFolder);
        if(!config.isDirectory()){
            System.err.println("The configured Freeling config folder '"
                + configFolder + "' is not a directory!\n");
            System.exit(1);
        }
        String nativeLib = FilenameUtils.concat(sharedFolder, Freeling.DEFAULT_FREELING_LIB_PATH);
        if(new File(Freeling.DEFAULT_FREELING_LIB_PATH).isFile()){
            nativeLib = Freeling.DEFAULT_FREELING_LIB_PATH;
        }
        nativeLib = line.getOptionValue('l', nativeLib);
        if(!new File(nativeLib).isFile()){
            System.err.println("The configured Freeling native lib '"
                    + nativeLib + "' is not a file!\n");
            System.exit(1);
        }
        Freeling freeling = new Freeling(
            config.getPath(), Freeling.DEFAULT_CONFIGURATION_FILENAME_SUFFIX, 
            shared.getPath(), nativeLib, Freeling.DEFAULT_FREELING_LOCALE, 
            getInt(line, 'i', DEFAULT_INIT_THREADS), 
            getInt(line, 'm', DEFAULT_MAX_POOL_SIZE), 
            getInt(line, 'q', DEFAULT_MIN_QUEUE_SIZE));
        
        
        //init the Jetty Server
        Server server = new Server();
        Connector con = new SelectChannelConnector();
        //we need the port
        con.setPort(getInt(line,'p',DEFAULT_PORT));
        server.addConnector(con);

        //init the Servlet and the ServletContext
        Context context = new Context(server, "/", Context.SESSIONS);
        ServletHolder holder = new ServletHolder(RestServlet.class);
        holder.setInitParameter("javax.ws.rs.Application", FreelingApplication.class.getName());
        context.addServlet(holder, "/*");
        
        //now initialise the servlet context
        context.setAttribute(Constants.SERVLET_ATTRIBUTE_CONTENT_ITEM_FACTORY, 
            lookupService(ContentItemFactory.class));
        context.setAttribute(Constants.SERVLET_ATTRIBUTE_FREELING, freeling);
        context.setAttribute(Constants.SERVLET_ATTRIBUTE_MAX_RESOURCE_WAIT_TIEM, 
            getLong(line,'w',Constants.DEFAULT_RESOURCE_WAIT_TIME));
        //Freeling
        
        server.start();
        try {
            server.join();
        }catch (InterruptedException e) {
        }
        System.err.println("Shutting down Freeling");
        freeling.close();
    }
    
    private static int getInt(CommandLine line, char option, int defaultValue){
        String value = line.getOptionValue(option);
        if(value != null){
            return Integer.parseInt(value);
        } else {
            return defaultValue;
        }
    }
    private static long getLong(CommandLine line, char option, long defaultValue){
        String value = line.getOptionValue(option);
        if(value != null){
            return Long.parseLong(value);
        } else {
            return defaultValue;
        }
    }
    
    private static <T> T lookupService(Class<T> clazz){
        ServiceLoader<T> loader = ServiceLoader.load(clazz);
        Iterator<T> services = loader.iterator();
        if(services.hasNext()){
            return services.next();
        } else {
            throw new IllegalStateException("Unable to find implemetnation for service "+clazz);
        }
    }
    
    /**
     * 
     */
    private static void printHelp() {
        HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp(
            "java -Xmx{size} -jar io.insideout.stanbol.enhancer.nlp.freeling.server-*" +
            "-jar-with-dependencies.jar [options]",
            "Indexing Commandline Utility: \n"+
            "  size:  Heap requirements depend on the dataset and the configuration.\n"+
            "         1024m should be a reasonable default.\n",
            options,
            null);
    }

}