package sexpression;

import static error.ErrorManager.Severity.ERROR;
import static org.junit.Assert.*;

import java.math.BigInteger;

import org.junit.*;

import function.UserDefinedFunction;
import sexpression.LispNumber.InvalidNumberException;

public class SExpressionTester {

    private void assertSExpressionMatchesString(String expected, SExpression sExpression) {
        assertEquals(expected, sExpression.toString());
    }

    @Before
    public void setUp() throws Exception {}

    @Test
    public void testNilToString() {
        String input = "NIL";

        assertSExpressionMatchesString(input, Nil.getInstance());
    }

    @Test
    public void testNumberToString() {
        String input = "12";

        assertSExpressionMatchesString(input, new LispNumber(input));
    }

    @Test
    public void testNumberValueToString() {
        String expected = "12";

        assertSExpressionMatchesString(expected, new LispNumber("12"));
    }

    @Test
    public void testStringToString() {
        String input = "\"hi\"";

        assertSExpressionMatchesString(input, new LispString(input));
    }

    @Test
    public void testSymbolToString() {
        String input = "symbol";

        assertSExpressionMatchesString(input.toUpperCase(), new Symbol(input));
    }

    @Test
    public void testSimpleConsToString() {
        String expected = "(1)";
        Cons cons = new Cons(new LispNumber("1"), Nil.getInstance());

        assertSExpressionMatchesString(expected, cons);
    }

    @Test
    public void testComplexConsToString() {
        String expected = "(1 A \"string\")";
        Cons list = new Cons(new LispNumber("1"),
                             new Cons(new Symbol("a"), new Cons(new LispString("\"string\""), Nil.getInstance())));

        assertSExpressionMatchesString(expected, list);
    }

    @Test
    public void testImproperListToString() {
        String expected = "(A . B)";
        Cons list = new Cons(new Symbol("A"), new Symbol("B"));

        assertSExpressionMatchesString(expected, list);
    }

    @Test
    public void testLambdaExpressionToString() {
        String expected = "(LAMBDA)";
        LambdaExpression lambda = new LambdaExpression(new Cons(new Symbol("lambda"), Nil.getInstance()), null);

        assertSExpressionMatchesString(expected, lambda);
    }

    @Test
    public void testLambdaExpressionGetLambdaExpression() {
        String expected = "(LAMBDA)";
        LambdaExpression lambda = new LambdaExpression(new Cons(new Symbol("lambda"), Nil.getInstance()), null);

        assertSExpressionMatchesString(expected, lambda.getLambdaExpression());
    }

    @Test
    public void testLambdaExpressionGetFunction() {
        String expected = "(LAMBDA)";
        UserDefinedFunction function = new UserDefinedFunction(expected, Nil.getInstance(), Nil.getInstance());
        LambdaExpression lambda = new LambdaExpression(new Cons(new Symbol("lambda"), Nil.getInstance()), function);

        assertEquals(function, lambda.getFunction());
    }

    @Test
    public void testFirstOfNilIsNil() {
        assertEquals(Nil.getInstance(), Nil.getInstance().getFirst());
    }

    @Test
    public void testRestOfNilIsNil() {
        assertEquals(Nil.getInstance(), Nil.getInstance().getRest());
    }

    @Test
    public void afterSettingFirstOfNil_ShouldStillBeNil() {
        Cons nil = Nil.getInstance();
        nil.setFirst(new LispNumber("2"));

        assertEquals(Nil.getInstance(), nil.getFirst());
    }

    @Test
    public void afterSettingRestOfNil_ShouldStillBeNil() {
        Cons nil = Nil.getInstance();
        nil.setRest(new LispNumber("2"));

        assertEquals(Nil.getInstance(), nil.getRest());
    }

    @Test
    public void testNumberValue() {
        BigInteger value = new BigInteger("12");
        LispNumber number = new LispNumber(value.toString());

        assertEquals(value, number.getValue());
    }

    @Test(expected = InvalidNumberException.class)
    public void testInvalidNumberText_ThrowsException() {
        new LispNumber("a");
    }

    @Test
    public void testInvalidNumberException_HasCorrectSeverity() {
        try {
            new LispNumber("a");
        } catch (InvalidNumberException e) {
            assertEquals(ERROR, e.getSeverity());
        }
    }

    @Test
    public void testInvalidNumberException_HasMessageText() {
        try {
            new LispNumber("a");
        } catch (InvalidNumberException e) {
            String message = e.getMessage();

            assertNotNull(message);
            assertTrue(message.length() > 0);
        }
    }

    @Test
    public void testLispNumberConstants() {
        assertEquals(BigInteger.ZERO, LispNumber.ZERO.getValue());
        assertEquals(BigInteger.ONE, LispNumber.ONE.getValue());
    }

}
