package code.loop;

import code.loader.CurryModulePath;
import code.loader.LoadManager;
import code.loop.parser.ClpParser;
import code.modules.CurryModule;
import code.space.Computation;
import code.space.Space;
import code.stuff.Tracer;
import code.symbols.DataSymbol;
import code.symbols.Symbol;
import code.table.*;
import code.term.Term;
import code.term.Variable;
import code.type.visitor.PrintAsTxtLoadable;
import code.type.typechecker.TypeError;
import code.type.TypeExpression;
import code.type.TypeFactory;
import code.type.typechecker.TermGetTypeVisitor;

import java.io.*;
import java.util.Enumeration;

/**
 * This class interprets textual commands and takes appropriate actions.
 *
 * @author Sergio Antoy
 * @since June 17, 2003
 * Modified By :
 * Sunita Marathe Aug 16, 2005: to support pkg qualified java module names
 */
public class Command {

    private BufferedReader in;
    private BufferedWriter out;

    public Command(BufferedReader in, BufferedWriter out) {
        this.in = in;
        this.out = out;
    }

    /**
     * Interpret and execute the command.
     *
     * @param string The textual command string with parameters.
     * @return returns true if the RunEvalLoop should NOT go
     *         back to reading std in after this call.
     */
    public boolean command(String string) {
        try {
            boolean run = false;
            if (string.equals(":help")) {
                help();
            } else if (string.equals(":quit")) {
                quit();
            }
            // The following are commands display "something" inside the FLVM.
            else if (string.equals(":symbols")) {
                TablesPrinter.printMapTable();
                TablesPrinter.printModuleTable();
                TablesPrinter.printAllSymbolTables();
                TablesPrinter.printUndefinedSymbols();
            } else if (string.matches(":symbol[ ]+.+")) {
                String symbolName = string.replaceFirst(":symbol[ ]*", "").trim();
                symbol(symbolName);
            } else if (string.equals(":modules")) {
                TablesPrinter.printModuleTable();
            } else if (string.matches(":module[ ]+.+")) {
                String moduleName = string.replaceFirst(":module[ ]*", "").trim();
                module(moduleName);
            } else if (string.matches(":type[ ]+.+")) {
                String typeName = string.replaceFirst(":type[ ]*", "").trim();
                type(typeName);
            }
            else if (string.equals(":dumptypes")) {
                TypeFactory.dump("");
            } else if (string.matches(":dumptype[ ]+.+")) {
                String typeName = string.replaceFirst(":dumptype[ ]*", "").trim();
                TypeFactory.dump(typeName);
            } else if (string.matches(":greptype[ ]+.+")) {
                String typeName = string.replaceFirst(":greptype[ ]*", "").trim();
                TypeFactory.grep(typeName);
            }
            // The following are basic command line commands.
            else if (string.matches(":load[ ]*.+")) {
                String moduleName = string.replaceFirst(":load[ ]*", "").trim();
                LoadManager.load(moduleName);
            } else if (string.equals(":reload")) {
                // reload the current module.
                LoadManager.reload();
            } else if (string.equals(":path")) {
                // show the CurryModulePath
                CurryModulePath.show(out);
            } else if (string.matches(":path[ ]+-[ ]*.+")) {
                // remove specified path from CurryModulePath
                String path = string.replaceFirst(":path[ ]+-[ ]*", "").trim();
                code.loader.CurryModulePath.remove(path);
            } else if (string.matches(":path[ ]+\\+[ ]*.+")) {
                // add specified path to CurryModulePath
                String path = string.replaceFirst(":path[ ]+\\+[ ]*", "").trim();
                code.loader.CurryModulePath.append(path, CurryModulePath.CURRYPATH);
            } else if (string.matches(":path[ ]+.*")) {
                // set the CurryModulePath to the specified path
                String path = string.replaceFirst(":path[ ]+", "").trim();
                code.loader.CurryModulePath.set(path);
            }
            // The above were basic command line commands.
            // The following are commands to enable/disable tracing of various kinds.
            else if (string.matches(":computation[ ]+on")) {
                Tracer.computation = true;
            } else if (string.matches(":computation[ ]+off")) {
                Tracer.computation = false;
            } else if (string.matches(":reduction[ ]+on")) {
                Tracer.reduction = true;
            } else if (string.matches(":reduction[ ]+off")) {
                Tracer.reduction = false;
            } else if (string.matches(":instruction[ ]+on")) {
                Tracer.instruction = true;
            } else if (string.matches(":instruction[ ]+off")) {
                Tracer.instruction = false;
            } else if (string.matches(":space[ ]+on")) {
                Tracer.space = true;
            } else if (string.matches(":space[ ]+off")) {
                Tracer.space = false;
            } else if (string.matches(":rule[ ]+on")) {
                Tracer.rule = true;
            } else if (string.matches(":rule[ ]+off")) {
                Tracer.rule = false;
            } else if (string.matches(":time[ ]+on")) {
                Tracer.time = true;
            } else if (string.matches(":time[ ]+off")) {
                Tracer.time = false;
            } else if (string.matches(":output[ ]+on")) {
                Tracer.output = true;
            } else if (string.matches(":output[ ]+off")) {
                Tracer.output = false;
            } else if (string.matches(":symbols[ ]+on")) {
                Tracer.symbols = true;
            } else if (string.matches(":symbols[ ]+off")) {
                Tracer.symbols = false;
            } else if (string.matches(":clp[ ]+on")) {
                Tracer.clp = true;
            } else if (string.matches(":clp[ ]+off")) {
                Tracer.clp = false;
            } else if (string.matches(":typecheck[ ]+on")) {
                TermGetTypeVisitor.debug_typechecking = true;
            } else if (string.matches(":typecheck[ ]+off")) {
                TermGetTypeVisitor.debug_typechecking = false;
            } else if (string.matches(":typecache[ ]+on")) {
                Tracer.typecache = true;
            } else if (string.matches(":typecache[ ]+off")) {
                Tracer.typecache = false;
            }
            // The above were commands to enable/disable tracing of various kinds.
            else {
                out.write("Bad command \"" + string + "\"");
                out.newLine();
            }
            out.flush();
            return run;
        } catch (UndefinedSymbol ex) {
            System.err.println("ERROR: the following symbols are undefined: " + ex.getMessage());
            System.exit(1);
            return true;
        } catch (IOException ex) {
            throw new RuntimeException(ex.getMessage());
        }
    }

