/* 
 * JSweet transpiler - http://www.jsweet.org
 * Copyright (C) 2015 CINCHEO SAS <renaud.pawlak@cincheo.fr>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *  
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package org.jsweet;

import static org.jsweet.transpiler.TranspilationHandler.OUTPUT_LOGGER;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jsweet.transpiler.EcmaScriptComplianceLevel;
import org.jsweet.transpiler.JSweetFactory;
import org.jsweet.transpiler.JSweetOptions;
import org.jsweet.transpiler.JSweetProblem;
import org.jsweet.transpiler.JSweetTranspiler;
import org.jsweet.transpiler.ModuleKind;
import org.jsweet.transpiler.ModuleResolution;
import org.jsweet.transpiler.SourceFile;
import org.jsweet.transpiler.util.ConsoleTranspilationHandler;
import org.jsweet.transpiler.util.ErrorCountTranspilationHandler;
import org.jsweet.transpiler.util.ProcessUtil;
import org.jsweet.transpiler.util.Util;

import com.martiansoftware.jsap.FlaggedOption;
import com.martiansoftware.jsap.JSAP;
import com.martiansoftware.jsap.JSAPException;
import com.martiansoftware.jsap.JSAPResult;
import com.martiansoftware.jsap.Switch;
import com.martiansoftware.jsap.stringparsers.EnumeratedStringParser;
import com.martiansoftware.jsap.stringparsers.FileStringParser;

/**
 * The command line launcher for the JSweet transpiler.
 *
 * <pre>
 Command line options:
  [-h|--help]

  [-w|--watch]
        Start a process that watches the input directories for changes and
        re-run transpilation on-the-fly.

  [-v|--verbose]
        Turn on general information logging (INFO LEVEL)
        
  [-V|--veryVerbose]
        Turn on all levels of logging

  [--encoding <encoding>]
        Force the Java compiler to use a specific encoding (UTF-8, UTF-16, ...).
        (default: UTF-8)

  [--outEncoding <encoding>]
        Force the generated TypeScript output code for the given encoding (UTF-8, UTF-16, ...).
        (default: UTF-8)

  [--jdkHome <jdkHome>]
        Set the JDK home directory to be used to find the Java compiler. If not
        set, the transpiler will try to use the JAVA_HOME environment variable.
        Note that the expected JDK version is greater or equals to version 8.

  (-i|--input) input1:input2:...:inputN 
        An input directory (or column-separated input directories) containing
        Java files to be transpiled. Java files will be recursively looked up in
        sub-directories. Inclusion and exclusion patterns can be defined with
        the 'includes' and 'excludes' options.

  (--extraInput) input1:input2:...:inputN 
        An input directory (or column-separated input directories) containing
        Java source files to help the tranpilation (typically for libraries).
        Files in these directories will not generate any corresponding TS files 
        but will help resolving various generation issues (such as default 
        methods, tricking overloading cases, ...). 

  [--includes includes1:includes2:...:includesN ]
        A column-separated list of expressions matching files to be included
        (relatively to the input directory).

  [--excludes excludes1:excludes2:...:excludesN ]
        A column-separated list of expressions matching files to be excluded
        (relatively to the input directory).

  [(-d|--defInput) defInput1:defInput2:...:defInputN ]
        An input directory (or column-separated input directories) containing
        TypeScript definition files (*.d.ts) to be used for transpilation.
        Definition files will be recursively looked up in sub-diredctories.

  [--noRootDirectories]
        Skip the root directories (i.e. packages annotated with
        &#64;jsweet.lang.Root) so that the generated file hierarchy starts at the
        root directories rather than including the entire directory structure.

  [--tsout <tsout>]
        Specify where to place generated TypeScript files. (default: .ts)

  [(-o|--jsout) <jsout>]
        Specify where to place generated JavaScript files (ignored if jsFile is
        specified). (default: js)

  [--disableSinglePrecisionFloats]
        By default, for a target version >=ES5, JSweet will force Java floats to
        be mapped to JavaScript numbers that will be constrained with ES5
        Math.fround function. If this option is true, then the calls to
        Math.fround are erased and the generated program will use the JavaScript
        default precision (double precision).

  [--tsOnly]
        Do not compile the TypeScript output (let an external TypeScript
        compiler do so).
  
  [--ignoreDefinitions]
        Ignore definitions from def.* packages, so that they are not generated
        in d.ts definition files. If this option is not set, the transpiler
        generates d.ts definition files in the directory given by the tsout
        option.

  [--ignoreJavaErrors]
        Ignore Java compilation errors. Do not use unless you know what you are 
        doing.

  [--declaration]
        Generate the d.ts files along with the js files, so that other programs
        can use them to compile.

  [--dtsout <dtsout>]
        Specify where to place generated d.ts files when the declaration option
        is set (by default, d.ts files are generated in the JavaScript output
        directory - next to the corresponding js files).

  [--candiesJsOut <candiesJsOut>]
        Specify where to place extracted JavaScript files from candies.
        (default: js/candies)

  [--sourceRoot <sourceRoot>]
        Specify the location where debugger should locate Java files instead of
        source locations. Use this flag if the sources will be located at
        run-time in a different location than that at design-time. The location
        specified will be embedded in the sourceMap to direct the debugger where
        the source files will be located.

  [--classpath <classpath>]
        The JSweet transpilation classpath (candy jars). This classpath should
        at least contain the core candy.

  [(-m|--module) <module>]
        The module kind (none, commonjs, amd, system, umd, es2015). (default:
        none)

  [--moduleResolution <moduleResolution>]
        The module resolution strategy (classic, node). (default: classic)

  [-b|--bundle]
        Bundle up all the generated code in a single file, which can be used in
        the browser. The bundle files are called 'bundle.ts', 'bundle.d.ts', or
        'bundle.js' depending on the kind of generated code. NOTE: bundles are
        not compatible with any module kind other than 'none'.

  [(-f|--factoryClassName) <factoryClassName>]
        Use the given factory to tune the default transpiler behavior.

  [--sourceMap]
        Generate source map files for the Java files, so that it is possible to
        debug Java files directly with a debugger that supports source maps
        (most JavaScript debuggers).

  [--enableAssertions]
        Java 'assert' statements are transpiled as runtime JavaScript checks.

  [--header <header>]
        A file that contains a header to be written at the beginning of each
        generated file. If left unspecified, JSweet will generate a default
        header.

  [--workingDir <workingDir>]
        The directory JSweet uses to store temporary files such as extracted
        candies. JSweet uses '.jsweet' if left unspecified.

  [--targetVersion <targetVersion>]
        The EcmaScript target (JavaScript) version. Possible values: [ES3, ES5,
        ES6] (default: ES3)

  [--extraSystemPath <extraSystemPath>]
        Allow an extra path to be added to the system path.
        
  [--disableStaticsLazyInitialization]
        Do not generate lazy initialization code of static fields that is meant 
        to emulate the Java behavior. When disables, the code is more readable 
        but it may result into runtime static initialization issues (cross-class
        static dependencies).
 * </pre>
 * 
 * @author Renaud Pawlak
 */
