// GraphTea Project: http://github.com/graphtheorysoftware/GraphTea
// Copyright (C) 2012 Graph Theory Software Foundation: http://GraphTheorySoftware.com
// Copyright (C) 2008 Mathematical Science Department of Sharif University of Technology
// Distributed under the terms of the GNU General Public License (GPL): http://www.gnu.org/licenses/

package graphtea.plugins.commandline.parsers;

import bsh.EvalError;
import bsh.Interpreter;
import graphtea.platform.core.exception.ExceptionHandler;
import graphtea.platform.lang.CommandAttitude;
import graphtea.plugins.commandline.Shell;
import graphtea.plugins.commandline.ShellConsole;
import graphtea.plugins.commandline.commands.ShellCommandException;
import graphtea.ui.extension.ExtensionShellCommandProvider;

import java.awt.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Mohammad Ali Rostatmi
 * @email rostamiev@gmail.com
 */
public class InwardCommandParser {
    public HashMap<String, Method> commands = new HashMap<>();
    public HashMap<String, String> abbrs = new HashMap<>();

    private final Interpreter interpreter;
    private final Shell shell;
    public static String evaluations;


    public InwardCommandParser(Interpreter interpreter, Shell shell) {
        this.interpreter = interpreter;
        this.shell = shell;
        try {
            interpreter.set("me", this);
            interpreter.eval("help() { me.help();}");
            interpreter.eval("help(command) { me.help(command);}");
            evaluations = shell.getEvaluations();
        } catch (EvalError evalError) {
            ExceptionHandler.catchException(evalError);
        }
    }

    public void help() {
        try {
            ((ShellConsole) interpreter.get("console")).print("\nTo see details of commands (arguments , full description) run command: help(\"command\")\n\n"
                    , Color.red);
        } catch (EvalError evalError) {
            ExceptionHandler.catchException(evalError);
        }
        String h = "";
        List<String> hh = new ArrayList<>();

        for (String s : commands.keySet()) {
            hh.add(getHelpString(s));
        }

        for (Map.Entry<String, Class> e : shell.code_completion_dictionary.entrySet()) {
            String command = e.getKey();
            hh.add(getHelpString(command));
        }

        Collections.sort(hh);


        for (String s : hh)
            h += s;

        try {
            ((ShellConsole) interpreter.get("console")).println(h, Color.blue);
        } catch (EvalError evalError) {
            ExceptionHandler.catchException(evalError);
        }
    }

    public void help(String command) {
        String h = getHelpString(command);
        try {
            ((ShellConsole) interpreter.get("console")).println(h, Color.blue);
        } catch (EvalError evalError) {
            ExceptionHandler.catchException(evalError);
        }
    }

    private String getHelpString(String command) {
        String h = "";
        h += command;
        if (commands.containsKey(command)) {
            h += "(" + commands.get(command).getAnnotation(CommandAttitude.class).abbreviation() + ") :";
            h += commands.get(command).getAnnotation(CommandAttitude.class).description();
            h += "\n";
        } else if (shell.code_completion_dictionary.containsKey(command)) {
            Class claz = shell.code_completion_dictionary.get(command);
            if (claz != null) {
//                CommandAttitude an1 = (CommandAttitude) claz.getAnnotation(CommandAttitude.class);
//                if (an1 != null) {
//                    h += "(" + an1.abbreviation() + ") :";
//                    h += an1.description();
//                } if (Extension.class.isAssignableFrom(claz)) {
                ExtensionShellCommandProvider provider = ExtensionShellCommandProvider.commandsDict.get(command);
                if (provider != null) {
                    h += "(" + provider.abrv + ") :";
                    h += provider.desc;
//                    }
                }
            }
            h += "\n";
        }
        return h;
    }

    public Object evaluateCommand(String s, String name, String abbr) {
        evaluations += s;
        abbrs.put(abbr, name);
        try {
            return interpreter.eval(s);
        } catch (EvalError evalError) {
            ExceptionHandler.catchException(evalError);
        }
        return null;
    }


    /**
     * the Objects that the methods should be invoked on.
     */
    public HashMap<Method, Object> methodObjects = new HashMap<>();

    /**
     * imports all commands stored in annotated methods of o
     *
     * @param o The object
     */
    public void addCommands(Object o) {
        try {
            interpreter.set("current_interpreter", interpreter);
            evaluations = "import graphtea.graph.graph.*;" + evaluations;
            evaluations = "import graphtea.ui.lang.*;" + evaluations;
        } catch (EvalError evalError) {
            ExceptionHandler.catchException(evalError);
        }
        Class clazz = o.getClass();
        for (Method m : clazz.getMethods()) {
            CommandAttitude cm = m.getAnnotation(CommandAttitude.class);
            if (cm != null) {
                commands.put(cm.name(), m);
                abbrs.put(cm.abbreviation(), cm.name());
                methodObjects.put(m, o);

                String evaluation = cm.name() + "(";

                String temp = "";
                if (m.getParameterTypes().length != 0)
                    temp = "Object[] o = new Object[" + m.getParameterTypes().length + "];";

                int i = 0;
                for (Class c : m.getParameterTypes()) {
                    i++;
                    evaluation += c.getSimpleName() + " x" + i + "  ,";
                    temp += "o[" + (i - 1) + "] = x" + i + ";";
                }

                evaluation = (m.getParameterTypes().length == 0 ?
                        evaluation : evaluation.substring(0, evaluation.length() - 1))
                        + ")";

                if (m.getParameterTypes().length == 0)
                    evaluation += "{" + temp + "me.parseShell(\"" + cm.name() + "\"" + ",null,current_interpreter);}";
                else
                    evaluation += "{" + temp + "me.parseShell(\"" + cm.name() + "\"" + ",o,current_interpreter);}";
                evaluations += evaluation + "\n";
            }
        }
        try {
            interpreter.eval(evaluations);
        } catch (EvalError evalError) {
            ExceptionHandler.catchException(evalError);
        }
    }

    public Object parseShell(String command, Object[] ps, Interpreter in) throws ShellCommandException {
        Method m = commands.get(command);
        Object o = null;
        if (m != null) {
            try {
                o = m.invoke(methodObjects.get(m), ps);
                if (o != null && (o.getClass().isPrimitive() || o instanceof String)) {
//                    in.set("obj", o);
//                   in.eval("print(obj);");
                } else {
//                    if (!m.getAnnotation(CommandAttitude.class).result().equals("")) {
//                        in.eval(m.getAnnotation(CommandAttitude.class).result());
//                        in.set("obj", o);
//                        in.eval("print_out(obj);");
//                    }
                }
            } catch (IllegalAccessException | NumberFormatException | InvocationTargetException e) {
                ExceptionHandler.catchException(e);
            }
        } else {
            in.print("bad command!\n");
        }
        return o;
    }

    Object getValue(String value, Class valuetype) {
        if (valuetype.getName().equals("int"))
            return Integer.parseInt(value);
        else if (valuetype.getName().equals("double"))
            return Double.parseDouble(value);
        return value;

    }

}
