package function;

import static org.junit.Assert.assertEquals;
import static testutil.TestUtilities.assertSExpressionsMatch;

import org.junit.Test;

import function.ArgumentValidator.*;
import sexpression.*;

public class UserDefinedFunctionTester {

    private static final String FUNCTION_NAME = "TEST";

    private UserDefinedFunction createNoArgumentFunctionThatReturnsNil() {
        return new UserDefinedFunction(FUNCTION_NAME, Nil.getInstance(),
                                       new Cons(Nil.getInstance(), Nil.getInstance()));
    }

    private UserDefinedFunction createOneArgumentFunctionThatReturnsArgument() {
        return new UserDefinedFunction(FUNCTION_NAME, new Cons(new Symbol("X"), Nil.getInstance()),
                                       new Cons(new Symbol("X"), Nil.getInstance()));
    }

    @Test
    public void testNilFunctionCall() {
        UserDefinedFunction function = createNoArgumentFunctionThatReturnsNil();

        assertEquals(Nil.getInstance(), function.call(Nil.getInstance()));
    }

    @Test
    public void testNilFunctionToString() {
        UserDefinedFunction function = createNoArgumentFunctionThatReturnsNil();
        Cons expected = new Cons(new Symbol(FUNCTION_NAME),
                                 new Cons(Nil.getInstance(), new Cons(Nil.getInstance(), Nil.getInstance())));

        assertSExpressionsMatch(expected, function.getLambdaExpression());
    }

    @Test
    public void oneArgumentFunction_ReturnsCorrectValue() {
        UserDefinedFunction function = createOneArgumentFunctionThatReturnsArgument();
        SExpression argument = new LispNumber("23");
        Cons argumentList = new Cons(argument, Nil.getInstance());

        assertSExpressionsMatch(argument, function.call(argumentList));
    }

    @Test(expected = TooManyArgumentsException.class)
    public void oneArgumentFunction_ThrowsExceptionWithTooManyArguments() {
        UserDefinedFunction function = createOneArgumentFunctionThatReturnsArgument();
        SExpression argument = new LispNumber("23");
        Cons argumentList = new Cons(argument, new Cons(argument, Nil.getInstance()));

        function.call(argumentList);
    }

    @Test(expected = TooFewArgumentsException.class)
    public void oneArgumentFunction_ThrowsExceptionWithTooFewArguments() {
        UserDefinedFunction function = createOneArgumentFunctionThatReturnsArgument();

        function.call(Nil.getInstance());
    }

}