public class JSweetCommandLineLauncher {

	private static final Logger logger = Logger.getLogger(JSweetCommandLineLauncher.class);

	private static int errorCount = 0;

	private static Pattern toPattern(String expression) {
		if (!expression.contains("*") && !expression.contains(".")) {
			expression += "*";
		}
		return Pattern.compile(expression.replace(".", "\\.").replace("*", ".*"));
	}

	private JSweetCommandLineLauncher() {
	}

	/**
	 * JSweet transpiler command line entry point. To use the JSweet transpiler
	 * from Java, see {@link #transpileWithArgs(String[])} or
	 * {@link JSweetTranspiler}.
	 */
	public static void main(String[] args) {
		System.exit(transpileWithArgs(args));
	}

	/**
	 * API entry point with command line arguments (same as the main entry point
	 * but returns error codes instead of exiting the VM).
	 */
	public static int transpileWithArgs(String[] args) {
		try {
			JSAP jsapSpec = defineArgs();
			JSAPResult jsapArgs = parseArgs(jsapSpec, args);

			if (!jsapArgs.success()) {
				printUsage(jsapSpec);
				return -1;
			}

			if (jsapArgs.getBoolean("help")) {
				printUsage(jsapSpec);
			}

			LogManager.getLogger("org.jsweet").setLevel(Level.WARN);

			if (jsapArgs.getBoolean("verbose")) {
				LogManager.getLogger("org.jsweet").setLevel(Level.INFO);
			}

			if (jsapArgs.getBoolean("veryVerbose")) {
				LogManager.getLogger("org.jsweet").setLevel(Level.ALL);
			}

			JSweetConfig.initClassPath(jsapArgs.getString("jdkHome"));

			JSweetTranspilationTask transpilationTask = new JSweetTranspilationTask(jsapArgs);
			transpilationTask.run();
			if (jsapArgs.getBoolean("watch")) {
				new JSweetFileWatcher(transpilationTask).execute();
			}

		} catch (Throwable t) {
			t.printStackTrace();
			return 1;
		}
		return errorCount > 0 ? 1 : 0;

	}