    private void help() throws IOException {
        String helpmessage =
                "\n------------------------Basic Commands" +
                "\n:help                       Displays this help message." +
                "\n:quit" +
                "\n------------------------Commands that display something" +
                "\n:symbols                    Displays all symbols" +
                "\n:symbol symbolname          Displays specified symbol" +
                "\n:modules                    Displays all modules" +
                "\n:module modulename          Displays specified module" +
                "\n:type expression            Displays the type of the specified expression" +
                "\n:dumptypes                  Displays all type expressions" +
                "\n:dumptype type              Displays type expressions of type [TC|TV|FT|TCA]" +
                "\n:greptype name              Displays type expressions containing name" +
                "\n------------------------Module loading related commands" +
                "\n:load filename              Loads the module in the specified file (filename specified without path or extension)" +
                "\n:reload                     Reload the current Module" +
                "\n:path                       Prints out all paths in the CurryModulePath" +
                "\n:path + directory           Adds the directory to CurryModulePath" +
                "\n:path - directory           Removes the directory from CurryModulePath" +
                "\n:path directory             Sets the CurryModulePath to the specified directory" +
                "\n------------------------Various switches for tracing. These are off by default." +
                "\n:computation [on|off]" +
                "\n:reduction [on|off]" +
                "\n:instruction [on|off]" +
                "\n:space [on|off]" +
                "\n:rule [on|off]" +
                "\n:time [on|off]" +
                "\n:output [on|off]" +
                "\n:symbols [on|off]" +
                "\n:clp [on|off]" +
                "\n:typecheck [on|off]" +
                "\n:typecache [on|off]" +
                "\n------------------------End of commands" +
                "\n";

        out.write(helpmessage);
        out.newLine();
    }

    private void quit() throws IOException {
        if (!(Tracer.batch || Tracer.script || Tracer.test)) {
            out.write("bye");
            out.newLine();
        }
        out.flush();
        System.exit(0);
    }

    private void symbol(String symbolName) throws IOException {
        try {
            int id = ModuleTable.getId(symbolName);
            Symbol symbol = MapTable.getSymbol(id);
            StringWriter string = new StringWriter();
            code.symbols.PrintAsTxtLoadable ps = new code.symbols.PrintAsTxtLoadable(string);
            symbol.accept(ps, null);
            out.write(string.toString());
        } catch (UndefinedSymbol ex) {
            out.write("Undefined symbol \"" + symbolName + "\"");
            out.newLine();
            out.flush();
        } catch (AmbiguousSymbolException ex) {
            out.write("A symbol named \"" + symbolName + "\" is");
            out.newLine();
            Enumeration xenum = MapTable.getAllSymbols();
            while (xenum.hasMoreElements()) {
                Symbol symbol = (Symbol) xenum.nextElement();
                if (symbol.symbolName.equals(symbolName)) {
                    out.write("  in module " + symbol.moduleName);
                    out.newLine();
                }
            }
            out.write("Please qualify the name");
            out.newLine();
            out.flush();
        }
    }

    private void module(String moduleName) throws IOException {
        CurryModule module = ModuleTable.getModule(moduleName);
        if (module == null) {
            out.write("Module \"" + moduleName + "\" is not loaded.");
            out.newLine();
        } else
            module.printAsTxtLoadable(out);
    }

    private void type(String expression) throws IOException {
        try {
            ClpParser clpParser = new ClpParser(in, out);
            TypeExpression type = clpParser.parseTypeOnly(expression);
            out.write(expression + " :: " + PrintAsTxtLoadable.getString(type));
        } /*catch (UndefinedSymbol e) {
            if (TermGetTypeVisitor.debug_typechecking)
                e.printStackTrace();
            else
                System.err.println("Undefined Symbol:"+e.getMessage());
        } catch (TypeError e) {
            if (TermGetTypeVisitor.debug_typechecking)
                e.printStackTrace();
            else
                System.err.println("Type Error:"+e.getMessage());
        } */catch (Exception e) {
            if (TermGetTypeVisitor.debug_typechecking) e.printStackTrace();
            else System.err.println(e.getClass().getName()+":"+e.getMessage());
            return;
        }
        out.newLine();
        out.flush();
    }

    private void abort() {
        Space.instance.abort();
    }
}
