/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.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.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2007-2009 Nick Sieger <nicksieger@gmail.com>
 * Copyright (C) 2009 Joseph LaFata <joe@quibb.org>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import java.util.Set;
import org.jruby.ast.executable.Script;
import org.jruby.compiler.ASTCompiler;
import org.jruby.compiler.ASTCompiler19;
import org.jruby.exceptions.MainExitException;
import org.jruby.runtime.Constants;
import org.jruby.runtime.load.LoadService;
import org.jruby.util.ClassCache;
import org.jruby.util.JRubyFile;
import org.jruby.util.KCode;
import org.jruby.util.NormalizedFile;
import org.jruby.util.SafePropertyAccessor;
import org.objectweb.asm.Opcodes;

public class RubyInstanceConfig {

    /**
     * The max count of active methods eligible for JIT-compilation.
     */
    private static final int JIT_MAX_METHODS_LIMIT = 4096;

    /**
     * The max size of JIT-compiled methods (full class size) allowed.
     */
    private static final int JIT_MAX_SIZE_LIMIT = 10000;

    /**
     * The JIT threshold to the specified method invocation count.
     */
    private static final int JIT_THRESHOLD = 50;
    
    /** The version to use for generated classes. Set to current JVM version by default */
    public static final int JAVA_VERSION;
    
    /**
     * Default size for chained compilation.
     */
    private static final int CHAINED_COMPILE_LINE_COUNT_DEFAULT = 500;
    
    /**
     * The number of lines at which a method, class, or block body is split into
     * chained methods (to dodge 64k method-size limit in JVM).
     */
    public static final int CHAINED_COMPILE_LINE_COUNT
            = SafePropertyAccessor.getInt("jruby.compile.chainsize", CHAINED_COMPILE_LINE_COUNT_DEFAULT);

    public enum CompileMode {
        JIT, FORCE, OFF;

        public boolean shouldPrecompileCLI() {
            switch (this) {
            case JIT: case FORCE:
                return true;
            }
            return false;
        }

        public boolean shouldJIT() {
            switch (this) {
            case JIT: case FORCE:
                return true;
            }
            return false;
        }

        public boolean shouldPrecompileAll() {
            return this == FORCE;
        }
    }
    private InputStream input          = System.in;
    private PrintStream output         = System.out;
    private PrintStream error          = System.err;
    private Profile profile            = Profile.DEFAULT;
    private boolean objectSpaceEnabled
            = SafePropertyAccessor.getBoolean("jruby.objectspace.enabled", false);

    private CompileMode compileMode = CompileMode.JIT;
    private boolean runRubyInProcess   = true;
    private String currentDirectory;
    private Map environment;
    private String[] argv = {};

    private final boolean jitLogging;
    private final boolean jitLoggingVerbose;
    private final int jitLogEvery;
    private final int jitThreshold;
    private final int jitMax;
    private final int jitMaxSize;
    private final boolean samplingEnabled;
    private CompatVersion compatVersion;

    private ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
    private ClassLoader loader = contextLoader == null ? RubyInstanceConfig.class.getClassLoader() : contextLoader;

    private ClassCache<Script> classCache;

    // from CommandlineParser
    private List<String> loadPaths = new ArrayList<String>();
    private Set<String> excludedMethods = new HashSet<String>();
    private StringBuffer inlineScript = new StringBuffer();
    private boolean hasInlineScript = false;
    private String scriptFileName = null;
    private List<String> requiredLibraries = new ArrayList<String>();
    private boolean benchmarking = false;
    private boolean argvGlobalsOn = false;
    private boolean assumeLoop = false;
    private boolean assumePrinting = false;
    private Map optionGlobals = new HashMap();
    private boolean processLineEnds = false;
    private boolean split = false;
    // This property is a Boolean, to allow three values, so it can match MRI's nil, false and true
    private Boolean verbose = Boolean.FALSE;
    private boolean debug = false;
    private boolean showVersion = false;
    private boolean showBytecode = false;
    private boolean showCopyright = false;
    private boolean endOfArguments = false;
    private boolean shouldRunInterpreter = true;
    private boolean shouldPrintUsage = false;
    private boolean shouldPrintProperties=false;
    private KCode kcode = KCode.NONE;
    private String recordSeparator = "\n";
    private boolean shouldCheckSyntax = false;
    private String inputFieldSeparator = null;
    private boolean managementEnabled = true;
    private String inPlaceBackupExtension = null;
    private boolean parserDebug = false;
    private String threadDumpSignal = null;

    private int safeLevel = 0;

    private String jrubyHome;

    public static final boolean PEEPHOLE_OPTZ
            = SafePropertyAccessor.getBoolean("jruby.compile.peephole", true);
    public static boolean FASTEST_COMPILE_ENABLED
            = SafePropertyAccessor.getBoolean("jruby.compile.fastest");
    public static boolean FASTOPS_COMPILE_ENABLED
            = FASTEST_COMPILE_ENABLED
            || SafePropertyAccessor.getBoolean("jruby.compile.fastops");
    public static boolean FRAMELESS_COMPILE_ENABLED
            = FASTEST_COMPILE_ENABLED
            || SafePropertyAccessor.getBoolean("jruby.compile.frameless");
    public static boolean POSITIONLESS_COMPILE_ENABLED
            = FASTEST_COMPILE_ENABLED
            || SafePropertyAccessor.getBoolean("jruby.compile.positionless");
    public static boolean THREADLESS_COMPILE_ENABLED
            = FASTEST_COMPILE_ENABLED
            || SafePropertyAccessor.getBoolean("jruby.compile.threadless");
    public static boolean FASTCASE_COMPILE_ENABLED =
            SafePropertyAccessor.getBoolean("jruby.compile.fastcase");
    public static boolean FASTSEND_COMPILE_ENABLED =
            SafePropertyAccessor.getBoolean("jruby.compile.fastsend");
    public static boolean LAZYHANDLES_COMPILE = SafePropertyAccessor.getBoolean("jruby.compile.lazyHandles", false);
    public static boolean INLINE_DYNCALL_ENABLED
            = FASTEST_COMPILE_ENABLED
            || SafePropertyAccessor.getBoolean("jruby.compile.inlineDyncalls");
    public static final boolean FORK_ENABLED
            = SafePropertyAccessor.getBoolean("jruby.fork.enabled");
    public static final boolean POOLING_ENABLED
            = SafePropertyAccessor.getBoolean("jruby.thread.pool.enabled");
    public static final int POOL_MAX
            = SafePropertyAccessor.getInt("jruby.thread.pool.max", Integer.MAX_VALUE);
    public static final int POOL_MIN
            = SafePropertyAccessor.getInt("jruby.thread.pool.min", 0);
    public static final int POOL_TTL
            = SafePropertyAccessor.getInt("jruby.thread.pool.ttl", 60);

