/*
 * Copyright 2006-2019 DLR, Germany
 * 
 * SPDX-License-Identifier: EPL-1.0
 * 
 * http://www.rcenvironment.de/
 */

package de.rcenvironment.core.component.execution.internal;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.UUID;

import org.easymock.Capture;
import org.easymock.CaptureType;
import org.easymock.EasyMock;
import org.junit.Test;

import de.rcenvironment.core.component.api.LoopComponentConstants;
import de.rcenvironment.core.component.execution.api.ComponentExecutionContext;
import de.rcenvironment.core.component.execution.api.ComponentExecutionException;
import de.rcenvironment.core.component.execution.api.ComponentExecutionIdentifier;
import de.rcenvironment.core.component.execution.api.WorkflowGraphHop;
import de.rcenvironment.core.component.execution.internal.ComponentExecutionScheduler.State;
import de.rcenvironment.core.component.execution.internal.InternalTDImpl.InternalTDType;
import de.rcenvironment.core.component.model.api.ComponentDescription;
import de.rcenvironment.core.component.model.api.ComponentInterface;
import de.rcenvironment.core.component.model.configuration.api.ConfigurationDescription;
import de.rcenvironment.core.component.model.endpoint.api.EndpointDatum;
import de.rcenvironment.core.component.model.endpoint.api.EndpointDatumRecipient;
import de.rcenvironment.core.component.model.endpoint.api.EndpointDatumRecipientFactory;
import de.rcenvironment.core.component.model.endpoint.api.EndpointDefinition;
import de.rcenvironment.core.component.model.endpoint.api.EndpointGroupDefinition;
import de.rcenvironment.core.component.testutils.ComponentExecutionContextMock;
import de.rcenvironment.core.component.testutils.EndpointDatumDefaultStub;
import de.rcenvironment.core.datamodel.api.DataType;
import de.rcenvironment.core.datamodel.api.EndpointCharacter;
import de.rcenvironment.core.datamodel.api.TypedDatum;
import de.rcenvironment.core.datamodel.testutils.NotAValueTDStub;
import de.rcenvironment.core.datamodel.testutils.TypedDatumServiceDefaultStub;
import de.rcenvironment.core.datamodel.types.api.NotAValueTD;

/**
 * Test cases for the {@link ComponentExecutionScheduler}.
 * 
 * @author Doreen Seider
 */
public class ComponentExecutionSchedulerTest {

    private static final String EXPECTED = " expected";

    private static final String INPUT_1 = "input_1";

    private static final String INPUT_2 = "input_2";

    private static final String INPUT_3 = "input_3";

    private static final String INPUT_4 = "input_4";

    private static final String INPUT_5 = "input_5";

    private static final String INPUT_6 = "input_6";

    private static final String INPUT_7 = "input_7";

    private static final String OR_GROUP = "orGroup";

    private static final String AND_GROUP = "andGroup";

    private static final boolean CONNECTED = true;

    private static final boolean NOT_CONNECTED = false;

