/*******************************************************************************
 * Copyright (c) 2019 École Polytechnique de Montréal
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License 2.0 which
 * accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *******************************************************************************/

package org.eclipse.tracecompass.tmf.analysis.xml.core.tests.fsm.compile;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;

import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.compile.AnalysisCompilationData;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.compile.TmfXmlActionCu;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.compile.TmfXmlConditionCu;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.compile.TmfXmlFsmStateCu;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.compile.TmfXmlFsmStateCu.TmfXmlFsmCu;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenAction;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenActionResetStoredFields;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenActionSegment;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenActionUpdateStoredFields;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenCondition;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenCondition.ConditionOperator;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenFsm;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenFsmSimpleState;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenFsmStateTransition;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.values.DataDrivenValueConstant;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.values.DataDrivenValueEventField;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.values.DataDrivenValueEventName;
import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue;
import org.eclipse.tracecompass.tmf.analysis.xml.core.module.TmfXmlStrings;
import org.eclipse.tracecompass.tmf.analysis.xml.core.tests.common.TmfXmlTestUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

/**
 * Test the DataDrivenAction compilation from XML
 *
 * @author Geneviève Bastien
 */
@RunWith(Parameterized.class)
public class TmfXmlFsmCuTest {

    // String names for actions, tests and states
    private static final @NonNull String STATE1_NAME = "state1";
    private static final @NonNull String STATE2_NAME = "state2";
    private static final @NonNull String STATE_FINAL_NAME = "final";
    private static final @NonNull String ACTION1_NAME = "action1";
    private static final @NonNull String ACTION2_NAME = "action2";
    private static final @NonNull String TEST1_NAME = "test1";
    private static final @NonNull String TEST2_NAME = "test2";

    private static final @NonNull String FSM_START = "<fsm id=\"%s\" %s>";
    private static final @NonNull String FSM_END = "</fsm>";
    private static final @NonNull String STATE_START = "<state id=\"%s\">";
    private static final @NonNull String STATE_END = "</state>";
    private static final @NonNull String TRANSITION_STRING = "<transition %s %s %s %s %s %s/>";
    private static final @NonNull String SAVE_STORED_FIELDS = TmfXmlStrings.SAVE_STORED_FIELDS + "=\"true\"";
    private static final @NonNull String CLEAR_STORED_FIELDS = TmfXmlStrings.CLEAR_STORED_FIELDS + "=\"true\"";
    private static final @NonNull String EVENTS_STRING = TmfXmlStrings.EVENT + "=\"sys*\"";
    private static final @NonNull String ACTION_STRING = TmfXmlStrings.ACTION + "=\"%s\"";
    private static final @NonNull String TARGET_STRING = TmfXmlStrings.TARGET + "=\"%s\"";
    private static final @NonNull String TEST_STRING = TmfXmlStrings.COND + "=\"%s\"";
    private static final @NonNull String WRAPPER_STRING = "<doc>%s</doc>";

    // Dummy actions and conditions to use in the FSM strings
    private static final @NonNull String ACTION1 = "<action id=\"" + ACTION1_NAME + "\"><segment><segType segName=\"test\"/></segment></action>";
    private static final @NonNull String ACTION2 = "<action id=\"" + ACTION2_NAME + "\"><segment><segType segName=\"hello\"/></segment></action>";
    private static final @NonNull String TEST1 = "<test id=\"" + TEST1_NAME + "\"><if><condition><stateValue type=\"int\" value=\"2\"/><stateValue type=\"eventField\" value=\"cpu\" /></condition></if></test>";
    private static final @NonNull String TEST2 = "<test id=\"" + TEST2_NAME + "\"><if><condition><stateValue type=\"int\" value=\"0\"/><stateValue type=\"eventField\" value=\"cpu\" /></condition></if></test>";

