// 
// Decompiled by Procyon v0.6.0
// 

package org.jline.console.impl;

import java.lang.reflect.Method;
import org.jline.utils.AttributedString;
import org.jline.reader.Candidate;
import org.jline.reader.ParsedLine;
import org.jline.reader.EndOfFileException;
import org.jline.reader.SyntaxError;
import org.jline.reader.EOFError;
import java.io.BufferedReader;
import org.jline.reader.impl.completer.AggregateCompleter;
import java.net.URL;
import java.net.HttpURLConnection;
import java.net.URI;
import java.awt.Desktop;
import org.jline.builtins.Options;
import java.nio.file.LinkOption;
import java.nio.charset.StandardCharsets;
import java.nio.charset.Charset;
import org.jline.builtins.Styles;
import org.jline.utils.AttributedStringBuilder;
import org.jline.console.CommandRegistry;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.nio.file.PathMatcher;
import java.nio.file.InvalidPathException;
import java.nio.file.NoSuchFileException;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.Objects;
import java.nio.file.Files;
import java.nio.file.FileVisitOption;
import java.nio.file.FileSystems;
import java.io.File;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import org.jline.reader.impl.completer.ArgumentCompleter;
import org.jline.builtins.Completers;
import org.jline.reader.impl.completer.NullCompleter;
import org.jline.reader.impl.completer.StringsCompleter;
import org.jline.reader.Completer;
import java.util.ArrayList;
import org.jline.terminal.Terminal;
import org.jline.reader.Parser;
import java.util.Iterator;
import java.nio.file.Paths;
import org.jline.utils.OSUtils;
import org.jline.utils.Log;
import org.jline.console.CommandInput;
import java.util.function.Function;
import java.util.Collection;
import java.util.HashSet;
import java.util.EnumSet;
import org.jline.console.CommandMethods;
import java.util.HashMap;
import java.io.IOException;
import java.util.Set;
import org.jline.builtins.ConfigurationPath;
import org.jline.console.Printer;
import org.jline.reader.LineReader;
import java.util.List;
import java.util.Map;
import java.nio.file.Path;
import java.util.function.Supplier;
import org.jline.console.SystemRegistry;
import org.jline.console.ScriptEngine;
import org.jline.console.ConsoleEngine;

public class ConsoleEngineImpl extends JlineCommandRegistry implements ConsoleEngine
{
    private static final String VAR_CONSOLE_OPTIONS = "CONSOLE_OPTIONS";
    private static final String VAR_PATH = "PATH";
    private static final String[] OPTION_HELP;
    private static final String OPTION_VERBOSE = "-v";
    private static final String SLURP_FORMAT_TEXT = "TEXT";
    private static final String END_HELP = "END_HELP";
    private static final int HELP_MAX_SIZE = 30;
    protected final ScriptEngine engine;
    private Exception exception;
    private SystemRegistry systemRegistry;
    private String scriptExtension;
    private final Supplier<Path> workDir;
    private final Map<String, String> aliases;
    private final Map<String, List<String>> pipes;
    private Path aliasFile;
    private LineReader reader;
    private boolean executing;
    private final Printer printer;
    
    public ConsoleEngineImpl(final ScriptEngine engine, final Printer printer, final Supplier<Path> workDir, final ConfigurationPath configPath) throws IOException {
        this(null, engine, printer, workDir, configPath);
    }
    
    public ConsoleEngineImpl(final Set<Command> commands, final ScriptEngine engine, final Printer printer, final Supplier<Path> workDir, final ConfigurationPath configPath) throws IOException {
        this.scriptExtension = "jline";
        this.aliases = new HashMap<String, String>();
        this.pipes = new HashMap<String, List<String>>();
        this.executing = false;
        this.engine = engine;
        this.workDir = workDir;
        this.printer = printer;
        final Map<Command, String> commandName = new HashMap<Command, String>();
        final Map<Command, CommandMethods> commandExecute = new HashMap<Command, CommandMethods>();
        Set<Command> cmds;
        if (commands == null) {
            cmds = new HashSet<Command>(EnumSet.allOf(Command.class));
        }
        else {
            cmds = new HashSet<Command>(commands);
        }
        for (final Command c : cmds) {
            commandName.put(c, c.name().toLowerCase());
        }
        commandExecute.put(Command.DEL, new CommandMethods(this::del, this::variableCompleter));
        commandExecute.put(Command.SHOW, new CommandMethods(this::show, this::variableCompleter));
        commandExecute.put(Command.PRNT, new CommandMethods(this::prnt, this::prntCompleter));
        commandExecute.put(Command.SLURP, new CommandMethods(this::slurpcmd, this::slurpCompleter));
        commandExecute.put(Command.ALIAS, new CommandMethods(this::aliascmd, this::aliasCompleter));
        commandExecute.put(Command.UNALIAS, new CommandMethods(this::unalias, this::unaliasCompleter));
        commandExecute.put(Command.DOC, new CommandMethods(this::doc, this::docCompleter));
        commandExecute.put(Command.PIPE, new CommandMethods(this::pipe, this::defaultCompleter));
        this.aliasFile = configPath.getUserConfig("aliases.json");
        if (this.aliasFile == null) {
            this.aliasFile = configPath.getUserConfig("aliases.json", true);
            if (this.aliasFile == null) {
                Log.warn("Failed to write in user config path!");
                this.aliasFile = (OSUtils.IS_WINDOWS ? Paths.get("NUL", new String[0]) : Paths.get("/dev/null", new String[0]));
            }
            this.persist(this.aliasFile, this.aliases);
        }
        else {
            this.aliases.putAll((Map<? extends String, ? extends String>)this.slurp(this.aliasFile));
        }
        this.registerCommands(commandName, commandExecute);
    }
    
    @Override
    public void setLineReader(final LineReader reader) {
        this.reader = reader;
    }
    
    private Parser parser() {
        return this.reader.getParser();
    }
    
    private Terminal terminal() {
        return this.systemRegistry.terminal();
    }
    
    @Override
    public boolean isExecuting() {
        return this.executing;
    }
    
