package function.builtin.special;

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 SETQTest extends SymbolAndFunctionCleaner {

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

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

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

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

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

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

        evaluateString("(setq a 94)");
        assertSExpressionsMatch(new LispNumber("94"), evaluateString("a"));
    }

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

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

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

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

    @Test(expected = BadArgumentTypeException.class)
    public void setqWithNonSymbol() {
        evaluateString("(setq 1 2)");
    }

    @Test(expected = TooFewArgumentsException.class)
    public void setqWithTooFewArguments() {
        evaluateString("(setq x)");
    }

    @Test(expected = TooManyArgumentsException.class)
    public void setqWithTooManyArguments() {
        evaluateString("(setq a b c)");
    }

}