    // Their generated values
    private static final @NonNull DataDrivenAction ACTION1_DD = new DataDrivenActionSegment(new DataDrivenValueConstant(null, ITmfStateValue.Type.NULL, "test"),
            null, null, null, Collections.emptyMap());
    private static final @NonNull DataDrivenAction ACTION2_DD = new DataDrivenActionSegment(new DataDrivenValueConstant(null, ITmfStateValue.Type.NULL, "hello"),
            null, null, null, Collections.emptyMap());
    private static final @NonNull DataDrivenCondition EVENT_CONDITION = new DataDrivenCondition.DataDrivenRegexCondition(Pattern.compile("sys.*"),
            new DataDrivenValueEventName(null));
    private static final @NonNull DataDrivenCondition TEST1_DD = new DataDrivenCondition.DataDrivenComparisonCondition(new DataDrivenValueConstant(null, ITmfStateValue.Type.NULL, 2),
            new DataDrivenValueEventField(null, ITmfStateValue.Type.NULL, "cpu"), ConditionOperator.EQ);
    private static final @NonNull DataDrivenCondition TEST2_DD = new DataDrivenCondition.DataDrivenComparisonCondition(new DataDrivenValueConstant(null, ITmfStateValue.Type.NULL, 0),
            new DataDrivenValueEventField(null, ITmfStateValue.Type.NULL, "cpu"), ConditionOperator.EQ);

    // Describe some test cases of valid actions

    private static abstract class CompilationResult {

        public abstract @NonNull String getName();

        public abstract @NonNull String getXmlString();

        public abstract @Nullable DataDrivenFsm getResult();

        @Override
        public String toString() {
            return getName();
        }
    }

    private static final @NonNull CompilationResult VALID_ONE_STATE_FINAL = new CompilationResult() {

        @Override
        public String getName() {
            return "one_state_final";
        }

        @Override
        public String getXmlString() {
            return String.format(FSM_START, getName(), "initial=\"" + STATE1_NAME + "\"") +
                    String.format(STATE_START, STATE1_NAME) +
                    STATE_END +
                    FSM_END;
        }

        @Override
        public DataDrivenFsm getResult() {
            DataDrivenFsmSimpleState state = new DataDrivenFsmSimpleState(STATE1_NAME, Collections.emptyList(), DataDrivenAction.NO_ACTION, DataDrivenAction.NO_ACTION);
            return new DataDrivenFsm(getName(), state,
                    ImmutableMap.of(STATE1_NAME, state), DataDrivenCondition.TRUE_CONDITION, true, true);
        }

    };

    private static final @NonNull CompilationResult VALID_ONE_STATE_LOOP = new CompilationResult() {

        @Override
        public String getName() {
            return "one_state_loop";
        }

        @Override
        public String getXmlString() {
            return String.format(WRAPPER_STRING,
                    TEST1 + ACTION1 + String.format(FSM_START, getName(), "initial=\"" + STATE1_NAME + "\"") +
                            String.format(STATE_START, STATE1_NAME) +
                            String.format(TRANSITION_STRING, EVENTS_STRING, String.format(ACTION_STRING, ACTION1_NAME), String.format(TEST_STRING, TEST1_NAME), String.format(TARGET_STRING, STATE1_NAME), StringUtils.EMPTY, StringUtils.EMPTY) +
                            STATE_END +
                            FSM_END);
        }

        @Override
        public DataDrivenFsm getResult() {
            DataDrivenFsmSimpleState state = new DataDrivenFsmSimpleState(STATE1_NAME,
                    Collections.singletonList(new DataDrivenFsmStateTransition(EVENT_CONDITION,
                            TEST1_DD,
                            STATE1_NAME,
                            Collections.singletonList(ACTION1_DD))),
                    DataDrivenAction.NO_ACTION, DataDrivenAction.NO_ACTION);
            return new DataDrivenFsm(getName(), state,
                    ImmutableMap.of(STATE1_NAME, state), DataDrivenCondition.TRUE_CONDITION, true, true);
        }

    };