    @Override
    public void setSystemRegistry(final SystemRegistry systemRegistry) {
        this.systemRegistry = systemRegistry;
    }
    
    @Override
    public void setScriptExtension(final String extension) {
        this.scriptExtension = extension;
    }
    
    @Override
    public boolean hasAlias(final String name) {
        return this.aliases.containsKey(name);
    }
    
    @Override
    public String getAlias(final String name) {
        return this.aliases.getOrDefault(name, null);
    }
    
    @Override
    public Map<String, List<String>> getPipes() {
        return this.pipes;
    }
    
    @Override
    public List<String> getNamedPipes() {
        final List<String> out = new ArrayList<String>();
        final List<String> opers = new ArrayList<String>();
        for (final String p : this.pipes.keySet()) {
            if (p.matches("[a-zA-Z0-9]+")) {
                out.add(p);
            }
            else {
                opers.add(p);
            }
        }
        opers.addAll(this.systemRegistry.getPipeNames());
        for (final Map.Entry<String, String> entry : this.aliases.entrySet()) {
            if (opers.contains(entry.getValue().split(" ")[0])) {
                out.add(entry.getKey());
            }
        }
        return out;
    }
    
    @Override
    public List<Completer> scriptCompleters() {
        final List<Completer> out = new ArrayList<Completer>();
        out.add(new ArgumentCompleter(new Completer[] { new StringsCompleter((Supplier<Collection<String>>)this::scriptNames), new Completers.OptionCompleter(NullCompleter.INSTANCE, (Function<String, Collection<Completers.OptDesc>>)this::commandOptions, 1) }));
        out.add(new ArgumentCompleter(new Completer[] { new StringsCompleter((Supplier<Collection<String>>)this::commandAliasNames), NullCompleter.INSTANCE }));
        return out;
    }
    
    private Set<String> commandAliasNames() {
        final Set<String> opers = this.pipes.keySet().stream().filter(p -> !p.matches("\\w+")).collect((Collector<? super Object, ?, Set<String>>)Collectors.toSet());
        opers.addAll(this.systemRegistry.getPipeNames());
        return this.aliases.entrySet().stream().filter(e -> !opers.contains(e.getValue().split(" ")[0])).map((Function<? super Object, ?>)Map.Entry::getKey).collect((Collector<? super Object, ?, Set<String>>)Collectors.toSet());
    }
    
    private Set<String> scriptNames() {
        return this.scripts().keySet();
    }
    