	private static JSAP defineArgs() throws JSAPException {
		// Verbose output
		JSAP jsap = new JSAP();
		Switch switchArg;
		FlaggedOption optionArg;

		// Help
		switchArg = new Switch("help");
		switchArg.setShortFlag('h');
		switchArg.setLongFlag("help");
		switchArg.setDefault("false");
		jsap.registerParameter(switchArg);

		// Watch
		switchArg = new Switch("watch");
		switchArg.setShortFlag('w');
		switchArg.setLongFlag("watch");
		switchArg.setDefault("false");
		switchArg.setHelp(
				"Start a process that watches the input directories for changes and re-run transpilation on-the-fly.");
		jsap.registerParameter(switchArg);

		// Verbose
		switchArg = new Switch("verbose");
		switchArg.setLongFlag("verbose");
		switchArg.setShortFlag('v');
		switchArg.setHelp("Turn on general information logging (INFO LEVEL)");
		switchArg.setDefault("false");
		jsap.registerParameter(switchArg);

		switchArg = new Switch("veryVerbose");
		switchArg.setLongFlag("veryVerbose");
		switchArg.setShortFlag('V');
		switchArg.setHelp("Turn on all levels of logging.");
		switchArg.setDefault("false");
		jsap.registerParameter(switchArg);

		// Java compiler's encoding
		optionArg = new FlaggedOption(JSweetOptions.encoding);
		optionArg.setLongFlag(JSweetOptions.encoding);
		optionArg.setStringParser(JSAP.STRING_PARSER);
		optionArg.setRequired(false);
		optionArg.setDefault("UTF-8");
		optionArg.setHelp("Force the Java compiler to use a specific encoding (UTF-8, UTF-16, ...).");
		jsap.registerParameter(optionArg);

		// Output encoding
		optionArg = new FlaggedOption(JSweetOptions.outEncoding);
		optionArg.setLongFlag(JSweetOptions.outEncoding);
		optionArg.setStringParser(JSAP.STRING_PARSER);
		optionArg.setRequired(false);
		optionArg.setDefault("UTF-8");
		optionArg.setHelp("Force the generated TypeScript output code for the given encoding (UTF-8, UTF-16, ...).");
		jsap.registerParameter(optionArg);
		
		// JDK home directory
		optionArg = new FlaggedOption("jdkHome");
		optionArg.setLongFlag("jdkHome");
		optionArg.setStringParser(JSAP.STRING_PARSER);
		optionArg.setRequired(false);
		optionArg.setHelp(
				"Set the JDK home directory to be used to find the Java compiler. If not set, the transpiler will try to use the JAVA_HOME environment variable. Note that the expected JDK version is greater or equals to version 8.");
		jsap.registerParameter(optionArg);

		// Input directories
		optionArg = new FlaggedOption("input");
		optionArg.setShortFlag('i');
		optionArg.setLongFlag("input");
		optionArg.setList(true);
		optionArg.setStringParser(FileStringParser.getParser());
		optionArg.setListSeparator(File.pathSeparatorChar);
		optionArg.setRequired(true);
		optionArg.setHelp(
				"An input directory (or column-separated input directories) containing Java files to be transpiled. Java files will be recursively looked up in sub-directories. Inclusion and exclusion patterns can be defined with the 'includes' and 'excludes' options.");
		jsap.registerParameter(optionArg);

		// Extra input directories
		optionArg = new FlaggedOption("extraInput");
		optionArg.setLongFlag("extraInput");
		optionArg.setList(true);
		optionArg.setStringParser(FileStringParser.getParser());
		optionArg.setListSeparator(File.pathSeparatorChar);
		optionArg.setHelp(
				"An input directory (or column-separated input directories) containing Java source files to help the tranpilation (typically for libraries). Files in these directories will not generate any corresponding TS files but will help resolving various generation issues (such as default methods, tricking overloading cases, ...).");
		jsap.registerParameter(optionArg);

		// Included files
		optionArg = new FlaggedOption("includes");
		optionArg.setLongFlag("includes");
		optionArg.setList(true);
		optionArg.setListSeparator(File.pathSeparatorChar);
		optionArg.setHelp(
				"A column-separated list of expressions matching files to be included (relatively to the input directory).");
		jsap.registerParameter(optionArg);

		// Excluded files
		optionArg = new FlaggedOption("excludes");
		optionArg.setLongFlag("excludes");
		optionArg.setList(true);
		optionArg.setListSeparator(File.pathSeparatorChar);
		optionArg.setHelp(
				"A column-separated list of expressions matching files to be excluded (relatively to the input directory).");
		jsap.registerParameter(optionArg);

		// Definition directories
		optionArg = new FlaggedOption(JSweetOptions.defInput);
		optionArg.setShortFlag('d');
		optionArg.setLongFlag(JSweetOptions.defInput);
		optionArg.setList(true);
		optionArg.setStringParser(FileStringParser.getParser());
		optionArg.setListSeparator(File.pathSeparatorChar);
		optionArg.setRequired(false);
		optionArg.setHelp(
				"An input directory (or column-separated input directories) containing TypeScript definition files (*.d.ts) to be used for transpilation. Definition files will be recursively looked up in sub-diredctories.");
		jsap.registerParameter(optionArg);

		// Skip empty root dirs
		switchArg = new Switch(JSweetOptions.noRootDirectories);
		switchArg.setLongFlag(JSweetOptions.noRootDirectories);
		switchArg.setHelp(
				"Skip the root directories (i.e. packages annotated with @jsweet.lang.Root) so that the generated file hierarchy starts at the root directories rather than including the entire directory structure.");
		switchArg.setDefault("false");
		jsap.registerParameter(switchArg);

		// TypeScript output directory
		optionArg = new FlaggedOption(JSweetOptions.tsout);
		optionArg.setLongFlag(JSweetOptions.tsout);
		optionArg.setDefault(".ts");
		optionArg.setHelp("Specify where to place generated TypeScript files.");
		optionArg.setStringParser(FileStringParser.getParser());
		optionArg.setRequired(false);
		jsap.registerParameter(optionArg);

		// JavaScript output directory
		optionArg = new FlaggedOption(JSweetOptions.jsout);
		optionArg.setShortFlag('o');
		optionArg.setLongFlag(JSweetOptions.jsout);
		optionArg.setDefault("js");
		optionArg.setHelp("Specify where to place generated JavaScript files (ignored if jsFile is specified).");
		optionArg.setStringParser(FileStringParser.getParser());
		optionArg.setRequired(false);
		jsap.registerParameter(optionArg);

		// Disable single precision floats
		switchArg = new Switch(JSweetOptions.disableSinglePrecisionFloats);
		switchArg.setLongFlag(JSweetOptions.disableSinglePrecisionFloats);
		switchArg.setHelp(
				"By default, for a target version >=ES5, JSweet will force Java floats to be mapped to JavaScript numbers that will be constrained with ES5 Math.fround function. If this option is true, then the calls to Math.fround are erased and the generated program will use the JavaScript default precision (double precision).");
		jsap.registerParameter(switchArg);

        // Transients as non-enumerable properties
        switchArg = new Switch(JSweetOptions.nonEnumerableTransients);
        switchArg.setLongFlag(JSweetOptions.nonEnumerableTransients);
        switchArg.setHelp(
                "Generate Java transient fields as non-enumerable JavaScript properties..");
        jsap.registerParameter(switchArg);
		
		// Do not generate JavaScript
		switchArg = new Switch(JSweetOptions.tsOnly);
		switchArg.setLongFlag(JSweetOptions.tsOnly);
		switchArg.setHelp("Do not compile the TypeScript output (let an external TypeScript compiler do so).");
		jsap.registerParameter(switchArg);

		// Do not generate d.ts files that correspond to def.* packages
		switchArg = new Switch(JSweetOptions.ignoreDefinitions);
		switchArg.setLongFlag(JSweetOptions.ignoreDefinitions);
		switchArg.setHelp(
				"Ignore definitions from def.* packages, so that they are not generated in d.ts definition files. If this option is not set, the transpiler generates d.ts definition files in the directory given by the tsout option.");
		jsap.registerParameter(switchArg);

		switchArg = new Switch(JSweetOptions.ignoreJavaErrors);
		switchArg.setLongFlag(JSweetOptions.ignoreJavaErrors);
		switchArg.setHelp(
				"Ignore Java compilation errors. Do not use unless you know what you are doing.");
		jsap.registerParameter(switchArg);
		
		// Generates declarations
		switchArg = new Switch(JSweetOptions.declaration);
		switchArg.setLongFlag(JSweetOptions.declaration);
		switchArg.setHelp(
				"Generate the d.ts files along with the js files, so that other programs can use them to compile.");
		jsap.registerParameter(switchArg);

		// Declarations output directory
		optionArg = new FlaggedOption(JSweetOptions.dtsout);
		optionArg.setLongFlag(JSweetOptions.dtsout);
		optionArg.setHelp(
				"Specify where to place generated d.ts files when the declaration option is set (by default, d.ts files are generated in the JavaScript output directory - next to the corresponding js files).");
		optionArg.setStringParser(FileStringParser.getParser());
		optionArg.setRequired(false);
		jsap.registerParameter(optionArg);

		// Candies javascript output directory
		optionArg = new FlaggedOption(JSweetOptions.candiesJsOut);
		optionArg.setLongFlag(JSweetOptions.candiesJsOut);
		optionArg.setDefault("js/candies");
		optionArg.setHelp("Specify where to place extracted JavaScript files from candies.");
		optionArg.setStringParser(FileStringParser.getParser());
		optionArg.setRequired(false);
		jsap.registerParameter(optionArg);

		// Source root directory for source maps
		optionArg = new FlaggedOption(JSweetOptions.sourceRoot);
		optionArg.setLongFlag(JSweetOptions.sourceRoot);
		optionArg.setHelp(
				"Specify the location where debugger should locate Java files instead of source locations. Use this flag if the sources will be located at run-time in a different location than that at design-time. The location specified will be embedded in the sourceMap to direct the debugger where the source files will be located.");
		optionArg.setStringParser(FileStringParser.getParser());
		optionArg.setRequired(false);
		jsap.registerParameter(optionArg);

		// Classpath
		optionArg = new FlaggedOption(JSweetOptions.classpath);
		optionArg.setLongFlag(JSweetOptions.classpath);
		optionArg.setHelp(
				"The JSweet transpilation classpath (candy jars). This classpath should at least contain the core candy.");
		optionArg.setStringParser(JSAP.STRING_PARSER);
		optionArg.setRequired(false);
		jsap.registerParameter(optionArg);

		// Module
		optionArg = new FlaggedOption(JSweetOptions.module);
		optionArg.setLongFlag(JSweetOptions.module);
		optionArg.setShortFlag('m');
		optionArg.setDefault("none");
		optionArg.setHelp("The module kind (none, commonjs, amd, system, umd, es2015).");
		optionArg.setStringParser(EnumeratedStringParser.getParser("none;commonjs;amd;system;umd;es2015"));
		optionArg.setRequired(false);
		jsap.registerParameter(optionArg);

		// Module resolution
		optionArg = new FlaggedOption(JSweetOptions.moduleResolution);
		optionArg.setLongFlag(JSweetOptions.moduleResolution);
		optionArg.setDefault(ModuleResolution.classic.name());
		optionArg.setHelp("The module resolution strategy (classic, node).");
		optionArg.setStringParser(EnumeratedStringParser.getParser("classic;node"));
		optionArg.setRequired(false);
		jsap.registerParameter(optionArg);

		// Bundle
		switchArg = new Switch(JSweetOptions.bundle);
		switchArg.setLongFlag(JSweetOptions.bundle);
		switchArg.setShortFlag('b');
		switchArg.setHelp(
				"Bundle up all the generated code in a single file, which can be used in the browser. The bundle files are called 'bundle.ts', 'bundle.d.ts', or 'bundle.js' depending on the kind of generated code. NOTE: bundles are not compatible with any module kind other than 'none'.");
		switchArg.setDefault("false");
		jsap.registerParameter(switchArg);

		// Factory class name
		optionArg = new FlaggedOption("factoryClassName");
		optionArg.setLongFlag("factoryClassName");
		optionArg.setShortFlag('f');
		optionArg.setHelp("Use the given factory to tune the default transpiler behavior.");
		optionArg.setStringParser(JSAP.STRING_PARSER);
		optionArg.setRequired(false);
		jsap.registerParameter(optionArg);

		// // Adapters
		// optionArg = new FlaggedOption("adapters");
		// optionArg.setLongFlag("adapters");
		// optionArg.setList(true);
		// optionArg.setStringParser(JSAP.STRING_PARSER);
		// optionArg.setListSeparator(':');
		// optionArg.setRequired(false);
		// optionArg.setHelp(
		// "A column-separated list of adapter class names (fully qualified) to
		// be used to extend the transpiler behavior. As the name suggests, an
		// adapter is an object that adapts the TypeScript default printer in
		// order to tune the generated code. All the adapter classes must be
		// accessible within the 'jsweet_extension' directory to be created at
		// the root of the transpiled project. The adapter classes can be
		// provided as Java class files (*.class) or Java source files (*.java).
		// In the latter case, they will be on-the-fly compiled by JSweet.");
		// jsap.registerParameter(optionArg);

		// Debug
		switchArg = new Switch(JSweetOptions.sourceMap);
		switchArg.setLongFlag(JSweetOptions.sourceMap);
		switchArg.setHelp(
				"Generate source map files for the Java files, so that it is possible to debug Java files directly with a debugger that supports source maps (most JavaScript debuggers).");
		switchArg.setDefault("false");
		jsap.registerParameter(switchArg);

		// Enable assertions
		switchArg = new Switch(JSweetOptions.enableAssertions);
		switchArg.setLongFlag(JSweetOptions.enableAssertions);
		switchArg.setHelp("Java 'assert' statements are transpiled as runtime JavaScript checks.");
		switchArg.setDefault("false");
		jsap.registerParameter(switchArg);

		// Header file
		optionArg = new FlaggedOption(JSweetOptions.header);
		optionArg.setLongFlag(JSweetOptions.header);
		optionArg.setHelp(
				"A file that contains a header to be written at the beginning of each generated file. If left unspecified, JSweet will generate a default header.");
		optionArg.setStringParser(FileStringParser.getParser());
		optionArg.setRequired(false);
		jsap.registerParameter(optionArg);

		// Working directory
		optionArg = new FlaggedOption("workingDir");
		optionArg.setLongFlag("workingDir");
		optionArg.setHelp(
				"The directory JSweet uses to store temporary files such as extracted candies. JSweet uses '.jsweet' if left unspecified.");
		optionArg.setStringParser(FileStringParser.getParser());
		optionArg.setRequired(false);
		jsap.registerParameter(optionArg);

		optionArg = new FlaggedOption(JSweetOptions.targetVersion);
		optionArg.setLongFlag(JSweetOptions.targetVersion);
		optionArg.setHelp("The EcmaScript target (JavaScript) version. Possible values: "
				+ Arrays.asList(EcmaScriptComplianceLevel.values()));
		optionArg.setDefault("ES3");
		optionArg.setRequired(false);
		jsap.registerParameter(optionArg);

		// Extra system path
		optionArg = new FlaggedOption(JSweetOptions.extraSystemPath);
		optionArg.setLongFlag(JSweetOptions.extraSystemPath);
		optionArg.setHelp("Allow an extra path to be added to the system path.");
		optionArg.setStringParser(FileStringParser.getParser());
		optionArg.setRequired(false);
		jsap.registerParameter(optionArg);

        // Disable statics lazy initialization
        switchArg = new Switch(JSweetOptions.disableStaticsLazyInitialization);
        switchArg.setLongFlag(JSweetOptions.disableStaticsLazyInitialization);
        switchArg.setHelp(
                "Do not generate lazy initialization code of static fields that is meant " + 
                "to emulate the Java behavior. When disables, the code is more readable " + 
                "but it may result into runtime static initialization issues (cross-class " + 
                "static dependencies).");
        jsap.registerParameter(switchArg);

        // Sort class members
        switchArg = new Switch(JSweetOptions.sortClassMembers);
        switchArg.setLongFlag(JSweetOptions.sortClassMembers);
        switchArg.setHelp(
                "If enabled, class members are sorted using " +
                "PrinterAdapter#getClassMemberComparator(), to be overloaded by the user to "+
                "implement the desired order.");
        jsap.registerParameter(switchArg);
        
		return jsap;
	}