    private static final @NonNull CompilationResult VALID_SAVE_CLEAR_FIELDS = new CompilationResult() {

        @Override
        public String getName() {
            return "save_and_clear_fields";
        }

        @Override
        public String getXmlString() {
            return String.format(WRAPPER_STRING,
                    TEST1 + ACTION1 + String.format(FSM_START, getName(), "initial=\"" + STATE1_NAME + "\"") +
                            String.format(STATE_START, STATE1_NAME) +
                            String.format(TRANSITION_STRING, EVENTS_STRING, String.format(ACTION_STRING, ACTION1_NAME), String.format(TEST_STRING, TEST1_NAME), String.format(TARGET_STRING, STATE1_NAME), SAVE_STORED_FIELDS, CLEAR_STORED_FIELDS) +
                            STATE_END +
                            FSM_END);
        }

        @Override
        public DataDrivenFsm getResult() {
            DataDrivenFsmSimpleState state = new DataDrivenFsmSimpleState(STATE1_NAME,
                    Collections.singletonList(new DataDrivenFsmStateTransition(EVENT_CONDITION,
                            TEST1_DD,
                            STATE1_NAME,
                            ImmutableList.of(ACTION1_DD, DataDrivenActionUpdateStoredFields.getInstance(), DataDrivenActionResetStoredFields.getInstance()))),
                    DataDrivenAction.NO_ACTION, DataDrivenAction.NO_ACTION);
            return new DataDrivenFsm(getName(), state,
                    ImmutableMap.of(STATE1_NAME, state), DataDrivenCondition.TRUE_CONDITION, true, true);
        }

    };

    private static final @NonNull CompilationResult VALID_MULTIPLE_ACTIONS_TESTS = new CompilationResult() {

        @Override
        public String getName() {
            return "multiple_actions_and_tests";
        }

        @Override
        public String getXmlString() {
            return String.format(WRAPPER_STRING,
                    TEST1 + TEST2 + ACTION1 + ACTION2 +
                            String.format(FSM_START, getName(), "initial=\"" + STATE1_NAME + "\"") +
                            String.format(STATE_START, STATE1_NAME) +
                            String.format(TRANSITION_STRING, EVENTS_STRING, String.format(ACTION_STRING, ACTION1_NAME + ':' + ACTION2_NAME), String.format(TEST_STRING, TEST1_NAME + ':' + TEST2_NAME), String.format(TARGET_STRING, STATE1_NAME),
                                    StringUtils.EMPTY, StringUtils.EMPTY)
                            +
                            STATE_END +
                            FSM_END);
        }

        @Override
        public DataDrivenFsm getResult() {
            DataDrivenFsmSimpleState state = new DataDrivenFsmSimpleState(STATE1_NAME,
                    Collections.singletonList(new DataDrivenFsmStateTransition(EVENT_CONDITION,
                            new DataDrivenCondition.DataDrivenAndCondition(ImmutableList.of(TEST1_DD, TEST2_DD)),
                            STATE1_NAME,
                            ImmutableList.of(ACTION1_DD, ACTION2_DD))),
                    DataDrivenAction.NO_ACTION, DataDrivenAction.NO_ACTION);
            return new DataDrivenFsm(getName(), state,
                    ImmutableMap.of(STATE1_NAME, state), DataDrivenCondition.TRUE_CONDITION, true, true);
        }

    };