    public static final boolean NATIVE_NET_PROTOCOL
            = SafePropertyAccessor.getBoolean("jruby.native.net.protocol", false);

    public static boolean FULL_TRACE_ENABLED
            = SafePropertyAccessor.getBoolean("jruby.debug.fullTrace", false);

    public static final String COMPILE_EXCLUDE
            = SafePropertyAccessor.getProperty("jruby.jit.exclude");
    public static boolean nativeEnabled = true;

    public static final boolean REIFY_RUBY_CLASSES
            = SafePropertyAccessor.getBoolean("jruby.reify.classes", false);

    public static final boolean USE_GENERATED_HANDLES
            = SafePropertyAccessor.getBoolean("jruby.java.handles", false);

    public static final boolean DEBUG_LOAD_SERVICE
            = SafePropertyAccessor.getBoolean("jruby.debug.loadService", false);

    public static final boolean JUMPS_HAVE_BACKTRACE
            = SafePropertyAccessor.getBoolean("jruby.jump.backtrace", false);

    public static interface LoadServiceCreator {
        LoadService create(Ruby runtime);

        LoadServiceCreator DEFAULT = new LoadServiceCreator() {
                public LoadService create(Ruby runtime) {
                    return new LoadService(runtime);
                }
            };
    }

    private LoadServiceCreator creator = LoadServiceCreator.DEFAULT;


    static {
        String specVersion = null;
        try {
            specVersion = System.getProperty("jruby.bytecode.version");
            if (specVersion == null) {
                specVersion = System.getProperty("java.specification.version");
            }
            if (System.getProperty("jruby.native.enabled") != null) {
                nativeEnabled = Boolean.getBoolean("jruby.native.enabled");
            }
        } catch (SecurityException se) {
            nativeEnabled = false;
            specVersion = "1.5";
        }
        
        if (specVersion.equals("1.5")) {
            JAVA_VERSION = Opcodes.V1_5;
        } else {
            JAVA_VERSION = Opcodes.V1_6;
        }
    }

    public int characterIndex = 0;

    public RubyInstanceConfig(RubyInstanceConfig parentConfig) {
        setCurrentDirectory(parentConfig.getCurrentDirectory());
        samplingEnabled = parentConfig.samplingEnabled;
        compatVersion = parentConfig.compatVersion;
        compileMode = parentConfig.getCompileMode();
        jitLogging = parentConfig.jitLogging;
        jitLoggingVerbose = parentConfig.jitLoggingVerbose;
        jitLogEvery = parentConfig.jitLogEvery;
        jitThreshold = parentConfig.jitThreshold;
        jitMax = parentConfig.jitMax;
        jitMaxSize = parentConfig.jitMaxSize;
        managementEnabled = parentConfig.managementEnabled;
        runRubyInProcess = parentConfig.runRubyInProcess;
        excludedMethods = parentConfig.excludedMethods;
        threadDumpSignal = parentConfig.threadDumpSignal;
        
        classCache = new ClassCache<Script>(loader, jitMax);
    }

