package function.builtin;

import static function.builtin.APPLY.apply;
import static testutil.TestUtilities.assertSExpressionsMatch;
import static testutil.TestUtilities.evaluateString;
import static testutil.TestUtilities.parseString;

import org.junit.Test;

import function.ArgumentValidator.BadArgumentTypeException;
import function.ArgumentValidator.DottedArgumentListException;
import function.ArgumentValidator.TooFewArgumentsException;
import function.ArgumentValidator.TooManyArgumentsException;
import function.builtin.EVAL.UndefinedFunctionException;
import sexpression.Cons;
import testutil.SymbolAndFunctionCleaner;

public class APPLYTest extends SymbolAndFunctionCleaner {

    @Test
    public void applyWithSymbol() {
        String input = "(apply '+ '(1 2 3))";

        assertSExpressionsMatch(parseString("6"), evaluateString(input));
    }

    @Test
    public void applyWithLambdaExpression() {
        String input = "(apply (lambda (x) (+ x 1)) '(25))";

        assertSExpressionsMatch(parseString("26"), evaluateString(input));
    }

    @Test
    public void applyWithQuotedLambdaExpression() {
        String input = "(apply '(lambda (x) (+ x 1)) '(25))";

        assertSExpressionsMatch(parseString("26"), evaluateString(input));
    }

    @Test
    public void staticApplyCall() {
        String argumentList = "(+ (25 10))";
        Cons parsedArgumentList = (Cons) parseString(argumentList);

        assertSExpressionsMatch(parseString("35"), apply(parsedArgumentList));
    }

    @Test(expected = UndefinedFunctionException.class)
    public void applyWithUndefinedFunction() {
        evaluateString("(apply 'f '(1 2 3))");
    }

    @Test(expected = BadArgumentTypeException.class)
    public void applyWithNonListSecondArgument() {
        evaluateString("(apply '+ '2)");
    }

    @Test(expected = TooManyArgumentsException.class)
    public void applyWithTooManyArguments() {
        evaluateString("(apply '1 '2 '3)");
    }

    @Test(expected = TooFewArgumentsException.class)
    public void applyWithTooFewArguments() {
        evaluateString("(apply '1)");
    }

    @Test(expected = DottedArgumentListException.class)
    public void applyWithDottedArgumentList_ThrowsException() {
        evaluateString("(apply 'apply (cons 'T 'T))");
    }

}