	private static JSAPResult parseArgs(JSAP jsapSpec, String[] commandLineArgs) {
		OUTPUT_LOGGER.info("JSweet transpiler version " + JSweetConfig.getVersionNumber() + " (build date: "
				+ JSweetConfig.getBuildDate() + ")");

		if (jsapSpec == null) {
			throw new IllegalStateException("no args, please call setArgs before");
		}
		JSAPResult arguments = jsapSpec.parse(commandLineArgs);
		if (!arguments.success()) {
			// print out specific error messages describing the problems
			for (java.util.Iterator<?> errs = arguments.getErrorMessageIterator(); errs.hasNext();) {
				System.out.println("Error: " + errs.next());
			}
		}
		if (!arguments.success() || arguments.getBoolean("help")) {
		}

		return arguments;
	}

	private static void printUsage(JSAP jsapSpec) {
		System.out.println("Command line options:");
		System.out.println(jsapSpec.getHelp());
	}

	private static class JSweetTranspilationTask implements TranspilationTask {

		private JSAPResult jsapArgs;
		private List<File> inputDirList;
		private List<File> extraInputDirList;
		private LinkedList<File> javaInputFiles;
		private LinkedList<File> extraJavaInputFiles;

		public JSweetTranspilationTask(JSAPResult jsapArgs) {
			this.jsapArgs = jsapArgs;
			inputDirList = new ArrayList<File>();
			inputDirList.addAll(Arrays.asList(jsapArgs.getFileArray("input")));
			if (jsapArgs.userSpecified("extraInput")) {
				extraInputDirList = Arrays.asList(jsapArgs.getFileArray("extraInput"));
			}
			logger.info("input dirs: " + inputDirList);
		}