    public RubyInstanceConfig() {
        setCurrentDirectory(Ruby.isSecurityRestricted() ? "/" : JRubyFile.getFileProperty("user.dir"));
        samplingEnabled = SafePropertyAccessor.getBoolean("jruby.sampling.enabled", false);

        String compatString = SafePropertyAccessor.getProperty("jruby.compat.version", "RUBY1_8");
        if (compatString.equalsIgnoreCase("RUBY1_8")) {
            setCompatVersion(CompatVersion.RUBY1_8);
        } else if (compatString.equalsIgnoreCase("RUBY1_9")) {
            setCompatVersion(CompatVersion.RUBY1_9);
        } else {
            error.println("Compatibility version `" + compatString + "' invalid; use RUBY1_8 or RUBY1_9. Using RUBY1_8.");
            setCompatVersion(CompatVersion.RUBY1_8);
        }

        if (Ruby.isSecurityRestricted()) {
            compileMode = CompileMode.OFF;
            jitLogging = false;
            jitLoggingVerbose = false;
            jitLogEvery = 0;
            jitThreshold = -1;
            jitMax = 0;
            jitMaxSize = -1;
            managementEnabled = false;
        } else {
            String threshold = SafePropertyAccessor.getProperty("jruby.jit.threshold");
            String max = SafePropertyAccessor.getProperty("jruby.jit.max");
            String maxSize = SafePropertyAccessor.getProperty("jruby.jit.maxsize");
            
            if (COMPILE_EXCLUDE != null) {
                String[] elements = COMPILE_EXCLUDE.split(",");
                for (String element : elements) excludedMethods.add(element);
            }
            
            managementEnabled = SafePropertyAccessor.getBoolean("jruby.management.enabled", true);
            runRubyInProcess = SafePropertyAccessor.getBoolean("jruby.launch.inproc", true);
            boolean jitProperty = SafePropertyAccessor.getProperty("jruby.jit.enabled") != null;
            if (jitProperty) {
                error.print("jruby.jit.enabled property is deprecated; use jruby.compile.mode=(OFF|JIT|FORCE) for -C, default, and +C flags");
                compileMode = SafePropertyAccessor.getBoolean("jruby.jit.enabled") ? CompileMode.JIT : CompileMode.OFF;
            } else {
                String jitModeProperty = SafePropertyAccessor.getProperty("jruby.compile.mode", "JIT");

                if (jitModeProperty.equals("OFF")) {
                    compileMode = CompileMode.OFF;
                } else if (jitModeProperty.equals("JIT")) {
                    compileMode = CompileMode.JIT;
                } else if (jitModeProperty.equals("FORCE")) {
                    compileMode = CompileMode.FORCE;
                } else {
                    error.print("jruby.compile.mode property must be OFF, JIT, FORCE, or unset; defaulting to JIT");
                    compileMode = CompileMode.JIT;
                }
            }
            jitLogging = SafePropertyAccessor.getBoolean("jruby.jit.logging");
            jitLoggingVerbose = SafePropertyAccessor.getBoolean("jruby.jit.logging.verbose");
            String logEvery = SafePropertyAccessor.getProperty("jruby.jit.logEvery");
            jitLogEvery = logEvery == null ? 0 : Integer.parseInt(logEvery);
            jitThreshold = threshold == null ?
                    JIT_THRESHOLD : Integer.parseInt(threshold);
            jitMax = max == null ?
                    JIT_MAX_METHODS_LIMIT : Integer.parseInt(max);
            jitMaxSize = maxSize == null ?
                    JIT_MAX_SIZE_LIMIT : Integer.parseInt(maxSize);
        }

        // default ClassCache using jitMax as a soft upper bound
        classCache = new ClassCache<Script>(loader, jitMax);
        threadDumpSignal = SafePropertyAccessor.getProperty("jruby.thread.dump.signal", "USR2");

        if (FORK_ENABLED) {
            error.print("WARNING: fork is highly unlikely to be safe or stable on the JVM. Have fun!\n");
        }
    }

    public LoadServiceCreator getLoadServiceCreator() {
        return creator;
    }

    public void setLoadServiceCreator(LoadServiceCreator creator) {
        this.creator = creator;
    }

    public LoadService createLoadService(Ruby runtime) {
        return this.creator.create(runtime);
    }

    public String getBasicUsageHelp() {
        StringBuilder sb = new StringBuilder();
        sb
                .append("Usage: jruby [switches] [--] [programfile] [arguments]\n")
                .append("  -0[octal]       specify record separator (\\0, if no argument)\n")
                .append("  -a              autosplit mode with -n or -p (splits $_ into $F)\n")
                .append("  -b              benchmark mode, times the script execution\n")
                .append("  -c              check syntax only\n")
                .append("  -Cdirectory     cd to directory, before executing your script\n")
                .append("  -d              set debugging flags (set $DEBUG to true)\n")
                .append("  -e 'command'    one line of script. Several -e's allowed. Omit [programfile]\n")
                .append("  -Fpattern       split() pattern for autosplit (-a)\n")
                .append("  -i[extension]   edit ARGV files in place (make backup if extension supplied)\n")
                .append("  -Idirectory     specify $LOAD_PATH directory (may be used more than once)\n")
                .append("  -J[java option] pass an option on to the JVM (e.g. -J-Xmx512m)\n")
                .append("                    use --properties to list JRuby properties\n")
                .append("                    run 'java -help' for a list of other Java options\n")
                .append("  -Kkcode         specifies code-set (e.g. -Ku for Unicode\n")
                .append("  -l              enable line ending processing\n")
                .append("  -n              assume 'while gets(); ... end' loop around your script\n")
                .append("  -p              assume loop like -n but print line also like sed\n")
                .append("  -rlibrary       require the library, before executing your script\n")
                .append("  -s              enable some switch parsing for switches after script name\n")
                .append("  -S              look for the script in bin or using PATH environment variable\n")
                .append("  -T[level]       turn on tainting checks\n")
                .append("  -v              print version number, then turn on verbose mode\n")
                .append("  -w              turn warnings on for your script\n")
                .append("  -W[level]       set warning level; 0=silence, 1=medium, 2=verbose (default)\n")
                //.append("  -x[directory]   strip off text before #!ruby line and perhaps cd to directory\n")
                .append("  -X[option]      enable extended option (omit option to list)\n")
                .append("  -y              enable parsing debug output\n")
                .append("  --copyright     print the copyright\n")
                .append("  --debug         sets the execution mode most suitable for debugger functionality\n")
                .append("  --jdb           runs JRuby process under JDB\n")
                .append("  --properties    List all configuration Java properties (pass -J-Dproperty=value)\n")
                .append("  --sample        run with profiling using the JVM's sampling profiler\n")
                .append("  --client        use the non-optimizing \"client\" JVM (improves startup; default)\n")
                .append("  --server        use the optimizing \"server\" JVM (improves perf)\n")
                .append("  --manage        enable remote JMX management and monitoring of the VM and JRuby\n")
                .append("  --headless      do not launch a GUI window, no matter what\n")
                .append("  --1.8           specify Ruby 1.8.x compatibility (default)\n")
                .append("  --1.9           specify Ruby 1.9.x compatibility\n")
                .append("  --bytecode      show the JVM bytecode produced by compiling specified code\n")
                .append("  --version       print the version\n");

        return sb.toString();
    }

    public String getExtendedHelp() {
        StringBuilder sb = new StringBuilder();
        sb
                .append("These flags are for extended JRuby options.\n")
                .append("Specify them by passing -X<option>\n")
                .append("  -O              run with ObjectSpace disabled (default; improves performance)\n")
                .append("  +O              run with ObjectSpace enabled (reduces performance)\n")
                .append("  -C              disable all compilation\n")
                .append("  +C              force compilation of all scripts before they are run (except eval)\n");

        return sb.toString();
    }

