import junit.framework.AssertionFailedError;
import junit.framework.TestCase;

/**
 * Created on Mar 12, 2006
 */

public class InterpreterTest extends TestCase {
    static Interpreter interpreter;
    Tree<Token> t;
    static {
        if (interpreter == null) interpreter = new Interpreter();
    }
    
    protected void setUp() throws Exception {
        super.setUp();
        interpreter.setTerminated(false);
    }

    protected void tearDown() throws Exception {
        super.tearDown();
    }

    /**
     * Test method for 'Interpreter.Interpreter()'
     */
    public void testInterpreter() {

    }

    /**
     * Test method for 'Interpreter.setup()'
     */
    public void testSetup() {

    }

    /**
     * Test method for 'Interpreter.run()'
     */
    public void testRun() {

    }

    /**
     * Test method for 'Interpreter.evaluateDirection(Tree)'
     */
    public void testEvaluateDirection() {
        t = tree("direction{east}");
        assertEquals(0, interpreter.evaluateDirection(t));

        t = tree("direction{north}");
        assertEquals(90, interpreter.evaluateDirection(t));

        t = tree("direction{south}");
        assertEquals(270, interpreter.evaluateDirection(t));

        t = tree("direction{west}");
        assertEquals(180, interpreter.evaluateDirection(t));

        // Turtle starts out facing east, and these tests don't change that
        t = tree("direction{left}");
        assertEquals(90, interpreter.evaluateDirection(t));

        t = tree("direction{right}");
        assertEquals(270, interpreter.evaluateDirection(t));

        t = tree("direction{left 20}");
        assertEquals(20, interpreter.evaluateDirection(t));

        t = tree("direction{right 45}");
        assertEquals(315, interpreter.evaluateDirection(t));
    }

    /**
     * Test method for 'Interpreter.evaluateExpression(Tree)'
     */
    public void testEvaluateExpression() {
        t = tree("+{2 *{3 4}}");
        assertEquals(14, interpreter.evaluateExpression(t));
        
        t = tree("-{ -{20 8} 5}");
        assertEquals(7, interpreter.evaluateExpression(t));
        
        t = tree("-{20 -{8 5}}");
        assertEquals(17, interpreter.evaluateExpression(t));
        
        t = tree("/{ /{100 5} 5}");
        assertEquals(4, interpreter.evaluateExpression(t));
        
        t = tree("/{20 7}");
        assertEquals(2, interpreter.evaluateExpression(t));
        
        t = parseExpression("2 + 3 * 4");
        assertEquals(14, interpreter.evaluateExpression(t));
        
    }

    /***
     * Test method for 'Interpreter.evaluateCondition(Tree)'
     */
    public void testEvaluateCondition() {
        t = parseCondition("2 < 3");
        assertTrue(interpreter.evaluateCondition(t));

        t = parseCondition("2 > 3");
        assertFalse(interpreter.evaluateCondition(t));

        t = parseCondition("3 > 2");
        assertTrue(interpreter.evaluateCondition(t));

        t = parseCondition("3 < 2");
        assertFalse(interpreter.evaluateCondition(t));

        t = parseCondition("2 = 2");
        assertTrue(interpreter.evaluateCondition(t));

        t = parseCondition("2 = 3");
        assertFalse(interpreter.evaluateCondition(t));
        
        t = parseCondition("2 < 3 and 3 < 4");
        assertTrue(interpreter.evaluateCondition(t));
        
        t = parseCondition("2 > 3 and 3 < 4");
        assertFalse(interpreter.evaluateCondition(t));
        
        t = parseCondition("2 < 3 and 3 > 4");
        assertFalse(interpreter.evaluateCondition(t));
        
        t = parseCondition("2 > 3 and 3 > 4");
        assertFalse(interpreter.evaluateCondition(t));
        
        t = parseCondition("2 < 3 or 3 < 4");
        assertTrue(interpreter.evaluateCondition(t));
        
        t = parseCondition("2 > 3 or 3 < 4");
        assertTrue(interpreter.evaluateCondition(t));
        
        t = parseCondition("2 < 3 or 3 > 4");
        assertTrue(interpreter.evaluateCondition(t));
        
        t = parseCondition("2 > 3 or 3 > 4");
        assertFalse(interpreter.evaluateCondition(t));
        
        t = parseCondition("not 2 < 3");
        assertFalse(interpreter.evaluateCondition(t));

        t = parseCondition("not 2 > 3");
        assertTrue(interpreter.evaluateCondition(t));
    }

    /**
     * Test method for 'Interpreter.fetch(String)' and for
     * 'Interpreter.store(String, int)'
     */
    public void testFetchAndStore() {
        interpreter.store("hello", 23);
        assertEquals(23, interpreter.fetch("hello"));
        
        interpreter.store("goodbye", -49);
        assertEquals(23, interpreter.fetch("hello"));
        assertEquals(-49, interpreter.fetch("goodbye"));
        
        assertEquals(0, interpreter.fetch("newVariable"));
        interpreter.store("newVariable", 17);
        assertEquals(17, interpreter.fetch("newVariable"));
    }
    
