package sexpression;

import static org.junit.Assert.*;

import java.math.BigInteger;

import org.junit.*;

import error.ErrorManager;
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 testConsWithNonListCdrToString() {
        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 testCarOfNilIsNil() {
        assertEquals(Nil.getInstance().getCar(), Nil.getInstance());
    }

    @Test
    public void testCdrOfNilIsNil() {
        assertEquals(Nil.getInstance().getCdr(), Nil.getInstance());
    }

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

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

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

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

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

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

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

    @Test
    public void testInvalidNumberException_HasCorrectSeverity() {
        try {
            new LispNumber("a");
        } catch (InvalidNumberException e) {
            assertTrue(e.getSeverity() < ErrorManager.CRITICAL_LEVEL);
        }
    }

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

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

}