package org.cyclops.integrateddynamics.core.evaluate.variable;

import org.cyclops.cyclopscore.helper.CyclopsCoreInstance;
import org.cyclops.integrateddynamics.ModBaseMocked;
import org.cyclops.integrateddynamics.api.evaluate.EvaluationException;
import org.cyclops.integrateddynamics.api.evaluate.variable.IValue;
import org.cyclops.integrateddynamics.api.evaluate.variable.IValueType;
import org.cyclops.integrateddynamics.api.evaluate.variable.IValueTypeListProxy;
import org.cyclops.integrateddynamics.api.evaluate.variable.IVariable;
import org.cyclops.integrateddynamics.core.evaluate.operator.CurriedOperator;
import org.cyclops.integrateddynamics.core.evaluate.operator.OperatorBase;
import org.cyclops.integrateddynamics.core.evaluate.operator.Operators;
import org.hamcrest.CoreMatchers;
import org.junit.Before;
import org.junit.Test;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;

/**
 * Test the different integer operators.
 * @author rubensworks
 */
public class TestOperatorOperators {

    static { CyclopsCoreInstance.MOD = new ModBaseMocked(); }
    private static final DummyValueType DUMMY_TYPE = DummyValueType.TYPE;
    private static final DummyVariable<DummyValueType.DummyValue> DUMMY_VARIABLE =
            new DummyVariable<DummyValueType.DummyValue>(DUMMY_TYPE, DummyValueType.DummyValue.of());

    private DummyVariableBoolean bFalse;
    private DummyVariableBoolean bTrue;

    private DummyVariableString s1;

    private DummyVariableInteger i0;
    private DummyVariableInteger i1;
    private DummyVariableInteger i2;
    private DummyVariableInteger i3;
    private DummyVariableInteger i4;
    private DummyVariable i4Any;

    private DummyVariableOperator oGeneralIdentity;
    private DummyVariableOperator oLogicalNot;
    private DummyVariableOperator oLogicalAnd;
    private DummyVariableOperator oParseInt;
    private DummyVariableOperator oRelationalEquals;
    private DummyVariableOperator oRelationalGreaterThan;
    private DummyVariableOperator oRelationalLessThan;
    private DummyVariableOperator oArithmeticIncrement;
    private DummyVariableOperator oArithmeticModulus;
    private DummyVariableOperator oArithmeticAddition;
    private DummyVariableOperator oArithmeticMultiplication;
    private DummyVariableOperator oChoice;
    private DummyVariableOperator oPipe;
    private DummyVariableOperator oListLength;
    private DummyVariableOperator oListContains;
    private DummyVariableOperator oOperatorMap;
    private DummyVariableOperator oSubStr;
    private DummyVariableOperator oGetConstStr;

    private DummyVariableList lempty;
    private DummyVariableList lintegers;
    private DummyVariableList lbooleans;

    private DummyVariableString sAnd;
    private DummyVariableString sOther;