		@Override
		public List<File> getInputDirList() {
			return inputDirList;
		}

		@Override
		public void run() throws Exception {
			String classPath = jsapArgs.getString(JSweetOptions.classpath);
			logger.info("classpath: " + classPath);

			ErrorCountTranspilationHandler transpilationHandler = new ErrorCountTranspilationHandler(
					new ConsoleTranspilationHandler());

			try {

				String[] included = jsapArgs.getStringArray("includes");
				String[] excluded = jsapArgs.getStringArray("excludes");

				List<Pattern> includedPatterns = included == null ? null
						: Arrays.asList(included).stream().map(s -> toPattern(s)).collect(Collectors.toList());
				List<Pattern> excludedPatterns = excluded == null ? null
						: Arrays.asList(excluded).stream().map(s -> toPattern(s)).collect(Collectors.toList());

				logger.info("included: " + includedPatterns);
				logger.info("excluded: " + excludedPatterns);

				javaInputFiles = new LinkedList<File>();

				for (File inputDir : inputDirList) {
					Util.addFiles(f -> {
						String path = inputDir.toURI().relativize(f.toURI()).getPath();
						if (path.endsWith(".java")) {
							if (includedPatterns == null || includedPatterns.isEmpty() || includedPatterns != null
									&& includedPatterns.stream().anyMatch(p -> p.matcher(path).matches())) {
								if (excludedPatterns != null && !excludedPatterns.isEmpty()
										&& excludedPatterns.stream().anyMatch(p -> p.matcher(path).matches())) {
									return false;
								}
								return true;
							}
						}
						return false;
					}, inputDir, javaInputFiles);
				}

				extraJavaInputFiles = new LinkedList<File>();

				if (extraInputDirList != null) {
					for (File inputDir : extraInputDirList) {
						Util.addFiles(f -> {
							String path = inputDir.toURI().relativize(f.toURI()).getPath();
							if (path.endsWith(".java")) {
								if (includedPatterns == null || includedPatterns.isEmpty() || includedPatterns != null
										&& includedPatterns.stream().anyMatch(p -> p.matcher(path).matches())) {
									if (excludedPatterns != null && !excludedPatterns.isEmpty()
											&& excludedPatterns.stream().anyMatch(p -> p.matcher(path).matches())) {
										return false;
									}
									return true;
								}
							}
							return false;
						}, inputDir, extraJavaInputFiles);
					}
				}

				javaInputFiles.addAll(extraJavaInputFiles);

				File tsOutputDir = null;
				if (jsapArgs.userSpecified(JSweetOptions.tsout) && jsapArgs.getFile(JSweetOptions.tsout) != null) {
					tsOutputDir = jsapArgs.getFile(JSweetOptions.tsout);
					tsOutputDir.mkdirs();
				}
				logger.info("ts output dir: " + tsOutputDir);

				File jsOutputDir = null;
				if (jsapArgs.userSpecified(JSweetOptions.jsout) && jsapArgs.getFile(JSweetOptions.jsout) != null) {
					jsOutputDir = jsapArgs.getFile(JSweetOptions.jsout);
					jsOutputDir.mkdirs();
				}
				logger.info("js output dir: " + jsOutputDir);

				File dtsOutputDir = null;
				if (jsapArgs.userSpecified(JSweetOptions.dtsout) && jsapArgs.getFile(JSweetOptions.dtsout) != null) {
					dtsOutputDir = jsapArgs.getFile(JSweetOptions.dtsout);
				}

				File candiesJsOutputDir = null;
				if (jsapArgs.userSpecified(JSweetOptions.candiesJsOut) && jsapArgs.getFile(JSweetOptions.candiesJsOut) != null) {
					candiesJsOutputDir = jsapArgs.getFile(JSweetOptions.candiesJsOut);
				}

				File sourceRootDir = null;
				if (jsapArgs.userSpecified(JSweetOptions.sourceRoot) && jsapArgs.getFile(JSweetOptions.sourceRoot) != null) {
					sourceRootDir = jsapArgs.getFile(JSweetOptions.sourceRoot);
				}

				JSweetFactory factory = null;
				String factoryClassName = jsapArgs.getString("factoryClassName");

				if (factoryClassName != null) {
					try {
						factory = (JSweetFactory) Thread.currentThread().getContextClassLoader()
								.loadClass(factoryClassName).newInstance();
					} catch (Exception e) {
						try {
							// try forName just in case
							factory = (JSweetFactory) Class.forName(factoryClassName).newInstance();
						} catch (Exception e2) {
							throw new RuntimeException(
									"cannot find or instantiate factory class: " + factoryClassName
											+ " (make sure the class is in the plugin's classpath and that it defines an empty public constructor)",
									e2);
						}
					}
				}

				if (factory == null) {
					factory = new JSweetFactory();
				}

				JSweetTranspiler transpiler = new JSweetTranspiler(factory, jsapArgs.getFile("workingDir"), tsOutputDir,
						jsOutputDir, candiesJsOutputDir, classPath);

				if (jsapArgs.userSpecified(JSweetOptions.bundle)) {
					transpiler.setBundle(jsapArgs.getBoolean(JSweetOptions.bundle));
				}
				if (jsapArgs.userSpecified(JSweetOptions.noRootDirectories)) {
					transpiler.setNoRootDirectories(jsapArgs.getBoolean(JSweetOptions.noRootDirectories));
				}
				if (jsapArgs.userSpecified(JSweetOptions.sourceMap)) {
					transpiler.setGenerateSourceMaps(jsapArgs.getBoolean(JSweetOptions.sourceMap));
				}
				if (sourceRootDir != null) {
					transpiler.setSourceRoot(sourceRootDir);
				}
				if (jsapArgs.userSpecified(JSweetOptions.module)) {
					transpiler.setModuleKind(ModuleKind.valueOf(jsapArgs.getString(JSweetOptions.module)));
				}
				if (jsapArgs.userSpecified(JSweetOptions.moduleResolution)) {
					transpiler.setModuleResolution(
							ModuleResolution.valueOf(jsapArgs.getString(JSweetOptions.moduleResolution)));
				}
				if (jsapArgs.userSpecified(JSweetOptions.encoding)) {
					transpiler.setEncoding(jsapArgs.getString(JSweetOptions.encoding));
				}
				if (jsapArgs.userSpecified(JSweetOptions.outEncoding)) {
					transpiler.setOutEncoding(jsapArgs.getString(JSweetOptions.outEncoding));
				}
                if (jsapArgs.userSpecified(JSweetOptions.nonEnumerableTransients)) {
                    transpiler.setNonEnumerableTransients(jsapArgs.getBoolean(JSweetOptions.nonEnumerableTransients));
                }
				if (jsapArgs.userSpecified(JSweetOptions.enableAssertions)) {
					transpiler.setIgnoreAssertions(!jsapArgs.getBoolean(JSweetOptions.enableAssertions));
				}
				if (jsapArgs.userSpecified(JSweetOptions.declaration)) {
					transpiler.setGenerateDeclarations(jsapArgs.getBoolean(JSweetOptions.declaration));
				}
				if (jsapArgs.userSpecified(JSweetOptions.tsOnly)) {
					transpiler.setGenerateJsFiles(!jsapArgs.getBoolean(JSweetOptions.tsOnly));
				}
				if (jsapArgs.userSpecified(JSweetOptions.ignoreDefinitions)) {
					transpiler.setGenerateDefinitions(!jsapArgs.getBoolean(JSweetOptions.ignoreDefinitions));
				}
				if (jsapArgs.userSpecified(JSweetOptions.ignoreJavaErrors)) {
					transpiler.setIgnoreJavaErrors(jsapArgs.getBoolean(JSweetOptions.ignoreJavaErrors));
				}
				if (jsapArgs.userSpecified(JSweetOptions.dtsout)) {
					transpiler.setDeclarationsOutputDir(dtsOutputDir);
				}
				if (jsapArgs.userSpecified(JSweetOptions.header)) {
					transpiler.setHeaderFile(jsapArgs.getFile(JSweetOptions.header));
				}
				if (jsapArgs.userSpecified(JSweetOptions.targetVersion)) {
					transpiler.setEcmaTargetVersion(
							JSweetTranspiler.getEcmaTargetVersion(jsapArgs.getString(JSweetOptions.targetVersion)));
				}
				if (jsapArgs.userSpecified(JSweetOptions.disableSinglePrecisionFloats)) {
					transpiler.setDisableSinglePrecisionFloats(jsapArgs.getBoolean(JSweetOptions.disableSinglePrecisionFloats));
				}
				if (jsapArgs.userSpecified(JSweetOptions.extraSystemPath)) {
					ProcessUtil.addExtraPath(jsapArgs.getString(JSweetOptions.extraSystemPath));
				}
                if (jsapArgs.userSpecified(JSweetOptions.disableStaticsLazyInitialization)) {
                    transpiler.setLazyInitializedStatics(!jsapArgs.getBoolean(JSweetOptions.disableStaticsLazyInitialization));
                }								
                if (jsapArgs.userSpecified(JSweetOptions.sortClassMembers)) {
                    transpiler.setSortClassMembers(jsapArgs.getBoolean(JSweetOptions.sortClassMembers));
                }                               
				
				if (tsOutputDir != null) {
					transpiler.setTsOutputDir(tsOutputDir);
				}
				if (jsOutputDir != null) {
					transpiler.setJsOutputDir(jsOutputDir);
				}

				// transpiler.setAdapters(Arrays.asList(jsapArgs.getStringArray("adapters")));

				List<File> files = Arrays.asList(jsapArgs.getFileArray(JSweetOptions.defInput));
				logger.info("definition input dirs: " + files);

				for (File f : files) {
					transpiler.addTsDefDir(f);
				}

				transpiler.transpile(transpilationHandler, extraJavaInputFiles.stream().map(f -> f.toString())
						.collect(Collectors.toSet()), SourceFile.toSourceFiles(javaInputFiles));
			} catch (NoClassDefFoundError error) {
				transpilationHandler.report(JSweetProblem.JAVA_COMPILER_NOT_FOUND, null,
						JSweetProblem.JAVA_COMPILER_NOT_FOUND.getMessage());
			}

			errorCount = transpilationHandler.getErrorCount();
			if (errorCount > 0) {
				OUTPUT_LOGGER.info("transpilation failed with " + errorCount + " error(s) and "
						+ transpilationHandler.getWarningCount() + " warning(s)");
			} else {
				if (transpilationHandler.getWarningCount() > 0) {
					OUTPUT_LOGGER.info(
							"transpilation completed with " + transpilationHandler.getWarningCount() + " warning(s)");
				} else {
					OUTPUT_LOGGER.info("transpilation successfully completed with no errors and no warnings");
				}
			}

		}

	}

}
