package function.builtin.special;

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.TooFewArgumentsException;
import testutil.SymbolAndFunctionCleaner;

public class CASETest extends SymbolAndFunctionCleaner {

    @Test
    public void caseWithKeyOnly() {
        String input = "(case t)";

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

    @Test
    public void caseWithEmptyConsequent() {
        String input = "(case :a ((:a)))";

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

    @Test
    public void caseWithOneClause_Match() {
        String input = "(case :a ((:a) 'banana))";

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

    @Test
    public void caseWithOneClause_NoMatch() {
        String input = "(case :a ((:b) 'banana))";

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

    @Test
    public void caseWithSeveralClauses_Match() {
        String input = "(case :a ((:b) 'orange) ((:a) 'banana))";

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

    @Test
    public void caseWithSeveralClauses_NoMatch() {
        String input = "(case :a ((:b) 'orange) ((:c) 'banana))";

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

    @Test
    public void caseWithSeveralItemsInKeyList_Match() {
        String input = "(case :a ((:b :a) 'orange) ((:c :d) 'banana))";

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

    @Test
    public void caseWithSeveralItemsInKeyList_NoMatch() {
        String input = "(case :a ((:b :f) 'orange) ((:c :d) 'banana))";

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

    @Test
    public void caseWithSymbolicKeyList_Match() {
        String input = "(case :a (:a 'orange))";

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

    @Test
    public void caseWithSymbolicKeyList_NoMatch() {
        String input = "(case :a (:b 'orange))";

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

    @Test
    public void caseDoesNotEvaluateKeyList() {
        String input = "(case 'x ((x) t))";

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

    @Test
    public void caseWithEmptyKeyList() {
        String input = "(case nil (() 'orange))";

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

    @Test
    public void caseWithNil() {
        String input = "(case nil ((nil) 'orange))";

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

    @Test
    public void caseWithEmptyList() {
        String input = "(case () ((()) 'orange))";

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

    @Test
    public void caseWithList() {
        String input = "(case '(5 4 3) (((1 2) (5 4 3)) 'orange))";

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

    @Test
    public void caseWithDefaultClause() {
        String input = "(case nil (() 'banana) (t 'orange))";

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

    @Test
    public void caseWithOutOfOrderDefaultClause() {
        String input = "(case :a (t 'orange) (:a 'banana))";

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

    @Test
    public void caseWithKeyListContainingT() {
        String input = "(case t ((t) 'banana))";

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

    @Test
    public void caseWithMultipleMatches_ReturnsFirst() {
        String input = "(case 2 ((0) 'banana) ((1) 'apple) ((2) 'avocado) ((2) 'greenbean))";

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

    @Test
    public void caseEvaluatesMultipleConsequents() {
        String input = "(case 2 (1 1) (2 (setq x 'x) (setq y 'y)) (3 3)))";

        evaluateString(input);

        assertSExpressionsMatch(parseString("x"), evaluateString("x"));
        assertSExpressionsMatch(parseString("y"), evaluateString("y"));
    }

    @Test
    public void caseReturnsValueOfLastConsequent() {
        String input = "(case 2 (1 1) (2 3 4 5) (3 3))";

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

    @Test
    public void caseOnlyEvaluatesConsequentInFirstMatchingClause() {
        String input = "(case 2 ((0) (setq zero 0)) ((1) (setq one 1)) ((2) (setq two '2)) ((2) (setq two 'two)))";

        evaluateString("(setq zero nil)");
        evaluateString("(setq one nil)");
        evaluateString(input);

        assertSExpressionsMatch(parseString("nil"), evaluateString("zero"));
        assertSExpressionsMatch(parseString("nil"), evaluateString("one"));
        assertSExpressionsMatch(parseString("2"), evaluateString("two"));
    }

    @Test(expected = TooFewArgumentsException.class)
    public void caseWithTooFewArguments() {
        evaluateString("(case)");
    }

    @Test(expected = BadArgumentTypeException.class)
    public void caseWithNonListClause() {
        evaluateString("(case :a t)");
    }

    @Test(expected = BadArgumentTypeException.class)
    public void caseWithEmptyClause() {
        evaluateString("(case :a ())");
    }

    @Test(expected = BadArgumentTypeException.class)
    public void caseWithNilClause() {
        evaluateString("(case :a nil)");
    }

}
