package function.builtin;

import static org.junit.Assert.assertNull;
import static testutil.TestUtilities.*;

import org.junit.*;

import function.ArgumentValidator.*;
import function.builtin.EVAL.UndefinedSymbolException;
import sexpression.LispNumber;
import table.*;

public class SETTest {

    private ExecutionContext executionContext;

    public SETTest() {
        this.executionContext = ExecutionContext.getInstance();
    }

    @Before
    public void setUp() {
        executionContext.clearContext();
    }

    @After
    public void tearDown() {
        executionContext.clearContext();
    }

    @Test
    public void set() {
        evaluateString("(set 'a 23)");
        assertSExpressionsMatch(new LispNumber("23"), evaluateString("a"));
    }

    @Test
    public void lookupDefinedSymbol() {
        evaluateString("(set 'a 23)");
        assertSExpressionsMatch(new LispNumber("23"), executionContext.lookupSymbolValue("A"));
    }

    @Test
    public void lookupUndefinedSymbol() {
        assertNull(executionContext.lookupSymbolValue("A"));
    }

    @Test
    public void setGlobalVariable() {
        evaluateString("(set 'a 23)");
        SymbolTable global = executionContext.getScope();
        executionContext.setScope(new SymbolTable(global));

        evaluateString("(set 'a 94)");
        executionContext.setScope(global);
        assertSExpressionsMatch(new LispNumber("94"), evaluateString("a"));
    }

    @Test(expected = UndefinedSymbolException.class)
    public void setLocalVariableDefined_DoesNotSetGlobal() {
        SymbolTable global = executionContext.getScope();
        SymbolTable local = new SymbolTable(global);
        local.put("A", new LispNumber("99"));
        executionContext.setScope(local);

        evaluateString("(set 'a 94)");
        executionContext.setScope(global);
        evaluateString("a");
    }

    @Test
    public void setLocalVariableUndefined_SetsGlobal() {
        SymbolTable global = executionContext.getScope();
        SymbolTable local = new SymbolTable(global);
        executionContext.setScope(local);

        evaluateString("(set 'a 94)");
        executionContext.setScope(global);
        assertSExpressionsMatch(new LispNumber("94"), evaluateString("a"));
    }

    @Test(expected = BadArgumentTypeException.class)
    public void setWithNonSymbol() {
        evaluateString("(set '1 2)");
    }

    @Test(expected = TooFewArgumentsException.class)
    public void setWithTooFewArguments() {
        evaluateString("(set 'x)");
    }

    @Test(expected = TooManyArgumentsException.class)
    public void setWithTooManyArguments() {
        evaluateString("(set 'a 'b 'c)");
    }

}