    @Before
    public void before() {
        ValueTypeListProxyFactories.load();

        bFalse = new DummyVariableBoolean(ValueTypeBoolean.ValueBoolean.of(false));
        bTrue  = new DummyVariableBoolean(ValueTypeBoolean.ValueBoolean.of(true));

        s1 = new DummyVariableString(ValueTypeString.ValueString.of("1"));

        i0 = new DummyVariableInteger(ValueTypeInteger.ValueInteger.of(0));
        i1 = new DummyVariableInteger(ValueTypeInteger.ValueInteger.of(1));
        i2 = new DummyVariableInteger(ValueTypeInteger.ValueInteger.of(2));
        i3 = new DummyVariableInteger(ValueTypeInteger.ValueInteger.of(3));
        i4 = new DummyVariableInteger(ValueTypeInteger.ValueInteger.of(4));
        i4Any = new DummyVariable<>(ValueTypes.CATEGORY_ANY, ValueTypeInteger.ValueInteger.of(4));

        oGeneralIdentity          = new DummyVariableOperator(ValueTypeOperator.ValueOperator.of(Operators.GENERAL_IDENTITY));
        oLogicalNot               = new DummyVariableOperator(ValueTypeOperator.ValueOperator.of(Operators.LOGICAL_NOT));
        oLogicalAnd               = new DummyVariableOperator(ValueTypeOperator.ValueOperator.of(Operators.LOGICAL_AND));
        oParseInt                 = new DummyVariableOperator(ValueTypeOperator.ValueOperator.of(Operators.PARSE_INTEGER));
        oRelationalEquals         = new DummyVariableOperator(ValueTypeOperator.ValueOperator.of(Operators.RELATIONAL_EQUALS));
        oRelationalGreaterThan    = new DummyVariableOperator(ValueTypeOperator.ValueOperator.of(Operators.RELATIONAL_GT));
        oRelationalLessThan       = new DummyVariableOperator(ValueTypeOperator.ValueOperator.of(Operators.RELATIONAL_LT));
        oArithmeticIncrement      = new DummyVariableOperator(ValueTypeOperator.ValueOperator.of(Operators.ARITHMETIC_INCREMENT));
        oArithmeticModulus        = new DummyVariableOperator(ValueTypeOperator.ValueOperator.of(Operators.ARITHMETIC_MODULUS));
        oArithmeticAddition       = new DummyVariableOperator(ValueTypeOperator.ValueOperator.of(Operators.ARITHMETIC_ADDITION));
        oArithmeticMultiplication = new DummyVariableOperator(ValueTypeOperator.ValueOperator.of(Operators.ARITHMETIC_MULTIPLICATION));
        oChoice                   = new DummyVariableOperator(ValueTypeOperator.ValueOperator.of(Operators.GENERAL_CHOICE));
        oPipe                     = new DummyVariableOperator(ValueTypeOperator.ValueOperator.of(Operators.OPERATOR_PIPE));
        oListLength               = new DummyVariableOperator(ValueTypeOperator.ValueOperator.of(Operators.LIST_LENGTH));
        oListContains             = new DummyVariableOperator(ValueTypeOperator.ValueOperator.of(Operators.LIST_CONTAINS));
        oOperatorMap              = new DummyVariableOperator(ValueTypeOperator.ValueOperator.of(Operators.OPERATOR_MAP));
        oSubStr                   = new DummyVariableOperator(ValueTypeOperator.ValueOperator.of(Operators.STRING_SUBSTRING));
        oGetConstStr              = new DummyVariableOperator(ValueTypeOperator.ValueOperator.of(new OperatorBase("NONE", "NONE", "NONE", "NONE", false, new IValueType[0], ValueTypes.STRING, variables -> ValueTypeString.ValueString.of("HI"), null) {
            @Override
            protected String getUnlocalizedType() {
                return "NONE";
            }
        }));

        lempty = new DummyVariableList(ValueTypeList.ValueList.ofAll());
        lintegers = new DummyVariableList(ValueTypeList.ValueList.ofAll(i0.getValue(), i1.getValue(), i2.getValue(), i3.getValue()));
        lbooleans = new DummyVariableList(ValueTypeList.ValueList.ofAll(bFalse.getValue(), bTrue.getValue(), bFalse.getValue(), bTrue.getValue()));

        sAnd = new DummyVariableString(ValueTypeString.ValueString.of("integrateddynamics:logical_and"));
        sOther = new DummyVariableString(ValueTypeString.ValueString.of("integrateddynamics:other"));
    }

    /**
     * ----------------------------------- APPLY -----------------------------------
     */