    public String getPropertyHelp() {
        StringBuilder sb = new StringBuilder();
        sb
                .append("These properties can be used to alter runtime behavior for perf or compatibility.\n")
                .append("Specify them by passing -J-D<property>=<value>\n")
                .append("\nCOMPILER SETTINGS:\n")
                .append("    jruby.compile.mode=JIT|FORCE|OFF\n")
                .append("       Set compilation mode. JIT is default; FORCE compiles all, OFF disables\n")
                .append("    jruby.compile.fastest=true|false\n")
                .append("       (EXPERIMENTAL) Turn on all experimental compiler optimizations\n")
                .append("    jruby.compile.frameless=true|false\n")
                .append("       (EXPERIMENTAL) Turn on frameless compilation where possible\n")
                .append("    jruby.compile.positionless=true|false\n")
                .append("       (EXPERIMENTAL) Turn on compilation that avoids updating Ruby position info. Default is false\n")
                .append("    jruby.compile.threadless=true|false\n")
                .append("       (EXPERIMENTAL) Turn on compilation without polling for \"unsafe\" thread events. Default is false\n")
                .append("    jruby.compile.fastops=true|false\n")
                .append("       (EXPERIMENTAL) Turn on fast operators for Fixnum. Default is false\n")
                .append("    jruby.compile.fastcase=true|false\n")
                .append("       (EXPERIMENTAL) Turn on fast case/when for all-Fixnum whens. Default is false\n")
                .append("    jruby.compile.chainsize=<line count>\n")
                .append("       Set the number of lines at which compiled bodies are \"chained\". Default is " + CHAINED_COMPILE_LINE_COUNT_DEFAULT + "\n")
                .append("    jruby.compile.lazyHandles=true|false\n")
                .append("       Generate method bindings (handles) for compiled methods lazily. Default is false.\n")
                .append("    jruby.compile.peephole=true|false\n")
                .append("       Enable or disable peephole optimizations. Default is true (on).\n")
                .append("\nJIT SETTINGS:\n")
                .append("    jruby.jit.threshold=<invocation count>\n")
                .append("       Set the JIT threshold to the specified method invocation count. Default is " + JIT_THRESHOLD + ".\n")
                .append("    jruby.jit.max=<method count>\n")
                .append("       Set the max count of active methods eligible for JIT-compilation.\n")
                .append("       Default is " + JIT_MAX_METHODS_LIMIT + " per runtime. A value of 0 disables JIT, -1 disables max.\n")
                .append("    jruby.jit.maxsize=<jitted method size (full .class)>\n")
                .append("       Set the maximum full-class byte size allowed for jitted methods. Default is Integer.MAX_VALUE\n")
                .append("    jruby.jit.logging=true|false\n")
                .append("       Enable JIT logging (reports successful compilation). Default is false\n")
                .append("    jruby.jit.logging.verbose=true|false\n")
                .append("       Enable verbose JIT logging (reports failed compilation). Default is false\n")
                .append("    jruby.jit.logEvery=<method count>\n")
                .append("       Log a message every n methods JIT compiled. Default is 0 (off).\n")
                .append("    jruby.jit.exclude=<ClsOrMod,ClsOrMod::method_name,-::method_name>\n")
                .append("       Exclude methods from JIT by class/module short name, c/m::method_name,\n")
                .append("       or -::method_name for anon/singleton classes/modules. Comma-delimited.\n")
                .append("\nNATIVE SUPPORT:\n")
                .append("    jruby.native.enabled=true|false\n")
                .append("       Enable/disable native extensions (like JNA for non-Java APIs; Default is true\n")
                .append("       (This affects all JRuby instances in a given JVM)\n")
                .append("    jruby.native.verbose=true|false\n")
                .append("       Enable verbose logging of native extension loading. Default is false.\n")
                .append("    jruby.fork.enabled=true|false\n")
                .append("       (EXPERIMENTAL, maybe dangerous) Enable fork(2) on platforms that support it.\n")
                .append("\nTHREAD POOLING:\n")
                .append("    jruby.thread.pool.enabled=true|false\n")
                .append("       Enable reuse of native backing threads via a thread pool. Default is false.\n")
                .append("    jruby.thread.pool.min=<min thread count>\n")
                .append("       The minimum number of threads to keep alive in the pool. Default is 0.\n")
                .append("    jruby.thread.pool.max=<max thread count>\n")
                .append("       The maximum number of threads to allow in the pool. Default is unlimited.\n")
                .append("    jruby.thread.pool.ttl=<time to live, in seconds>\n")
                .append("       The maximum number of seconds to keep alive an idle thread. Default is 60.\n")
                .append("\nMISCELLANY:\n")
                .append("    jruby.compat.version=RUBY1_8|RUBY1_9\n")
                .append("       Specify the major Ruby version to be compatible with; Default is RUBY1_8\n")
                .append("    jruby.objectspace.enabled=true|false\n")
                .append("       Enable or disable ObjectSpace.each_object (default is disabled)\n")
                .append("    jruby.launch.inproc=true|false\n")
                .append("       Set in-process launching of e.g. system('ruby ...'). Default is true\n")
                .append("    jruby.bytecode.version=1.5|1.6\n")
                .append("       Set bytecode version for JRuby to generate. Default is current JVM version.\n")
                .append("    jruby.management.enabled=true|false\n")
                .append("       Set whether JMX management is enabled. Default is true.\n")
                .append("    jruby.debug.fullTrace=true|false\n")
                .append("       Set whether full traces are enabled (c-call/c-return). Default is false.\n")
                .append("    jruby.debug.loadService=true|false\n")
                .append("       Log the process of locating and loading libraries. Default is false.\n")
                .append("    jruby.jump.backtrace=true|false\n")
                .append("       Make non-local flow jumps generate backtraces. Default is false.\n");

        return sb.toString();
    }