    @Override
    public Map<String, Boolean> scripts() {
        final Map<String, Boolean> out = new HashMap<String, Boolean>();
        try {
            final List<Path> scripts = new ArrayList<Path>();
            if (this.engine.hasVariable("PATH")) {
                final List<String> dirs = new ArrayList<String>();
                for (String file : (List)this.engine.get("PATH")) {
                    file = (file.startsWith("~") ? file.replace("~", System.getProperty("user.home")) : file);
                    final File dir = new File(file);
                    if (dir.exists() && dir.isDirectory()) {
                        dirs.add(file);
                    }
                }
                for (final String pp : dirs) {
                    for (final String e : this.scriptExtensions()) {
                        final String regex = pp + "/*." + e;
                        final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + regex);
                        try (final Stream<Path> pathStream = Files.walk(new File(regex).getParentFile().toPath(), new FileVisitOption[0])) {
                            final Stream<Path> stream = pathStream;
                            final PathMatcher obj = pathMatcher;
                            Objects.requireNonNull(obj);
                            final Stream<Path> filter = stream.filter(obj::matches);
                            final List<Path> obj2 = scripts;
                            Objects.requireNonNull((ArrayList)obj2);
                            filter.forEach(obj2::add);
                            if (pathStream == null) {
                                continue;
                            }
                        }
                    }
                }
            }
            for (final Path p : scripts) {
                final String name = p.getFileName().toString();
                final int idx = name.lastIndexOf(".");
                out.put(name.substring(0, idx), name.substring(idx + 1).equals(this.scriptExtension));
            }
        }
        catch (final NoSuchFileException e2) {
            this.error("Failed reading PATH. No file found: " + e2.getMessage());
        }
        catch (final InvalidPathException e3) {
            this.error("Failed reading PATH. Invalid path:");
            this.error(e3.toString());
        }
        catch (final Exception e4) {
            this.error("Failed reading PATH:");
            this.trace(e4);
            this.engine.put("exception", e4);
        }
        return out;
    }
    
    @Override
    public Object[] expandParameters(final String[] args) throws Exception {
        final Object[] out = new Object[args.length];
        final String regexPath = "(.*)\\$\\{(.*?)}(/.*)";
        for (int i = 0; i < args.length; ++i) {
            if (args[i].matches(regexPath)) {
                final Matcher matcher = Pattern.compile(regexPath).matcher(args[i]);
                if (!matcher.find()) {
                    throw new IllegalArgumentException();
                }
                out[i] = matcher.group(1) + this.engine.get(matcher.group(2)) + matcher.group(3);
            }
            else if (args[i].startsWith("${")) {
                final String expanded = this.expandName(args[i]);
                final String statement = expanded.startsWith("$") ? args[i].substring(2, args[i].length() - 1) : expanded;
                out[i] = this.engine.execute(statement);
            }
            else if (args[i].startsWith("$")) {
                out[i] = this.engine.get(this.expandName(args[i]));
            }
            else {
                out[i] = this.engine.deserialize(args[i]);
            }
        }
        return out;
    }
    
    private String expandToList(final String[] args) {
        return this.expandToList(Arrays.asList(args));
    }
    
    @Override
    public String expandToList(final List<String> params) {
        final StringBuilder sb = new StringBuilder();
        sb.append("[");
        boolean first = true;
        for (final String param : params) {
            if (!first) {
                sb.append(",");
            }
            if (param.equalsIgnoreCase("true") || param.equalsIgnoreCase("false") || param.equalsIgnoreCase("null")) {
                sb.append(param.toLowerCase());
            }
            else if (this.isNumber(param)) {
                sb.append(param);
            }
            else {
                sb.append(param.startsWith("$") ? param.substring(1) : this.quote(param));
            }
            first = false;
        }
        sb.append("]");
        return sb.toString();
    }
    
    protected String expandName(final String name) {
        final String regexVar = "[a-zA-Z_]+[a-zA-Z0-9_-]*";
        String out = name;
        if (name.matches("^\\$" + regexVar)) {
            out = name.substring(1);
        }
        else if (name.matches("^\\$\\{" + regexVar + "}.*")) {
            final Matcher matcher = Pattern.compile("^\\$\\{(" + regexVar + ")}(.*)").matcher(name);
            if (!matcher.find()) {
                throw new IllegalArgumentException();
            }
            out = matcher.group(1) + matcher.group(2);
        }
        return out;
    }
    
    private boolean isNumber(final String str) {
        return str.matches("-?\\d+(\\.\\d+)?");
    }
    
    private boolean isCodeBlock(final String line) {
        return line.contains("\n") && line.trim().endsWith("}");
    }
    
    private boolean isCommandLine(final String line) {
        String command = this.parser().getCommand(line);
        boolean out = false;
        if (command != null && command.startsWith(":")) {
            command = command.substring(1);
            if (this.hasAlias(command)) {
                command = this.getAlias(command);
            }
            if (this.systemRegistry.hasCommand(command)) {
                out = true;
            }
            else {
                final ScriptFile sf = new ScriptFile(command, "", new String[0]);
                if (sf.isScript()) {
                    out = true;
                }
            }
        }
        return out;
    }
    
    private String quote(final String var) {
        if ((var.startsWith("\"") && var.endsWith("\"")) || (var.startsWith("'") && var.endsWith("'"))) {
            return var;
        }
        if (var.contains("\\\"")) {
            return "'" + var + "'";
        }
        return "\"" + var + "\"";
    }
    
    private List<String> scriptExtensions() {
        final List<String> extensions = new ArrayList<String>(this.engine.getExtensions());
        extensions.add(this.scriptExtension);
        return extensions;
    }
    
    @Override
    public Object execute(final Path script, final String cmdLine, final String[] args) throws Exception {
        final ScriptFile file = new ScriptFile(script, cmdLine, args);
        file.execute();
        return file.getResult();
    }
    
    @Override
    public String expandCommandLine(final String line) {
        String out;
        if (this.isCommandLine(line)) {
            final StringBuilder sb = new StringBuilder();
            final List<String> ws = this.parser().parse(line, 0, Parser.ParseContext.COMPLETE).words();
            final int idx = ws.get(0).lastIndexOf(":");
            if (idx > 0) {
                sb.append(ws.get(0).substring(0, idx));
            }
            final String[] argv = new String[ws.size()];
            for (int i = 1; i < ws.size(); ++i) {
                argv[i] = ws.get(i);
                if (argv[i].startsWith("${")) {
                    final Matcher argvMatcher = Pattern.compile("\\$\\{(.*)}").matcher(argv[i]);
                    if (argvMatcher.find()) {
                        argv[i] = argv[i].replace(argv[i], argvMatcher.group(1));
                    }
                }
                else if (argv[i].startsWith("$")) {
                    argv[i] = argv[i].substring(1);
                }
                else {
                    argv[i] = this.quote(argv[i]);
                }
            }
            final String cmd = this.hasAlias(ws.get(0).substring(idx + 1)) ? this.getAlias(ws.get(0).substring(idx + 1)) : ws.get(0).substring(idx + 1);
            sb.append(SystemRegistry.class.getCanonicalName()).append(".get().invoke('").append(cmd).append("'");
            for (int j = 1; j < argv.length; ++j) {
                sb.append(", ");
                sb.append(argv[j]);
            }
            sb.append(")");
            out = sb.toString();
        }
        else {
            out = line;
        }
        return out;
    }
    
    @Override
    public Object execute(final String cmd, String line, final String[] args) throws Exception {
        if (line.trim().startsWith("#")) {
            return null;
        }
        Object out = null;
        final ScriptFile file = new ScriptFile(cmd, line, args);
        if (file.execute()) {
            out = file.getResult();
        }
        else {
            line = line.trim();
            if (this.isCodeBlock(line)) {
                final StringBuilder sb = new StringBuilder();
                for (final String s : line.split("\\r?\\n")) {
                    sb.append(this.expandCommandLine(s));
                    sb.append("\n");
                }
                line = sb.toString();
            }
            if (this.engine.hasVariable(line)) {
                out = this.engine.get(line);
            }
            else if (this.parser().getVariable(line) == null) {
                out = this.engine.execute(line);
                this.engine.put("_", out);
            }
            else {
                this.engine.execute(line);
            }
        }
        return out;
    }
    
    @Override
    public void purge() {
        this.engine.del("_*");
    }
    
    @Override
    public void putVariable(final String name, final Object value) {
        this.engine.put(name, value);
    }
    
    @Override
    public Object getVariable(final String name) {
        if (!this.engine.hasVariable(name)) {
            throw new IllegalArgumentException("Variable " + name + " does not exists!");
        }
        return this.engine.get(name);
    }
    
    @Override
    public boolean hasVariable(final String name) {
        return this.engine.hasVariable(name);
    }
    
    @Override
    public boolean executeWidget(final Object function) {
        this.engine.put("_reader", this.reader);
        this.engine.put("_widgetFunction", function);
        try {
            if (this.engine.getEngineName().equals("GroovyEngine")) {
                this.engine.execute("def _buffer() {_reader.getBuffer()}");
                this.engine.execute("def _widget(w) {_reader.callWidget(w)}");
            }
            this.engine.execute("_widgetFunction()");
        }
        catch (final Exception e) {
            this.trace(e);
            return false;
        }
        finally {
            this.purge();
        }
        return true;
    }
    
    private Map<String, Object> consoleOptions() {
        return this.engine.hasVariable("CONSOLE_OPTIONS") ? ((Map)this.engine.get("CONSOLE_OPTIONS")) : new HashMap<String, Object>();
    }
    
    @Override
    public <T> T consoleOption(final String option, final T defval) {
        T out = defval;
        try {
            out = (T)this.consoleOptions().getOrDefault(option, defval);
        }
        catch (final Exception e) {
            this.trace(new Exception("Bad CONSOLE_OPTION value: " + e.getMessage()));
        }
        return out;
    }
    
    @Override
    public void setConsoleOption(final String name, final Object value) {
        this.consoleOptions().put(name, value);
    }
    
    private boolean consoleOption(final String option) {
        boolean out = false;
        try {
            out = this.consoleOptions().containsKey(option);
        }
        catch (final Exception e) {
            this.trace(new Exception("Bad CONSOLE_OPTION value: " + e.getMessage()));
        }
        return out;
    }
    
    @Override
    public ExecutionResult postProcess(final String line, final Object result, final String output) {
        final Object _output = (output != null && !output.trim().isEmpty() && !this.consoleOption("no-splittedOutput")) ? output.split("\\r?\\n") : output;
        final String consoleVar = this.parser().getVariable(line);
        if (consoleVar != null && result != null) {
            this.engine.put("output", _output);
        }
        ExecutionResult out;
        if (this.systemRegistry.hasCommand(this.parser().getCommand(line))) {
            out = this.postProcess(line, (consoleVar != null && result == null) ? _output : result);
        }
        else {
            final Object _result = (result == null) ? _output : result;
            final int status = this.saveResult(consoleVar, _result);
            out = new ExecutionResult(status, (consoleVar != null && !consoleVar.startsWith("_")) ? null : _result);
        }
        return out;
    }
    
    private ExecutionResult postProcess(final String line, final Object result) {
        int status = 0;
        Object out = (result instanceof String && ((String)result).trim().isEmpty()) ? null : result;
        final String consoleVar = this.parser().getVariable(line);
        if (consoleVar != null) {
            status = this.saveResult(consoleVar, result);
            out = null;
        }
        else if (!this.parser().getCommand(line).equals("show")) {
            if (result != null) {
                status = this.saveResult("_", result);
            }
            else {
                status = 1;
            }
        }
        return new ExecutionResult(status, out);
    }
    
    @Override
    public ExecutionResult postProcess(final Object result) {
        return new ExecutionResult(this.saveResult(null, result), result);
    }
    
    private int saveResult(final String var, final Object result) {
        int out;
        try {
            this.engine.put("_executionResult", result);
            if (var != null) {
                if (var.contains(".") || var.contains("[")) {
                    this.engine.execute(var + " = _executionResult");
                }
                else {
                    this.engine.put(var, result);
                }
            }
            out = (int)this.engine.execute("_executionResult ? 0 : 1");
        }
        catch (final Exception e) {
            this.trace(e);
            out = 1;
        }
        return out;
    }
    
    @Override
    public Object invoke(final CommandRegistry.CommandSession session, final String command, final Object... args) throws Exception {
        this.exception = null;
        Object out = null;
        if (this.hasCommand(command)) {
            out = this.getCommandMethods(command).execute().apply(new CommandInput(command, args, session));
        }
        else {
            final String[] _args = new String[args.length];
            for (int i = 0; i < args.length; ++i) {
                if (!(args[i] instanceof String)) {
                    throw new IllegalArgumentException();
                }
                _args[i] = args[i].toString();
            }
            final ScriptFile sf = new ScriptFile(command, "", _args);
            if (sf.execute()) {
                out = sf.getResult();
            }
        }
        if (this.exception != null) {
            throw this.exception;
        }
        return out;
    }
    
    @Override
    public void trace(final Object object) {
        Object toPrint = object;
        final int level = this.consoleOption("trace", 0);
        final Map<String, Object> options = new HashMap<String, Object>();
        if (level < 2) {
            options.put("exception", "message");
        }
        if (level == 0) {
            if (!(object instanceof Throwable)) {
                toPrint = null;
            }
        }
        else if (level == 1) {
            if (object instanceof SystemRegistryImpl.CommandData) {
                toPrint = ((SystemRegistryImpl.CommandData)object).rawLine();
            }
        }
        else if (level > 1 && object instanceof SystemRegistryImpl.CommandData) {
            toPrint = object.toString();
        }
        this.printer.println(options, toPrint);
    }
    
    private void error(final String message) {
        final AttributedStringBuilder asb = new AttributedStringBuilder();
        asb.styled(Styles.prntStyle().resolve(".em"), message);
        asb.println(this.terminal());
    }
    
    @Override
    public void println(final Object object) {
        this.printer.println(object);
    }
    
    private Object show(final CommandInput input) {
        final String[] usage = { "show -  list console variables", "Usage: show [VARIABLE]", "  -? --help                       Displays command help" };
        try {
            this.parseOptions(usage, input.args());
            final Map<String, Object> options = new HashMap<String, Object>();
            options.put("maxDepth", 0);
            this.printer.println(options, this.engine.find((input.args().length > 0) ? input.args()[0] : null));
        }
        catch (final Exception e) {
            this.exception = e;
        }
        return null;
    }
    
    private Object del(final CommandInput input) {
        final String[] usage = { "del -  delete console variables, methods, classes and imports", "Usage: del [var1] ...", "  -? --help                       Displays command help" };
        try {
            this.parseOptions(usage, input.args());
            this.engine.del(input.args());
        }
        catch (final Exception e) {
            this.exception = e;
        }
        return null;
    }
    
    private Object prnt(final CommandInput input) {
        final Exception result = this.printer.prntCommand(input);
        if (result != null) {
            this.exception = result;
        }
        return null;
    }
    
    private Object slurpcmd(final CommandInput input) {
        final String[] usage = { "slurp -  slurp file or string variable context to object", "Usage: slurp [OPTIONS] file|variable", "  -? --help                       Displays command help", "  -e --encoding=ENCODING          Encoding (default UTF-8)", "  -f --format=FORMAT              Serialization format" };
        Object out = null;
        try {
            final Options opt = this.parseOptions(usage, input.xargs());
            if (!opt.args().isEmpty()) {
                final Object _arg = opt.argObjects().get(0);
                if (!(_arg instanceof String)) {
                    throw new IllegalArgumentException("Invalid parameter type: " + _arg.getClass().getSimpleName());
                }
                final String arg = (String)_arg;
                final Charset encoding = opt.isSet("encoding") ? Charset.forName(opt.get("encoding")) : StandardCharsets.UTF_8;
                final String format = opt.isSet("format") ? opt.get("format") : this.engine.getSerializationFormats().get(0);
                try {
                    final Path path = Paths.get(arg, new String[0]);
                    if (Files.exists(path, new LinkOption[0])) {
                        if (!format.equals("TEXT")) {
                            out = this.slurp(path, encoding, format);
                        }
                        else {
                            out = Files.readAllLines(Paths.get(arg, new String[0]), encoding);
                        }
                    }
                    else if (!format.equals("TEXT")) {
                        out = this.engine.deserialize(arg, format);
                    }
                    else {
                        out = arg.split("\n");
                    }
                }
                catch (final Exception e) {
                    out = this.engine.deserialize(arg, format);
                }
            }
        }
        catch (final Exception e2) {
            this.exception = e2;
        }
        return out;
    }
    
    @Override
    public void persist(final Path file, final Object object) {
        this.engine.persist(file, object);
    }
    
    @Override
    public Object slurp(final Path file) throws IOException {
        return this.slurp(file, StandardCharsets.UTF_8, this.engine.getSerializationFormats().get(0));
    }
    
    private Object slurp(final Path file, final Charset encoding, final String format) throws IOException {
        final byte[] encoded = Files.readAllBytes(file);
        return this.engine.deserialize(new String(encoded, encoding), format);
    }
    
    private Object aliascmd(final CommandInput input) {
        final String[] usage = { "alias -  create command alias", "Usage: alias [ALIAS] [COMMANDLINE]", "  -? --help                       Displays command help" };
        Object out = null;
        try {
            final Options opt = this.parseOptions(usage, input.args());
            final List<String> args = opt.args();
            if (args.isEmpty()) {
                out = this.aliases;
            }
            else if (args.size() == 1) {
                out = this.aliases.getOrDefault(args.get(0), null);
            }
            else {
                String alias = String.join(" ", args.subList(1, args.size()));
                for (int j = 0; j < 10; ++j) {
                    alias = alias.replaceAll("%" + j, "\\$" + j);
                    alias = alias.replaceAll("%\\{" + j + "}", "\\$\\{" + j + "\\}");
                    alias = alias.replaceAll("%\\{" + j + ":-", "\\$\\{" + j + ":-");
                }
                alias = alias.replaceAll("%@", "\\$@");
                alias = alias.replaceAll("%\\{@}", "\\${@}");
                this.aliases.put(args.get(0), alias);
                this.persist(this.aliasFile, this.aliases);
            }
        }
        catch (final Exception e) {
            this.exception = e;
        }
        return out;
    }
    
    private Object unalias(final CommandInput input) {
        final String[] usage = { "unalias -  remove command alias", "Usage: unalias [ALIAS...]", "  -? --help                       Displays command help" };
        try {
            final Options opt = this.parseOptions(usage, input.args());
            for (final String a : opt.args()) {
                this.aliases.remove(a);
            }
            this.persist(this.aliasFile, this.aliases);
        }
        catch (final Exception e) {
            this.exception = e;
        }
        return null;
    }
    
    private Object pipe(final CommandInput input) {
        final String[] usage = { "pipe -  create/delete pipe operator", "Usage: pipe [OPERATOR] [PREFIX] [POSTFIX]", "       pipe --list", "       pipe --delete [OPERATOR...]", "  -? --help                       Displays command help", "  -d --delete                     Delete pipe operators", "  -l --list                       List pipe operators" };
        try {
            final Options opt = this.parseOptions(usage, input.args());
            final Map<String, Object> options = new HashMap<String, Object>();
            if (opt.isSet("delete")) {
                if (opt.args().size() == 1 && opt.args().get(0).equals("*")) {
                    this.pipes.clear();
                }
                else {
                    for (final String p : opt.args()) {
                        this.pipes.remove(p.trim());
                    }
                }
            }
            else if (opt.isSet("list") || opt.args().isEmpty()) {
                options.put("maxDepth", 0);
                this.printer.println(options, this.pipes);
            }
            else if (opt.args().size() != 3) {
                this.exception = new IllegalArgumentException("Bad number of arguments!");
            }
            else if (this.systemRegistry.getPipeNames().contains(opt.args().get(0))) {
                this.exception = new IllegalArgumentException("Reserved pipe operator");
            }
            else {
                final List<String> fixes = new ArrayList<String>();
                fixes.add(opt.args().get(1));
                fixes.add(opt.args().get(2));
                this.pipes.put(opt.args().get(0), fixes);
            }
        }
        catch (final Exception e) {
            this.exception = e;
        }
        return null;
    }
    
    private Object doc(final CommandInput input) {
        final String[] usage = { "doc -  open document on browser", "Usage: doc [OBJECT]", "  -? --help                       Displays command help" };
        try {
            this.parseOptions(usage, input.xargs());
            if (input.xargs().length == 0) {
                return null;
            }
            if (!Desktop.isDesktopSupported()) {
                throw new IllegalStateException("Desktop is not supported!");
            }
            Map<String, Object> docs;
            try {
                docs = this.consoleOption("docs", (Map<String, Object>)null);
            }
            catch (final Exception e) {
                final Exception exception = new IllegalStateException("Bad documents configuration!");
                exception.addSuppressed(e);
                throw exception;
            }
            if (docs == null) {
                throw new IllegalStateException("No documents configuration!");
            }
            boolean done = false;
            final Object arg = input.xargs()[0];
            if (arg instanceof String) {
                final String address = docs.get(input.args()[0]);
                if (address != null) {
                    done = true;
                    if (!this.urlExists(address)) {
                        throw new IllegalArgumentException("Document not found: " + address);
                    }
                    Desktop.getDesktop().browse(new URI(address));
                }
            }
            if (!done) {
                String name;
                if (arg instanceof String && ((String)arg).matches("([a-z]+\\.)+[A-Z][a-zA-Z]+")) {
                    name = (String)arg;
                }
                else {
                    name = arg.getClass().getCanonicalName();
                }
                name = name.replaceAll("\\.", "/") + ".html";
                Object doc = null;
                for (final Map.Entry<String, Object> entry : docs.entrySet()) {
                    if (name.matches(entry.getKey())) {
                        doc = entry.getValue();
                        break;
                    }
                }
                if (doc == null) {
                    throw new IllegalArgumentException("No document configuration for " + name);
                }
                String url = name;
                if (doc instanceof Collection) {
                    for (final Object o : (Collection)doc) {
                        url = o + name;
                        if (this.urlExists(url)) {
                            Desktop.getDesktop().browse(new URI(url));
                            done = true;
                        }
                    }
                }
                else {
                    url = doc + name;
                    if (this.urlExists(url)) {
                        Desktop.getDesktop().browse(new URI(url));
                        done = true;
                    }
                }
                if (!done) {
                    throw new IllegalArgumentException("Document not found: " + url);
                }
            }
        }
        catch (final Exception e2) {
            this.exception = e2;
        }
        return null;
    }
    
    private boolean urlExists(final String weburl) {
        try {
            final URL url = URI.create(weburl).toURL();
            final HttpURLConnection huc = (HttpURLConnection)url.openConnection();
            huc.setRequestMethod("HEAD");
            return huc.getResponseCode() == 200;
        }
        catch (final Exception e) {
            return false;
        }
    }
    
    private List<Completer> slurpCompleter(final String command) {
        final List<Completer> completers = new ArrayList<Completer>();
        final List<Completers.OptDesc> optDescs = this.commandOptions(command);
        for (final Completers.OptDesc o : optDescs) {
            if (o.shortOption() != null && o.shortOption().equals("-f")) {
                final List<String> formats = new ArrayList<String>(this.engine.getDeserializationFormats());
                formats.add("TEXT");
                o.setValueCompleter(new StringsCompleter(formats));
                break;
            }
        }
        final AggregateCompleter argCompleter = new AggregateCompleter(new Completer[] { new Completers.FilesCompleter(this.workDir), new VariableReferenceCompleter(this.engine) });
        completers.add(new ArgumentCompleter(new Completer[] { NullCompleter.INSTANCE, new Completers.OptionCompleter(Arrays.asList(argCompleter, NullCompleter.INSTANCE), optDescs, 1) }));
        return completers;
    }
    
    private List<Completer> variableCompleter(final String command) {
        final List<Completer> completers = new ArrayList<Completer>();
        completers.add(new StringsCompleter(() -> this.engine.find().keySet()));
        return completers;
    }
    
    private List<Completer> prntCompleter(final String command) {
        final List<Completer> completers = new ArrayList<Completer>();
        completers.add(new ArgumentCompleter(new Completer[] { NullCompleter.INSTANCE, new Completers.OptionCompleter(Arrays.asList(new VariableReferenceCompleter(this.engine), NullCompleter.INSTANCE), (Function<String, Collection<Completers.OptDesc>>)this::commandOptions, 1) }));
        return completers;
    }
    
    private List<Completer> aliasCompleter(final String command) {
        final List<Completer> completers = new ArrayList<Completer>();
        final ArrayList<StringsCompleter> list;
        final List<Completer> params = (List<Completer>)(list = new ArrayList<StringsCompleter>());
        final Map<String, String> aliases = this.aliases;
        Objects.requireNonNull(aliases);
        list.add(new StringsCompleter((Supplier<Collection<String>>)aliases::keySet));
        params.add(new AliasValueCompleter(this.aliases));
        completers.add(new ArgumentCompleter(new Completer[] { NullCompleter.INSTANCE, new Completers.OptionCompleter(params, (Function<String, Collection<Completers.OptDesc>>)this::commandOptions, 1) }));
        return completers;
    }
    
    private List<Completer> unaliasCompleter(final String command) {
        final ArrayList<ArgumentCompleter> list;
        final List<Completer> completers = (List<Completer>)(list = new ArrayList<ArgumentCompleter>());
        final Completer[] completers2 = { NullCompleter.INSTANCE, null };
        final int n = 1;
        final Map<String, String> aliases = this.aliases;
        Objects.requireNonNull(aliases);
        completers2[n] = new Completers.OptionCompleter(new StringsCompleter((Supplier<Collection<String>>)aliases::keySet), (Function<String, Collection<Completers.OptDesc>>)this::commandOptions, 1);
        list.add(new ArgumentCompleter(completers2));
        return completers;
    }
    
    private List<String> docs() {
        final List<String> out = new ArrayList<String>();
        final Map<String, String> docs = this.consoleOption("docs", (Map<String, String>)null);
        if (docs == null) {
            return out;
        }
        for (final String v : this.engine.find().keySet()) {
            out.add("$" + v);
        }
        if (!docs.isEmpty()) {
            for (final String d : docs.keySet()) {
                if (d.matches("\\w+")) {
                    out.add(d);
                }
            }
        }
        return out;
    }
    
    private List<Completer> docCompleter(final String command) {
        final List<Completer> completers = new ArrayList<Completer>();
        completers.add(new ArgumentCompleter(new Completer[] { NullCompleter.INSTANCE, new Completers.OptionCompleter(Arrays.asList(new StringsCompleter((Supplier<Collection<String>>)this::docs), NullCompleter.INSTANCE), (Function<String, Collection<Completers.OptDesc>>)this::commandOptions, 1) }));
        return completers;
    }
    
    static {
        OPTION_HELP = new String[] { "-?", "--help" };
    }
    
    public enum Command
    {
        SHOW, 
        DEL, 
        PRNT, 
        ALIAS, 
        PIPE, 
        UNALIAS, 
        DOC, 
        SLURP;
    }
    
    private class ScriptFile
    {
        private Path script;
        private String extension;
        private String cmdLine;
        private String[] args;
        private boolean verbose;
        private Object result;
        
        public ScriptFile(String command, final String cmdLine, final String[] args) {
            this.extension = "";
            this.cmdLine = cmdLine;
            try {
                if (!ConsoleEngineImpl.this.parser().validCommandName(command)) {
                    command = cmdLine.split("\\s+")[0];
                    this.extension = this.fileExtension(command);
                    if (this.isScript()) {
                        this.extension = "";
                        this.script = Paths.get(command, new String[0]);
                        if (Files.exists(this.script, new LinkOption[0])) {
                            this.scriptExtension(command);
                        }
                    }
                }
                else {
                    this.script = Paths.get(command, new String[0]);
                    if (Files.exists(this.script, new LinkOption[0])) {
                        this.scriptExtension(command);
                    }
                    else if (ConsoleEngineImpl.this.engine.hasVariable("PATH")) {
                        boolean found = false;
                        for (final String p : (List)ConsoleEngineImpl.this.engine.get("PATH")) {
                            for (final String e : ConsoleEngineImpl.this.scriptExtensions()) {
                                final String file = command + "." + e;
                                final Path path = Paths.get(p, file);
                                if (Files.exists(path, new LinkOption[0])) {
                                    this.script = path;
                                    this.extension = e;
                                    found = true;
                                    break;
                                }
                            }
                            if (found) {
                                break;
                            }
                        }
                    }
                }
                this.doArgs(args);
            }
            catch (final Exception e2) {
                Log.trace("Not a script file: " + command);
            }
        }
        
        public ScriptFile(final Path script, final String cmdLine, final String[] args) {
            this.extension = "";
            if (!Files.exists(script, new LinkOption[0])) {
                throw new IllegalArgumentException("Script file not found!");
            }
            this.script = script;
            this.cmdLine = cmdLine;
            this.scriptExtension(script.getFileName().toString());
            this.doArgs(args);
        }
        
        private String fileExtension(final String fileName) {
            return fileName.contains(".") ? fileName.substring(fileName.lastIndexOf(".") + 1) : "";
        }
        
        private void scriptExtension(final String command) {
            this.extension = this.fileExtension(this.script.getFileName().toString());
            if (!this.isEngineScript() && !this.isConsoleScript()) {
                throw new IllegalArgumentException("Command not found: " + command);
            }
        }
        
        private void doArgs(final String[] args) {
            final List<String> _args = new ArrayList<String>();
            if (this.isConsoleScript()) {
                _args.add(this.script.toAbsolutePath().toString());
            }
            for (final String a : args) {
                if (this.isConsoleScript()) {
                    if (!a.equals("-v")) {
                        _args.add(a);
                    }
                    else {
                        this.verbose = true;
                    }
                }
                else {
                    _args.add(a);
                }
            }
            this.args = _args.toArray(new String[0]);
        }
        
        private boolean isEngineScript() {
            return ConsoleEngineImpl.this.engine.getExtensions().contains(this.extension);
        }
        
        private boolean isConsoleScript() {
            return ConsoleEngineImpl.this.scriptExtension.equals(this.extension);
        }
        
        private boolean isScript() {
            return ConsoleEngineImpl.this.engine.getExtensions().contains(this.extension) || ConsoleEngineImpl.this.scriptExtension.equals(this.extension);
        }
        
        public boolean execute() throws Exception {
            if (!this.isScript()) {
                return false;
            }
            this.result = null;
            if (Arrays.asList(this.args).contains(ConsoleEngineImpl.OPTION_HELP[0]) || Arrays.asList(this.args).contains(ConsoleEngineImpl.OPTION_HELP[1])) {
                try (final BufferedReader br = Files.newBufferedReader(this.script)) {
                    int size = 0;
                    final StringBuilder usage = new StringBuilder();
                    boolean helpEnd = false;
                    boolean headComment = false;
                    String l;
                    while ((l = br.readLine()) != null) {
                        ++size;
                        String line;
                        l = (line = l.replaceAll("\\s+$", ""));
                        if (size > 30 || line.endsWith("END_HELP")) {
                            helpEnd = line.endsWith("END_HELP");
                            break;
                        }
                        if (headComment || size < 3) {
                            final String ltr = l.trim();
                            if (ltr.startsWith("*") || ltr.startsWith("#")) {
                                headComment = true;
                                line = ((ltr.length() > 1) ? ltr.substring(2) : "");
                            }
                            else if (ltr.startsWith("/*") || ltr.startsWith("//")) {
                                headComment = true;
                                line = ((ltr.length() > 2) ? ltr.substring(3) : "");
                            }
                        }
                        usage.append(line).append('\n');
                    }
                    if (usage.length() > 0) {
                        usage.append("\n");
                        if (!helpEnd) {
                            usage.insert(0, "\n");
                        }
                        throw new Options.HelpException(usage.toString());
                    }
                    this.internalExecute();
                }
            }
            else {
                this.internalExecute();
            }
            return true;
        }
        
        private String expandParameterName(final String parameter) {
            if (parameter.startsWith("$")) {
                return ConsoleEngineImpl.this.expandName(parameter);
            }
            if (ConsoleEngineImpl.this.isNumber(parameter)) {
                return parameter;
            }
            return ConsoleEngineImpl.this.quote(parameter);
        }
        
        private void internalExecute() throws Exception {
            if (this.isEngineScript()) {
                this.result = ConsoleEngineImpl.this.engine.execute(this.script, ConsoleEngineImpl.this.expandParameters(this.args));
            }
            else if (this.isConsoleScript()) {
                ConsoleEngineImpl.this.executing = true;
                boolean done = true;
                String line = "";
                try (final BufferedReader br = Files.newBufferedReader(this.script)) {
                    String l;
                    while ((l = br.readLine()) != null) {
                        if (!l.trim().isEmpty() && !l.trim().startsWith("#")) {
                            try {
                                line += l;
                                ConsoleEngineImpl.this.parser().parse(line, line.length() + 1, Parser.ParseContext.ACCEPT_LINE);
                                done = true;
                                for (int i = 1; i < this.args.length; ++i) {
                                    line = line.replaceAll("\\s\\$" + i + "\\b", " " + this.expandParameterName(this.args[i]) + " ");
                                    line = line.replaceAll("\\$\\{" + i + "(|:-.*)}", this.expandParameterName(this.args[i]));
                                }
                                line = line.replaceAll("\\$\\{@}", ConsoleEngineImpl.this.expandToList(this.args));
                                line = line.replaceAll("\\$@", ConsoleEngineImpl.this.expandToList(this.args));
                                line = line.replaceAll("\\s\\$\\d\\b", "");
                                line = line.replaceAll("\\$\\{\\d+}", "");
                                final Matcher matcher = Pattern.compile("\\$\\{\\d+:-(.*?)}").matcher(line);
                                if (matcher.find()) {
                                    line = matcher.replaceAll(this.expandParameterName(matcher.group(1)));
                                }
                                if (this.verbose) {
                                    final AttributedStringBuilder asb = new AttributedStringBuilder();
                                    asb.styled(Styles.prntStyle().resolve(".vs"), line);
                                    asb.toAttributedString().println(ConsoleEngineImpl.this.terminal());
                                    ConsoleEngineImpl.this.terminal().flush();
                                }
                                ConsoleEngineImpl.this.println(ConsoleEngineImpl.this.systemRegistry.execute(line));
                                line = "";
                                continue;
                            }
                            catch (final EOFError e) {
                                done = false;
                                line += "\n";
                                continue;
                            }
                            catch (final SyntaxError e2) {
                                throw e2;
                            }
                            catch (final EndOfFileException e3) {
                                done = true;
                                this.result = ConsoleEngineImpl.this.engine.get("_return");
                                ConsoleEngineImpl.this.postProcess(this.cmdLine, this.result);
                            }
                            catch (final Exception e4) {
                                ConsoleEngineImpl.this.executing = false;
                                throw new IllegalArgumentException(line + "\n" + e4.getMessage());
                            }
                            break;
                        }
                        done = true;
                    }
                    if (!done) {
                        ConsoleEngineImpl.this.executing = false;
                        throw new IllegalArgumentException("Incompleted command: \n" + line);
                    }
                    ConsoleEngineImpl.this.executing = false;
                }
            }
        }
        
        public Object getResult() {
            return this.result;
        }
        
        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder();
            sb.append("[");
            try {
                sb.append("script:").append(this.script.normalize());
            }
            catch (final Exception e) {
                sb.append(e.getMessage());
            }
            sb.append(", ");
            sb.append("extension:").append(this.extension);
            sb.append(", ");
            sb.append("cmdLine:").append(this.cmdLine);
            sb.append(", ");
            sb.append("args:").append(Arrays.asList(this.args));
            sb.append(", ");
            sb.append("verbose:").append(this.verbose);
            sb.append(", ");
            sb.append("result:").append(this.result);
            sb.append("]");
            return sb.toString();
        }
    }
    
    protected static class VariableReferenceCompleter implements Completer
    {
        private final ScriptEngine engine;
        
        public VariableReferenceCompleter(final ScriptEngine engine) {
            this.engine = engine;
        }
        
        @Override
        public void complete(final LineReader reader, final ParsedLine commandLine, final List<Candidate> candidates) {
            assert commandLine != null;
            assert candidates != null;
            final String word = commandLine.word();
            try {
                if (!word.contains(".") && !word.contains("}")) {
                    for (final String v : this.engine.find().keySet()) {
                        final String c = "${" + v + "}";
                        candidates.add(new Candidate(AttributedString.stripAnsi(c), c, null, null, null, null, false));
                    }
                }
                else if (word.startsWith("${") && word.contains("}") && word.contains(".")) {
                    final String var = word.substring(2, word.indexOf(125));
                    if (this.engine.hasVariable(var)) {
                        final String curBuf = word.substring(0, word.lastIndexOf("."));
                        final String objStatement = curBuf.replace("${", "").replace("}", "");
                        final Object obj = curBuf.contains(".") ? this.engine.execute(objStatement) : this.engine.get(var);
                        final Map<?, ?> map = (obj instanceof Map) ? ((Map)obj) : null;
                        Set<String> identifiers = new HashSet<String>();
                        if (map != null && !map.isEmpty() && map.keySet().iterator().next() instanceof String) {
                            identifiers = (Set<String>)map.keySet();
                        }
                        else if (map == null && obj != null) {
                            identifiers = this.getClassMethodIdentifiers(obj.getClass());
                        }
                        for (final String key : identifiers) {
                            candidates.add(new Candidate(AttributedString.stripAnsi(curBuf + "." + key), key, null, null, null, null, false));
                        }
                    }
                }
            }
            catch (final Exception ex) {}
        }
        
        private Set<String> getClassMethodIdentifiers(Class<?> clazz) {
            final Set<String> out = new HashSet<String>();
            do {
                for (final Method m : clazz.getMethods()) {
                    if (!m.isSynthetic() && m.getParameterCount() == 0) {
                        final String name = m.getName();
                        if (name.matches("get[A-Z].*")) {
                            out.add(this.convertGetMethod2identifier(name));
                        }
                    }
                }
                clazz = clazz.getSuperclass();
            } while (clazz != null);
            return out;
        }
        
        private String convertGetMethod2identifier(final String name) {
            final char[] c = name.substring(3).toCharArray();
            c[0] = Character.toLowerCase(c[0]);
            return new String(c);
        }
    }
    
    private static class AliasValueCompleter implements Completer
    {
        private final Map<String, String> aliases;
        
        public AliasValueCompleter(final Map<String, String> aliases) {
            this.aliases = aliases;
        }
        
        @Override
        public void complete(final LineReader reader, final ParsedLine commandLine, final List<Candidate> candidates) {
            assert commandLine != null;
            assert candidates != null;
            final List<String> words = commandLine.words();
            if (words.size() > 1) {
                final String h = words.get(words.size() - 2);
                if (h != null && !h.isEmpty()) {
                    final String v = this.aliases.get(h);
                    if (v != null) {
                        candidates.add(new Candidate(AttributedString.stripAnsi(v), v, null, null, null, null, true));
                    }
                }
            }
        }
    }
}