    @Test
    public void testApply() throws EvaluationException {
        IValue res1 = Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oGeneralIdentity, bFalse});
        assertThat("result is a boolean", res1, instanceOf(ValueTypeBoolean.ValueBoolean.class));
        assertThat("id(false) == false", ((ValueTypeBoolean.ValueBoolean) res1).getRawValue(), is(false));

        IValue res2 = Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oGeneralIdentity, bTrue});
        assertThat("id(true) == true", ((ValueTypeBoolean.ValueBoolean) res2).getRawValue(), is(true));

        IValue res3 = Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oLogicalNot, bTrue});
        assertThat("not(true) == false", ((ValueTypeBoolean.ValueBoolean) res3).getRawValue(), is(false));

        IValue res4 = Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oLogicalNot, bFalse});
        assertThat("not(true) == false", ((ValueTypeBoolean.ValueBoolean) res4).getRawValue(), is(true));
    }

    @Test
    public void testApplyCurring() throws EvaluationException {
        IValue res1 = Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oLogicalAnd, bTrue});
        assertThat("result is an operator", res1, instanceOf(ValueTypeOperator.ValueOperator.class));
        assertThat("result is a curriedoperator", ((ValueTypeOperator.ValueOperator) res1).getRawValue(), instanceOf(CurriedOperator.class));

        DummyVariableOperator oLogicalAndCurriedTrue = new DummyVariableOperator((ValueTypeOperator.ValueOperator) res1);
        IValue res2_1 = Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oLogicalAndCurriedTrue, bTrue});
        assertThat("result is a boolean", res2_1, instanceOf(ValueTypeBoolean.ValueBoolean.class));
        assertThat("and(true)(true) == true", ((ValueTypeBoolean.ValueBoolean) res2_1).getRawValue(), is(true));
        IValue res2_2 = Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oLogicalAndCurriedTrue, bFalse});
        assertThat("and(true)(false) == false", ((ValueTypeBoolean.ValueBoolean) res2_2).getRawValue(), is(false));

        IValue res3 = Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oLogicalAnd, bFalse});
        assertThat("result is a curriedoperator", ((ValueTypeOperator.ValueOperator) res3).getRawValue(), instanceOf(CurriedOperator.class));

        DummyVariableOperator oLogicalAndCurriedFalse = new DummyVariableOperator((ValueTypeOperator.ValueOperator) res3);
        IValue res4_1 = Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oLogicalAndCurriedFalse, bTrue});
        assertThat("result is a boolean", res2_1, instanceOf(ValueTypeBoolean.ValueBoolean.class));
        assertThat("and(false)(true) == false", ((ValueTypeBoolean.ValueBoolean) res4_1).getRawValue(), is(false));
        IValue res4_2 = Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oLogicalAndCurriedFalse, bFalse});
        assertThat("and(false)(false) == false", ((ValueTypeBoolean.ValueBoolean) res4_2).getRawValue(), is(false));
    }

    @Test(expected = EvaluationException.class)
    public void testInvalidInputSizeApplyLarge() throws EvaluationException {
        Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oGeneralIdentity, bFalse, bFalse});
    }

    @Test(expected = EvaluationException.class)
    public void testInvalidInputSizeApplyCurryingLarge() throws EvaluationException {
        DummyVariableOperator curriedOperatorValue = new DummyVariableOperator((ValueTypeOperator.ValueOperator)
                Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oLogicalAnd, bTrue}));
        Operators.OPERATOR_APPLY.evaluate(new IVariable[]{curriedOperatorValue, bFalse, bFalse});
    }

    @Test(expected = EvaluationException.class)
    public void testInvalidInputSizeApplySmall() throws EvaluationException {
        Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oGeneralIdentity});
    }

    @Test(expected = EvaluationException.class)
    public void testInvalidInputSizeApplyCurringSmall() throws EvaluationException {
        DummyVariableOperator curriedOperatorValue = new DummyVariableOperator((ValueTypeOperator.ValueOperator)
                Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oLogicalAnd, bTrue}));
        Operators.OPERATOR_APPLY.evaluate(new IVariable[]{curriedOperatorValue});
    }

    @Test(expected = EvaluationException.class)
    public void testInvalidOperatorTypeApply() throws EvaluationException {
        Operators.OPERATOR_APPLY.evaluate(new IVariable[]{bFalse, bFalse});
    }

    @Test(expected = EvaluationException.class)
    public void testInvalidOperatorInputTypeApply() throws EvaluationException {
        Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oLogicalNot, oGeneralIdentity});
    }

    @Test(expected = EvaluationException.class)
    public void testInvalidOperatorInputTypeAnyApply() throws EvaluationException {
        Operators.OPERATOR_APPLY.evaluate(new IVariable[]{i4Any, i4Any});
    }

    @Test
    public void testValidateTypesApply() {
        assertThat(Operators.OPERATOR_APPLY.validateTypes(new IValueType[]{}), notNullValue());
        assertThat(Operators.OPERATOR_APPLY.validateTypes(new IValueType[]{ValueTypes.OPERATOR}), notNullValue());
        assertThat(Operators.OPERATOR_APPLY.validateTypes(new IValueType[]{ValueTypes.INTEGER}), notNullValue());
        assertThat(Operators.OPERATOR_APPLY.validateTypes(new IValueType[]{ValueTypes.OPERATOR, ValueTypes.CATEGORY_ANY}), nullValue());
    }

    @Test
    public void testConditionalOutputTypesApply() {
        assertThat(Operators.OPERATOR_APPLY.getConditionalOutputType(new IVariable[]{oGeneralIdentity, bFalse}),
                CoreMatchers.<IValueType>is(ValueTypes.BOOLEAN));
        assertThat(Operators.OPERATOR_APPLY.getConditionalOutputType(new IVariable[]{oGeneralIdentity, i0}),
                CoreMatchers.<IValueType>is(ValueTypes.INTEGER));
        assertThat(Operators.OPERATOR_APPLY.getConditionalOutputType(new IVariable[]{oGeneralIdentity, oGeneralIdentity}),
                CoreMatchers.<IValueType>is(ValueTypes.OPERATOR));

        assertThat(Operators.OPERATOR_APPLY.getConditionalOutputType(new IVariable[]{oLogicalNot, bFalse}),
                CoreMatchers.<IValueType>is(ValueTypes.BOOLEAN));

        // In some cases, validation can succeed because of parameters being ANY.
        // In this case, the tested method should be able to handle ANY variables with possibly non-operator values.
        assertThat(Operators.OPERATOR_APPLY.getConditionalOutputType(new IVariable[]{i4Any, i4Any}),
                CoreMatchers.<IValueType>is(ValueTypes.CATEGORY_ANY));
    }

    @Test
    public void testConditionalOutputTypesApplyCurring() throws EvaluationException {
        DummyVariableOperator curriedOperatorValue = new DummyVariableOperator((ValueTypeOperator.ValueOperator)
                Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oLogicalAnd, bTrue}));

        assertThat(Operators.OPERATOR_APPLY.getConditionalOutputType(new IVariable[]{curriedOperatorValue, bFalse}),
                CoreMatchers.<IValueType>is(ValueTypes.BOOLEAN));
    }

    /**
     * ----------------------------------- APPLY_2 -----------------------------------
     */

    @Test
    public void testApply2() throws EvaluationException {
        IValue res1 = Operators.OPERATOR_APPLY_2.evaluate(new IVariable[]{oLogicalAnd, bTrue, bFalse});
        assertThat("result is a boolean", res1, instanceOf(ValueTypeBoolean.ValueBoolean.class));
        assertThat("and(true, false) == false", ((ValueTypeBoolean.ValueBoolean) res1).getRawValue(), is(false));

        IValue res2 = Operators.OPERATOR_APPLY_2.evaluate(new IVariable[]{oLogicalAnd, bTrue, bTrue});
        assertThat("and(true, true) == true", ((ValueTypeBoolean.ValueBoolean) res2).getRawValue(), is(true));

        IValue res3 = Operators.OPERATOR_APPLY_2.evaluate(new IVariable[]{oRelationalGreaterThan, i0, i1});
        assertThat("0 > 1 == false", ((ValueTypeBoolean.ValueBoolean) res3).getRawValue(), is(false));

        IValue res4 = Operators.OPERATOR_APPLY_2.evaluate(new IVariable[]{oRelationalGreaterThan, i2, i1});
        assertThat("2 > 1 == true", ((ValueTypeBoolean.ValueBoolean) res4).getRawValue(), is(true));
    }

    @Test
    public void testApply2OnOpWith3Inputs() throws EvaluationException {
        IValue res1 = Operators.OPERATOR_APPLY_2.evaluate(new IVariable[]{oChoice, bTrue, i1});
        assertThat("result is an operator", res1, instanceOf(ValueTypeOperator.ValueOperator.class));
        assertThat("apply_2(?, true, 1)", ((ValueTypeOperator.ValueOperator) res1).getRawValue().getInputTypes().length, is(1));
        assertThat("apply_2(?, true, 1)", ((ValueTypeOperator.ValueOperator) res1).getRawValue().getRequiredInputLength(), is(1));
        assertThat("apply_2(?, true, 1)", ((ValueTypeOperator.ValueOperator) res1).getRawValue().getInputTypes()[0], is(ValueTypes.CATEGORY_ANY));
        assertThat("apply_2(?, true, 1)", ((ValueTypeOperator.ValueOperator) res1).getRawValue().getOutputType(), is(ValueTypes.CATEGORY_ANY));

        IValue res2 = Operators.OPERATOR_APPLY.evaluate(new IVariable[]{new Variable<>(res1), i2});
        assertThat("result is an integer", res2, instanceOf(ValueTypeInteger.ValueInteger.class));
        assertThat("apply_2(?, true, 1)(2)", ((ValueTypeInteger.ValueInteger) res2).getRawValue(), is(1));
    }

    @Test(expected = EvaluationException.class)
    public void testInvalidInputSizeApply2Large() throws EvaluationException {
        Operators.OPERATOR_APPLY_2.evaluate(new IVariable[]{oLogicalAnd, bFalse, bFalse, bFalse});
    }

    @Test(expected = EvaluationException.class)
    public void testInvalidInputSizeApply2Small() throws EvaluationException {
        Operators.OPERATOR_APPLY_2.evaluate(new IVariable[]{oLogicalAnd, bTrue});
    }

    @Test(expected = EvaluationException.class)
    public void testInvalidOperatorTypeApply2() throws EvaluationException {
        Operators.OPERATOR_APPLY_2.evaluate(new IVariable[]{bFalse, bFalse, bFalse});
    }

    @Test(expected = EvaluationException.class)
    public void testInvalidOperatorInputTypeApply2() throws EvaluationException {
        Operators.OPERATOR_APPLY_2.evaluate(new IVariable[]{oLogicalNot, oGeneralIdentity, bTrue});
    }

    @Test
    public void testValidateTypesApply2() {
        assertThat(Operators.OPERATOR_APPLY_2.validateTypes(new IValueType[]{}), notNullValue());
        assertThat(Operators.OPERATOR_APPLY_2.validateTypes(new IValueType[]{ValueTypes.OPERATOR}), notNullValue());
        assertThat(Operators.OPERATOR_APPLY_2.validateTypes(new IValueType[]{ValueTypes.OPERATOR, ValueTypes.CATEGORY_ANY}), notNullValue());
        assertThat(Operators.OPERATOR_APPLY_2.validateTypes(new IValueType[]{ValueTypes.OPERATOR, ValueTypes.CATEGORY_ANY, ValueTypes.CATEGORY_ANY}), nullValue());
    }

    @Test
    public void testConditionalOutputTypesApply2() {
        assertThat(Operators.OPERATOR_APPLY_2.getConditionalOutputType(new IVariable[]{oLogicalAnd, bFalse, bFalse}),
                CoreMatchers.<IValueType>is(ValueTypes.BOOLEAN));
        assertThat(Operators.OPERATOR_APPLY_2.getConditionalOutputType(new IVariable[]{oArithmeticModulus, i0, i0}),
                CoreMatchers.<IValueType>is(ValueTypes.INTEGER));

        assertThat(Operators.OPERATOR_APPLY_2.getConditionalOutputType(new IVariable[]{oRelationalEquals, bFalse, bFalse}),
                CoreMatchers.<IValueType>is(ValueTypes.BOOLEAN));
    }

    /**
     * ----------------------------------- APPLY_3 -----------------------------------
     */

    @Test
    public void testApply3() throws EvaluationException {
        IValue res1 = Operators.OPERATOR_APPLY_3.evaluate(new IVariable[]{oSubStr, i0, i1, sAnd});
        assertThat("result is a string", res1, instanceOf(ValueTypeString.ValueString.class));
        assertThat("apply_3(substr, 0, 1, 'operator...') == 'o''", ((ValueTypeString.ValueString) res1).getRawValue(), is("i"));
    }

    @Test
    public void testConditionalOutputTypesApply3() {
        assertThat(Operators.OPERATOR_APPLY_3.getConditionalOutputType(new IVariable[]{oSubStr, i0, i1, sAnd}),
                CoreMatchers.<IValueType>is(ValueTypes.STRING));
    }

    @Test
    public void testValidateTypesApply3() {
        assertThat(Operators.OPERATOR_APPLY_3.validateTypes(new IValueType[]{}), notNullValue());
        assertThat(Operators.OPERATOR_APPLY_3.validateTypes(new IValueType[]{ValueTypes.OPERATOR}), notNullValue());
        assertThat(Operators.OPERATOR_APPLY_3.validateTypes(new IValueType[]{ValueTypes.OPERATOR, ValueTypes.CATEGORY_ANY}), notNullValue());
        assertThat(Operators.OPERATOR_APPLY_3.validateTypes(new IValueType[]{ValueTypes.OPERATOR, ValueTypes.CATEGORY_ANY, ValueTypes.CATEGORY_ANY}), notNullValue());
        assertThat(Operators.OPERATOR_APPLY_3.validateTypes(new IValueType[]{ValueTypes.OPERATOR, ValueTypes.CATEGORY_ANY, ValueTypes.CATEGORY_ANY, ValueTypes.CATEGORY_ANY}), nullValue());
    }

    @Test
    public void testApply3ThreeAnd() throws EvaluationException {
        DummyVariableOperator oPipeFlip = new DummyVariableOperator((ValueTypeOperator.ValueOperator) Operators.OPERATOR_FLIP.evaluate(new IVariable[]{oPipe}));
        DummyVariableOperator oThreeAnd_1 = new DummyVariableOperator((ValueTypeOperator.ValueOperator) Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oPipeFlip, oLogicalAnd}));
        DummyVariableOperator oThreeAnd = new DummyVariableOperator((ValueTypeOperator.ValueOperator) Operators.OPERATOR_APPLY_2.evaluate(new IVariable[]{oPipeFlip, oThreeAnd_1, oLogicalAnd}));

        IValue res1 = Operators.OPERATOR_APPLY_3.evaluate(new IVariable[]{oThreeAnd, bTrue, bTrue, bTrue});
        assertThat("result is a boolean", res1.getType(), is(ValueTypes.BOOLEAN));
        assertThat("result is a boolean", res1, instanceOf(ValueTypeBoolean.ValueBoolean.class));
        assertThat("apply_3(&& &&, true, true, true)", ((ValueTypeBoolean.ValueBoolean) res1).getRawValue(), is(true));

        assertThat(Operators.OPERATOR_APPLY_3.getConditionalOutputType(new IVariable[]{oThreeAnd, bTrue, bTrue, bTrue}), is(ValueTypes.BOOLEAN));
    }

    /**
     * ----------------------------------- APPLY_N -----------------------------------
     */

    @Test
    public void testApplyN() throws EvaluationException {
        DummyVariableList applyArgs = new DummyVariableList(ValueTypeList.ValueList.ofAll(i0.getValue(), i1.getValue(), sAnd.getValue()));
        IValue res1 = Operators.OPERATOR_APPLY_N.evaluate(new IVariable[]{oSubStr, applyArgs});
        assertThat("result is a string", res1, instanceOf(ValueTypeString.ValueString.class));
        assertThat("apply_n(substr, [0, 1, 'operator...']) == 'o''", ((ValueTypeString.ValueString) res1).getRawValue(), is("i"));
    }

    @Test
    public void testConditionalOutputTypesApplyN() {
        DummyVariableList applyArgs = new DummyVariableList(ValueTypeList.ValueList.ofAll(i0.getValue(), i1.getValue(), sAnd.getValue()));
        assertThat(Operators.OPERATOR_APPLY_N.getConditionalOutputType(new IVariable[]{oSubStr, applyArgs}),
                CoreMatchers.<IValueType>is(ValueTypes.STRING));
    }

    @Test
    public void testValidateTypesApplyN() {
        assertThat(Operators.OPERATOR_APPLY_N.validateTypes(new IValueType[]{}), notNullValue());
        assertThat(Operators.OPERATOR_APPLY_N.validateTypes(new IValueType[]{ValueTypes.OPERATOR}), notNullValue());
        assertThat(Operators.OPERATOR_APPLY_N.validateTypes(new IValueType[]{ValueTypes.OPERATOR, ValueTypes.CATEGORY_ANY}), nullValue());
        assertThat(Operators.OPERATOR_APPLY_N.validateTypes(new IValueType[]{ValueTypes.OPERATOR, ValueTypes.CATEGORY_ANY, ValueTypes.CATEGORY_ANY}), notNullValue());
    }

    /**
     * ----------------------------------- APPLY_0 -----------------------------------
     */

    @Test
    public void testApply0() throws EvaluationException {
        IValue res1 = Operators.OPERATOR_APPLY_0.evaluate(new IVariable[]{oGetConstStr});
        assertThat("result is a string", res1, instanceOf(ValueTypeString.ValueString.class));
        assertThat("apply_0(getConstStr) == 'HI'", ((ValueTypeString.ValueString) res1).getRawValue(), is("HI"));
    }

    @Test
    public void testConditionalOutputTypesApply0() {
        assertThat(Operators.OPERATOR_APPLY_0.getConditionalOutputType(new IVariable[]{oGetConstStr}),
                CoreMatchers.<IValueType>is(ValueTypes.STRING));
    }

    @Test
    public void testValidateTypesApply0() {
        assertThat(Operators.OPERATOR_APPLY_0.validateTypes(new IValueType[]{}), notNullValue());
        assertThat(Operators.OPERATOR_APPLY_0.validateTypes(new IValueType[]{ValueTypes.OPERATOR}), nullValue());
        assertThat(Operators.OPERATOR_APPLY_0.validateTypes(new IValueType[]{ValueTypes.OPERATOR, ValueTypes.CATEGORY_ANY}), notNullValue());
    }

    /**
     * ----------------------------------- MAP -----------------------------------
     */

    @Test
    public void testMap() throws EvaluationException {
        IValue res1 = Operators.OPERATOR_MAP.evaluate(new IVariable[]{oArithmeticIncrement, lintegers});
        assertThat("result is a list", res1, instanceOf(ValueTypeList.ValueList.class));
        IValueTypeListProxy list1 = ((ValueTypeList.ValueList) res1).getRawValue();
        assertThat("map([0, 1, 2, 3], ++)[0] == 1", ((ValueTypeInteger.ValueInteger) list1.get(0)).getRawValue(), is(1));
        assertThat("map([0, 1, 2, 3], ++)[0] == 2", ((ValueTypeInteger.ValueInteger) list1.get(1)).getRawValue(), is(2));
        assertThat("map([0, 1, 2, 3], ++)[0] == 3", ((ValueTypeInteger.ValueInteger) list1.get(2)).getRawValue(), is(3));
        assertThat("map([0, 1, 2, 3], ++)[0] == 4", ((ValueTypeInteger.ValueInteger) list1.get(3)).getRawValue(), is(4));
        assertThat(list1.getValueType(), CoreMatchers.<IValueType>is(ValueTypes.CATEGORY_NUMBER));

        IValue res2 = Operators.OPERATOR_MAP.evaluate(new IVariable[]{oLogicalNot, lbooleans});
        IValueTypeListProxy list2 = ((ValueTypeList.ValueList) res2).getRawValue();
        assertThat("map([false, true, false, true], !)[0] == true", ((ValueTypeBoolean.ValueBoolean) list2.get(0)).getRawValue(), is(true));
        assertThat("map([false, true, false, true], !)[1] == false", ((ValueTypeBoolean.ValueBoolean) list2.get(1)).getRawValue(), is(false));
        assertThat("map([false, true, false, true], !)[2] == true", ((ValueTypeBoolean.ValueBoolean) list2.get(2)).getRawValue(), is(true));
        assertThat("map([false, true, false, true], !)[3] == false", ((ValueTypeBoolean.ValueBoolean) list2.get(3)).getRawValue(), is(false));
        assertThat(list2.getValueType(), CoreMatchers.<IValueType>is(ValueTypes.BOOLEAN));

        DummyVariableOperator curriedOperatorValue = new DummyVariableOperator((ValueTypeOperator.ValueOperator)
                Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oLogicalAnd, bTrue}));
        IValue res3 = Operators.OPERATOR_MAP.evaluate(new IVariable[]{curriedOperatorValue, lbooleans});
        IValueTypeListProxy list3 = ((ValueTypeList.ValueList) res3).getRawValue();
        assertThat("map([false, true, false, true], true&&)[0] == false", ((ValueTypeBoolean.ValueBoolean) list3.get(0)).getRawValue(), is(false));
        assertThat("map([false, true, false, true], true&&)[1] == true", ((ValueTypeBoolean.ValueBoolean) list3.get(1)).getRawValue(), is(true));
        assertThat("map([false, true, false, true], true&&)[2] == false", ((ValueTypeBoolean.ValueBoolean) list3.get(2)).getRawValue(), is(false));
        assertThat("map([false, true, false, true], true&&)[3] == true", ((ValueTypeBoolean.ValueBoolean) list3.get(3)).getRawValue(), is(true));
        assertThat(list3.getValueType(), CoreMatchers.<IValueType>is(ValueTypes.BOOLEAN));

        IValue res4 = Operators.OPERATOR_MAP.evaluate(new IVariable[]{oLogicalAnd, lbooleans});
        IValueTypeListProxy list4 = ((ValueTypeList.ValueList) res4).getRawValue();
        assertThat("map([false, true, false, true], &&)[0] is an operator", list4.get(0).getType(), is(ValueTypes.OPERATOR));
        assertThat("map([false, true, false, true], &&)[1] is an operator", list4.get(1).getType(), is(ValueTypes.OPERATOR));
        assertThat("map([false, true, false, true], &&)[2] is an operator", list4.get(2).getType(), is(ValueTypes.OPERATOR));
        assertThat("map([false, true, false, true], &&)[3] is an operator", list4.get(3).getType(), is(ValueTypes.OPERATOR));
        assertThat("map([false, true, false, true], &&)[0](true) == false",
                ((ValueTypeBoolean.ValueBoolean) (((ValueTypeOperator.ValueOperator) list4.get(0)).getRawValue().evaluate(bTrue))).getRawValue(), is(false));
        assertThat("map([false, true, false, true], &&)[1](true) == true",
                ((ValueTypeBoolean.ValueBoolean) (((ValueTypeOperator.ValueOperator) list4.get(1)).getRawValue().evaluate(bTrue))).getRawValue(), is(true));
        assertThat("map([false, true, false, true], &&)[2](true) == false",
                ((ValueTypeBoolean.ValueBoolean) (((ValueTypeOperator.ValueOperator) list4.get(2)).getRawValue().evaluate(bTrue))).getRawValue(), is(false));
        assertThat("map([false, true, false, true], &&)[3](true) == true",
                ((ValueTypeBoolean.ValueBoolean) (((ValueTypeOperator.ValueOperator) list4.get(3)).getRawValue().evaluate(bTrue))).getRawValue(), is(true));
        assertThat(list4.getValueType(), CoreMatchers.<IValueType>is(ValueTypes.OPERATOR));
    }

    @Test(expected = EvaluationException.class)
    public void testInvalidInputSizeMapLarge() throws EvaluationException {
        Operators.OPERATOR_MAP.evaluate(new IVariable[]{oArithmeticIncrement, lintegers, lintegers});
    }

    @Test(expected = EvaluationException.class)
    public void testInvalidInputSizeMapSmall() throws EvaluationException {
        Operators.OPERATOR_MAP.evaluate(new IVariable[]{oArithmeticIncrement});
    }

    @Test(expected = EvaluationException.class)
    public void testInvalidOperatorTypeMap() throws EvaluationException {
        Operators.OPERATOR_MAP.evaluate(new IVariable[]{bFalse, bFalse});
    }

    @Test(expected = EvaluationException.class)
    public void testInvalidOperatorInputTypeMap() throws EvaluationException {
        Operators.OPERATOR_MAP.evaluate(new IVariable[]{oArithmeticIncrement, oArithmeticIncrement});
    }

    @Test
    public void testValidateTypesMap() {
        assertThat(Operators.OPERATOR_MAP.validateTypes(new IValueType[]{}), notNullValue());
        assertThat(Operators.OPERATOR_MAP.validateTypes(new IValueType[]{ValueTypes.OPERATOR}), notNullValue());
        assertThat(Operators.OPERATOR_MAP.validateTypes(new IValueType[]{ValueTypes.OPERATOR, ValueTypes.BOOLEAN}), notNullValue());
        assertThat(Operators.OPERATOR_MAP.validateTypes(new IValueType[]{ValueTypes.OPERATOR, ValueTypes.CATEGORY_ANY}), nullValue());
    }

    @Test
    public void testConditionalOutputTypesMap() throws EvaluationException {
        assertThat(Operators.OPERATOR_MAP.getConditionalOutputType(new IVariable[]{oArithmeticIncrement, lintegers}),
                CoreMatchers.<IValueType>is(ValueTypes.LIST));
        assertThat(Operators.OPERATOR_MAP.getConditionalOutputType(new IVariable[]{oLogicalNot, lbooleans}),
                CoreMatchers.<IValueType>is(ValueTypes.LIST));

        DummyVariableOperator curriedOperatorValue = new DummyVariableOperator((ValueTypeOperator.ValueOperator)
                Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oLogicalAnd, bTrue}));
        assertThat(Operators.OPERATOR_MAP.getConditionalOutputType(new IVariable[]{curriedOperatorValue, lbooleans}),
                CoreMatchers.<IValueType>is(ValueTypes.LIST));
    }

    /**
     * ----------------------------------- PREDICATE CONJUNCTION -----------------------------------
     */

    @Test
    public void testPredicateConjunction() throws EvaluationException {
        DummyVariableOperator zeroLessThan = new DummyVariableOperator((ValueTypeOperator.ValueOperator)
                Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oRelationalLessThan, i0}));
        DummyVariableOperator twoGreaterThan = new DummyVariableOperator((ValueTypeOperator.ValueOperator)
                Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oRelationalGreaterThan, i2}));
        DummyVariableOperator zeroLessThanandTwoGreaterThan = new DummyVariableOperator((ValueTypeOperator.ValueOperator)
                Operators.OPERATOR_CONJUNCTION.evaluate(new IVariable[]{zeroLessThan, twoGreaterThan}));

        IValue res1 = Operators.OPERATOR_APPLY.evaluate(new IVariable[]{zeroLessThanandTwoGreaterThan, i0});
        assertThat("result is a boolean", res1, instanceOf(ValueTypeBoolean.ValueBoolean.class));
        assertThat("0< && 2>(0) == false", ((ValueTypeBoolean.ValueBoolean) res1).getRawValue(), is(false));
        IValue res2 = Operators.OPERATOR_APPLY.evaluate(new IVariable[]{zeroLessThanandTwoGreaterThan, i1});
        assertThat("0< && 2>(1) == true", ((ValueTypeBoolean.ValueBoolean) res2).getRawValue(), is(true));
        IValue res3 = Operators.OPERATOR_APPLY.evaluate(new IVariable[]{zeroLessThanandTwoGreaterThan, i2});
        assertThat("0< && 2>(2) == false", ((ValueTypeBoolean.ValueBoolean) res3).getRawValue(), is(false));
    }

    @Test(expected = EvaluationException.class)
    public void testInvalidInputSizePredicateConjunctionLarge() throws EvaluationException {
        DummyVariableOperator zeroLessThan = new DummyVariableOperator((ValueTypeOperator.ValueOperator)
                Operators.OPERATOR_APPLY.evaluate(new IVariable[]{oRelationalLessThan, i0}));

        Operators.OPERATOR_CONJUNCTION.evaluate(new IVariable[]{zeroLessThan, zeroLessThan, zeroLessThan});
    }

    @Test(expected = EvaluationException.class)