    public String getVersionString() {
        String ver = null;
        String patchDelimeter = null;
        int patchlevel = 0;
        switch (getCompatVersion()) {
        case RUBY1_8:
            ver = Constants.RUBY_VERSION;
            patchlevel = Constants.RUBY_PATCHLEVEL;
            patchDelimeter = " patchlevel ";
            break;
        case RUBY1_9:
            ver = Constants.RUBY1_9_VERSION;
            patchlevel = Constants.RUBY1_9_PATCHLEVEL;
            patchDelimeter = " trunk ";
            break;
        }

        String fullVersion = String.format(
                "jruby %s (ruby %s%s%d) (%s %s) (%s %s) [%s-java]",
                Constants.VERSION, ver, patchDelimeter, patchlevel,
                Constants.COMPILE_DATE, Constants.REVISION,
                System.getProperty("java.vm.name"), System.getProperty("java.version"),
                SafePropertyAccessor.getProperty("os.arch", "unknown")
                );

        return fullVersion;
    }

    public String getCopyrightString() {
        return "JRuby - Copyright (C) 2001-2009 The JRuby Community (and contribs)";
    }

    public void processArguments(String[] arguments) {
        new ArgumentProcessor(arguments).processArguments();
        String rubyopt = System.getenv("RUBYOPT");
        if (rubyopt != null) {
            String[] rubyoptArgs = rubyopt.split("\\s+");
            for (int i = 0; i < rubyoptArgs.length; i++) {
                if (!rubyoptArgs[i].startsWith("-")) {
                    rubyoptArgs[i] = "-" + rubyoptArgs[i];
                }
            }
            new ArgumentProcessor(rubyoptArgs, false).processArguments();
        }
    }

    public CompileMode getCompileMode() {
        return compileMode;
    }

    public void setCompileMode(CompileMode compileMode) {
        this.compileMode = compileMode;
    }

    public boolean isJitLogging() {
        return jitLogging;
    }

    public boolean isJitLoggingVerbose() {
        return jitLoggingVerbose;
    }

    public int getJitLogEvery() {
        return jitLogEvery;
    }

    public boolean isSamplingEnabled() {
        return samplingEnabled;
    }

    public int getJitThreshold() {
        return jitThreshold;
    }

    public int getJitMax() {
        return jitMax;
    }

    public int getJitMaxSize() {
        return jitMaxSize;
    }

    public boolean isRunRubyInProcess() {
        return runRubyInProcess;
    }

    public void setRunRubyInProcess(boolean flag) {
        this.runRubyInProcess = flag;
    }

    public void setInput(InputStream newInput) {
        input = newInput;
    }

    public InputStream getInput() {
        return input;
    }

    public CompatVersion getCompatVersion() {
        return compatVersion;
    }

    public void setCompatVersion(CompatVersion compatVersion) {
        // Until we get a little more solid on 1.9 support we will only run interpreted mode
        if (compatVersion == CompatVersion.RUBY1_9) compileMode = CompileMode.OFF;
        if (compatVersion == null) compatVersion = CompatVersion.RUBY1_8;

        this.compatVersion = compatVersion;
    }

    public void setOutput(PrintStream newOutput) {
        output = newOutput;
    }

    public PrintStream getOutput() {
        return output;
    }

    public void setError(PrintStream newError) {
        error = newError;
    }

    public PrintStream getError() {
        return error;
    }

    public void setCurrentDirectory(String newCurrentDirectory) {
        currentDirectory = newCurrentDirectory;
    }

    public String getCurrentDirectory() {
        return currentDirectory;
    }

    public void setProfile(Profile newProfile) {
        profile = newProfile;
    }

    public Profile getProfile() {
        return profile;
    }

    public void setObjectSpaceEnabled(boolean newObjectSpaceEnabled) {
        objectSpaceEnabled = newObjectSpaceEnabled;
    }

    public boolean isObjectSpaceEnabled() {
        return objectSpaceEnabled;
    }

    public void setEnvironment(Map newEnvironment) {
        environment = newEnvironment;
    }

    public Map getEnvironment() {
        return environment;
    }

    public ClassLoader getLoader() {
        return loader;
    }

    public void setLoader(ClassLoader loader) {
        // Setting the loader needs to reset the class cache
        if(this.loader != loader) {
            this.classCache = new ClassCache<Script>(loader, this.classCache.getMax());
        }
        this.loader = loader;
    }

    public String[] getArgv() {
        return argv;
    }

    public void setArgv(String[] argv) {
        this.argv = argv;
    }

    public String getJRubyHome() {
        if (jrubyHome == null) {
            // try the normal property first
            if (!Ruby.isSecurityRestricted()) {
                jrubyHome = SafePropertyAccessor.getProperty("jruby.home");
            }

            if (jrubyHome != null) {
                // verify it if it's there
                jrubyHome = verifyHome(jrubyHome);
            } else {
                try {
                    // try loading from classloader resources
                    jrubyHome = getClass().getResource("/META-INF/jruby.home")
                        .toURI().getSchemeSpecificPart();
                } catch (Exception e) {}

                if (jrubyHome != null) {
                    // verify it if it's there
                    jrubyHome = verifyHome(jrubyHome);
                } else {
                    // otherwise fall back on system temp location
                    jrubyHome = System.getProperty("java.io.tmpdir");
                }
            }
        }
        return jrubyHome;
    }

    public void setJRubyHome(String home) {
        jrubyHome = verifyHome(home);
    }

