package function.builtin;

import static org.junit.Assert.assertNull;
import static testutil.TestUtilities.assertSExpressionsMatch;
import static testutil.TestUtilities.evaluateString;

import org.junit.Test;

import function.ArgumentValidator.BadArgumentTypeException;
import function.ArgumentValidator.TooFewArgumentsException;
import function.ArgumentValidator.TooManyArgumentsException;
import function.builtin.EVAL.UndefinedSymbolException;
import sexpression.LispNumber;
import table.SymbolTable;
import testutil.SymbolAndFunctionCleaner;

public class SETTest extends SymbolAndFunctionCleaner {

    @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)");
    }

}
