package at.oefai.aaa.agent.jam;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
import java.util.ListIterator;
import java.util.StringTokenizer;

import at.oefai.aaa.agent.jam.types.Binding;
import at.oefai.aaa.agent.jam.types.ExpList;
import at.oefai.aaa.agent.jam.types.Expression;
import at.oefai.aaa.agent.jam.types.Value;
import at.oefai.aaa.agent.jam.types.ValueLong;
import at.oefai.aaa.agent.jam.types.ValueObject;
import at.oefai.aaa.agent.jam.types.ValueReal;
import at.oefai.aaa.agent.jam.types.ValueString;

/**
 * Base class for defining primitive functionality.
 * @author Marc Huber
 * @author Jaeho Lee
 */
public class SystemFunctions implements Functions {
    // a logger for info and debug output, root logger for a start
    private final AgentLogger log;
    private final Interpreter interpreter;

    /** Primary constructor */
    SystemFunctions(Interpreter pInterpreter) {
        this.interpreter = pInterpreter;
        this.log = pInterpreter.getLog();
    }

    // Member functions

    /**  */
    public Value execute(String name, ExpList args, Binding binding, Goal currentGoal) {
        // cache arity
        int arity = args.size();

        // NUMERIC ACTIONS

        // Addition
        if (name.equals("+")) {
            if (arity <= 0) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            Value result = exp.eval(binding);
            if (arity == 1)
                return result;
            while (ele.hasNext()) {
                exp = ele.next();
                result = result.add(exp.eval(binding));
            }
            return result;
        }

        // Subtraction
        else if (name.equals("-")) {
            if (arity <= 0) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            Value result = exp.eval(binding);
            //      if (arity == 1)
            // return -result;
            while (ele.hasNext()) {
                exp = ele.next();
                result = result.sub(exp.eval(binding));
            }
            return result;
        }

        // Multiplication
        else if (name.equals("*")) {
            if (arity <= 0) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            Value result = exp.eval(binding);
            if (arity == 1)
                return result;
            while (ele.hasNext()) {
                exp = ele.next();
                result = result.mul(exp.eval(binding));
            }
            return result;
        }

        // Division
        else if (name.equals("/")) {
            if (arity <= 0) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            Value result = exp.eval(binding);
            while (ele.hasNext()) {
                exp = ele.next();
                result = result.div(exp.eval(binding));
            }
            return result;
        }

        // Modulo
        else if (name.equals("%")) {
            if (arity <= 0) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            Value result = exp.eval(binding);
            if (arity == 1)
                return result;
            while (ele.hasNext()) {
                exp = ele.next();
                result = result.mod(exp.eval(binding));
            }
            return result;
        }

        // Absolute value (of first argument only)
        else if (name.equals("abs")) {
            if (arity <= 0) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            Value result = exp.eval(binding);
            if ((result instanceof ValueLong) || (result instanceof ValueReal)) {
                return ((result.getReal() > 0) ? result : result.neg());
            }
            return result;
        }

        // RELATION ACTIONS

        // Equality
        else if (name.equals("==")) {
            if (arity != 2) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp1 = ele.next();
            Expression exp2 = ele.next();
            return (exp1.equals(exp2, binding)) ? Value.TRUE : Value.FALSE;
        }

        // Inequality
        else if (name.equals("!=")) {
            if (arity != 2) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp1 = ele.next();
            Expression exp2 = ele.next();
            return (!exp1.equals(exp2, binding)) ? Value.TRUE : Value.FALSE;
        }

        // All these comparisons could be converted to arbitrary arity

        // Smaller cardinality
        else if (name.equals("<")) {
            if (arity != 2) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp1 = ele.next();
            Expression exp2 = ele.next();
            return (exp1.lessthan(exp2, binding)) ? Value.TRUE : Value.FALSE;
        }

        // Smaller or equal cardinality
        else if (name.equals("<=")) {
            if (arity != 2) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp1 = ele.next();
            Expression exp2 = ele.next();
            return (exp1.equals(exp2, binding) || exp1.lessthan(exp2, binding)) ? Value.TRUE : Value.FALSE;
        }

        // Greater cardinality
        else if (name.equals(">")) {
            if (arity != 2) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp1 = ele.next();
            Expression exp2 = ele.next();
            return (!exp1.equals(exp2, binding) && !exp1.lessthan(exp2, binding)) ? Value.TRUE : Value.FALSE;
        }

        // Greater or equal cardinality
        else if (name.equals(">=")) {
            if (arity != 2) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp1 = ele.next();
            Expression exp2 = ele.next();
            return (exp1.equals(exp2, binding) || !exp1.lessthan(exp2, binding)) ? Value.TRUE : Value.FALSE;
        }

        // Boolean AND
        else if (name.equals("&&")) {
            if (arity <= 0) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp;
            while (ele.hasNext()) {
                exp = ele.next();
                if (!(exp.eval(binding).isTrue()))
                    return Value.FALSE;
            }
            return Value.TRUE;
        }

        // Boolean OR
        else if (name.equals("||")) {
            if (arity <= 0) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp;
            while (ele.hasNext()) {
                exp = ele.next();
                if ((exp.eval(binding).isTrue()))
                    return Value.TRUE;
            }
            return Value.FALSE;
        }

        // Boolean Negation
        else if (name.equals("!")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            return ((exp.eval(binding).isTrue()) ? Value.FALSE : Value.TRUE);
        }

        // Bitwise AND
        else if (name.equals("and")) {
            if (arity != 2) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp1, exp2;

            exp1 = ele.next();
            exp2 = ele.next();
            Value val1 = exp1.eval(binding);
            Value val2 = exp2.eval(binding);
            if ((!(val1 instanceof ValueLong)) || !(val2 instanceof ValueLong)) {
                this.log.warning("Invalid type (non-ValueLong) of arguments to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            long result = val1.getLong() & val2.getLong();
            return Value.newValue(result);
        }

        // Bitwise OR
        else if (name.equals("or")) {
            if (arity != 2) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp1 = ele.next();
            Expression exp2 = ele.next();
            Value val1 = exp1.eval(binding);
            Value val2 = exp2.eval(binding);
            if ((!(val1 instanceof ValueLong)) || !(val2 instanceof ValueLong)) {
                this.log.warning("Invalid type of arguments to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            long result = val1.getLong() | val2.getLong();
            return Value.newValue(result);
        }

        // Bitwise XOR
        else if (name.equals("xor")) {
            if (arity != 2) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp1 = ele.next();
            Expression exp2 = ele.next();
            Value val1 = exp1.eval(binding);
            Value val2 = exp2.eval(binding);
            if ((!(val1 instanceof ValueLong)) || !(val2 instanceof ValueLong)) {
                this.log.warning("Invalid type of arguments to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            long result = val1.getLong() ^ val2.getLong();
            return Value.newValue(result);
        }

        // Bitwise complement
        else if (name.equals("not")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp1 = ele.next();
            Value val1 = exp1.eval(binding);
            if (!(val1 instanceof ValueLong)) {
                this.log.warning("Invalid type of argument to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            long result = ~val1.getLong();
            return Value.newValue(result);
        }

        // MISCELLANEOUS UTILITY ACTIONS

        // Create a new Java object using Java reflection
        else if (name.equals("new")) {
            if (arity <= 0) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            String className = exp.eval(binding).getString();
            Class[] argArray1 = new Class[args.size() - 1];
            Object[] argArray2 = new Object[args.size() - 1];
            Class<?> c = null;
            Constructor constructor;
            Object obj = null;
            this.log.fine("In NEW function, className=." + className);
            if (className != null && className.length() != 0) {
                try {
                    c = Class.forName(className);
                } catch (ClassNotFoundException e) {
                    this.log.warning("\nCould not find class \"" + className + "\"!\n");
                    return Value.FALSE;
                }
            }
            // Build an array of argument class types so we can find
            // the right constructor to call.
            int argNum = 0;
            while (ele.hasNext()) {
                exp = ele.next();
                Value v = exp.eval(binding);
                if (v instanceof ValueLong) {
                    //argArray1[argNum] = new Long(v.getLong()).getClass();
                    //argArray2[argNum] = new Long(v.getLong());
                    argArray1[argNum] = int.class;
                    argArray2[argNum] = new Integer((int) v.getLong());
                } else if (v instanceof ValueReal) {
                    //argArray1[argNum] = new Double(v.getReal()).getClass();
                    //argArray2[argNum] = new Double(v.getReal());
                    argArray1[argNum] = double.class;
                    argArray2[argNum] = new Double(v.getReal());
                } else if (v instanceof ValueString) {
                    argArray1[argNum] = ("").getClass(); // new String(v.getString()).getClass();
                    argArray2[argNum] = v.getString();
                } else if (v instanceof ValueObject) {
                    argArray1[argNum] = v.getObject().getClass();
                    argArray2[argNum] = v.getObject();
                }
                argNum++;
            }
            /*
                   for (argNum = 0; argNum < args.getCount()-1; argNum++) {
              log.info("arg[ " + argNum + "]=" + argArray1[argNum]);
                   }
             */
            try {
                constructor = c.getConstructor(argArray1);
            } catch (NoSuchMethodException e) {
                this.log.warning("NoSuchMethodException");
                e.printStackTrace();
                return Value.FALSE;
            }
            // Create the object using the matching constructor
            try {
                obj = constructor.newInstance(argArray2);
            } catch (InstantiationException e) {
                this.log.warning("InstantiationException");
                e.printStackTrace();
                return Value.FALSE;
            } catch (IllegalAccessException e) {
                this.log.warning("IllegalAccessException");
                e.printStackTrace();
                return Value.FALSE;
            } catch (IllegalArgumentException e) {
                this.log.warning("IllegalArgumentException");
                e.printStackTrace();
                return Value.FALSE;
            } catch (InvocationTargetException e) {
                this.log.warning("InvocationTargetException");
                e.printStackTrace();
                return Value.FALSE;
            }
            if (obj != null) {
                return Value.newValue(obj);
            }
            this.log.warning("ERROR: Public class constructor not found with matching argument list!");
            return Value.FALSE;
        }

        // Output information to standard output
        else if (name.equals("print")) {
            if (arity <= 0) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp;
            StringBuffer sb = new StringBuffer();
            while (ele.hasNext()) {
                exp = ele.next();
                sb.append(exp.eval(binding).verboseString(binding));
            }
            this.log.info(sb.toString());
            return Value.TRUE;
        }

        // Output information to standard output with a newline at the end
        else if (name.equals("println")) {
            ListIterator<Expression> ele = args.listIterator();
            Expression exp;
            StringBuffer sb = new StringBuffer();
            while (ele.hasNext()) {
                exp = ele.next();
                sb.append(exp.eval(binding).verboseString(binding));
            }
            this.log.info(sb.toString() + "\n"); // too verbose ?
            // log.info(s); // less verbose
            return Value.TRUE;
        }

        // Delay in milliseconds
        else if (name.equals("sleep")) {
            if (arity <= 0) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp;
            while (ele.hasNext()) {
                exp = ele.next();
                if (exp.eval(binding)instanceof ValueLong) {
                    try {
                        //Thread.sleep(exp.eval(binding).getLong());
                        synchronized (Thread.currentThread()) {
                            Thread.currentThread().wait(exp.eval(binding).getLong());
                        }
                    } catch (InterruptedException e) {
                        this.log.fine("Interrupted sleep primitive!");
                    }
                }
            }
            return Value.TRUE;
        }

        // Get the current time
        // [Modified from Chip McVey at Johns Hopkins University's Applied Physics Lab]
        if (name.equals("getTime")) {
            if (arity == 0) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            Date date = new Date();
            binding.setValue(exp, Value.newValue(date.getTime()));
            return Value.TRUE;
        }

        // Do nothing
        else if (name.equals("noop")) {
            //log.info("No-operation.");
            return Value.TRUE;
        }

        // Always fail
        else if (name.equals("fail")) {
            return Value.FALSE;
        }

        // Takes a variable as its only parameter and changes its binding
        // to Value.UNDEFINED, i.e. the variable is unbound afterwards.
        else if (name.equals("unassign")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression uExp = ele.next();
            binding.unbindVariable(uExp);
            return Value.TRUE;
        }

        // return True if a variable is defined
        else if (name.equals("isDefined")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            Expression uExp = args.getFirst();
            if (uExp.eval(binding).isDefined()) {
                return Value.TRUE;
            }
            return Value.FALSE;
        }

        // STRING AND TOKENIZING ACTIONS

        // String Length
        // [Modified from Chip McVey at Johns Hopkins University's Applied Physics Lab]
        else if (name.equals("strlen")) {
            if ((arity == 0) || (arity > 2)) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            String bigString = exp.eval(binding).getString();
            if (arity == 1) {
                return Value.newValue(bigString.length());
            }
            // (arity == 2+) bind to second (a variable it should be)
            exp = ele.next();
            binding.setValue(exp, Value.newValue(bigString.length()));
            return Value.TRUE;
        }

        // Remaining string tokens
        // [Modified from Chip McVey at Johns Hopkins University's Applied Physics Lab]
        else if (name.equals("rest")) {
            if ((arity == 0) || (arity > 2)) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            String bigString = exp.eval(binding).getString();
            StringTokenizer st = new StringTokenizer(bigString);
            String first;
            Value result;
            // Modified by Martin Klesen (DFKI) Mar Wed 24 1999
            if (st.hasMoreTokens()) {
                first = st.nextToken();
                result = Value.newValue(bigString.substring(bigString.indexOf(first) + first.length()).trim());
            } else {
                return Value.FALSE;
            }
            if (arity == 1) {
                return result;
            }
            // (arity == 2+) bind to second arg variable
            exp = ele.next();
            binding.setValue(exp, result);
            return Value.TRUE;
        }

        // returns true if the first argument (string) equals any of the tokens in the second
        else if (name.equals("isIn")) {
            if (arity != 2) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            Expression exp = args.getFirst();
            String smallString = exp.eval(binding).getString();
            exp = args.getLast();
            String bigString = exp.eval(binding).getString();
            StringTokenizer st = new StringTokenizer(bigString);
            while (st.hasMoreTokens()) {
                if (st.nextToken().equals(smallString)) {
                    return Value.TRUE;
                }
            }
            return Value.FALSE;
        }

        // string startsWith test
        if (name.equals("startsWith")) {
            if (arity != 2) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            String bigString = exp.eval(binding).getString();
            exp = ele.next();
            String first = exp.eval(binding).getString();
            if (bigString.startsWith(first)) {
                return Value.TRUE;
            }
            return Value.FALSE;
        }

        // Last token in string
        // [Modified from Chip McVey at Johns Hopkins University's Applied Physics Lab]
        if (name.equals("last")) {
            if ((arity == 0) || (arity > 2)) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            String bigString = exp.eval(binding).getString();
            StringTokenizer st = new StringTokenizer(bigString);
            Value result = null;
            while (st.hasMoreTokens())
                result = Value.newValue(st.nextToken());
            if (result == null)
                return Value.FALSE;
            if (arity == 1) {
                return result;
            }
            // (arity == 2+) bind to second
            exp = ele.next();
            binding.setValue(exp, result);
            return Value.TRUE;
        }

        // First token in string
        // [Modified from Chip McVey at Johns Hopkins University's Applied Physics Lab]
        if (name.equals("first")) {
            if ((arity == 0) || (arity > 2)) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            String bigString = exp.eval(binding).getString();
            StringTokenizer st = new StringTokenizer(bigString);
            Value result;
            if (st.hasMoreTokens()) {
                result = Value.newValue(st.nextToken());
            } else {
                return Value.FALSE;
            }
            if (arity == 1) {
                return result;
            }
            // (arity == 2+) bind to second
            exp = ele.next();
            binding.setValue(exp, result);
            return Value.TRUE;
        }

        // Parse a string containing JAM agent definitions (i.e., World Model,
        // Goals, Plans)
        if (name.equals("parseString")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            String pString = exp.eval(binding).getString();
            // Modified by Martin Klesen (DFKI) Apr Mon 19 1999
            // Catch a parse error rather than dying on it.
            try {
                this.interpreter.parseString(pString);
            } catch(ParseException pe) {
                this.log.severe("JAM: Parser returned ParseException: " + pe);
                pe.printStackTrace();
                return Value.FALSE;
            }
            return Value.TRUE;
        }

        // Takes a variable as its only parameter and changes its binding
        // to ValueLong, if its value is a signed decimal integer
        // [From Martin Klesen: DFKI]
        if (name.equals("parseInt")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            Value val = exp.eval(binding);
            if (val instanceof ValueString) {
                int i = Integer.parseInt(val.getString());
                binding.setValue(exp, Value.newValue(i));
                return Value.TRUE;
            }
            this.log.warning("ERROR in parseInt: " + val.toString() + " must be a String!");
            return Value.FALSE;
        }

        // shuffle a list of relations attained with a RETRIEVEALL action
        else if (name.equals("shuffleRest")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            Expression exp = args.getFirst();
            WorldModelRelationIterator wmri = (WorldModelRelationIterator) exp.eval(binding).getObject();
            wmri.shuffleRest();
            return Value.TRUE;
        }

        // DEBUGGING ACTIONS

        // Display current world model state
        else if (name.equals("printWorldModel")) {
            this.log.info(this.interpreter.getWorldModel().verboseString());
            return Value.TRUE;
        }

        // Display current intention structure state
        else if (name.equals("printIntentionStructure")) {
            this.log.info(this.interpreter.getIntentionStructure().verboseString());
            return Value.TRUE;
        }

        // Turn debug information on/off [added by Stefan Rank]
        else if (name.equals("setShowAppraisal")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            this.log.setShowAppraisal(exp.eval(binding).isTrue());
            return Value.TRUE;
        }

        // Turn debug information on/off
        else if (name.equals("setShowWorldModel")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            this.log.setShowWorldModel(exp.eval(binding).isTrue());
            return Value.TRUE;
        }

        // Turn debug information on/off
        else if (name.equals("setShowGoalList")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            this.log.setShowGoalList(exp.eval(binding).isTrue());
            return Value.TRUE;
        }

        // Turn debug information on/off
        else if (name.equals("setShowAPL")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            this.log.setShowAPL(exp.eval(binding).isTrue());
            return Value.TRUE;
        }

        // Turn debug information on/off
        else if (name.equals("setShowIntentionStructure")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            this.log.setShowIntentionStructure(exp.eval(binding).isTrue());
            return Value.TRUE;
        }

        // Turn debug information on/off
        else if (name.equals("setShowActionFailure")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            this.log.setShowActionFailure(exp.eval(binding).isTrue());
            return Value.TRUE;
        }


        // META-LEVEL ACTIONS

        // Get the currently executing Goal
        else if (name.equals("getCurrentGoal")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            binding.setValue(exp, Value.newValue(currentGoal));
            return Value.TRUE;
        }

        else if (name.equals("getCurrentGoalRelation")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            Expression varExp = args.getFirst();
            Relation rel = new Relation(currentGoal.getGoalAction().getRelation(), currentGoal.getGoalBinding());
            binding.setValue(varExp, Value.newValue(rel));
            return Value.TRUE;
        }

        // Print info about the currently executing Goal
        else if (name.equals("printGoal")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            Goal g = (Goal) exp.eval(binding).getObject();
            this.log.info(g.formattedString());
            return Value.TRUE;
        }

        // Get the currently executing Plan
        else if (name.equals("getCurrentPlan")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            if (currentGoal != null && currentGoal.getIntention() != null && currentGoal.getIntention().getPlan() != null) {
                this.log.fine("PlanRuntimeSimpleState: current plan is - \""
                         + currentGoal.getIntention().getPlan().getName() + "\"");
                binding.setValue(exp, Value.newValue(currentGoal.getIntention().getPlan()));
                return Value.TRUE;
            }
            binding.setValue(exp, Value.newValue((Object)null));
            return Value.TRUE;
        }

        // Print the currently executing Goal
        else if (name.equals("printPlan")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            Plan p = (Plan) exp.eval(binding).getObject();
            this.log.info(p.verboseString(null));
            return Value.TRUE;
        }

        // Get the attributes for the Plan
        else if (name.equals("getPlanAttributes")) {
            if (arity != 2) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            Plan p = (Plan) exp.eval(binding).getObject();
            exp = ele.next();
            binding.setValue(exp, Value.newValue(p.getAttributes()));
            return Value.TRUE;
        }

        // Get the value for a specific attribute for an APL Element
        //
        // NOTE: Assumes values are numeric and returns -1 as a special
        // return code to indicate that the attribute was not found.
        //
        // Needs more protection against improper parameters!!!!
        else if (name.equals("getAttributeValue")) {
            if (arity != 3) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            APLElement a = (APLElement) exp.eval(binding).getObject();
            exp = ele.next();
            String attribute = exp.eval(binding).getString();
            exp = ele.next();
            // Find the specified Attribute
            String attributes = a.getPlan().getAttributes();
            int index = attributes.indexOf(attribute);
            if (index == -1) {
                binding.setValue(exp, Value.newValue( -1));
                return Value.TRUE;
            }
            // Extract the corresponding value
            String tmp1 = attributes.substring(index);
            StringTokenizer st = new StringTokenizer(tmp1);
            // Advance to the token just after the attribute
            String tmp2 = st.nextToken();
            tmp2 = st.nextToken();
            this.log.fine("getAttributeValue: value returned is " + tmp2);
            binding.setValue(exp, Value.newValue(Double.valueOf(tmp2).doubleValue()));
            return Value.TRUE;
        }

        // Select a random APL element
        else if (name.equals("getAPLElement")) {
            if (arity != 3) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            APL a = (APL) exp.eval(binding).getObject();
            exp = ele.next();
            int num = (int) exp.eval(binding).getLong();
            APLElement selectedElement = a.getNth(num);
            exp = ele.next();
            binding.setValue(exp, Value.newValue(selectedElement));
            return Value.TRUE;
        }

        // Print the passed-in APL
        else if (name.equals("printAPL")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            APL a = (APL) exp.eval(binding).getObject();
            this.log.info(a.verboseString());
            return Value.TRUE;
        }

        // Print the passed-in APL Element
        else if (name.equals("printAPLElement")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            APLElement a = (APLElement) exp.eval(binding).getObject();
            this.log.info(a.verboseString());
            return Value.TRUE;
        }

        // Select a random APL element
        else if (name.equals("selectRandomAPLElement")) {
            if (arity != 2) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            APL a = (APL) exp.eval(binding).getObject();
            APLElement selectedElement = a.getUtilityRandom();
            exp = ele.next();
            binding.setValue(exp, Value.newValue(selectedElement));
            return Value.TRUE;
        }

        // Intend the APL element
        else if (name.equals("intendAPLElement")) {
            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }
            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            APLElement a = (APLElement) exp.eval(binding).getObject();
            this.interpreter.getIntentionStructure().intend(a);
            return Value.TRUE;
        }

        // Communication actions

        //
        // Make a connection to another agent as a client
        // IN:int port
        // IN:String hostName
        // OUT:BufferedReader inputStream
        // OUT:PrintWriter outputStream
        //
        else if (name.equals("connectToAgentAsClient")) {

            if (arity != 4) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }

            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            int port = (int) exp.eval(binding).getLong();
            exp = ele.next();
            String host = exp.eval(binding).getString();

            BufferedReader in = null;
            PrintWriter out;
            Socket socket;

            try {
                socket = new Socket(host, port);

                in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                out = new PrintWriter(socket.getOutputStream());

                exp = ele.next();
                binding.setValue(exp, Value.newValue(in));
                exp = ele.next();
                binding.setValue(exp, Value.newValue(out));
                return Value.TRUE;
            } catch (IOException e) {
                this.log.warning("JAM::ConnectToAgentAsClient:IOException : " + e);
                return Value.FALSE;
            }
        }

        //
        // Make a connection to another agent as a server
        // IN:int port
        // OUT:BufferedReader inputStream
        // OUT:PrintWriter outputStream
        //
        else if (name.equals("connectToAgentAsServer")) {

            if (arity != 3) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }

            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            int port = (int) exp.eval(binding).getLong();

            BufferedReader in = null;
            PrintWriter out;
            Socket socket;
            ServerSocket server;

            try {
                server = new ServerSocket(port);

                socket = server.accept();
                in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                out = new PrintWriter(socket.getOutputStream());

                exp = ele.next();
                binding.setValue(exp, Value.newValue(in));
                exp = ele.next();
                binding.setValue(exp, Value.newValue(out));

                return Value.TRUE;
            } catch (IOException e) {
                this.log.warning("JAM::ConnectToAgentAsServer:IOException : " + e);
                return Value.FALSE;
            }

        }

        //
        // Closes the connection to another agent
        // IN:BufferedReader inputStream
        // IN:PrintWriter outputStream
        //
        // Note that it closes both streams and changes the bindings to
        // Value.UNDEFINED
        //
        else if (name.equals("closeConnection")) {

            if (arity != 2) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }

            try {
                ListIterator<Expression> ele = args.listIterator();
                // close the inputStream
                Expression exp = ele.next();
                BufferedReader sIn = (BufferedReader) exp.eval(binding).getObject();
                sIn.close();
                binding.setValue(exp, Value.UNDEFINED);
                // close the outputStream
                exp = ele.next();
                PrintWriter sOut = (PrintWriter) exp.eval(binding).getObject();
                sOut.flush();
                sOut.close();
                binding.setValue(exp, Value.UNDEFINED);
            } catch (IOException e) {
                this.log.warning("JAM::closeConnection:IOException : " + e);
                return Value.FALSE;
            }

            return Value.TRUE;
        }

        //
        // Send a string-encoded message to another agent
        // IN:PrintWriter outputStream
        // IN:String message
        //
        else if (name.equals("sendMessage")) {

            if (arity != 2) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }

            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            PrintWriter sOut = (PrintWriter) exp.eval(binding).getObject();
            exp = ele.next();
            String message = exp.eval(binding).getString();

            sOut.println(message);

            return Value.TRUE;
        }

        //
        // Receive a string-encoded message from another agent (note; blocking)
        // IN:BufferedReader inputStream
        // OUT:String message
        //
        else if (name.equals("recvMessage")) {

            if (arity != 2) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }

            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            BufferedReader sIn = (BufferedReader) exp.eval(binding).getObject();
            String message;

            try {
                exp = ele.next();
                message = sIn.readLine();

                // Modified by Martin Klesen (DFKI) Mar Wed 17 1999
                if (message == null) {
                    binding.setValue(exp, Value.UNDEFINED);
                } else {
                    binding.setValue(exp, Value.newValue(message));
                }
                return Value.TRUE;
            } catch (IOException e) {
                this.log.warning("JAM::ConnectToAgentAsServer:IOException : " + e);
                return Value.FALSE;
            }
        }

        //
        // Save a "checkpoint" of this Jam agent's run-time state to
        // a file. This has a number of possible uses, including to be
        // used later to recover from an agent failure, moved to
        // another platform to implement migration, or instantiated
        // on the local machine to create a "clone".
        //
        // In:String filename - The file in which to store the serialized
        //                      agent
        //
        if (name.equals("checkpointAgentToFile")) {

            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }

            ListIterator<Expression> ele = args.listIterator();
            String filename = ele.next().eval(binding).getString();

            FileOutputStream fos;
            ObjectOutputStream out;

            try {
                fos = new FileOutputStream(filename);
                out = new ObjectOutputStream(fos);
                out.writeObject(this.interpreter);
                return Value.TRUE;
            } catch (IOException e) {
                this.log.warning("I/O Error *" + e + "* writing agent to " + "\"" + filename + "\"!");
                return Value.FALSE;
            }

        }

        //
        // Save a "checkpoint" of this Jam agent's run-time state to
        // an array of bytes.
        //
        // Out:ByteArrayOutputStream outStream - Store the serialized agent
        //
        if (name.equals("checkpointAgent")) {

            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }

            ListIterator<Expression> ele = args.listIterator();
            Expression outArray = ele.next();

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream out;

            try {
                out = new ObjectOutputStream(baos);
                out.writeObject(this.interpreter);

                binding.setValue(outArray, Value.newValue(baos.toByteArray()));

                return Value.TRUE;
            } catch (IOException e) {
                this.log.warning("I/O Error *" + e + "* checkpointing agent!");
                return Value.FALSE;
            }
        }

        //
        // Contact the mobile agent server on the destination platform and send
        // checkpoint to that agent.
        // IN:String hostName
        // IN:int port
        //
        if (name.equals("agentGo")) {

            if (arity != 2) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }

            ListIterator<Expression> ele = args.listIterator();
            Expression exp = ele.next();
            String host = exp.eval(binding).getString();
            exp = ele.next();
            int port = (int) exp.eval(binding).getLong();
            exp = ele.next();

            Socket socket;
            ObjectOutputStream out;

            // Connect to the restoration agent as a client and write out
            // the agent's internals
            //
            try {
                socket = new Socket(host, port);
                out = new ObjectOutputStream(socket.getOutputStream());
                out.writeObject(this.interpreter);
                out.close();
            } catch (IOException e) {
                this.log.warning("JAM::GoAgent:IOException : " + e);
                e.printStackTrace();
                return Value.FALSE;
            }

            if (!this.interpreter.getAgentHasMoved()) {
                System.exit(0);
            }
            this.interpreter.setAgentHasMoved(false);

            return Value.TRUE;
        }

        //
        // Execute the program and args passed in as a string
        //
        // In:String execString - The program and arguments
        //
        if (name.equals("exec")) {

            if (arity != 1) {
                this.log.warning("Invalid number of arguments: " + arity + " to function \"" + name + "\"\n");
                return Value.FALSE;
            }

            ListIterator<Expression> ele = args.listIterator();
            String execString = ele.next().eval(binding).getString();

            Runtime r = Runtime.getRuntime();
            try {
                r.exec(execString);
            } catch (IOException ie) {
                this.log.warning("exec: " + ie);
                return Value.FALSE;
            }

            return Value.TRUE;
        }

        // Cause the agent to exit immediately.
        if (name.equals("exit")) {
            System.exit(0);
            return Value.TRUE;
        }

        // An action that always fails
        if (name.equals("fail")) {
            return Value.FALSE;
        }

        // **Nothing matches**
        return Value.UNDEFINED;
    }

}