    // We require the home directory to be absolute
    private String verifyHome(String home) {
        if (home.equals(".")) {
            home = System.getProperty("user.dir");
        }
        if (home.startsWith("cp:")) {
            home = home.substring(3);
        } else if (!home.startsWith("file:")) {
            NormalizedFile f = new NormalizedFile(home);
            if (!f.isAbsolute()) {
                home = f.getAbsolutePath();
            }
            if (!f.exists()) {
                System.err.println("Warning: JRuby home \"" + f + "\" does not exist, using " + System.getProperty("java.io.tmpdir"));
                return System.getProperty("java.io.tmpdir");
            }
        }
        return home;
    }

    private class ArgumentProcessor {
        private String[] arguments;
        private int argumentIndex = 0;
        private boolean processArgv;

        public ArgumentProcessor(String[] arguments) {
            this(arguments, true);
        }

        public ArgumentProcessor(String[] arguments, boolean processArgv) {
            this.arguments = arguments;
            this.processArgv = processArgv;
        }

        public void processArguments() {
            while (argumentIndex < arguments.length && isInterpreterArgument(arguments[argumentIndex])) {
                processArgument();
                argumentIndex++;
            }

            if (!hasInlineScript && scriptFileName == null) {
                if (argumentIndex < arguments.length) {
                    setScriptFileName(arguments[argumentIndex]); //consume the file name
                    argumentIndex++;
                }
            }

            if (processArgv) processArgv();
        }

        private void processArgv() {
            List<String> arglist = new ArrayList<String>();
            for (; argumentIndex < arguments.length; argumentIndex++) {
                String arg = arguments[argumentIndex];
                if (argvGlobalsOn && arg.startsWith("-")) {
                    arg = arg.substring(1);
                    if (arg.indexOf('=') > 0) {
                        String[] keyvalue = arg.split("=", 2);
                        optionGlobals.put(keyvalue[0], keyvalue[1]);
                    } else {
                        optionGlobals.put(arg, null);
                    }
                } else {
                    argvGlobalsOn = false;
                    arglist.add(arg);
                }
            }

            // Remaining arguments are for the script itself
            for (String arg : argv) arglist.add(arg);
            argv = arglist.toArray(new String[arglist.size()]);
        }

        private boolean isInterpreterArgument(String argument) {
            return argument.length() > 0 && (argument.charAt(0) == '-' || argument.charAt(0) == '+') && !endOfArguments;
        }

        private String getArgumentError(String additionalError) {
            return "jruby: invalid argument\n" + additionalError + "\n";
        }