    /**
     * Test method for assignment statements.
     */
    public void testAssignmentStatement() {
        assertEquals(0, interpreter.fetch("assignment"));
        interpret("={assignment 87}");
        assertEquals(87, interpreter.fetch("assignment"));
    }
    
    public void testBlock() {
        interpret("block{ ={x 1} ={y 2} ={z 3} ={x 4} }");
        assertEquals(4, interpreter.fetch("x"));
        assertEquals(2, interpreter.fetch("y"));
        assertEquals(3, interpreter.fetch("z"));
    }

    /**
     * Test method for if and if-else statements.
     */
    public void testIfStatement() {
        interpret("if{ ={2 2} block{={ifVar 99}}}");
        assertEquals(99, interpreter.fetch("ifVar"));

        interpret("if{ >{2 2} block{={ifVar 11}}}");
        assertEquals(99, interpreter.fetch("ifVar"));

        interpret("if{ >{2 2} block{={ifVar 22} block{}}}");
        assertEquals(99, interpreter.fetch("ifVar"));

        interpret("if{ ={2 2} block{={ifVar 33}} block{= ifVar 44}}");
        assertEquals(33, interpreter.fetch("ifVar"));

        interpret("if{ >{2 2} block{={ifVar 55}} block{={ifVar 66}}}");
        assertEquals(66, interpreter.fetch("ifVar"));

        interpret("if{ >{2 2} block{} block{={ifVar 77}}}");
        assertEquals(77, interpreter.fetch("ifVar"));

        interpret("if{ >{2 2} block{} block{}}");
        assertEquals(77, interpreter.fetch("ifVar"));
    }
    
    /**
     * Test method for while loops.
     */
    public void testWhileLoop() {
        interpret("={c 1}");
        interpret("while{ <{c 101} block{ ={sum +{sum c}} ={c +{c 1}}}}");
        assertEquals(5050, interpreter.fetch("sum"));
        
        interpret("while{>{2 3} ={sum 0}}");
        assertEquals(5050, interpreter.fetch("sum"));
    }
    
    /**
     * Test method for repeat loops.
     */
    public void testRepeatLoop() {
        interpret("={p 1}");
        interpret("repeat{10 block{={p *{2 p}}}}");
        assertEquals(1024, interpreter.fetch("p"));
        
        interpret("repeat{0 block{={p *{2 p}}}}");
        assertEquals(1024, interpreter.fetch("p"));
    }
    
    /**
     * Test method for complete programs, without procedures.
     */
    public void testProgram() {
        Tree<Token> program;
        
        program = makeProgram("={x +{2 2}}");
        interpret(program);
        assertEquals(4, interpreter.fetch("x"));
        
        program = makeProgram("={x 7} ={x 12}");
        interpret(program);
        assertEquals(12, interpreter.fetch("x"));
        
        program = makeProgram("={x 1} ={n 0} repeat{5 block {={n +{n 1}} ={x *{n x}}}}");
        interpret(program);
        assertEquals(120, interpreter.fetch("x"));
    }
    
    /**
     * Test method for local and global variables, including parameters.
     */
    public void testLocalAndGlobalVariables() {
        Tree<Token> program;
        Tree<Token> procedure;
        Tree<Token> procedure2;
        
        program = makeProgram("={x 11}");
        interpret(program);
        assertEquals(11, interpreter.fetch("x"));
        
        program = makeProgram("penup");
        interpret(program);
        assertEquals("New programs should not get values from old programs\n",
                     0, interpreter.fetch("x"));

        procedure = makeProcedure("foo1", "", "={a 22}");
        program = makeProgram("call{foo1}", procedure);
        interpret(program);
        assertEquals("Local variable should be discarded after call\n",
                     0, interpreter.fetch("a"));
        
        procedure = makeProcedure("foo2", "", "={a 33}");
        program = makeProgram("={a 0} call{foo2}", procedure);
        interpret(program);
        assertEquals("Global variable should be kept after call\n",
                     33, interpreter.fetch("a"));
        
        procedure = makeProcedure("foo3", "", "={b 44}");
        program = makeProgram("={b 0} call{foo3}", procedure);
        interpret(program);
        assertEquals("Global variable should be set by procedure\n",
                     44, interpreter.fetch("b"));
        
        procedure = makeProcedure("foo4", "c", "={x 66} ={c 77}");
        program = makeProgram("={c 55} ={x 0} call{foo4 88}", procedure);
        interpret(program);
        assertEquals("Global variable should be set by procedure\n",
                     66, interpreter.fetch("x"));
        assertEquals("Parameter should override global variable\n",
                     55, interpreter.fetch("c"));
        
        procedure = makeProcedure("foo5", "a",
                                  "={x a} ={a 22} call{bar a} ={z a}");
        procedure2 = makeProcedure("bar", "b", "={y b} ={a 33}");
        program = makeProgram("={x 1} ={y 2} ={z 3} call{foo5 11}",
                              procedure, procedure2);
        interpret(program);
        assertEquals(11, interpreter.fetch("x"));
        assertEquals(22, interpreter.fetch("y"));
        assertEquals(33, interpreter.fetch("z"));
    }