    private static final @NonNull CompilationResult VALID_MULTIPLE_STATES_TRANSITIONS = new CompilationResult() {

        @Override
        public String getName() {
            return "multiple_states_and_transitions";
        }

        @Override
        public String getXmlString() {
            return String.format(WRAPPER_STRING,
                    TEST1 + TEST2 + ACTION1 + ACTION2 +
                            String.format(FSM_START, getName(), "initial=\"" + STATE1_NAME + "\"") +
                            String.format(STATE_START, STATE1_NAME) +
                            String.format(TRANSITION_STRING, EVENTS_STRING, String.format(ACTION_STRING, ACTION2_NAME), String.format(TEST_STRING, TEST1_NAME + ':' + TEST2_NAME), String.format(TARGET_STRING, STATE1_NAME), StringUtils.EMPTY,
                                    StringUtils.EMPTY)
                            +
                            String.format(TRANSITION_STRING, EVENTS_STRING, String.format(ACTION_STRING, ACTION1_NAME), String.format(TEST_STRING, TEST1_NAME), String.format(TARGET_STRING, STATE2_NAME), StringUtils.EMPTY, StringUtils.EMPTY) +
                            STATE_END +
                            String.format(STATE_START, STATE2_NAME) +
                            String.format(TRANSITION_STRING, EVENTS_STRING, StringUtils.EMPTY, String.format(TEST_STRING, TEST1_NAME + ':' + TEST2_NAME), String.format(TARGET_STRING, STATE_FINAL_NAME), StringUtils.EMPTY, StringUtils.EMPTY) +
                            String.format(TRANSITION_STRING, EVENTS_STRING, String.format(ACTION_STRING, ACTION1_NAME), String.format(TEST_STRING, TEST2_NAME), String.format(TARGET_STRING, STATE1_NAME), StringUtils.EMPTY, StringUtils.EMPTY) +
                            STATE_END +
                            "<final id=\"" + STATE_FINAL_NAME + "\"/>" +
                            FSM_END);
        }

        @Override
        public DataDrivenFsm getResult() {
            DataDrivenFsmSimpleState state = new DataDrivenFsmSimpleState(STATE1_NAME,
                    ImmutableList.of(
                            new DataDrivenFsmStateTransition(EVENT_CONDITION,
                                    new DataDrivenCondition.DataDrivenAndCondition(ImmutableList.of(TEST1_DD, TEST2_DD)),
                                    STATE1_NAME,
                                    Collections.singletonList(ACTION2_DD)),
                            new DataDrivenFsmStateTransition(EVENT_CONDITION,
                                    TEST1_DD,
                                    STATE2_NAME,
                                    Collections.singletonList(ACTION1_DD))),
                    DataDrivenAction.NO_ACTION, DataDrivenAction.NO_ACTION);

            DataDrivenFsmSimpleState state2 = new DataDrivenFsmSimpleState(STATE2_NAME,
                    ImmutableList.of(
                            new DataDrivenFsmStateTransition(EVENT_CONDITION,
                                    new DataDrivenCondition.DataDrivenAndCondition(ImmutableList.of(TEST1_DD, TEST2_DD)),
                                    STATE_FINAL_NAME,
                                    Collections.emptyList()),
                            new DataDrivenFsmStateTransition(EVENT_CONDITION,
                                    TEST2_DD,
                                    STATE1_NAME,
                                    Collections.singletonList(ACTION1_DD))),
                    DataDrivenAction.NO_ACTION, DataDrivenAction.NO_ACTION);
            DataDrivenFsmSimpleState finalState = new DataDrivenFsmSimpleState(STATE_FINAL_NAME, Collections.emptyList(), DataDrivenAction.NO_ACTION, DataDrivenAction.NO_ACTION);
            return new DataDrivenFsm(getName(), state,
                    ImmutableMap.of(STATE1_NAME, state, STATE2_NAME, state2, STATE_FINAL_NAME, finalState), DataDrivenCondition.TRUE_CONDITION, true, true);
        }

    };

    private static final @NonNull CompilationResult VALID_ON_ENTRY_EXIT = new CompilationResult() {

        @Override
        public String getName() {
            return "on_entry_exit";
        }

        @Override
        public String getXmlString() {
            return String.format(WRAPPER_STRING,
                    TEST1 + TEST2 + ACTION1 + ACTION2 +
                            String.format(FSM_START, getName(), "initial=\"" + STATE1_NAME + "\"") +
                            String.format(STATE_START, STATE1_NAME) +
                            "<onentry action=\"" + ACTION1_NAME + "\"/>" +
                            "<onexit action=\"" + ACTION2_NAME + "\"/>" +
                            String.format(TRANSITION_STRING, EVENTS_STRING, StringUtils.EMPTY, String.format(TEST_STRING, TEST1_NAME), String.format(TARGET_STRING, STATE_FINAL_NAME), StringUtils.EMPTY, StringUtils.EMPTY) +
                            STATE_END +
                            "<final id=\"" + STATE_FINAL_NAME + "\"/>" +
                            FSM_END);
        }

        @Override
        public DataDrivenFsm getResult() {
            DataDrivenFsmSimpleState state = new DataDrivenFsmSimpleState(STATE1_NAME,
                    Collections.singletonList(new DataDrivenFsmStateTransition(EVENT_CONDITION,
                            TEST1_DD,
                            STATE_FINAL_NAME,
                            Collections.emptyList())),
                    ACTION1_DD, ACTION2_DD);
            DataDrivenFsmSimpleState finalState = new DataDrivenFsmSimpleState(STATE_FINAL_NAME, Collections.emptyList(), DataDrivenAction.NO_ACTION, DataDrivenAction.NO_ACTION);
            return new DataDrivenFsm(getName(), state,
                    ImmutableMap.of(STATE1_NAME, state, STATE_FINAL_NAME, finalState), DataDrivenCondition.TRUE_CONDITION, true, true);
        }

    };