        private void processArgument() {
            String argument = arguments[argumentIndex];
            FOR : for (characterIndex = 1; characterIndex < argument.length(); characterIndex++) {
                switch (argument.charAt(characterIndex)) {
                case '0': {
                    String temp = grabOptionalValue();
                    if (null == temp) {
                        recordSeparator = "\u0000";
                    } else if (temp.equals("0")) {
                        recordSeparator = "\n\n";
                    } else if (temp.equals("777")) {
                        recordSeparator = "\uFFFF"; // Specify something that can't separate
                    } else {
                        try {
                            int val = Integer.parseInt(temp, 8);
                            recordSeparator = "" + (char) val;
                        } catch (Exception e) {
                            MainExitException mee = new MainExitException(1, getArgumentError(" -0 must be followed by either 0, 777, or a valid octal value"));
                            mee.setUsageError(true);
                            throw mee;
                        }
                    }
                    break FOR;
                }
                case 'a':
                    split = true;
                    break;
                case 'b':
                    benchmarking = true;
                    break;
                case 'c':
                    shouldCheckSyntax = true;
                    break;
                case 'C':
                    try {
                        String saved = grabValue(getArgumentError(" -C must be followed by a directory expression"));
                        File base = new File(currentDirectory);
                        File newDir = new File(saved);
                        if (newDir.isAbsolute()) {
                            currentDirectory = newDir.getCanonicalPath();
                        } else {
                            currentDirectory = new File(base, newDir.getPath()).getCanonicalPath();
                        }
                        if (!(new File(currentDirectory).isDirectory())) {
                            MainExitException mee = new MainExitException(1, "jruby: Can't chdir to " + saved + " (fatal)");
                            mee.setUsageError(true);
                            throw mee;
                        }
                    } catch (IOException e) {
                        MainExitException mee = new MainExitException(1, getArgumentError(" -C must be followed by a valid directory"));
                        mee.setUsageError(true);
                        throw mee;
                    }
                    break;
                case 'd':
                    debug = true;
                    verbose = Boolean.TRUE;
                    break;
                case 'e':
                    inlineScript.append(grabValue(getArgumentError(" -e must be followed by an expression to evaluate")));
                    inlineScript.append('\n');
                    hasInlineScript = true;
                    break FOR;
                case 'F':
                    inputFieldSeparator = grabValue(getArgumentError(" -F must be followed by a pattern for input field separation"));
                    break;
                case 'h':
                    shouldPrintUsage = true;
                    shouldRunInterpreter = false;
                    break;
                case 'i' :
                    inPlaceBackupExtension = grabOptionalValue();
                    if(inPlaceBackupExtension == null) inPlaceBackupExtension = "";
                    break FOR;
                case 'I':
                    String s = grabValue(getArgumentError("-I must be followed by a directory name to add to lib path"));
                    String[] ls = s.split(java.io.File.pathSeparator);
                    for (int i = 0; i < ls.length; i++) {
                        loadPaths.add(ls[i]);
                    }
                    break FOR;
                case 'K':
                    // FIXME: No argument seems to work for -K in MRI plus this should not
                    // siphon off additional args 'jruby -K ~/scripts/foo'.  Also better error
                    // processing.
                    String eArg = grabValue(getArgumentError("provide a value for -K"));
                    kcode = KCode.create(null, eArg);
                    break;
                case 'l':
                    processLineEnds = true;
                    break;
                case 'n':
                    assumeLoop = true;
                    break;
                case 'p':
                    assumePrinting = true;
                    assumeLoop = true;
                    break;
                case 'r':
                    requiredLibraries.add(grabValue(getArgumentError("-r must be followed by a package to require")));
                    break FOR;
                case 's' :
                    argvGlobalsOn = true;
                    break;
                case 'S':
                    runBinScript();
                    break FOR;
                case 'T' :{
                    String temp = grabOptionalValue();
                    int value = 1;

                    if(temp!=null) {
                        try {
                            value = Integer.parseInt(temp, 8);
                        } catch(Exception e) {
                            value = 1;
                        }
                    }

                    safeLevel = value;

                    break FOR;
                }
                case 'v':
                    verbose = Boolean.TRUE;
                    setShowVersion(true);
                    break;
                case 'w':
                    verbose = Boolean.TRUE;
                    break;
                case 'W': {
                    String temp = grabOptionalValue();
                    int value = 2;
                    if (null != temp) {
                        if (temp.equals("2")) {
                            value = 2;
                        } else if (temp.equals("1")) {
                            value = 1;
                        } else if (temp.equals("0")) {
                            value = 0;
                        } else {
                            MainExitException mee = new MainExitException(1, getArgumentError(" -W must be followed by either 0, 1, 2 or nothing"));
                            mee.setUsageError(true);
                            throw mee;
                        }
                    }
                    switch (value) {
                    case 0:
                        verbose = null;
                        break;
                    case 1:
                        verbose = Boolean.FALSE;
                        break;
                    case 2:
                        verbose = Boolean.TRUE;
                        break;
                    }


                    break FOR;
                }
                // FIXME: -x flag not supported
//                    case 'x' :
//                        break;
                case 'X':
                    String extendedOption = grabOptionalValue();

                    if (extendedOption == null) {
                        throw new MainExitException(0, "jruby: missing extended option, listing available options\n" + getExtendedHelp());
                    } else if (extendedOption.equals("-O")) {
                        objectSpaceEnabled = false;
                    } else if (extendedOption.equals("+O")) {
                        objectSpaceEnabled = true;
                    } else if (extendedOption.equals("-C")) {
                        compileMode = CompileMode.OFF;
                    } else if (extendedOption.equals("+C")) {
                        compileMode = CompileMode.FORCE;
                    } else {
                        MainExitException mee =
                                new MainExitException(1, "jruby: invalid extended option " + extendedOption + " (-X will list valid options)\n");
                        mee.setUsageError(true);

                        throw mee;
                    }
                    break FOR;
                case 'y':
                    parserDebug = true;
                    break FOR;
                case '-':
                    if (argument.equals("--command") || argument.equals("--bin")) {
                        characterIndex = argument.length();
                        runBinScript();
                        break;
                    } else if (argument.equals("--compat")) {
                        characterIndex = argument.length();
                        setCompatVersion(CompatVersion.getVersionFromString(grabValue(getArgumentError("--compat must be RUBY1_8 or RUBY1_9"))));
                        break FOR;
                    } else if (argument.equals("--copyright")) {
                        setShowCopyright(true);
                        shouldRunInterpreter = false;
                        break FOR;
                    } else if (argument.equals("--debug")) {
                        compileMode = CompileMode.OFF;
                        FULL_TRACE_ENABLED = true;
                        System.setProperty("jruby.reflection", "true");
                        break FOR;
                    } else if (argument.equals("--jdb")) {
                        debug = true;
                        verbose = Boolean.TRUE;
                        break;
                    } else if (argument.equals("--help")) {
                        shouldPrintUsage = true;
                        shouldRunInterpreter = false;
                        break;
                    } else if (argument.equals("--properties")) {
                        shouldPrintProperties = true;
                        shouldRunInterpreter = false;
                        break;
                    } else if (argument.equals("--version")) {
                        setShowVersion(true);
                        break FOR;
                    } else if (argument.equals("--bytecode")) {
                        setShowBytecode(true);
                        break FOR;
                    } else if (argument.equals("--fast")) {
                        compileMode = CompileMode.FORCE;
                        FASTEST_COMPILE_ENABLED = true;
                        FASTOPS_COMPILE_ENABLED = true;
                        FRAMELESS_COMPILE_ENABLED = true;
                        POSITIONLESS_COMPILE_ENABLED = true;
                        FASTCASE_COMPILE_ENABLED = true;
                        FASTSEND_COMPILE_ENABLED = true;
                        INLINE_DYNCALL_ENABLED = true;
                        RubyException.TRACE_TYPE = RubyException.RUBY_COMPILED;
                        break FOR;
                    } else if (argument.equals("--1.9")) {
                        setCompatVersion(CompatVersion.RUBY1_9);
                        break FOR;
                    } else if (argument.equals("--1.8")) {
                        setCompatVersion(CompatVersion.RUBY1_8);
                        break FOR;
                    } else {
                        if (argument.equals("--")) {
                            // ruby interpreter compatibilty
                            // Usage: ruby [switches] [--] [programfile] [arguments])
                            endOfArguments = true;
                            break;
                        }
                    }
                default:
                    throw new MainExitException(1, "jruby: unknown option " + argument);
                }
            }
        }

        private void runBinScript() {
            String scriptName = grabValue("jruby: provide a bin script to execute");
            if (scriptName.equals("irb")) {
                scriptName = "jirb";
            }

            scriptFileName = resolveScript(scriptName);

            if (scriptFileName.equals(scriptName)) {
                requiredLibraries.add("jruby/commands");
                inlineScript.append("JRuby::Commands." + scriptName);
                inlineScript.append("\n");
                hasInlineScript = true;
            }

            endOfArguments = true;
        }

