package function;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static sexpression.Nil.NIL;
import static testutil.TestUtilities.assertSExpressionsMatch;

import org.junit.Test;

import error.ErrorManager;
import function.ArgumentValidator.TooFewArgumentsException;
import function.ArgumentValidator.TooManyArgumentsException;
import function.UserDefinedFunction.IllegalKeywordRestPositionException;
import sexpression.Cons;
import sexpression.LispNumber;
import sexpression.SExpression;
import sexpression.Symbol;

public class UserDefinedFunctionTest {

    private static final String FUNCTION_NAME = "TEST";

    private UserDefinedFunction createNoArgumentFunctionThatReturnsNil() {
        return new UserDefinedFunction(FUNCTION_NAME, NIL, new Cons(NIL, NIL));
    }

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

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

        assertEquals(NIL, function.call(NIL));
    }

    @Test
    public void nilFunctionToString() {
        UserDefinedFunction function = createNoArgumentFunctionThatReturnsNil();
        Cons expected = new Cons(new Symbol(FUNCTION_NAME), new Cons(NIL, new Cons(NIL, NIL)));

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

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

        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));

        function.call(argumentList);
    }

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

        function.call(NIL);
    }

    @Test
    public void illegalKeywordRestPositionException_HasCorrectAttributes() {
        IllegalKeywordRestPositionException e = new IllegalKeywordRestPositionException(FUNCTION_NAME, NIL);

        assertNotNull(e.getMessage());
        assertTrue(e.getMessage().length() > 0);
        assertEquals(ErrorManager.Severity.ERROR, e.getSeverity());
    }

}