    private static final @NonNull CompilationResult VALID_PRECONDITION = new CompilationResult() {

        @Override
        public String getName() {
            return "with_precondition";
        }

        @Override
        public String getXmlString() {
            return String.format(WRAPPER_STRING,
                    TEST1 + TEST2 + ACTION1 + ACTION2 +
                            String.format(FSM_START, getName(), "initial=\"" + STATE1_NAME + "\"") +
                            "<" + TmfXmlStrings.PRECONDITION + " " + EVENTS_STRING + " " + String.format(TEST_STRING, TEST1_NAME) + "/>" +
                            "<" + TmfXmlStrings.PRECONDITION + " " + TmfXmlStrings.EVENT + "=\"test\" " + String.format(TEST_STRING, TEST1_NAME) + " " + String.format(ACTION_STRING, ACTION1_NAME) + "/>" +
                            String.format(STATE_START, STATE1_NAME) +
                            String.format(TRANSITION_STRING, EVENTS_STRING, String.format(ACTION_STRING, ACTION1_NAME + ':' + ACTION2_NAME), String.format(TEST_STRING, TEST1_NAME + ':' + TEST2_NAME), String.format(TARGET_STRING, STATE1_NAME),
                                    StringUtils.EMPTY, StringUtils.EMPTY) +
                            STATE_END +
                            FSM_END);
        }

        @Override
        public DataDrivenFsm getResult() {
            DataDrivenFsmSimpleState state = new DataDrivenFsmSimpleState(STATE1_NAME,
                    Collections.singletonList(new DataDrivenFsmStateTransition(EVENT_CONDITION,
                            new DataDrivenCondition.DataDrivenAndCondition(ImmutableList.of(TEST1_DD, TEST2_DD)),
                            STATE1_NAME,
                            ImmutableList.of(ACTION1_DD, ACTION2_DD))),
                    DataDrivenAction.NO_ACTION, DataDrivenAction.NO_ACTION);
            DataDrivenCondition preCond = new DataDrivenCondition.DataDrivenOrCondition(
                    ImmutableList.of(
                            new DataDrivenCondition.DataDrivenAndCondition(ImmutableList.of(EVENT_CONDITION, TEST1_DD)),
                            new DataDrivenCondition.DataDrivenAndCondition(ImmutableList.of(
                                    new DataDrivenCondition.DataDrivenRegexCondition(Pattern.compile("test"),
                                    new DataDrivenValueEventName(null)), TEST1_DD))
                            ));
            return new DataDrivenFsm(getName(), state,
                    ImmutableMap.of(STATE1_NAME, state), preCond, true, true);
        }

    };

    private static final @NonNull CompilationResult VALID_INITIAL_STATE = new CompilationResult() {

        @Override
        public String getName() {
            return "initial state";
        }

        @Override
        public String getXmlString() {
            return String.format(WRAPPER_STRING,
                    TEST1 + TEST2 + ACTION1 + ACTION2 +
                            String.format(FSM_START, getName(), StringUtils.EMPTY) +
                            "<initialState>" +
                            String.format(TRANSITION_STRING, EVENTS_STRING, String.format(ACTION_STRING, ACTION1_NAME), String.format(TEST_STRING, TEST1_NAME), String.format(TARGET_STRING, STATE1_NAME),
                                    StringUtils.EMPTY, StringUtils.EMPTY) +
                            "</initialState>" +
                            String.format(STATE_START, STATE1_NAME) +
                            String.format(TRANSITION_STRING, EVENTS_STRING, String.format(ACTION_STRING, ACTION1_NAME + ':' + ACTION2_NAME), String.format(TEST_STRING, TEST1_NAME + ':' + TEST2_NAME), String.format(TARGET_STRING, STATE1_NAME),
                                    StringUtils.EMPTY, StringUtils.EMPTY) +
                            STATE_END +
                            FSM_END);
        }

        @Override
        public DataDrivenFsm getResult() {
            DataDrivenFsmSimpleState initial = new DataDrivenFsmSimpleState(TmfXmlStrings.INITIAL_STATE,
                    Collections.singletonList(new DataDrivenFsmStateTransition(EVENT_CONDITION,
                            TEST1_DD,
                            STATE1_NAME,
                            Collections.singletonList(ACTION1_DD))),
                    DataDrivenAction.NO_ACTION, DataDrivenAction.NO_ACTION);
            DataDrivenFsmSimpleState state = new DataDrivenFsmSimpleState(STATE1_NAME,
                    Collections.singletonList(new DataDrivenFsmStateTransition(EVENT_CONDITION,
                            new DataDrivenCondition.DataDrivenAndCondition(ImmutableList.of(TEST1_DD, TEST2_DD)),
                            STATE1_NAME,
                            ImmutableList.of(ACTION1_DD, ACTION2_DD))),
                    DataDrivenAction.NO_ACTION, DataDrivenAction.NO_ACTION);
            return new DataDrivenFsm(getName(), initial,
                    ImmutableMap.of(STATE1_NAME, state), DataDrivenCondition.TRUE_CONDITION, true, true);
        }

    };