        private String resolveScript(String scriptName) {
            // This try/catch is to allow failing over to the "commands" logic
            // when running from within a jruby-complete jar file, which has
            // jruby.home = a jar file URL that does not resolve correctly with
            // JRubyFile.create.
            try {
                // try cwd first
                File fullName = JRubyFile.create(currentDirectory, scriptName);
                if (fullName.exists() && fullName.isFile()) {
                    return fullName.getAbsolutePath();
                }

                fullName = JRubyFile.create(getJRubyHome(), "bin/" + scriptName);
                if (fullName.exists() && fullName.isFile()) {
                    return fullName.getAbsolutePath();
                }

                String path = System.getenv("PATH");
                if (path != null) {
                    String[] paths = path.split(System.getProperty("path.separator"));
                    for (int i = 0; i < paths.length; i++) {
                        fullName = JRubyFile.create(paths[i], scriptName);
                        if (fullName.exists() && fullName.isFile()) {
                            return fullName.getAbsolutePath();
                        }
                    }
                }
            } catch (IllegalArgumentException iae) {
                if (debug) System.err.println("warning: could not resolve -S script on filesystem: " + scriptName);
            }
            return scriptName;
        }

        private String grabValue(String errorMessage) {
            characterIndex++;
            if (characterIndex < arguments[argumentIndex].length()) {
                return arguments[argumentIndex].substring(characterIndex);
            }
            argumentIndex++;
            if (argumentIndex < arguments.length) {
                return arguments[argumentIndex];
            }

            MainExitException mee = new MainExitException(1, errorMessage);
            mee.setUsageError(true);

            throw mee;
        }

        private String grabOptionalValue() {
            characterIndex++;
            if (characterIndex < arguments[argumentIndex].length()) {
                return arguments[argumentIndex].substring(characterIndex);
            }
            return null;
        }
    }

    public byte[] inlineScript() {
        return inlineScript.toString().getBytes();
    }

    public List<String> requiredLibraries() {
        return requiredLibraries;
    }

    public List<String> loadPaths() {
        return loadPaths;
    }

    public boolean shouldRunInterpreter() {
        if(isShowVersion() && (hasInlineScript || scriptFileName != null)) {
            return true;
        }
        return isShouldRunInterpreter();
    }

    public boolean shouldPrintUsage() {
        return shouldPrintUsage;
    }

    public boolean shouldPrintProperties() {
        return shouldPrintProperties;
    }

    private boolean isSourceFromStdin() {
        return getScriptFileName() == null;
    }

    public boolean isInlineScript() {
        return hasInlineScript;
    }

    public InputStream getScriptSource() {
        try {
            // KCode.NONE is used because KCODE does not affect parse in Ruby 1.8
            // if Ruby 2.0 encoding pragmas are implemented, this will need to change
            if (hasInlineScript) {
                return new ByteArrayInputStream(inlineScript());
            } else if (isSourceFromStdin()) {
                // can't use -v and stdin
                if (isShowVersion()) {
                    return null;
                }
                return getInput();
            } else {
                File file = JRubyFile.create(getCurrentDirectory(), getScriptFileName());
                return new BufferedInputStream(new FileInputStream(file), 8192);
            }
        } catch (IOException e) {
            throw new MainExitException(1, "Error opening script file: " + e.getMessage());
        }
    }

    public String displayedFileName() {
        if (hasInlineScript) {
            if (scriptFileName != null) {
                return scriptFileName;
            } else {
                return "-e";
            }
        } else if (isSourceFromStdin()) {
            return "-";
        } else {
            return getScriptFileName();
        }
    }

    private void setScriptFileName(String scriptFileName) {
        this.scriptFileName = scriptFileName;
    }

    public String getScriptFileName() {
        return scriptFileName;
    }

    public boolean isBenchmarking() {
        return benchmarking;
    }

    public boolean isAssumeLoop() {
        return assumeLoop;
    }

    public boolean isAssumePrinting() {
        return assumePrinting;
    }

    public boolean isProcessLineEnds() {
        return processLineEnds;
    }

    public boolean isSplit() {
        return split;
    }

    public boolean isVerbose() {
        return verbose == Boolean.TRUE;
    }

    public Boolean getVerbose() {
        return verbose;
    }

    public boolean isDebug() {
        return debug;
    }

    public boolean isParserDebug() {
        return parserDebug;
    }

    public boolean isShowVersion() {
        return showVersion;
    }
    
    public boolean isShowBytecode() {
        return showBytecode;
    }

    public boolean isShowCopyright() {
        return showCopyright;
    }

    protected void setShowVersion(boolean showVersion) {
        this.showVersion = showVersion;
    }
    
    protected void setShowBytecode(boolean showBytecode) {
        this.showBytecode = showBytecode;
    }

    protected void setShowCopyright(boolean showCopyright) {
        this.showCopyright = showCopyright;
    }

    public boolean isShouldRunInterpreter() {
        return shouldRunInterpreter;
    }

    public boolean isShouldCheckSyntax() {
        return shouldCheckSyntax;
    }

    public String getInputFieldSeparator() {
        return inputFieldSeparator;
    }

    public KCode getKCode() {
        return kcode;
    }

    public String getRecordSeparator() {
        return recordSeparator;
    }

    public int getSafeLevel() {
        return safeLevel;
    }

    public void setRecordSeparator(String recordSeparator) {
        this.recordSeparator = recordSeparator;
    }

    public ClassCache getClassCache() {
        return classCache;
    }

    public String getInPlaceBackupExtention() {
        return inPlaceBackupExtension;
    }

    public void setClassCache(ClassCache classCache) {
        this.classCache = classCache;
    }

    public Map getOptionGlobals() {
        return optionGlobals;
    }
    
    public boolean isManagementEnabled() {
        return managementEnabled;
    }
    
    public Set getExcludedMethods() {
        return excludedMethods;
    }

    public ASTCompiler newCompiler() {
        if (getCompatVersion() == CompatVersion.RUBY1_8) {
            return new ASTCompiler();
        } else {
            return new ASTCompiler19();
        }
    }

    public String getThreadDumpSignal() {
        return threadDumpSignal;
    }
}