    /**
     * Test method for 'Interpreter.callProcedure(String, Tree)'
     */
    public void testCallProcedure() {
        Tree<Token> program;
        Tree<Token> procedure;

        String factorialN = "={fact 1} while{ >{n 0} block{ ={fact *{n fact}} ={n -{n 1}}}} = {result fact}";
        procedure = makeProcedure("factorial",  "n", factorialN);
        program = makeProgram("={result 0} ={x 5} call{factorial x}", procedure);
        interpret(program);
        assertEquals(120, interpreter.fetch("result"));
        
        program = makeProgram("={result 0} call{factorial 5}", procedure);
        interpret(program);
        assertEquals(120, interpreter.fetch("result"));
        
        procedure = makeProcedure("setX", "v", "={x v}");
        program = makeProgram("={x 0} call{setX 34}", procedure);
        interpret(program);
        assertEquals(34, interpreter.fetch("x"));
        assertEquals(0, interpreter.fetch("v"));

        String s = "if{ >{x y} ={x -{x y}} ={y -{y x}} }";
        Tree<Token> greatestCommonFactor = makeProcedure("gcd", "x y", "while{not{={x y}} " + s + "} ={gcd x}");
        
        program = makeProgram("={gcd 0} call{gcd 18 24 }", greatestCommonFactor);
        System.out.print(program);
        interpret(program);
        assertEquals(6, interpreter.fetch("gcd"));
        
        program = makeProgram("={gcd 0} call{gcd 42 231}", greatestCommonFactor);
        interpret(program);
        assertEquals(21, interpreter.fetch("gcd"));
    }
    
    /**
     * Helper method to interpret a program given as a String, where the
     * String is formatted as in the tree(String) method.
     * 
     * @param treeString The program to be interpreted.
     */
    private void interpret(String treeString) {
        interpreter.interpret(tree(treeString));
    }
    
    /**
     * Helper method to interpret a program given as a Tree.
     * 
     * @param treeString The program to be interpreted.
     */
    private void interpret(Tree tree) {
        interpreter.interpret(tree);
    }
    
    /**
     * Creates a Tree from a given String. The string has the form
     * "root { subtree1 subtree2 ... subtreeN }", where each subtree
     * has the same form.
     * 
     * For example, "+{2 *{3 4}}" yields the tree<br>
     * <pre>    +
     *         / \
     *        2   *
     *           / \
     *          3   4  </pre><br>
     *          
     * <b>Note:</b> The implementation of this method has been moved to
     * JUnitTestUtilityMethods so that it is easier to use from other
     * JUnit test classes.
     * 
     * @param input The String to turn into a Tree.
     * @return The newly created Tree.
     */
    private static Tree<Token> tree(String input) {
        return JUnitTestUtilityMethods.tree(input);
    }

    /**
     * Parses a String representing an expression; the syntax of the String is
     * the same as that in Logo2006.
     * 
     * @param string The expression to be parsed.
     * @return The abstract syntax tree representing the expression.
     * @throws AssertionFailedError If the string contains syntax errors.
     */
    private Tree<Token> parseExpression(String string) throws AssertionFailedError {
        Parser parser = new Parser(string);
        if (parser.expression()) return (Tree<Token>) parser.stack.peek();
        else  throw new AssertionFailedError("Couldn't parse: " + string);
    }

    /**
     * Parses a String representing a condition; the syntax of the String is
     * the same as that in Logo2006.
     * 
     * @param string The condition to be parsed.
     * @return The abstract syntax tree representing the condition.
     * @throws AssertionFailedError If the string contains syntax errors.
     */
    private Tree<Token> parseCondition(String string) throws AssertionFailedError {
        Parser parser = new Parser(string);
        if (parser.condition()) return (Tree<Token>) parser.stack.peek();
        else  throw new AssertionFailedError("Couldn't parse: " + string);
    }
    
    /**
     * Creates an AST representing a procedure with the given name, formal
     * parameters, and body, all provided as Strings.
     * 
     * @param name The name for the procedure.
     * @param args The formal parameters of the procedure.
     * @param body The procedure body.
     * @return The AST representing the procedure.
     */
    private Tree<Token> makeProcedure(String name, String args, String body) {
        String procedure = "define {" + name + " parameters {" + args + "} block{" + body + "}}";
        return tree(procedure);
    }
    
    /**
     * Creates an AST from a string representing a program, and any number
     * of ASTs representing procedures.
     * 
     * @param programCode The text of the main program.
     * @param procedures ASTs representing available procedures.
     * @return A combined AST representing the entire program.
     */
    private Tree<Token> makeProgram(String programCode, Tree... procedures) {
        String program = "program{ block{" + programCode + "} procedures}";
        Tree<Token> parseTree = tree(program);
        Tree<Token> proceduresNode = parseTree.lastChild();
        for (Tree<Token> procedure : procedures) {
            proceduresNode.addChild(procedure);
        }
        return parseTree;
    }
}