    private static final @NonNull CompilationResult VALID_INITIAL = new CompilationResult() {

        @Override
        public String getName() {
            return "initial_element";
        }

        @Override
        public String getXmlString() {
            return String.format(WRAPPER_STRING,
                    TEST1 + ACTION1 + String.format(FSM_START, getName(), StringUtils.EMPTY) +
                            "<initial>" +
                            String.format(TRANSITION_STRING, StringUtils.EMPTY, StringUtils.EMPTY, StringUtils.EMPTY, String.format(TARGET_STRING, STATE1_NAME),
                                    StringUtils.EMPTY, StringUtils.EMPTY) +
                            "</initial>" +
                            String.format(STATE_START, STATE1_NAME) +
                            String.format(TRANSITION_STRING, EVENTS_STRING, String.format(ACTION_STRING, ACTION1_NAME), String.format(TEST_STRING, TEST1_NAME), String.format(TARGET_STRING, STATE1_NAME), StringUtils.EMPTY, StringUtils.EMPTY) +
                            STATE_END +
                            FSM_END);
        }

        @Override
        public DataDrivenFsm getResult() {
            DataDrivenFsmSimpleState state = new DataDrivenFsmSimpleState(STATE1_NAME,
                    Collections.singletonList(new DataDrivenFsmStateTransition(EVENT_CONDITION,
                            TEST1_DD,
                            STATE1_NAME,
                            Collections.singletonList(ACTION1_DD))),
                    DataDrivenAction.NO_ACTION, DataDrivenAction.NO_ACTION);
            return new DataDrivenFsm(getName(), state,
                    ImmutableMap.of(STATE1_NAME, state), DataDrivenCondition.TRUE_CONDITION, true, true);
        }

    };

    private static final @NonNull CompilationResult VALID_NO_INITIAL = new CompilationResult() {

        @Override
        public String getName() {
            return "valid_no_initial";
        }

        @Override
        public String getXmlString() {
            return String.format(FSM_START, getName(), StringUtils.EMPTY) +
                    String.format(STATE_START, STATE1_NAME) +
                    STATE_END +
                    FSM_END;
        }

        @Override
        public DataDrivenFsm getResult() {
            DataDrivenFsmSimpleState state = new DataDrivenFsmSimpleState(STATE1_NAME, Collections.emptyList(), DataDrivenAction.NO_ACTION, DataDrivenAction.NO_ACTION);
            return new DataDrivenFsm(getName(), state,
                    ImmutableMap.of(STATE1_NAME, state), DataDrivenCondition.TRUE_CONDITION, true, true);
        }

    };

    private static final @NonNull CompilationResult INVALID_UNDEFINED_ACTION = new CompilationResult() {

        @Override
        public String getName() {
            return "undefined_action";
        }

        @Override
        public String getXmlString() {
            return String.format(FSM_START, getName(), "initial=\"" + STATE1_NAME + "\"") +
                    String.format(STATE_START, STATE1_NAME) +
                    String.format(TRANSITION_STRING, EVENTS_STRING, String.format(ACTION_STRING, "action1"), StringUtils.EMPTY, String.format(TARGET_STRING, "state1"), StringUtils.EMPTY, StringUtils.EMPTY) +
                    STATE_END +
                    FSM_END;
        }

        @Override
        public DataDrivenFsm getResult() {
            return null;
        }

    };