    /**
     * Set up: one input (single, required); one value and 'finish value' received. Expected: Scheduling state become
     * {@link State#PROCESS_INPUT_DATA} and {@link State#FINISHED}.
     * 
     * @throws Exception on unexpected error
     */
    @Test
    public void testSingleRequiredInputForSuccess() throws Exception {

        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Single,
            EndpointDefinition.InputExecutionContraint.Required));
        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>(CaptureType.ALL);
        ComponentExecutionScheduler compExeScheduler = setUpExecutionScheduler(inputMockInfos, capturedEvent);

        List<EndpointDatum> endpointDatumsSent = new ArrayList<>();
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new InternalTDImpl(InternalTDType.WorkflowFinish)));

        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);

        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsSent.get(0));
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.FINISHED, compExeScheduler.getSchedulingState());
    }

    /**
     * Set up: one input (single, required); two values received, which are queued. Expected: Failure event posted to
     * {@link ComponentStateMachine}.
     * 
     * @throws Exception on unexpected error
     */
    @Test
    public void testSingleRequiredInputForFailure() throws Exception {

        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Single,
            EndpointDefinition.InputExecutionContraint.Required));
        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>(CaptureType.ALL);
        ComponentExecutionScheduler compExeScheduler = setUpExecutionScheduler(inputMockInfos, capturedEvent);

        List<EndpointDatum> endpointDatumsSent = new ArrayList<>();
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new TypedDatumMock(DataType.Float)));

        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);
        assertNewSchedulingStateAndSchedulingFailureEventPosted(capturedEvent);
    }

    /**
     * Set up: one input (queue, required); multiple values and 'finish value' received. Expected: Scheduling state become multiple times
     * {@link State#PROCESS_INPUT_DATA} and finally {@link State#FINISHED}.
     * 
     * @throws Exception on unexpected error
     */
    @Test
    public void testQueuedInputForSuccess() throws Exception {

        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Queue,
            EndpointDefinition.InputExecutionContraint.Required));
        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>(CaptureType.ALL);
        ComponentExecutionScheduler compExeScheduler = setUpExecutionScheduler(inputMockInfos, capturedEvent);

        List<EndpointDatum> endpointDatumsSent = new ArrayList<>();
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new InternalTDImpl(InternalTDType.WorkflowFinish)));

        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);

        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsSent.get(0));
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsSent.get(1));
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsSent.get(2));
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.FINISHED, compExeScheduler.getSchedulingState());
    }

    /**
     * Set up: one input (constant, required); one value and 'finish value' received. Expected: Scheduling state become
     * {@link State#PROCESS_INPUT_DATA} and {@link State#FINISHED}.
     * 
     * @throws Exception on unexpected error
     */
    @Test
    public void testConstantInputForSuccess() throws Exception {

        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Constant,
            EndpointDefinition.InputExecutionContraint.Required));
        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>(CaptureType.ALL);
        ComponentExecutionScheduler compExeScheduler = setUpExecutionScheduler(inputMockInfos, capturedEvent);

        List<EndpointDatum> endpointDatumsSent = new ArrayList<>();
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new InternalTDImpl(InternalTDType.WorkflowFinish)));

        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);

        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsSent.get(0));
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.FINISHED, compExeScheduler.getSchedulingState());
    }

    /**
     * Set up: one input (constant, required) and one input(single, required) in two different and groups, Groups in one parent Or group.
     * 
     * Expected: Constant input runs on receiving an input.
     * 
     * @throws ComponentExecutionException on unexpected error
     */
    @Test
    public void testConstantInputWithOrAndAndGroup() throws ComponentExecutionException {
        InputGroupMockInformation mainGroup = new InputGroupMockInformation(OR_GROUP, EndpointGroupDefinition.LogicOperation.Or);
        InputGroupMockInformation andSingleGroup =
            new InputGroupMockInformation("AndSingle", EndpointGroupDefinition.LogicOperation.And, OR_GROUP);
        InputGroupMockInformation andConstGroup =
            new InputGroupMockInformation("AndConst", EndpointGroupDefinition.LogicOperation.And, OR_GROUP);

        List<InputGroupMockInformation> inputGroupMockInfos = new ArrayList<>();
        inputGroupMockInfos.add(mainGroup);
        inputGroupMockInfos.add(andSingleGroup);
        inputGroupMockInfos.add(andConstGroup);
        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Constant,
            EndpointDefinition.InputExecutionContraint.Required, "AndConst", CONNECTED));
        inputMockInfos.add(new InputMockInformation(INPUT_2, EndpointDefinition.InputDatumHandling.Queue,
            EndpointDefinition.InputExecutionContraint.Required, "AndSingle", CONNECTED));

        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>(CaptureType.ALL);
        ComponentExecutionIdentifier executionId = new ComponentExecutionIdentifier(UUID.randomUUID().toString());
        ComponentExecutionScheduler compExeScheduler =
            setUpExecutionScheduler(inputMockInfos, inputGroupMockInfos, capturedEvent, executionId);

        List<EndpointDatum> endpointDatumsSent = new ArrayList<>();
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_2, new TypedDatumMock(DataType.Float)));
        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertTrue(compExeScheduler.isExecutable());
    }

    /**
     * Set up: one input (constant, required); two values received. Expected: Failure event posted to {@link ComponentStateMachine}.
     * 
     * @throws Exception on unexpected error
     */
    @Test
    public void testConstantInputForFailure() throws Exception {

        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Constant,
            EndpointDefinition.InputExecutionContraint.Required));
        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>(CaptureType.ALL);
        ComponentExecutionScheduler compExeScheduler = setUpExecutionScheduler(inputMockInfos, capturedEvent);

        List<EndpointDatum> endpointDatumsSent = new ArrayList<>();
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new TypedDatumMock(DataType.Float)));

        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);
        assertNewSchedulingStateAndSchedulingFailureEventPosted(capturedEvent);
    }

    /**
     * Setup: different kind of inputs; multiple values received. Expected: Appropriate scheduling.
     * 
     * @throws Exception on unexpected error
     */
    @Test
    public void testMultipleInputsForSuccess() throws Exception {

        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Constant,
            EndpointDefinition.InputExecutionContraint.Required, CONNECTED));
        inputMockInfos.add(new InputMockInformation(INPUT_2, EndpointDefinition.InputDatumHandling.Single,
            EndpointDefinition.InputExecutionContraint.Required, CONNECTED));
        inputMockInfos.add(new InputMockInformation(INPUT_3, EndpointDefinition.InputDatumHandling.Queue,
            EndpointDefinition.InputExecutionContraint.Required, CONNECTED));
        inputMockInfos.add(new InputMockInformation(INPUT_4, EndpointDefinition.InputDatumHandling.Queue,
            EndpointDefinition.InputExecutionContraint.Required, CONNECTED));
        inputMockInfos.add(new InputMockInformation(INPUT_5, EndpointDefinition.InputDatumHandling.Queue,
            EndpointDefinition.InputExecutionContraint.RequiredIfConnected, NOT_CONNECTED));
        inputMockInfos.add(new InputMockInformation(INPUT_6, EndpointDefinition.InputDatumHandling.Single,
            EndpointDefinition.InputExecutionContraint.NotRequired, CONNECTED));
        inputMockInfos.add(new InputMockInformation(INPUT_7, EndpointDefinition.InputDatumHandling.Queue,
            EndpointDefinition.InputExecutionContraint.NotRequired, NOT_CONNECTED));
        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>(CaptureType.ALL);
        ComponentExecutionScheduler compExeScheduler = setUpExecutionScheduler(inputMockInfos, capturedEvent);

        List<EndpointDatum> endpointDatumsToSend1 = new ArrayList<>();
        endpointDatumsToSend1.add(new EndpointDatumMock(INPUT_2, new TypedDatumMock(DataType.Float)));
        endpointDatumsToSend1.add(new EndpointDatumMock(INPUT_3, new TypedDatumMock(DataType.Float)));
        endpointDatumsToSend1.add(new EndpointDatumMock(INPUT_4, new TypedDatumMock(DataType.Float)));
        endpointDatumsToSend1.add(new EndpointDatumMock(INPUT_3, new TypedDatumMock(DataType.Float)));
        endpointDatumsToSend1.add(new EndpointDatumMock(INPUT_1, new TypedDatumMock(DataType.Float)));

        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsToSend1);

        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsToSend1.get(0),
            endpointDatumsToSend1.get(1), endpointDatumsToSend1.get(2), endpointDatumsToSend1.get(4));

        List<EndpointDatum> endpointDatumsToSend2 = new ArrayList<>();
        endpointDatumsToSend2.add(new EndpointDatumMock(INPUT_2, new TypedDatumMock(DataType.Float)));
        endpointDatumsToSend2.add(new EndpointDatumMock(INPUT_4, new TypedDatumMock(DataType.Float)));
        endpointDatumsToSend2.add(new EndpointDatumMock(INPUT_1, new InternalTDImpl(InternalTDType.WorkflowFinish)));
        endpointDatumsToSend2.add(new EndpointDatumMock(INPUT_6, new TypedDatumMock(DataType.Float)));
        endpointDatumsToSend2.add(new EndpointDatumMock(INPUT_2, new InternalTDImpl(InternalTDType.WorkflowFinish)));
        endpointDatumsToSend2.add(new EndpointDatumMock(INPUT_3, new InternalTDImpl(InternalTDType.WorkflowFinish)));
        endpointDatumsToSend2.add(new EndpointDatumMock(INPUT_4, new InternalTDImpl(InternalTDType.WorkflowFinish)));
        endpointDatumsToSend2.add(new EndpointDatumMock(INPUT_6, new InternalTDImpl(InternalTDType.WorkflowFinish)));

        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsToSend2);

        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsToSend1.get(4),
            endpointDatumsToSend1.get(3), endpointDatumsToSend2.get(0), endpointDatumsToSend2.get(1));
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.FINISHED, compExeScheduler.getSchedulingState());
    }

    /**
     * Setup: different kind of inputs; multiple values received. Expected: Appropriate scheduling.
     * 
     * @throws Exception on unexpected error
     */
    @Test
    public void testHandlingIfInputValuesAreLeftAfterFinished() throws Exception {
        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Single,
            EndpointDefinition.InputExecutionContraint.Required));
        inputMockInfos.add(new InputMockInformation(INPUT_2, EndpointDefinition.InputDatumHandling.Queue,
            EndpointDefinition.InputExecutionContraint.Required));
        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>(CaptureType.ALL);
        ComponentExecutionScheduler compExeScheduler = setUpExecutionScheduler(inputMockInfos, capturedEvent);

        List<EndpointDatum> endpointDatumsSent = new ArrayList<>();
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_2, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_2, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new InternalTDImpl(InternalTDType.WorkflowFinish)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_2, new InternalTDImpl(InternalTDType.WorkflowFinish)));

        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);

        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsSent.get(0),
            endpointDatumsSent.get(1));
        assertSchedulingFailureEventPosted(capturedEvent);
    }

    /**
     * Setup: different kind of inputs; multiple values received. Expected: Appropriate scheduling.
     * 
     * @throws Exception on unexpected error
     */
    @Test
    public void testHandlingIfInputsAreRequiredButNotConnected() throws Exception {
        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Single,
            EndpointDefinition.InputExecutionContraint.Required, NOT_CONNECTED));
        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>();
        ExecutionSpecificComponentExecutionContextMock compExeCtxMock = new ExecutionSpecificComponentExecutionContextMock(inputMockInfos);
        ComponentExecutionScheduler compExeScheduler =
            new ComponentExecutionScheduler(createCompExeRelatedInstancesStub(compExeCtxMock,
                createComponentStateMachineMock(capturedEvent)));
        try {
            compExeScheduler.initialize(compExeCtxMock);
            fail(ComponentExecutionException.class.getSimpleName() + EXPECTED);
        } catch (ComponentExecutionException e) {
            assertTrue(e.getMessage().contains("not connected"));
            assertTrue(true);
        }
    }

    /**
     * Set up: two inputs (constant, single; both required); one value each; one value to input of type single; reset value to one of them,
     * one value to single, one value to constant.
     * 
     * Expected: {@link State#PROCESS_INPUT_DATA} 1) after both of the inputs have received values for the first time, 2) after value at
     * input of type single was received, 3) after both of the inputs have received values after reset was performed
     * 
     * @throws Exception on unexpected error
     */
    @Test
    public void testResetComponentForSuccess() throws Exception {
        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Constant,
            EndpointDefinition.InputExecutionContraint.Required));
        inputMockInfos.add(new InputMockInformation(INPUT_2, EndpointDefinition.InputDatumHandling.Queue,
            EndpointDefinition.InputExecutionContraint.Required));
        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>(CaptureType.ALL);
        ComponentExecutionIdentifier executionId = new ComponentExecutionIdentifier(UUID.randomUUID().toString());
        ComponentExecutionScheduler compExeScheduler = setUpExecutionScheduler(inputMockInfos, capturedEvent, executionId);

        List<EndpointDatum> endpointDatumsSent = new ArrayList<>();
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_2, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_2, new TypedDatumMock(DataType.Float)));

        Queue<WorkflowGraphHop> resetCycleHops = new LinkedList<>();
        WorkflowGraphHop workflowGraphHopMock = EasyMock.createStrictMock(WorkflowGraphHop.class);
        EasyMock.expect(workflowGraphHopMock.getHopExecutionIdentifier()).andReturn(executionId).anyTimes();
        EasyMock.replay(workflowGraphHopMock);
        resetCycleHops.add(workflowGraphHopMock);
        InternalTDImpl resetTD = new InternalTDImpl(InternalTDType.NestedLoopReset, resetCycleHops);
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_2, resetTD));

        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);

        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsSent.get(0),
            endpointDatumsSent.get(1));
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsSent.get(0),
            endpointDatumsSent.get(2));
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.RESET, compExeScheduler.getSchedulingState());
        assertEquals(resetTD, compExeScheduler.getResetDatum());
        compExeScheduler.enable();

        endpointDatumsSent = new ArrayList<>();
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_2, new TypedDatumMock(DataType.Float)));

        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);

        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA, compExeScheduler.getSchedulingState());
    }

    /**
     * Set up: two inputs (constant, single; both required); reset value to one of them; reset value has wrong recepient's execution
     * identifier.
     * 
     * Expected: Failure due to wrong execution identifier
     * 
     * @throws Exception on unexpected error
     */
    @Test
    public void testResetComponentForFailure() throws Exception {

        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Constant,
            EndpointDefinition.InputExecutionContraint.Required));
        inputMockInfos.add(new InputMockInformation(INPUT_2, EndpointDefinition.InputDatumHandling.Queue,
            EndpointDefinition.InputExecutionContraint.Required));
        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>(CaptureType.ALL);
        ComponentExecutionScheduler compExeScheduler = setUpExecutionScheduler(inputMockInfos, capturedEvent);

        List<EndpointDatum> endpointDatumsSent = new ArrayList<>();
        Queue<WorkflowGraphHop> resetCycleHops = new LinkedList<>();
        WorkflowGraphHop workflowGraphHopMock = EasyMock.createStrictMock(WorkflowGraphHop.class);
        EasyMock.expect(workflowGraphHopMock.getHopExecutionIdentifier())
            .andReturn(new ComponentExecutionIdentifier(UUID.randomUUID().toString())).anyTimes();
        EasyMock.replay(workflowGraphHopMock);
        resetCycleHops.add(workflowGraphHopMock);
        InternalTDImpl resetTD = new InternalTDImpl(InternalTDType.NestedLoopReset, resetCycleHops);
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_2, resetTD));

        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);
        assertSchedulingFailureEventPosted(capturedEvent);
    }

    /**
     * Set up: two inputs (constant, queue; both required); reset to outputs sent; reset value received at one of the inputs.
     * 
     * Expected: {@link State#LOOP_RESET} 1) after both of the reset values were received
     * 
     * @throws Exception on unexpected error
     */
    @Test
    public void testResetLoopForSuccess() throws Exception {
        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Constant,
            EndpointDefinition.InputExecutionContraint.Required, AND_GROUP, CONNECTED));
        inputMockInfos.add(new InputMockInformation(INPUT_2, EndpointDefinition.InputDatumHandling.Queue,
            EndpointDefinition.InputExecutionContraint.Required, AND_GROUP, CONNECTED));
        inputMockInfos.add(new InputMockInformation(INPUT_3, EndpointDefinition.InputDatumHandling.Constant,
            EndpointDefinition.InputExecutionContraint.Required, OR_GROUP, CONNECTED));
        inputMockInfos.add(new InputMockInformation(INPUT_4, EndpointDefinition.InputDatumHandling.Queue,
            EndpointDefinition.InputExecutionContraint.Required, OR_GROUP, CONNECTED));

        List<InputGroupMockInformation> inputGroupMockInfos = new ArrayList<>();
        inputGroupMockInfos.add(new InputGroupMockInformation(OR_GROUP, EndpointGroupDefinition.LogicOperation.Or));
        inputGroupMockInfos.add(new InputGroupMockInformation(AND_GROUP, EndpointGroupDefinition.LogicOperation.And, OR_GROUP));

        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>(CaptureType.ALL);
        ComponentExecutionIdentifier executionId = new ComponentExecutionIdentifier(UUID.randomUUID().toString());
        ComponentExecutionScheduler compExeScheduler =
            setUpExecutionScheduler(inputMockInfos, inputGroupMockInfos, capturedEvent, executionId);

        sendAndCheckSendingDataToLoopDriverComponent(compExeScheduler, capturedEvent);

        compExeScheduler.fetchEndpointDatums();
        List<String> identifiers = new ArrayList<>();
        identifiers.add(UUID.randomUUID().toString());
        identifiers.add(UUID.randomUUID().toString());
        assertFalse(compExeScheduler.isLoopResetRequested());
        compExeScheduler.addResetDataIdSent(identifiers.get(0));
        compExeScheduler.addResetDataIdSent(identifiers.get(1));
        assertTrue(compExeScheduler.isLoopResetRequested());

        Queue<WorkflowGraphHop> resetCycleHops = new LinkedList<>();
        WorkflowGraphHop workflowGraphHopMock = EasyMock.createStrictMock(WorkflowGraphHop.class);
        EasyMock.expect(workflowGraphHopMock.getHopExecutionIdentifier()).andReturn(executionId).anyTimes();
        EasyMock.replay(workflowGraphHopMock);
        resetCycleHops.add(workflowGraphHopMock);

        List<EndpointDatum> endpointDatumsSent = new ArrayList<>();
        for (String id : identifiers) {
            InternalTDImpl resetTD = new InternalTDImpl(InternalTDType.NestedLoopReset, id, resetCycleHops);
            endpointDatumsSent.add(new EndpointDatumMock(INPUT_2, resetTD));
        }
        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);

        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.LOOP_RESET, compExeScheduler.getSchedulingState());
        compExeScheduler.enable();
        sendAndCheckSendingDataToLoopDriverComponent(compExeScheduler, capturedEvent);
    }

    private void sendAndCheckSendingDataToLoopDriverComponent(ComponentExecutionScheduler compExeScheduler,
        Capture<ComponentStateMachineEvent> capturedEvent) throws InterruptedException, ComponentExecutionException {

        List<EndpointDatum> endpointDatumsSent = new ArrayList<>();
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_2, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_2, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_3, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_4, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_4, new TypedDatumMock(DataType.Float)));

        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);

        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsSent.get(0),
            endpointDatumsSent.get(2));
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsSent.get(1),
            endpointDatumsSent.get(2));
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsSent.get(3));
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsSent.get(4));
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsSent.get(5));
    }

    /**
     * Set up: two inputs (constant, queue; both required); reset to outputs sent; reset value received at one of the inputs, second value
     * has wrong identifier.
     * 
     * Expected: Failure due to wrong reset value identifier received
     * 
     * @throws Exception on unexpected error
     */
    @Test
    public void testResetLoopForFailure() throws Exception {

    }

    /**
     * Set up: two inputs (constant, single; both required); failure value received at one of the inputs.
     * 
     * Expected: {@link State#FAILURE_FORWARD} 1) after failure values were received
     * 
     * @throws Exception on unexpected error
     */
    @Test
    public void testFailureValueAtComponentForSuccess() throws Exception {
        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Constant,
            EndpointDefinition.InputExecutionContraint.Required));
        inputMockInfos.add(new InputMockInformation(INPUT_2, EndpointDefinition.InputDatumHandling.Single,
            EndpointDefinition.InputExecutionContraint.Required));
        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>(CaptureType.ALL);
        ComponentExecutionIdentifier executionId = new ComponentExecutionIdentifier(UUID.randomUUID().toString());
        ComponentExecutionScheduler compExeScheduler = setUpExecutionScheduler(inputMockInfos, capturedEvent, executionId);

        List<EndpointDatum> endpointDatumsSent = new ArrayList<>();

        Queue<WorkflowGraphHop> hopsToTraverse = new LinkedList<>();
        WorkflowGraphHop workflowGraphHopMock = EasyMock.createStrictMock(WorkflowGraphHop.class);
        EasyMock.expect(workflowGraphHopMock.getHopExecutionIdentifier()).andReturn(executionId).anyTimes();
        EasyMock.replay(workflowGraphHopMock);
        hopsToTraverse.add(workflowGraphHopMock);
        InternalTDImpl failureTD1 = new InternalTDImpl(InternalTDType.FailureInLoop, hopsToTraverse);
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, failureTD1));
        InternalTDImpl failureTD2 = new InternalTDImpl(InternalTDType.FailureInLoop, hopsToTraverse);
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_2, failureTD2));

        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.FAILURE_FORWARD, compExeScheduler.getSchedulingState());
        assertEquals(failureTD1, compExeScheduler.getFailureDatum());
        compExeScheduler.enable();
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.FAILURE_FORWARD, compExeScheduler.getSchedulingState());
        assertEquals(failureTD2, compExeScheduler.getFailureDatum());
    }

    /**
     * Set up: two inputs (constant, single; both required); failure value received at one of the inputs.
     * 
     * Expected: {@link State#FAILURE_FORWARD} 1) after failure values were received
     * 
     * @throws Exception on unexpected error
     */
    @Test
    public void testFailureValueAtLoopDriverForSuccess() throws Exception {
        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Constant,
            EndpointDefinition.InputExecutionContraint.Required));
        inputMockInfos.add(new InputMockInformation(INPUT_2, EndpointDefinition.InputDatumHandling.Single,
            EndpointDefinition.InputExecutionContraint.Required));
        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>(CaptureType.ALL);
        ComponentExecutionIdentifier executionId = new ComponentExecutionIdentifier(UUID.randomUUID().toString());
        ComponentExecutionScheduler compExeScheduler = setUpExecutionScheduler(inputMockInfos, capturedEvent, executionId);
        List<EndpointDatum> endpointDatumsSent = new ArrayList<>();

        Queue<WorkflowGraphHop> hopsToTraverse = new LinkedList<>();
        InternalTDImpl failureTD1 = new InternalTDImpl(InternalTDType.FailureInLoop, "id-1", hopsToTraverse, "11");
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, failureTD1));
        InternalTDImpl failureTD2 = new InternalTDImpl(InternalTDType.FailureInLoop, "id-2", hopsToTraverse, "21");
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_2, failureTD2));

        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);

        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA_WITH_NOT_A_VALUE_DATA, compExeScheduler.getSchedulingState());
        Map<String, EndpointDatum> endpointDatumsReturned = compExeScheduler.fetchEndpointDatums();
        assertEquals(2, endpointDatumsReturned.size());
        assertTrue(endpointDatumsReturned.get(INPUT_1).getValue() instanceof NotAValueTD);
        assertEquals("id-1", ((NotAValueTD) endpointDatumsReturned.get(INPUT_1).getValue()).getIdentifier());
        assertTrue(endpointDatumsReturned.get(INPUT_2).getValue() instanceof NotAValueTD);
        assertEquals("id-2", ((NotAValueTD) endpointDatumsReturned.get(INPUT_2).getValue()).getIdentifier());
    }

    /**
     * Set up: two inputs (constant, queue; both required); same parent group for both of them; three values.
     * 
     * Expected: {@link State#PROCESS_INPUT_DATA} 1) after each value received
     * 
     * @throws Exception on unexpected error
     */
    @Test
    public void testOrGroupForSuccess() throws Exception {

        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Constant,
            EndpointDefinition.InputExecutionContraint.Required, OR_GROUP, CONNECTED));
        inputMockInfos.add(new InputMockInformation(INPUT_2, EndpointDefinition.InputDatumHandling.Queue,
            EndpointDefinition.InputExecutionContraint.Required, OR_GROUP, CONNECTED));

        List<InputGroupMockInformation> inputGroupMockInfos = new ArrayList<>();
        inputGroupMockInfos.add(new InputGroupMockInformation(OR_GROUP, EndpointGroupDefinition.LogicOperation.Or));

        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>();
        ComponentExecutionScheduler compExeScheduler =
            setUpExecutionScheduler(inputMockInfos, inputGroupMockInfos, capturedEvent);

        List<EndpointDatum> endpointDatumsSent = new ArrayList<>();
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_2, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_2, new TypedDatumMock(DataType.Float)));

        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);

        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsSent.get(0));
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsSent.get(1));
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsSent.get(2));
    }

    /**
     * Set up: one input (queue; required); 'not a value' datum received.
     * 
     * Expected: {@link State#PROCESS_INPUT_DATA_WITH_NOT_A_VALUE_DATA} after value was received
     * 
     * @throws Exception on unexpected error
     */
    @Test
    public void testNotAValueDataForSuccess() throws Exception {

        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Queue,
            EndpointDefinition.InputExecutionContraint.Required));
        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>();
        ComponentExecutionScheduler compExeScheduler = setUpExecutionScheduler(inputMockInfos, capturedEvent);

        List<EndpointDatum> endpointDatumsSent = new ArrayList<>();
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new NotAValueTDStub()));
        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);

        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA_WITH_NOT_A_VALUE_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsSent.get(0));
    }

    /**
     * Set up: one input (queue; required); 'not a value' datum received, which was sent or which was received before.
     * 
     * Expected: Failure because 'not a value' was received again or twice
     * 
     * @throws Exception on unexpected error
     */
    @Test
    public void testNotAValueDataForFailure() throws Exception {

        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Queue,
            EndpointDefinition.InputExecutionContraint.Required));
        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>(CaptureType.ALL);
        ComponentExecutionScheduler compExeScheduler = setUpExecutionScheduler(inputMockInfos, capturedEvent);

        List<EndpointDatum> endpointDatumsSent = new ArrayList<>();
        String id = UUID.randomUUID().toString();
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new NotAValueTDStub(id)));
        compExeScheduler.addNotAValueDatumSent(id);
        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);

        assertSchedulingFailureEventPosted(capturedEvent);

        capturedEvent.reset();
        compExeScheduler = setUpExecutionScheduler(inputMockInfos, capturedEvent);

        endpointDatumsSent.clear();
        id = UUID.randomUUID().toString();
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new NotAValueTDStub(id)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new NotAValueTDStub(id)));
        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);

        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA_WITH_NOT_A_VALUE_DATA, compExeScheduler.getSchedulingState());
        checkEndpointDatumsSetSchedulerActiveAgain(compExeScheduler, compExeScheduler.fetchEndpointDatums(), endpointDatumsSent.get(0));

        assertSchedulingFailureEventPosted(capturedEvent);
    }

    /**
     * Set up: one input (queue; required); datum received: 1) NotAValue 2) Internal 3) whose data type is not compatible with input's data
     * type.
     * 
     * Expected: 1, 2: not failure; 3: failure
     * 
     * @throws Exception on unexpected error
     */
    @Test
    public void testDataTypeCheck() throws Exception {
        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Queue,
            EndpointDefinition.InputExecutionContraint.Required));
        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>();
        ComponentExecutionScheduler compExeScheduler = setUpExecutionScheduler(inputMockInfos, capturedEvent);

        List<EndpointDatum> endpointDatumsSent = new ArrayList<>();
        endpointDatumsSent
            .add(new EndpointDatumMock(INPUT_1, new InternalTDImpl(InternalTDType.FailureInLoop, new LinkedList<WorkflowGraphHop>(), "1")));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new NotAValueTDStub("id")));

        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);

        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.PROCESS_INPUT_DATA_WITH_NOT_A_VALUE_DATA, compExeScheduler.getSchedulingState());

        endpointDatumsSent.clear();
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new TypedDatumMock(DataType.Matrix)));

        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);

        assertSchedulingFailureEventPosted(capturedEvent);
    }

    /**
     * Tests if the {@link ComponentExecutionScheduler} only post events to the {@link ComponentStateMachine} if set to active.
     * 
     * @throws Exception on unexpected error
     */
    @Test
    public void testIdleMode() throws Exception {
        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Single,
            EndpointDefinition.InputExecutionContraint.Required));
        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>(CaptureType.ALL);
        ComponentExecutionScheduler compExeScheduler = setUpExecutionScheduler(inputMockInfos, capturedEvent);
        compExeScheduler.disable();

        List<EndpointDatum> endpointDatumsSent = new ArrayList<>();
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new TypedDatumMock(DataType.Float)));
        endpointDatumsSent.add(new EndpointDatumMock(INPUT_1, new InternalTDImpl(InternalTDType.WorkflowFinish)));

        sendValuesToExecutionScheduler(compExeScheduler, endpointDatumsSent);
        assertNoNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.IDLING, compExeScheduler.getSchedulingState());

        compExeScheduler.disable();
        assertFalse(capturedEvent.hasCaptured());
        compExeScheduler.enable();
        assertNewSchedulingStateEventPosted(capturedEvent);
    }

    /**
     * Tests if the {@link ComponentExecutionScheduler} considers exclusively inputs with character of {@link EndpointCharacter#SAME_LOOP}
     * for finish detection if not outer loop input exists and the component is not a loop driver.
     * 
     * @throws ComponentExecutionException on unexpected error
     */
    @Test
    public void testFinishNoLoopDriverWithoutOuterLoopInputs() throws ComponentExecutionException {
        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Single,
            EndpointDefinition.InputExecutionContraint.Required, EndpointCharacter.SAME_LOOP));
        inputMockInfos.add(new InputMockInformation(INPUT_2, EndpointDefinition.InputDatumHandling.Single,
            EndpointDefinition.InputExecutionContraint.Required, EndpointCharacter.SAME_LOOP));
        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>(CaptureType.ALL);
        ComponentExecutionScheduler compExeScheduler = setUpExecutionScheduler(inputMockInfos, capturedEvent, false, false);

        EndpointDatum endpointDatum1 = new EndpointDatumMock(INPUT_1, new InternalTDImpl(InternalTDType.WorkflowFinish));
        compExeScheduler.validateAndQueueEndpointDatum(endpointDatum1);
        assertNoNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.IDLING, compExeScheduler.getSchedulingState());
        EndpointDatum endpointDatum2 = new EndpointDatumMock(INPUT_2, new InternalTDImpl(InternalTDType.WorkflowFinish));
        compExeScheduler.validateAndQueueEndpointDatum(endpointDatum2);
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.FINISHED, compExeScheduler.getSchedulingState());
        assertFalse(compExeScheduler.isEnabled());
    }

    /**
     * Tests if the {@link ComponentExecutionScheduler} considers exclusively inputs with character of {@link EndpointCharacter#SAME_LOOP}
     * for finish detection if component is a loop driver having outer loop inputs.
     * 
     * @throws ComponentExecutionException on unexpected error
     */
    @Test
    public void testFinishLoopDriverWithOuterLoopInputs() throws ComponentExecutionException {
        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Single,
            EndpointDefinition.InputExecutionContraint.Required, EndpointCharacter.OUTER_LOOP));
        inputMockInfos.add(new InputMockInformation(INPUT_2, EndpointDefinition.InputDatumHandling.Single,
            EndpointDefinition.InputExecutionContraint.Required, EndpointCharacter.SAME_LOOP));
        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>(CaptureType.ALL);
        ComponentExecutionScheduler compExeScheduler =
            setUpExecutionScheduler(inputMockInfos, capturedEvent, true, false);

        EndpointDatum endpointDatum1 = new EndpointDatumMock(INPUT_1, new InternalTDImpl(InternalTDType.WorkflowFinish));
        compExeScheduler.validateAndQueueEndpointDatum(endpointDatum1);
        assertNoNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.IDLING, compExeScheduler.getSchedulingState());
        EndpointDatum endpointDatum2 = new EndpointDatumMock(INPUT_2, new InternalTDImpl(InternalTDType.WorkflowFinish));
        compExeScheduler.validateAndQueueEndpointDatum(endpointDatum2);
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.FINISHED, compExeScheduler.getSchedulingState());
        assertFalse(compExeScheduler.isEnabled());
    }

    /**
     * Tests if the {@link ComponentExecutionScheduler} considers exclusively inputs with character of {@link EndpointCharacter#OUTER_LOOP}
     * for finish detection if at least one exists and the component is no loop driver.
     * 
     * @throws ComponentExecutionException on unexpected error
     */
    @Test
    public void testFinishNonDriverWithOuterLoopInputs() throws ComponentExecutionException {
        testFinishConsiderOuterLoopInputs(false);
    }

    /**
     * Tests if the {@link ComponentExecutionScheduler} considers exclusively inputs with character of {@link EndpointCharacter#OUTER_LOOP}
     * for finish detection if component is a nested loop driver.
     * 
     * @throws ComponentExecutionException on unexpected error
     */
    @Test
    public void testFinishNestedLoopDriver() throws ComponentExecutionException {
        testFinishConsiderOuterLoopInputs(true);
    }

    private void testFinishConsiderOuterLoopInputs(boolean isNestedLoopDriver) throws ComponentExecutionException {
        List<InputMockInformation> inputMockInfos = new ArrayList<>();
        inputMockInfos.add(new InputMockInformation(INPUT_1, EndpointDefinition.InputDatumHandling.Single,
            EndpointDefinition.InputExecutionContraint.Required, EndpointCharacter.OUTER_LOOP));
        inputMockInfos.add(new InputMockInformation(INPUT_2, EndpointDefinition.InputDatumHandling.Single,
            EndpointDefinition.InputExecutionContraint.Required, EndpointCharacter.OUTER_LOOP));
        inputMockInfos.add(new InputMockInformation(INPUT_3, EndpointDefinition.InputDatumHandling.Single,
            EndpointDefinition.InputExecutionContraint.Required, EndpointCharacter.SAME_LOOP));
        Capture<ComponentStateMachineEvent> capturedEvent = new Capture<>(CaptureType.ALL);
        ComponentExecutionScheduler compExeScheduler =
            setUpExecutionScheduler(inputMockInfos, capturedEvent, isNestedLoopDriver, isNestedLoopDriver);

        EndpointDatum endpointDatum1 = new EndpointDatumMock(INPUT_1, new InternalTDImpl(InternalTDType.WorkflowFinish));
        compExeScheduler.validateAndQueueEndpointDatum(endpointDatum1);
        assertNoNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.IDLING, compExeScheduler.getSchedulingState());
        EndpointDatum endpointDatum3 = new EndpointDatumMock(INPUT_3, new InternalTDImpl(InternalTDType.WorkflowFinish));
        compExeScheduler.validateAndQueueEndpointDatum(endpointDatum3);
        assertNoNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.IDLING, compExeScheduler.getSchedulingState());
        EndpointDatum endpointDatum2 = new EndpointDatumMock(INPUT_2, new InternalTDImpl(InternalTDType.WorkflowFinish));
        compExeScheduler.validateAndQueueEndpointDatum(endpointDatum2);
        assertNewSchedulingStateEventPosted(capturedEvent);
        assertEquals(State.FINISHED, compExeScheduler.getSchedulingState());
        assertFalse(compExeScheduler.isEnabled());
    }

    private ComponentExecutionScheduler setUpExecutionScheduler(List<InputMockInformation> inputMockInfos,
        Capture<ComponentStateMachineEvent> capturedEvent)
        throws ComponentExecutionException {
        return setUpExecutionScheduler(inputMockInfos, capturedEvent, new ComponentExecutionIdentifier(UUID.randomUUID().toString()));
    }

    private ComponentExecutionScheduler setUpExecutionScheduler(List<InputMockInformation> inputMockInfos,
        Capture<ComponentStateMachineEvent> capturedEvent, boolean isLoopDriver, boolean isNestedLoopDriver)
        throws ComponentExecutionException {
        return setUpExecutionScheduler(inputMockInfos, capturedEvent, new ComponentExecutionIdentifier(UUID.randomUUID().toString()),
            isLoopDriver, isNestedLoopDriver);
    }

    private ComponentExecutionScheduler setUpExecutionScheduler(List<InputMockInformation> inputMockInfos,
        List<InputGroupMockInformation> inputGroupMockInfos, Capture<ComponentStateMachineEvent> capturedEvent)
        throws ComponentExecutionException {
        return setUpExecutionScheduler(inputMockInfos, inputGroupMockInfos, capturedEvent,
            new ComponentExecutionIdentifier(UUID.randomUUID().toString()));
    }

    private ComponentExecutionScheduler setUpExecutionScheduler(List<InputMockInformation> inputMockInfos,
        Capture<ComponentStateMachineEvent> capturedEvent, ComponentExecutionIdentifier executionId, boolean isLoopDriver,
        boolean isNestedLoopDriver)
        throws ComponentExecutionException {
        return setUpExecutionScheduler(inputMockInfos, new ArrayList<InputGroupMockInformation>(), capturedEvent, executionId, isLoopDriver,
            isNestedLoopDriver);
    }

    private ComponentExecutionScheduler setUpExecutionScheduler(List<InputMockInformation> inputMockInfos,
        Capture<ComponentStateMachineEvent> capturedEvent, ComponentExecutionIdentifier executionId)
        throws ComponentExecutionException {
        return setUpExecutionScheduler(inputMockInfos, new ArrayList<InputGroupMockInformation>(), capturedEvent, executionId);
    }

    private ComponentExecutionScheduler setUpExecutionScheduler(List<InputMockInformation> inputMockInfos,
        List<InputGroupMockInformation> inputGroupMockInfos, Capture<ComponentStateMachineEvent> capturedEvent,
        ComponentExecutionIdentifier executionId)
        throws ComponentExecutionException {
        ExecutionSpecificComponentExecutionContextMock compExeCtxMock =
            new ExecutionSpecificComponentExecutionContextMock(executionId.toString(), inputMockInfos, inputGroupMockInfos);
        ComponentExecutionScheduler compExeScheduler =
            new ComponentExecutionScheduler(
                createCompExeRelatedInstancesStub(compExeCtxMock, createComponentStateMachineMock(capturedEvent)));
        compExeScheduler.bindTypedDatumService(new TypedDatumServiceDefaultStub());
        compExeScheduler.initialize(compExeCtxMock);
        compExeScheduler.enable();
        return compExeScheduler;
    }

    private ComponentExecutionScheduler setUpExecutionScheduler(List<InputMockInformation> inputMockInfos,
        List<InputGroupMockInformation> inputGroupMockInfos, Capture<ComponentStateMachineEvent> capturedEvent,
        ComponentExecutionIdentifier executionId,
        boolean isLoopDriver, boolean isNestedLoopDriver) throws ComponentExecutionException {
        ExecutionSpecificComponentExecutionContextMock compExeCtxMock = new ExecutionSpecificComponentExecutionContextMock(
            executionId.toString(), inputMockInfos, inputGroupMockInfos, isLoopDriver, isNestedLoopDriver);
        ComponentExecutionScheduler compExeScheduler =
            new ComponentExecutionScheduler(
                createCompExeRelatedInstancesStub(compExeCtxMock, createComponentStateMachineMock(capturedEvent)));
        compExeScheduler.bindTypedDatumService(new TypedDatumServiceDefaultStub());
        compExeScheduler.initialize(compExeCtxMock);
        compExeScheduler.enable();
        return compExeScheduler;
    }

    private ComponentStateMachine createComponentStateMachineMock(Capture<ComponentStateMachineEvent> capturedEvent) {
        ComponentStateMachine stateMachineMock = EasyMock.createStrictMock(ComponentStateMachine.class);
        stateMachineMock.postEvent(EasyMock.capture(capturedEvent));
        EasyMock.expectLastCall().asStub();
        EasyMock.replay(stateMachineMock);
        return stateMachineMock;
    }

    private ComponentExecutionRelatedInstances createCompExeRelatedInstancesStub(ComponentExecutionContext compExeCtx,
        ComponentStateMachine compStateMachine) {
        ComponentExecutionRelatedInstances compExeRelatedInstances = new ComponentExecutionRelatedInstances();
        compExeRelatedInstances.compExeCtx = compExeCtx;
        compExeRelatedInstances.compStateMachine = compStateMachine;
        return compExeRelatedInstances;
    }

    private void sendValuesToExecutionScheduler(ComponentExecutionScheduler compExeScheduler,
        final List<EndpointDatum> endpointDatumsSent) {
        for (EndpointDatum endpointDatum : endpointDatumsSent) {
            compExeScheduler.validateAndQueueEndpointDatum(endpointDatum);
        }
    }

    private void checkEndpointDatumsSetSchedulerActiveAgain(ComponentExecutionScheduler compExeScheduler,
        Map<String, EndpointDatum> endpointDatumsReturned,
        EndpointDatum... endpointDatumsExpected) {
        assertEquals(Arrays.asList(endpointDatumsExpected).size(), endpointDatumsReturned.size());
        for (EndpointDatum endpointDatum : endpointDatumsExpected) {
            assertTrue(endpointDatumsReturned.containsKey(endpointDatum.getInputName()));
            assertTrue(endpointDatumsReturned.containsValue(endpointDatum));
        }
        compExeScheduler.enable();
    }

    private void assertNoNewSchedulingStateEventPosted(Capture<ComponentStateMachineEvent> capturedEvent) {
        assertFalse(capturedEvent.hasCaptured());
    }

    private void assertNewSchedulingStateAndSchedulingFailureEventPosted(Capture<ComponentStateMachineEvent> capturedEvent) {
        assertTrue(capturedEvent.hasCaptured());
        assertEquals(2, capturedEvent.getValues().size());
        assertEquals(ComponentStateMachineEventType.NEW_SCHEDULING_STATE, capturedEvent.getValues().get(0).getType());
        assertEquals(ComponentStateMachineEventType.SCHEDULING_FAILED, capturedEvent.getValues().get(1).getType());
        capturedEvent.reset();
    }

    private void assertNewSchedulingStateEventPosted(Capture<ComponentStateMachineEvent> capturedEvent) {
        assertTrue(capturedEvent.hasCaptured());
        assertEquals(ComponentStateMachineEventType.NEW_SCHEDULING_STATE, capturedEvent.getValue().getType());
        capturedEvent.reset();
    }

    private void assertSchedulingFailureEventPosted(Capture<ComponentStateMachineEvent> capturedEvent) {
        assertTrue(capturedEvent.hasCaptured());
        assertEquals(ComponentStateMachineEventType.SCHEDULING_FAILED, capturedEvent.getValue().getType());
        capturedEvent.reset();
    }

    /**
     * Mock for arbitrary {@link TypedDatum} objects.
     * 
     * @author Doreen Seider
     */
    private final class TypedDatumMock implements TypedDatum {

        private final DataType dataType;

        private TypedDatumMock(DataType dataType) {
            this.dataType = dataType;
        }

        @Override
        public DataType getDataType() {
            return dataType;
        }

    }

    /**
     * Mock for {@link EndpointDatum} object, which mocks only scheduling-related methods.
     * 
     * @author Doreen Seider
     */
    private final class EndpointDatumMock extends EndpointDatumDefaultStub {

        private final String inputName;

        private final TypedDatum value;

        private EndpointDatumMock(String inputName, TypedDatum value) {
            this.inputName = inputName;
            this.value = value;
        }

        @Override
        public String getInputName() {
            return inputName;
        }

        @Override
        public TypedDatum getValue() {
            return value;
        }

        @Override
        public EndpointDatumRecipient getEndpointDatumRecipient() {
            return EndpointDatumRecipientFactory.createEndpointDatumRecipient(inputName, null, null, null);
        }

    }

    /**
     * Mock for {@link ComponentExecutionContext} objects, which mocks only scheduling-related methods.
     * 
     * @author Doreen Seider
     */
    private final class ExecutionSpecificComponentExecutionContextMock extends ComponentExecutionContextMock {

        private static final long serialVersionUID = 6234658796478889718L;

        private final List<InputMockInformation> inputMockInformations;

        private final List<InputGroupMockInformation> inputGroupMockInformations;

        private final boolean isLoopDriver;

        private final boolean isNestedLoopDriver;

        private ExecutionSpecificComponentExecutionContextMock(List<InputMockInformation> inputMockInformations) {
            this(inputMockInformations, new ArrayList<InputGroupMockInformation>());
        }

        private ExecutionSpecificComponentExecutionContextMock(String executionId, List<InputMockInformation> inputMockInformations,
            List<InputGroupMockInformation> inputGroupMockInformations, boolean isLoopDriver, boolean isNestedLoopDriver) {
            super(executionId);
            this.inputMockInformations = inputMockInformations;
            this.inputGroupMockInformations = inputGroupMockInformations;
            this.isLoopDriver = isLoopDriver;
            this.isNestedLoopDriver = isNestedLoopDriver;
        }

        private ExecutionSpecificComponentExecutionContextMock(String executionId, List<InputMockInformation> inputMockInformations,
            List<InputGroupMockInformation> inputGroupMockInformations) {
            this(executionId, inputMockInformations, inputGroupMockInformations, false, false);
        }

        private ExecutionSpecificComponentExecutionContextMock(List<InputMockInformation> inputMockInformations,
            List<InputGroupMockInformation> inputGroupMockInformations) {
            super();
            this.inputMockInformations = inputMockInformations;
            this.inputGroupMockInformations = inputGroupMockInformations;
            this.isLoopDriver = false;
            this.isNestedLoopDriver = false;
        }

        @Override
        public ComponentDescription getComponentDescription() {
            ComponentDescription componentDescriptionMock = EasyMock.createStrictMock(ComponentDescription.class);

            ComponentInterface compInterfaceMock = EasyMock.createStrictMock(ComponentInterface.class);
            EasyMock.expect(compInterfaceMock.getIsLoopDriver()).andStubReturn(isLoopDriver);
            EasyMock.replay(compInterfaceMock);
            EasyMock.expect(componentDescriptionMock.getComponentInterface()).andStubReturn(compInterfaceMock);

            ConfigurationDescription configDescMock = EasyMock.createStrictMock(ConfigurationDescription.class);
            EasyMock.expect(configDescMock.getConfigurationValue(LoopComponentConstants.CONFIG_KEY_IS_NESTED_LOOP))
                .andStubReturn(String.valueOf(isNestedLoopDriver));
            EasyMock.replay(configDescMock);

            EasyMock.expect(componentDescriptionMock.getConfigurationDescription()).andStubReturn(configDescMock);

            EasyMock.expect(componentDescriptionMock.getInputDescriptionsManager())
                .andStubReturn(InputDescriptionManagerMockFactory.createInputDescriptionManagerMock(inputMockInformations,
                    inputGroupMockInformations));
            EasyMock.replay(componentDescriptionMock);
            return componentDescriptionMock;
        }

    }

}