    private static final @NonNull CompilationResult INVALID_UNDEFINED_CONDITION = new CompilationResult() {

        @Override
        public String getName() {
            return "undefined_condition";
        }

        @Override
        public String getXmlString() {
            return String.format(FSM_START, getName(), "initial=\"" + STATE1_NAME + "\"") +
                    String.format(STATE_START, STATE1_NAME) +
                    String.format(TRANSITION_STRING, EVENTS_STRING, String.format(TEST_STRING, TEST1_NAME), StringUtils.EMPTY, String.format(TARGET_STRING, STATE1_NAME), StringUtils.EMPTY, StringUtils.EMPTY) +
                    STATE_END +
                    FSM_END;
        }

        @Override
        public DataDrivenFsm getResult() {
            return null;
        }

    };

    private static final @NonNull CompilationResult INVALID_UNDEFINED_TARGET = new CompilationResult() {

        @Override
        public String getName() {
            return "undefined_target";
        }

        @Override
        public String getXmlString() {
            return String.format(FSM_START, getName(), "initial=\"" + STATE1_NAME + "\"") +
                    String.format(STATE_START, STATE1_NAME) +
                    String.format(TRANSITION_STRING, EVENTS_STRING, String.format(TEST_STRING, TEST1_NAME), StringUtils.EMPTY, String.format(TARGET_STRING, "state2"), StringUtils.EMPTY, StringUtils.EMPTY) +
                    STATE_END +
                    FSM_END;
        }

        @Override
        public DataDrivenFsm getResult() {
            return null;
        }

    };

    /**
     * @return The arrays of parameters
     */
    @Parameters(name = "{index}: {0}")
    public static Iterable<Object[]> getParameters() {
        return Arrays.asList(new Object[][] {
                { VALID_ONE_STATE_FINAL },
                { VALID_ONE_STATE_LOOP },
                { VALID_SAVE_CLEAR_FIELDS },
                { VALID_MULTIPLE_ACTIONS_TESTS },
                { VALID_MULTIPLE_STATES_TRANSITIONS },
                { VALID_ON_ENTRY_EXIT },
                { VALID_PRECONDITION },
                { VALID_INITIAL_STATE },
                { VALID_INITIAL },
                { INVALID_UNDEFINED_ACTION },
                { INVALID_UNDEFINED_CONDITION },
                { INVALID_UNDEFINED_TARGET },
                { VALID_NO_INITIAL },
        });
    }

    private final CompilationResult fExpected;

    /**
     * Constructor
     *
     * @param expected
     *            The expected result
     */
    public TmfXmlFsmCuTest(CompilationResult expected) {
        fExpected = expected;
    }

    /**
     * Test the compilation of a valid action strings
     *
     * @throws SAXException
     *             Exception thrown by parser
     * @throws IOException
     *             Exception thrown by parser
     * @throws ParserConfigurationException
     *             Exception thrown by parser
     */
    @Test
    public void testFsm() throws SAXException, IOException, ParserConfigurationException {
        AnalysisCompilationData data = new AnalysisCompilationData();

        compileActionsAndTests(fExpected.getXmlString(), data);
        Element xmlElement = TmfXmlTestUtils.getXmlElement(TmfXmlStrings.FSM, fExpected.getXmlString());
        assertNotNull(xmlElement);
        TmfXmlFsmCu compiledFsm = TmfXmlFsmStateCu.compileFsm(data, xmlElement);
        if (fExpected.getResult() == null) {
            assertNull("Expected null action" + fExpected.getName(), compiledFsm);
        } else {
            assertNotNull("Expected non null " + fExpected.getName(), compiledFsm);
            assertEquals(fExpected.getName() + " generated", fExpected.getResult(), compiledFsm.generate());
        }
    }

    private static void compileActionsAndTests(@NonNull String xmlString, @NonNull AnalysisCompilationData data) throws SAXException, IOException, ParserConfigurationException {
        List<@NonNull Element> xmlElements = TmfXmlTestUtils.getXmlElements(TmfXmlStrings.ACTION, xmlString);
        for (Element xmlElement : xmlElements) {
            assertNotNull(TmfXmlActionCu.compileNamedAction(data, xmlElement));
        }
        xmlElements = TmfXmlTestUtils.getXmlElements(TmfXmlStrings.TEST, xmlString);
        for (Element xmlElement : xmlElements) {
            assertNotNull(TmfXmlConditionCu.compileNamedCondition(data, xmlElement));
        }
    }

}
