/* Copyright or (C) or Copr. GET / ENST, Telecom-Paris, Ludovic Apvrille
 *
 * ludovic.apvrille AT enst.fr
 *
 * This software is a computer program whose purpose is to allow the
 * edition of TURTLE analysis, design and deployment diagrams, to
 * allow the generation of RT-LOTOS or Java code from this diagram,
 * and at last to allow the analysis of formal validation traces
 * obtained from external tools, e.g. RTL from LAAS-CNRS and CADP
 * from INRIA Rhone-Alpes.
 *
 * This software is governed by the CeCILL  license under French law and
 * abiding by the rules of distribution of free software.  You can  use,
 * modify and/ or redistribute the software under the terms of the CeCILL
 * license as circulated by CEA, CNRS and INRIA at the following URL
 * "http://www.cecill.info".
 *
 * As a counterpart to the access to the source code and  rights to copy,
 * modify and redistribute granted by the license, users are provided only
 * with a limited warranty  and the software's author,  the holder of the
 * economic rights,  and the successive licensors  have only  limited
 * liability.
 *
 * In this respect, the user's attention is drawn to the risks associated
 * with loading,  using,  modifying and/or developing or reproducing the
 * software by the user in light of its specific status of free software,
 * that may mean  that it is complicated to manipulate,  and  that  also
 * therefore means  that it is reserved for developers  and  experienced
 * professionals having in-depth computer knowledge. Users are therefore
 * encouraged to load and test the software's suitability as regards their
 * requirements in conditions enabling the security of their systems and/or
 * data to be ensured and,  more generally, to use and operate it in the
 * same conditions as regards security.
 *
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL license and that you accept its terms.
 */

package avatartranslator;

import avatartranslator.intboolsolver.AvatarIBSolver;
import myutil.MyMath;
import myutil.TraceManager;

import java.util.*;


/**
 * Class AvatarStateMachine
 * State machine, with composite states
 * Creation: 20/05/2010
 *
 * @author Ludovic APVRILLE
 * @version 1.0 20/05/2010
 */
public class AvatarStateMachine extends AvatarElement {
    private static int ID_ELT = 0;
    // To be used by code generator for fast access to states
    public AvatarStateElement[] allStates;
    protected List<AvatarStateMachineElement> elements;
    protected AvatarStartState startState;
    protected List<AvatarStateMachineElement> states;
    protected AvatarStateMachineOwner block;

    public AvatarStateMachine(AvatarStateMachineOwner _block, String _name, Object _referenceObject) {
        super(_name, _referenceObject);
        block = _block;
        elements = new LinkedList<AvatarStateMachineElement>();
    }

    public AvatarStateMachineOwner getOwner() {
        return block;
    }

    public AvatarStartState getStartState() {
        return startState;
    }

    public void setStartState(AvatarStartState _state) {
        startState = _state;
    }

    public int getNbOfStatesElement() {
        int cpt = 0;
        for (AvatarStateMachineElement asme : elements) {
            if (asme instanceof AvatarStateElement) {
                cpt++;
            }
        }
        return cpt;
    }

    public void clear() {
        elements.clear();
        startState = null;
        allStates = null;
        states = null;
    }

    public void makeBasicSM(AvatarStateMachineOwner owner) {
        elements.clear();

        if (startState == null) {
            startState = new AvatarStartState("StartState", null, owner);
        } else {
            startState.removeAllNexts();
        }
        addElement(startState);

        AvatarTransition at = new AvatarTransition(owner, "Transition", startState.getReferenceObject());
        AvatarStopState stopS = new AvatarStopState("StopState", startState.getReferenceObject(), owner);
        addElement(at);
        addElement(stopS);
        startState.addNext(at);
        at.addNext(stopS);

    }

    public boolean isBasicStateMachine() {
        if (startState == null) {
            return true;
        }

        if (elements.size() > 3) {
            return false;
        }

        boolean hasStartState = false, hasStopState = false, hasBasicTransition = false;
        for (AvatarStateMachineElement asme : elements) {
            if (asme instanceof AvatarStartState) {
                hasStartState = true;
            }
            if (asme instanceof AvatarStartState) {
                hasStopState = true;
            }
            if (asme instanceof AvatarTransition) {
                AvatarTransition at = (AvatarTransition) asme;
                if (at.isEmpty()) {
                    hasBasicTransition = true;
                }
            }
        }

        return hasStartState && hasStopState && hasBasicTransition;

    }

    /**
     * Make sure that there is a start state, a stop state and that all
     * elements apart from regular states are followed by a stop or a next
     */
    public void makeCorrect(AvatarStateMachineOwner owner) {
        if (startState == null) {
            TraceManager.addDev("Null start state");
            makeBasicSM(owner);
            return;
        }

        // Remove nexts when not in the list of elements
        for (AvatarStateMachineElement asme : getListOfElements()) {
            ArrayList<AvatarStateMachineElement> removedNext = new ArrayList<>();
            for (AvatarStateMachineElement nextElt : asme.getNexts()) {
                if (!(getListOfElements().contains(nextElt))) {
                    TraceManager.addDev("Removing: " + nextElt);
                    removedNext.add(nextElt);
                }
            }
            asme.getNexts().removeAll(removedNext);
        }

        // We check that all elements are reachable from start.
        HashSet<AvatarStateMachineElement> reachable = new HashSet<>();
        ArrayList<AvatarStateMachineElement> pending = new ArrayList<>();

        pending.add(startState);


        while (pending.size() > 0) {
            AvatarStateMachineElement current = pending.get(0);
            reachable.add(current);
            pending.remove(0);
            if (current.getNexts().size() == 0) {
                if (!((current instanceof AvatarStopState) || (current instanceof AvatarState))) {
                    // We need to add a next
                    if (current instanceof AvatarTransition) {
                        AvatarStopState stopS = new AvatarStopState("StopState", current.getReferenceObject(), owner);
                        addElement(stopS);
                        current.addNext(stopS);
                    } else {
                        AvatarTransition at = new AvatarTransition(owner, "Transition", current.getReferenceObject());
                        AvatarStopState stopS = new AvatarStopState("StopState", current.getReferenceObject(), owner);
                        addElement(at);
                        addElement(stopS);
                        current.addNext(at);
                        at.addNext(stopS);
                    }
                }
            }

            for (AvatarStateMachineElement nextElt : current.getNexts()) {
                if (!(reachable.contains(nextElt))) {
                    pending.add(nextElt);
                }
            }
        }

        // We remove all elements that are not reachable
        ArrayList<AvatarElement> toRemove = new ArrayList<>();
        for (AvatarStateMachineElement asme : getListOfElements()) {
            if (!(reachable.contains(asme))) {
                TraceManager.addDev("Not reachable: " + asme);
                toRemove.add(asme);
            }
        }
        elements.removeAll(toRemove);

    }

    public void addElement(AvatarStateMachineElement _element) {
        if (_element != null) {
            elements.add(_element);
            //TraceManager.addDev("Adding element " + _element);
            states = null;
        } else {
            TraceManager.addDev("NULL element found " + _element);
        }
    }

    public void removeElement(AvatarStateMachineElement _element) {
        elements.remove(_element);
        states = null;
    }

    public List<AvatarStateMachineElement> getListOfElements() {
        return elements;
    }

    private void makeStates() {
        states = new LinkedList<AvatarStateMachineElement>();
        for (AvatarStateMachineElement asme : elements) {
            if (asme instanceof AvatarState) {
                states.add(asme);
            }
        }
    }

    public AvatarState getStateByName(String _name) {
        for (AvatarElement ae : elements) {
            if (ae instanceof AvatarState) {
                if (ae.getName().compareTo(_name) == 0) {
                    return (AvatarState) ae;
                }
            }
        }
        return null;
    }

    public void makeAllStates() {
        int cpt = 0;
        allStates = new AvatarStateElement[getNbOfStatesElement()];
        for (AvatarStateMachineElement asme : elements) {
            if (asme instanceof AvatarStateElement) {
                allStates[cpt] = (AvatarStateElement) asme;
                cpt++;
            }
        }
    }

    public boolean hasTimerOperatorWithAttribute(AvatarAttribute aa) {
        for(AvatarStateMachineElement asme: elements) {
            if (asme instanceof AvatarTimerOperator) {
                TraceManager.addDev("Found timer operator with name=" + ((AvatarTimerOperator)(asme)).getTimer().getName() +
                        " comparing with " + aa.getName());
                if (   ((AvatarTimerOperator)(asme)).getTimer().getName().compareTo(aa.getName()) == 0) {
                    return true;
                }
            }
        }
        return false;
    }

    public int stateNb() {
        if (states == null) {
            makeStates();
        }

        return states.size();
    }

    public int getNbOfASMGraphicalElements() {
        int cpt = 0;
        for (AvatarElement elt : elements) {
            if (elt.getReferenceObject() != null) {
                cpt++;
            }
        }
        return cpt;
    }

    public AvatarState getState(int index) {
        if (states == null) {
            makeStates();
        }

        try {
            return (AvatarState) (states.get(index));
        } catch (Exception e) {
        }
        return null;
    }

    public boolean isSignalUsed(AvatarSignal _sig) {
        for (AvatarStateMachineElement asme : elements) {
            if (asme instanceof AvatarActionOnSignal) {
                AvatarActionOnSignal aaos = (AvatarActionOnSignal) asme;
                if (aaos.getSignal() == _sig) {
                    return true;
                }
            }
        }

        return false;
    }

    public boolean isTimerUsed(AvatarAttribute _timer) {
        for (AvatarStateMachineElement asme : elements) {
            if (asme instanceof AvatarTimerOperator) {
                AvatarTimerOperator ato = (AvatarTimerOperator) asme;
                if (ato.getTimer() == _timer) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Analyze the usage of a regular attribute (int, bool)
     *
     * @param _aa
     * @return
     */
    public boolean isRegularAttributeUsed(AvatarAttribute _aa) {
        boolean ret;
        AvatarIBSolver.clearAttributes();
        for (AvatarStateMachineElement asme : elements) {
            if (asme instanceof AvatarTransition) {
                // Must check the guard, the delays and all the actions
                AvatarTransition at = (AvatarTransition) asme;

                if (at.isGuarded()) {
                    ret = isInExpression(at.getGuard().toString(), _aa);
                    if (ret) {
                        return true;
                    }
                }

                if (at.hasDelay()) {
                    ret = isInExpression(at.getMinDelay().toString(), _aa);
                    if (ret) {
                        return true;
                    }
                    ret = isInExpression(at.getMaxDelay().toString(), _aa);
                    if (ret) {
                        return true;
                    }
                }

                for (AvatarAction act : at.getActions()) {
                    ret = isInExpression(act.toString(), _aa);
                    if (ret) {
                        return true;
                    }
                }

            } else if (asme instanceof AvatarActionOnSignal) {
                for (String s : ((AvatarActionOnSignal) asme).getValues()) {
                    ret = isInExpression(s, _aa);
                    if (ret) {
                        return true;
                    }
                }

            } else if (asme instanceof AvatarRandom) {
                AvatarRandom ar = (AvatarRandom) asme;
                String s = ar.getVariable();
                ret = isInExpression(s, _aa);
                if (ret) {
                    return true;
                }
                s = ar.getMinValue();
                ret = isInExpression(s, _aa);
                if (ret) {
                    return true;
                }
                s = ar.getMaxValue();
                ret = isInExpression(s, _aa);
                if (ret) {
                    return true;
                }
            }
        }

        return false;
    }

    private boolean isInExpression(String expr, AvatarAttribute _aa) {
        return AvatarIBSolver.parser.indexOfVariable(expr, _aa.getName()) > -1;
    }

    private int getSimplifiedElementsAux(LinkedHashMap<AvatarStateMachineElement, Integer> simplifiedElements, Set<AvatarStateMachineElement> visited,
                                         AvatarStateMachineElement root, int counter) {
        if (visited.contains(root)) {
            Integer name = simplifiedElements.get(root);
            if (name == null) {
                if (root == this.startState)
                    simplifiedElements.put(root, Integer.valueOf(0));
                else {
                    counter++;
                    simplifiedElements.put(root, Integer.valueOf(counter));
                }
            }
        } else {
            visited.add(root);
            for (AvatarStateMachineElement asme : root.nexts)
                counter = this.getSimplifiedElementsAux(simplifiedElements, visited, asme, counter);
        }

        return counter;
    }

    public LinkedHashMap<AvatarStateMachineElement, Integer> getSimplifiedElements() {
        LinkedHashMap<AvatarStateMachineElement, Integer> simplifiedElements = new LinkedHashMap<AvatarStateMachineElement, Integer>();
        this.getSimplifiedElementsAux(simplifiedElements, new HashSet<AvatarStateMachineElement>(), startState, 0);
        return simplifiedElements;
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer("State machine Id=" + getID() + "\n");

        for (AvatarStateMachineElement element : elements) {
            sb.append("\t" + element.getClass() + "->" + element.toString() + "\n");
        }


        return sb.toString();
    }

    public String toStringRecursive() {
        AvatarStartState ass = getStartState();
        if (ass == null) {
            return "empty asm";
        }

        return toStringRecursiveElt(ass);
    }

    public String toTextFormat() {
        AvatarStartState ass = getStartState();
        if (ass == null) {
            return "Empty state machine";
        }

        return toTextFormatElt(ass);
    }

    public String toStringRecursiveElt(AvatarStateMachineElement _asme) {
        //TraceManager.addDev("Handling element:" + _asme.toString());
        String ret = "";
        ret += "* " + _asme.toStringExtendedID() + "\n";

        for (AvatarStateMachineElement asme : _asme.getNexts()) {
            ret += "\tnext: " + asme.toStringExtendedID() + "\n";

        }
        for (AvatarStateMachineElement asme : _asme.getNexts()) {
            ret += toStringRecursiveElt(asme);
        }

        return ret;

    }

    public String toTextFormatElt(AvatarStateMachineElement _asme) {
        String ret = "";
        ret += "* " + _asme.toStringExtendedID() + "\n";

        for (AvatarStateMachineElement asme : _asme.getNexts()) {
            ret += "\tnext: " + asme.toStringExtendedID() + "\n";

        }
        for (AvatarStateMachineElement asme : _asme.getNexts()) {
            ret += toTextFormatElt(asme);
        }

        return ret;

    }


    // Add missing implicit states.
    public void makeFullStates(AvatarBlock _block) {
        addStatesToEmptyNonTerminalEmptyNext(_block);
        addStateAfterActionOnSignal(_block);
        addStatesToTransitionsBetweenTwoNonStates(_block);
        addStatesToActionTransitions(_block);
        addStatesToNonEmptyTransitionsBetweenNonStateToState(_block);
    }


    private void addStatesToEmptyNonTerminalEmptyNext(AvatarBlock _b) {
        List<AvatarStateMachineElement> toConsider = new ArrayList<AvatarStateMachineElement>();
        for (AvatarStateMachineElement elt : elements) {
            if (!(elt instanceof AvatarStopState)) {
                if (elt.getNext(0) == null) {
                    // Missing state
                    toConsider.add(elt);
                }
            }
        }

        for (AvatarStateMachineElement elt : toConsider) {
            AvatarStopState stopMe = new AvatarStopState("stopCreated", elt.getReferenceObject(), _b);
            addElement(stopMe);
            AvatarTransition tr = new AvatarTransition(_b, "trForStopCreated", elt.getReferenceObject());
            addElement(tr);
            elt.addNext(tr);
            tr.addNext(stopMe);
        }

    }

    private void addStateAfterActionOnSignal(AvatarBlock _block) {
        List<AvatarStateMachineElement> toAdd = new ArrayList<AvatarStateMachineElement>();
        int id = 0;

        for (AvatarStateMachineElement elt : elements) {
            if (elt instanceof AvatarActionOnSignal) {
                if (elt.getNext(0) instanceof AvatarTransition) {
                    AvatarTransition tr = (AvatarTransition) elt.getNext(0);
                    // We create an intermediate state
                    AvatarState state = new AvatarState("IntermediateState4__" + id, elt.getReferenceObject(), _block);
                    state.setCommit(true);
                    toAdd.add(state);
                    AvatarTransition at1 = new AvatarTransition(_block, "TransitionForIntermediateState4__" + id, elt.getReferenceObject());
                    toAdd.add(at1);

                    elt.removeAllNexts();
                    elt.addNext(at1);
                    at1.addNext(state);
                    state.addNext(tr);

                    id++;
                }
            }
        }

        for (AvatarStateMachineElement add : toAdd) {
            elements.add(add);
        }
    }


    private void addStatesToNonEmptyTransitionsBetweenNonStateToState(AvatarBlock _block) {
        AvatarStateMachineElement next;
        AvatarStateMachineElement previous;
        List<AvatarStateMachineElement> toAdd = new ArrayList<AvatarStateMachineElement>();
        int id = 0;

        for (AvatarStateMachineElement elt : elements) {
            if (elt instanceof AvatarTransition) {
                AvatarTransition tr = (AvatarTransition) elt;

                if (tr.hasDelay() || tr.isGuarded() || tr.hasAction()) {
                    previous = getPreviousElementOf(elt);
                    next = elt.getNext(0);

                    // If the next is a state, but not the previous one
                    if ((previous != null) && (next != null)) {
                        if ((!(previous instanceof AvatarStateElement)) && (next instanceof AvatarStateElement)) {
                            // We create an intermediate state
                            AvatarState state = new AvatarState("IntermediateState1__" + id, elt.getReferenceObject(), _block);
                            state.setCommit(true);
                            toAdd.add(state);
                            AvatarTransition at1 = new AvatarTransition(_block, "TransitionForIntermediateState1__" + id, elt.getReferenceObject());
                            toAdd.add(at1);

                            previous.removeAllNexts();
                            previous.addNext(at1);
                            at1.addNext(state);
                            state.addNext(tr);

                            id++;
                        }
                    }
                }

            }
        }

        for (AvatarStateMachineElement add : toAdd) {
            elements.add(add);
        }

    }

    private void addStatesToTransitionsBetweenTwoNonStates(AvatarBlock _block) {
        AvatarStateMachineElement next;
        AvatarStateMachineElement previous;
        List<AvatarStateMachineElement> toAdd = new ArrayList<AvatarStateMachineElement>();
        int id = 0;

        for (AvatarStateMachineElement elt : elements) {
            if (elt instanceof AvatarTransition) {
                AvatarTransition tr = (AvatarTransition) elt;
                previous = getPreviousElementOf(elt);
                next = elt.getNext(0);

                // If the next and previous are non states
                if ((previous != null) && (next != null)) {
                    if ((!(previous instanceof AvatarStateElement)) && (!(next instanceof AvatarStateElement))) {
                        // We create an intermediate state
                        AvatarState state = new AvatarState("IntermediateState2__" + id, elt.getReferenceObject(), _block);
                        toAdd.add(state);
                        AvatarTransition at1 = new AvatarTransition(_block, "TransitionForIntermediateState2__" + id, elt.getReferenceObject());
                        toAdd.add(at1);


                        previous.removeAllNexts();
                        previous.addNext(at1);
                        at1.addNext(state);
                        state.addNext(tr);

                        id++;
                    }
                }

            }
        }

        for (AvatarStateMachineElement add : toAdd) {
            elements.add(add);
        }

    }


    // Hanlding transitions with actions which have a non state
    // after

    // Then, handling transitions with actions which have a non state
    // before
    private void addStatesToActionTransitions(AvatarBlock _block) {
        AvatarStateMachineElement next;
        AvatarStateMachineElement previous;
        List<AvatarStateMachineElement> toAdd = new ArrayList<AvatarStateMachineElement>();
        int id = 0;
        for (AvatarStateMachineElement elt : elements) {
            if (elt instanceof AvatarTransition) {
                AvatarTransition tr = (AvatarTransition) elt;

                // tr with actions?
                if (tr.getNbOfAction() > 0) {
                    previous = getPreviousElementOf(elt);
                    next = elt.getNext(0);

                    if (!(next instanceof AvatarState)) {
                        // We create an intermediate state
                        AvatarState state = new AvatarState("IntermediateState3__" + id, elt.getReferenceObject(), _block);
                        state.setCommit(true);
                        toAdd.add(state);
                        AvatarTransition at1 = new AvatarTransition(_block, "TransitionForIntermediateState3__" + id, elt.getReferenceObject());
                        toAdd.add(at1);

                        tr.removeAllNexts();
                        tr.addNext(state);
                        state.addNext(at1);
                        at1.addNext(next);

                        id++;
                    }
                }
            }

        }

        for (AvatarStateMachineElement add : toAdd) {
            elements.add(add);
        }
        toAdd.clear();

        for (AvatarStateMachineElement elt : elements) {
            if (elt instanceof AvatarTransition) {
                AvatarTransition tr = (AvatarTransition) elt;

                // tr with actions?
                if (tr.getNbOfAction() > 0) {
                    previous = getPreviousElementOf(elt);
                    if (!(previous instanceof AvatarStateElement)) {
                        // We create an intermediate state
                        AvatarState state = new AvatarState("IntermediateState__" + id, elt.getReferenceObject(), _block);
                        state.setCommit(true);
                        toAdd.add(state);
                        AvatarTransition at1 = new AvatarTransition(_block, "TransitionForIntermediateState__" + id, elt.getReferenceObject());
                        toAdd.add(at1);

                        previous.removeAllNexts();
                        previous.addNext(at1);
                        at1.addNext(state);
                        state.addNext(tr);
                        id++;
                    }
                }
            }
        }

        for (AvatarStateMachineElement add : toAdd) {
            elements.add(add);
        }

    }

    public void removeRandoms(AvatarBlock _block) {
        int id = 0;
        List<AvatarStateMachineElement> toRemove = new ArrayList<AvatarStateMachineElement>();
        List<AvatarStateMachineElement> toAdd = new ArrayList<AvatarStateMachineElement>();
        AvatarStateMachineElement next;
        AvatarStateMachineElement previous;

        for (AvatarStateMachineElement elt : elements) {
            if (elt instanceof AvatarRandom) {
                AvatarRandom random = (AvatarRandom) elt;
                previous = getPreviousElementOf(elt);
                next = elt.getNext(0);
                toRemove.add(elt);

                // Is the variable of random used in expression?
                // If yes, they must be removed. If not, we can directly use the expressions.
                String minExpr = random.getMinValue();
                String maxExpr = random.getMaxValue();
                String var = random.getVariable();

                String maxExprToBeUsed = "";


                // Creating elements
                AvatarTransition at1 = new AvatarTransition(_block, "Transition1ForRandom__ " + elt.getName() + "__" + id, elt.getReferenceObject());

                // If maxExpr contains var then use __tmpMax instead of var in maxExpr


                if (AvatarIBSolver.parser.indexOfVariable(maxExpr, var) > -1) {
                    AvatarAttribute aa = _block.addIntegerAttribute("tmpMaxRandom__");
                    maxExprToBeUsed = aa.getName();
                    at1.addAction(aa.getName() + " = " + maxExpr, "");
                } else {
                    maxExprToBeUsed = maxExpr;
                }

                at1.addAction(random.getVariable() + "=" + minExpr, "");
                AvatarState randomState = new AvatarState("StateForRandom__" + elt.getName() + "__" + id, elt.getReferenceObject(), _block);
                AvatarState beforeRandom = new AvatarState("StateBeforeRandom__" + elt.getName() + "__" + id, elt.getReferenceObject(), _block);
                AvatarTransition at2 = new AvatarTransition(_block, "Transition2ForRandom__" + elt.getName() + "__" + id, elt.getReferenceObject());
                at2.setGuard("[" + random.getVariable() + " < " + maxExprToBeUsed + "]", "");
                at2.addAction(random.getVariable() + "=" + random.getVariable() + " + 1", "");
                AvatarTransition at3 = new AvatarTransition(_block, "Transition3ForRandom__ " + elt.getName() + "__" + id, elt.getReferenceObject());
                AvatarState afterRandom = new AvatarState("StateAfterRandom__" + elt.getName() + "__" + id, elt.getReferenceObject(), _block);

                // Adding elements
                toAdd.add(at1);
                toAdd.add(randomState);
                toAdd.add(beforeRandom);
                toAdd.add(at2);
                toAdd.add(at3);
                toAdd.add(afterRandom);

                // Linking elements
                if (previous != null) {
                    previous.removeAllNexts();
                    previous.addNext(beforeRandom);
                }
                beforeRandom.addNext(at1);
                at1.addNext(randomState);
                randomState.addNext(at2);
                randomState.addNext(at3);
                at2.addNext(randomState);
                at3.addNext(afterRandom);
                afterRandom.addNext(next);

                id++;

            }
        }

        for (AvatarStateMachineElement trash : toRemove) {
            elements.remove(trash);
        }

        for (AvatarStateMachineElement newOnes : toAdd) {
            elements.add(newOnes);
        }
    }


    // Assumes no after clause on composite relation
    public void removeCompositeStates(AvatarBlock _block) {
        //TraceManager.addDev("\n-------------- Remove composite states ---------------\n");

        /*LinkedList<AvatarState> lists =*/
        List<AvatarStartState> compositeStateToBeRemoved = removeAllInternalStartStates();
        removeAllInternalStopStatesByRegularStates();

        AvatarTransition at = getAvatarCompositeTransition();

        if (at == null) {
            return;
        }

        // We modify all composite states with intermediate states
        for (int i = 0; i < elements.size(); i++) {
            AvatarStateMachineElement element = elements.get(i);
            if (element instanceof AvatarState) {
                modifyStateForCompositeSupport((AvatarState) element);
            }
        }

        // For each composite transition: We link it to all the subStates of the current state
        AvatarState src;

        Vector<AvatarTransition> transitions = getAvatarCompositeTransitions();

        for (AvatarTransition atc : transitions) {
            src = (AvatarState) (getPreviousElementOf(atc));
            atc.setAsVerifiable(false);
            for (int j = 0; j < elements.size(); j++) {
                AvatarStateMachineElement elt = elements.get(j);
                if ((elt instanceof AvatarState) && (elt.hasInUpperState(src))) {
                    AvatarTransition att = cloneCompositeTransition(atc);
                    elt.addNext(att);
                    att.setAsVerifiable(false);
                }
            }
        }

        /*while (((at = getAvatarCompositeTransition()) != null)) {
            src = (AvatarState) (getPreviousElementOf(at));
            elements.remove(at);

            // Link a clone of the transition  to all internal states

            for (int j = 0; j < elements.size(); j++) {
                AvatarStateMachineElement elt = elements.get(j);
                if ((elt instanceof AvatarState) && (elt.hasInUpperState(src))) {
                    AvatarTransition att = cloneCompositeTransition(at);
                    elt.addNext(att);
                    att.setAsVerifiable(false);
                    at.setAsVerifiable(false);
                }
            }

        }*/

        // Removing composite states
        /*for(AvatarStateMachineElement elt: compositeStateToBeRemoved) {
            elements.remove(elt);
        }*/

    }


    private void removeAllInternalStopStatesByRegularStates() {
        Vector<AvatarStopState> v = new Vector<>();
        Vector<AvatarState> toAdd = new Vector<>();

        for (AvatarStateMachineElement element : elements) {
            if (element instanceof AvatarTransition) {
                AvatarTransition at = (AvatarTransition) element;
                //TraceManager.addDev("at? element=" + element);
                // Transition fully in the internal state?
                if (element.getNext(0) instanceof AvatarStopState) {
                    AvatarStopState stop = (AvatarStopState) (element.getNext(0));
                    if (stop.getState() != null) {
                        AvatarState newState = new AvatarState(stop.getName(), stop.getReferenceObject(), block);
                        element.removeNext(0);
                        element.addNext(newState);
                        toAdd.add(newState);
                        v.add(stop);
                    }

                }
            }
        }
        for (AvatarStopState s : v) {
            elements.remove(s);
        }
        for (AvatarState st : toAdd) {
            elements.add(st);
        }
    }


    private void modifyStateForCompositeSupport(AvatarState _state) {
        // Each time there is a transition with an after or more than one action, we must rework the transition
        // We first gather all transitions internal to that state

        Vector<AvatarStateMachineElement> v = new Vector<AvatarStateMachineElement>();

        for (AvatarStateMachineElement element : elements) {
            if (element instanceof AvatarTransition) {
                AvatarTransition at = (AvatarTransition) element;
                //TraceManager.addDev("at? element=" + element);
                // Transition fully in the internal state?
                if (element.getNext(0).hasInUpperState(_state) == true) {
                    AvatarStateMachineElement previous = getPreviousElementOf(element);
                    if (previous.hasInUpperState(_state) == true) {
                        if (!(at.isEmpty())) {
                            v.add(at);
                            at.setNotCheckable();
                        }
                    }
                }
            }
        }

        for (AvatarStateMachineElement element : v) {
            //TraceManager.addDev(">" + element + "<");
            splitAvatarTransition((AvatarTransition) element, _state);
        }

    }


    private void splitAvatarTransition(AvatarTransition _at, AvatarState _currentState) {
        if (_at.hasDelay()) {
            AvatarStateMachineElement element = getPreviousElementOf(_at);
            if (element.hasInUpperState(_currentState) == true) {
                if (!(element instanceof AvatarState)) {
                    // Must add an intermediate state
                    String tmp = findUniqueStateName("splitstate_after__");
                    AvatarState as = new AvatarState(tmp, _currentState.getReferenceObject(), block);
                    addElement(as);
                    as.setHidden(true);
                    as.setState(_currentState);
                    AvatarTransition atn = new AvatarTransition(_at.getBlock(), "splittransition_after", null);
                    addElement(atn);
                    element.removeNext(_at);
                    element.addNext(atn);
                    atn.addNext(as);
                    as.addNext(_at);
                    splitAvatarTransition(_at, _currentState);
                }
            }
        } else {

            if (_at.getNbOfAction() > 1) {
                //TraceManager.addDev("New split state");
                String tmp = findUniqueStateName("splitstate_action__");
                AvatarState as = new AvatarState(tmp, null, block);
                as.setHidden(true);
                as.setState(_currentState);
                AvatarTransition at = (AvatarTransition) (_at.basicCloneMe(block));
                _at.removeAllActionsButTheFirstOne();
                at.removeFirstAction();
                at.addNext(_at.getNext(0));
                _at.removeAllNexts();
                _at.addNext(as);
                as.addNext(at);
                addElement(as);
                addElement(at);

                splitAvatarTransition(_at, _currentState);
            }
        }
    }

//
//    private List<AvatarTransition> getAllAvatarCompositeTransitions() {
//        List<AvatarTransition> ats = new ArrayList<AvatarTransition>();
//        for (AvatarStateMachineElement element : elements) {
//            if (element instanceof AvatarTransition) {
//                if ((isACompositeTransition((AvatarTransition) element))) {
//                    ats.add((AvatarTransition) element);
//                }
//            }
//        }
//
//        return ats;
//    }


    private void addFullInternalStates(AvatarState state, AvatarTransition _at) {
        // First:split internal transitions
        Vector<AvatarStateMachineElement> v = new Vector<AvatarStateMachineElement>();

        for (AvatarStateMachineElement element : elements) {
            //TraceManager.addDev("\nIs in composite state " + state + ": >" + element + "< ???");
            if (element instanceof AvatarTransition) {
                //TraceManager.addDev("at? element=" + element);
                if (element.getNext(0).hasInUpperState(state) == true) {
                    if (getPreviousElementOf(element).hasInUpperState(state) == true) {
                        v.add(element);
                    }
                }
            } else if (element.hasInUpperState(state) == true) {
                // We found a candidate!
                if (element != _at) {
                    v.add(element);
                }
            }
        }

        //TraceManager.addDev("*** Analyzing components in state " + state);
        // Split avatar transitions
        for (AvatarStateMachineElement element : v) {
            //TraceManager.addDev(">" + element + "<");
            if (element instanceof AvatarTransition) {
                splitAvatarTransition((AvatarTransition) element, state);
            }
        }
    }

    private AvatarTransition getAvatarCompositeTransition() {

        for (AvatarStateMachineElement element : elements) {
            if (element instanceof AvatarTransition) {
                if ((isACompositeTransition((AvatarTransition) element))) {
                    return (AvatarTransition) element;

                }
            }
        }

        return null;
    }

    private Vector<AvatarTransition> getAvatarCompositeTransitions() {

        Vector<AvatarTransition> transitions = new Vector<>();

        for (AvatarStateMachineElement element : elements) {
            if (element instanceof AvatarTransition) {
                if ((isACompositeTransition((AvatarTransition) element))) {
                    transitions.add((AvatarTransition) element);
                }
            }
        }

        return transitions;
    }


    // Checks whether the previous element is a state with an internal state machine
    public boolean isACompositeTransition(AvatarTransition _at) {
        AvatarStateMachineElement element = getPreviousElementOf(_at);
        if (element == null) {
            return false;
        }

        if (!(element instanceof AvatarState)) {
            return false;
        }

        AvatarState state = (AvatarState) element;

        if (!hasInternalComponents(state)) {
            return false;
        }

        AvatarStateMachineElement next = _at.getNext(0);
        if (next.hasInUpperState(state)) {
            return false;
        }

        return true;
    }

    private boolean hasInternalComponents(AvatarState _state) {
        for (AvatarStateMachineElement element : elements) {
            if (element.getState() == _state) {
                return true;
            }
        }

        return false;
    }

    /*private void removeCompositeTransition(AvatarTransition _at, AvatarBlock _block) {
      AvatarState state = (AvatarState)(getPreviousElementOf(_at));

      removeStopStatesOf(state);

      // Remove "after" and replace them with timers
      AvatarTransition at = removeAfter(_at, _block);

      // Put state after transition
      modifyAvatarTransition(at);

      Vector <AvatarStateMachineElement> v = new Vector<AvatarStateMachineElement>();

      for(AvatarStateMachineElement element: elements) {
      //TraceManager.addDev("\nIs in composite state " + state + ": >" + element + "< ???");
      if (element instanceof AvatarTransition) {
      //TraceManager.addDev("at? element=" + element);
      if (element.getNext(0).hasInUpperState(state) == true) {
      if (getPreviousElementOf(element).hasInUpperState(state) == true) {
      v.add(element);
      }
      }
      } else if (element.hasInUpperState(state) == true) {
      // We found a candidate!
      if (element != _at) {
      v.add(element);
      }
      }
      }

      //TraceManager.addDev("*** Analyzing components in state " + state);
      // Split avatar transitions
      for(AvatarStateMachineElement element: v) {
      TraceManager.addDev(">" + element + "<");
      if (element instanceof AvatarTransition) {
      splitAvatarTransition((AvatarTransition)element, state);
      }
      }

      //TraceManager.addDev("\nAdding new elements in state");
      v.clear();
      for(AvatarStateMachineElement element: elements) {
      //TraceManager.addDev("\nIs in composite state " + state + ": >" + element + "< ???");
      if (element.hasInUpperState(state) == true) {
      // We found a candidate!
      if ((element != _at) && (element != at)) {
      v.add(element);
      }
      }
      }


      for(AvatarStateMachineElement element: v) {
      adaptCompositeTransition(at, element, 0);
      }

      removeElement(at);

      }*/


    /*private void removeCompositeTransitions(ArrayList<AvatarTransition> _ats, AvatarBlock _block) {


    // Put state after transition
    modifyAvatarTransition(at);

    Vector <AvatarStateMachineElement> v = new Vector<AvatarStateMachineElement>();

    for(AvatarStateMachineElement element: elements) {
    //TraceManager.addDev("\nIs in composite state " + state + ": >" + element + "< ???");
    if (element instanceof AvatarTransition) {
    //TraceManager.addDev("at? element=" + element);
    if (element.getNext(0).hasInUpperState(state) == true) {
    if (getPreviousElementOf(element).hasInUpperState(state) == true) {
    v.add(element);
    }
    }
    } else if (element.hasInUpperState(state) == true) {
    // We found a candidate!
    if (element != _at) {
    v.add(element);
    }
    }
    }

    //TraceManager.addDev("*** Analyzing components in state " + state);
    // Split avatar transitions
    for(AvatarStateMachineElement element: v) {
    TraceManager.addDev(">" + element + "<");
    if (element instanceof AvatarTransition) {
    splitAvatarTransition((AvatarTransition)element, state);
    }
    }

    //TraceManager.addDev("\nAdding new elements in state");
    v.clear();
    for(AvatarStateMachineElement element: elements) {
    //TraceManager.addDev("\nIs in composite state " + state + ": >" + element + "< ???");
    if (element.hasInUpperState(state) == true) {
    // We found a candidate!
    if ((element != _at) && (element != at)) {
    v.add(element);
    }
    }
    }

    for(AvatarStateMachineElement element: v) {
    adaptCompositeTransition(at, element, 0);
    }

    removeElement(at);

    }*/

//    private void splitAvatarTransitionOld(AvatarTransition _at, AvatarState _currentState) {
//        /*if (_at.hasCompute()) {
//          AvatarState as0 = new AvatarState("splitstate0", null);
//          AvatarState as1 = new AvatarState("splitstate1", null);
//
//
//
//          AvatarTransition at = _at.basicCloneMe();
//          _at.removeAllActions();
//          _at.removeAllNexts();
//          _at.addNext(as);
//          as.addNext(at);
//          addElement(as);
//          addElement(at);
//          splitAvatarTransition(at);
//          }*/
//
//        TraceManager.addDev(" - - - - - - - - - - Split transition nbofactions=" + _at.getNbOfAction());
//        if (_at.getNbOfAction() > 1) {
//            TraceManager.addDev("New split state");
//            AvatarState as = new AvatarState("splitstate", null);
//            as.setHidden(true);
//            as.setState(_currentState);
//            AvatarTransition at = (AvatarTransition) (_at.basicCloneMe(block));
//            _at.removeAllActionsButTheFirstOne();
//            at.removeFirstAction();
//            at.addNext(_at.getNext(0));
//            _at.removeAllNexts();
//            _at.addNext(as);
//            as.addNext(at);
//            addElement(as);
//            addElement(at);
//
//            splitAvatarTransition(_at, _currentState);
//        }
//
//        if (_at.hasDelay()) {
//            AvatarStateMachineElement element = getPreviousElementOf(_at);
//            if (element.hasInUpperState(_currentState) == true) {
//                if (!(element instanceof AvatarState)) {
//                    // Must add an intermediate state
//                    String tmp = findUniqueStateName("internalstate__");
//                    AvatarState as = new AvatarState(tmp, _currentState.getReferenceObject());
//                    addElement(as);
//                    as.setHidden(true);
//                    as.setState(_currentState);
//                    AvatarTransition atn = new AvatarTransition(_at.getBlock(), "internaltransition", null);
//                    addElement(atn);
//                    element.removeNext(_at);
//                    element.addNext(atn);
//                    atn.addNext(as);
//                    as.addNext(_at);
//                    splitAvatarTransition(_at, _currentState);
//                }
//            }
//        }
//    }

//    private void adaptCompositeTransition(AvatarTransition _at, AvatarStateMachineElement _element, int _transitionID) {
//        AvatarState as;
//        AvatarTransition at;
//        LinkedList<AvatarStateMachineElement> ll;
//        String tmp;
//
//        // It cannot be a start / stop state since they have been previously removed ..
//        if (_element instanceof AvatarActionOnSignal) {
//            AvatarStateMachineElement element = _element.getNext(0);
//            if (element instanceof AvatarTransition) {
//                if (!(((AvatarTransition) element).isEmpty())) {
//                    //We need to create a new state
//                    tmp = findUniqueStateName("internalstate__");
//                    TraceManager.addDev("Creating state with name=" + tmp);
//                    as = new AvatarState(tmp, null);
//                    addElement(as);
//                    as.setHidden(true);
//                    at = new AvatarTransition(_at.getBlock(), "internaltransition", null);
//                    addElement(at);
//                    //_element -> at -> as -> element
//
//                    _element.removeNext(element);
//                    _element.addNext(at);
//
//                    at.addNext(as);
//                    as.addNext(element);
//
//                    at = cloneCompositeTransition(_at);
//                    //addElement(at);
//                    as.addNext(at);
//                } else {
//                    // We see if a state follows it. Otherwise, we create one
//                    if (!(element.getNext(0) instanceof AvatarState)) {
//                        //We need to create a new state
//                        tmp = findUniqueStateName("internalstate__");
//                        TraceManager.addDev("Creating state with name=" + tmp);
//                        as = new AvatarState(tmp, null);
//                        addElement(as);
//                        as.setHidden(true);
//                        at = new AvatarTransition(_at.getBlock(), "internaltransition", null);
//                        addElement(at);
//                        //_element -> at -> as -> element
//
//                        _element.removeNext(element);
//                        _element.addNext(at);
//
//                        at.addNext(as);
//                        as.addNext(element);
//
//                        at = cloneCompositeTransition(_at);
//                        //addElement(at);
//                        as.addNext(at);
//
//                    } else {
//                        //We link to this state-> will be done later
//                    }
//                }
//            }
//            /*ll = getPreviousElementsOf(_element);
//              for(AvatarStateMachineElement element: ll) {
//              if (element instanceof AvatarTransition) {
//              // if empty transition: we do just nothing
//              if (!(((AvatarTransition)element).isEmpty())) {
//              tmp = findUniqueStateName("internalstate__");
//              TraceManager.addDev("Creating state with name=" + tmp);
//              as = new AvatarState(tmp, null);
//              as.setHidden(true);
//              element.removeNext(_element);
//              element.addNext(as);
//              at = new AvatarTransition("internaltransition", null);
//              addElement(at);
//              at.addNext(_element);
//              as.addNext(at);
//              addElement(as);
//
//              at = cloneCompositeTransition(_at);
//              addElement(at);
//              as.addNext(at);
//              }
//
//              } else {
//              // Badly formed machine!
//              TraceManager.addError("Badly formed sm (removing composite transition)");
//              }
//              }*/
//
//        } else if (_element instanceof AvatarState) {
//            at = cloneCompositeTransition(_at);
//            //addElement(at);
//            _element.addNext(at);
//        } else if (_element instanceof AvatarTransition) {
//            // Nothing to do since they shall have been split before
//        } else {
//            // Nothing to do either
//        }
//    }


    // Return the first previous element met. Shall be used preferably only for transitions
    private AvatarStateMachineElement getPreviousElementOf(AvatarStateMachineElement _elt) {
        for (AvatarStateMachineElement element : elements) {
            if (element.hasNext(_elt)) {
                return element;
            }
        }

        return null;
    }

    private List<AvatarStateMachineElement> getPreviousElementsOf(AvatarStateMachineElement _elt) {
        List<AvatarStateMachineElement> ll = new LinkedList<AvatarStateMachineElement>();
        for (AvatarStateMachineElement element : elements) {
            if (element.hasNext(_elt)) {
                ll.add(element);
            }
        }

        return ll;
    }

    public AvatarState getStateWithName(String _name) {
        for (AvatarStateMachineElement element : elements) {
            if (element instanceof AvatarState) {
                if (element.getName().compareTo(_name) == 0) {
                    return (AvatarState) element;
                }
            }
        }
        return null;
    }


    public List<AvatarActionOnSignal> getAllAOSWithName(String _name) {
        List<AvatarActionOnSignal> list = new ArrayList<AvatarActionOnSignal>();
        for (AvatarStateMachineElement element : elements) {
            if (element instanceof AvatarActionOnSignal) {
                AvatarActionOnSignal aaos = (AvatarActionOnSignal) element;
                if (aaos.getSignal().getName().compareTo(_name) == 0) {
                    list.add(aaos);
                }
            }
        }
        return list;
    }

    // All transitions reaching a state that has an internal start state
    // shall in fact go directly to the nexts of the start state
    public List<AvatarStartState> removeAllInternalStartStates() {
        // identify allstart state
        List<AvatarStartState> ll = new LinkedList<AvatarStartState>();

        List<AvatarStartState> removedinfos = new LinkedList<>();

        for (AvatarStateMachineElement element : elements) {
            if ((element instanceof AvatarStartState) && (element.getState() != null)) {
                //TraceManager.addDev("-> -> found an internal state state");
                ll.add((AvatarStartState) element);
            }
        }

        AvatarState as0;
        List<AvatarStateMachineElement> le;
        for (AvatarStartState as : ll) {
            AvatarState astate = as.getState();
            if (as != null) {
                elements.remove(as);
                AvatarStateMachineElement elt = as.getNext(0);
                astate.addNext(elt);
                removedinfos.add(as);
                /*le = getPreviousElementsOf(astate);
                if (le.size() > 0) {
                    as0 = new AvatarState("entrance__" + astate.getName(), astate.getReferenceObject());
                    as0.addNext(as.getNext(0));
                    as0.setHidden(true);
                    as0.setState(astate);
                    for (AvatarStateMachineElement element : le) {
                        if (element instanceof AvatarTransition) {
                            element.removeAllNexts();
                            element.addNext(as0);
                        } else {
                            TraceManager.addDev("Badly formed state machine");
                        }
                    }

                    removedinfos.add(as.getState());
                    removedinfos.add(as0);

                    // Remove the start state and its next transition
                    removeElement(as);
                    addElement(as0);

                    //TraceManager.addDev("-> -> removed an internal state state!");
                } else {
                    TraceManager.addDev("Badly formed state machine");
                }*/
            }
        }

        return removedinfos;

    }

    public void removeAllSuperStates() {
        for (AvatarStateMachineElement element : elements) {
            element.setState(null);
        }
    }

    public AvatarStateMachineElement getStateMachineElementFromReferenceObject(Object _o) {
        for (AvatarStateMachineElement element : elements) {
            if (element.hasReferenceObject(_o)) {
                return element;
            }
        }
        return null;
    }

    public Object getReferenceObjectFromID(int _ID) {
        for (AvatarStateMachineElement element : elements) {
            if (element.getID() == _ID) {
                return element.getReferenceObject();
            }
        }
        return null;
    }

    // Return true iff at least one timer was removed
    public boolean removeTimers(String timerAttributeName, HashMap<String, AvatarSignal> sets,
                                HashMap<String, AvatarSignal> resets, HashMap<String, AvatarSignal> expires) {
        AvatarSetTimer ast;
        AvatarTimerOperator ato;

        List<AvatarStateMachineElement> olds = new LinkedList<AvatarStateMachineElement>();
        List<AvatarStateMachineElement> news = new LinkedList<AvatarStateMachineElement>();

        AvatarSignal as;


        for (AvatarStateMachineElement elt : elements) {
            // Set timer...
            if (elt instanceof AvatarSetTimer) {
                ast = (AvatarSetTimer) elt;
                as = sets.get(ast.getTimer().getName());
                if (as != null) {
                    AvatarActionOnSignal aaos = new AvatarActionOnSignal(elt.getName(), as, elt.getReferenceObject(), block);
                    aaos.addValue(timerAttributeName);
                    olds.add(elt);
                    news.add(aaos);


                    // Modifying the transition just before
                    List<AvatarStateMachineElement> previous = getPreviousElementsOf(ast);
                    if (previous.size() == 1) {
                        if (previous.get(0) instanceof AvatarTransition) {
                            AvatarTransition at = (AvatarTransition) (previous.get(0));
                            TraceManager.addDev("Timer value setting=" + ast.getTimerValue());
                            at.addAction(timerAttributeName + " = " + ast.getTimerValue(), "");
                        } else {
                            TraceManager.addError("The element before a set time is not a transition!");
                        }
                    } else {
                        TraceManager.addError("More than one transition before a set time!");
                    }
                }

                // Reset timer
            } else if (elt instanceof AvatarResetTimer) {
                ato = (AvatarTimerOperator) elt;
                as = resets.get(ato.getTimer().getName());
                if (as != null) {
                    AvatarActionOnSignal aaos = new AvatarActionOnSignal(elt.getName(), as, elt.getReferenceObject(), block);
                    olds.add(elt);
                    news.add(aaos);
                }

                // Expire timer
            } else if (elt instanceof AvatarExpireTimer) {
                ato = (AvatarTimerOperator) elt;
                as = expires.get(ato.getTimer().getName());
                if (as != null) {
                    AvatarActionOnSignal aaos = new AvatarActionOnSignal(elt.getName(), as, elt.getReferenceObject(), block);
                    olds.add(elt);
                    news.add(aaos);
                }
            }
        }

        // Replacing old elements with new ones
        AvatarStateMachineElement oldelt, newelt;
        for (int i = 0; i < olds.size(); i++) {
            oldelt = olds.get(i);
            newelt = news.get(i);
            replace(oldelt, newelt);
        }

        return (olds.size() > 0);
    }

    public void replace(AvatarStateMachineElement oldone, AvatarStateMachineElement newone) {

        //TraceManager.addDev("Replacing " + oldone + " with " + newone);

        addElement(newone);
        removeElement(oldone);

        // Previous elements
        List<AvatarStateMachineElement> previous = getPreviousElementsOf(oldone);
        for (AvatarStateMachineElement elt : previous) {
            elt.replaceAllNext(oldone, newone);
        }

        // Next elements
        for (int i = 0; i < oldone.nbOfNexts(); i++) {
            AvatarStateMachineElement elt = oldone.getNext(i);
            newone.addNext(elt);
        }
    }

    public AvatarTransition removeAfter(AvatarTransition _at, AvatarBlock _block) {
        String delay = _at.getMinDelay();
        if ((delay == null) || (delay.trim().length() == 0)) {
            return _at;
        }


        // We have to use a timer for this transition
        AvatarAttribute aa = _block.addTimerAttribute("timer__");
        AvatarAttribute val = _block.addIntegerAttribute(aa.getName() + "_val");

        //TraceManager.addDev("ADDING TIMER: " + aa.getName());

        // Timer is set at the entrance in the composite state
        List<AvatarTransition> ll = findEntranceTransitionElements((AvatarState) (getPreviousElementOf(_at)));

        AvatarTransition newat0, newat1;
        AvatarSetTimer ast;
        AvatarRandom ar;
        AvatarState as;
        for (AvatarTransition att : ll) {
            //TraceManager.addDev(" ------------------ Dealing with an entrance transition");
            ar = new AvatarRandom("randomfortimer", _block.getReferenceObject(), _block);
            ar.setVariable(val.getName());
            ar.setValues(_at.getMinDelay(), _at.getMaxDelay());

            ast = new AvatarSetTimer("settimer_" + aa.getName(), _block.getReferenceObject(), _block);
            ast.setTimerValue(val.getName());
            ast.setTimer(aa);

            newat0 = new AvatarTransition(_block, "transition_settimer_" + aa.getName(), _block.getReferenceObject());
            newat1 = new AvatarTransition(_block, "transition_settimer_" + aa.getName(), _block.getReferenceObject());

            elements.add(ar);
            elements.add(ast);
            elements.add(newat0);
            elements.add(newat1);

            newat1.addNext(att.getNext(0));
            att.removeAllNexts();
            att.addNext(ar);
            ar.addNext(newat0);
            newat0.addNext(ast);
            ast.addNext(newat1);

        }

        // Wait for timer expiration on the transition
        AvatarExpireTimer aet = new AvatarExpireTimer("expiretimer_" + aa.getName(), _block.getReferenceObject(), _block);
        aet.setTimer(aa);
        newat0 = new AvatarTransition(_block, "transition0_expiretimer_" + aa.getName(), _block.getReferenceObject());
        newat1 = new AvatarTransition(_block, "transition1_expiretimer_" + aa.getName(), _block.getReferenceObject());
        as = new AvatarState("state1_expiretimer_" + aa.getName(), _block.getReferenceObject(), _block);
        addElement(aet);
        addElement(newat0);
        addElement(newat1);
        addElement(as);

        newat0.addNext(aet);
        aet.addNext(newat1);
        newat1.addNext(as);
        _at.setDelays("", "", "", "");

        List<AvatarStateMachineElement> elts = getElementsLeadingTo(_at);

        for (AvatarStateMachineElement elt : elts) {
            elt.removeNext(_at);
            elt.addNext(newat0);
        }

        as.addNext(_at);

        return newat0;
    }

    public List<AvatarTransition> findEntranceTransitionElements(AvatarState _state) {
        //TraceManager.addDev("Searching for transitions entering:" + _state.getName());
        List<AvatarTransition> ll = new LinkedList<AvatarTransition>();

        for (AvatarStateMachineElement elt : elements) {
            if (elt instanceof AvatarTransition) {
                AvatarStateMachineElement element = getPreviousElementOf(elt);
                if (elt.getNext(0) == _state) {
                    ll.add((AvatarTransition) elt);
                } else if (element.inAnUpperStateOf(_state) && (elt.getNext(0).getState() == _state)) {
                    ll.add((AvatarTransition) elt);
                }
            }
        }
        //TraceManager.addDev("Nb of elements found:" + ll.size());
        return ll;
    }

    public List<AvatarStateMachineElement> getElementsLeadingTo(AvatarStateMachineElement _elt) {
        List<AvatarStateMachineElement> elts = new LinkedList<AvatarStateMachineElement>();

        for (AvatarStateMachineElement elt : elements) {
            if (elt.hasNext(_elt)) {
                elts.add(elt);
            }
        }

        return elts;
    }

    public void modifyAvatarTransition(AvatarTransition _at) {
        /*if ((_at.getNbOfAction() > 0) || (_at.hasCompute())) {
          return;
          }*/

        AvatarStateMachineElement next = _at.getNext(0);

        if (next instanceof AvatarState) {
            return;
        } else if ((next instanceof AvatarTimerOperator) || (next instanceof AvatarActionOnSignal)) {

            TraceManager.addDev("-> Timer modification");

            AvatarState myState = new AvatarState("statefortransition__" + ID_ELT, _at.getReferenceObject(), block);
            myState.setHidden(true);
            AvatarTransition at2 = new AvatarTransition(_at.getBlock(), "transitionfortransition__" + ID_ELT, _at.getReferenceObject());
            ID_ELT++;
            AvatarTransition at1 = (AvatarTransition) (next.getNext(0));

            next.removeAllNexts();
            next.addNext(at2);
            at2.addNext(myState);
            myState.addNext(at1);

            addElement(myState);
            addElement(at2);

            return;
        } else {
            AvatarState myState = new AvatarState("statefortransition__" + ID_ELT, _at.getReferenceObject(), block);
            AvatarTransition at = new AvatarTransition(_at.getBlock(), "transitionfortransition__", _at.getReferenceObject());
            at.addNext(_at.getNext(0));
            _at.removeAllNexts();
            _at.addNext(myState);
            myState.addNext(at);
            addElement(myState);
            addElement(at);
            return;
        }
    }

    public AvatarTransition cloneCompositeTransition(AvatarTransition _at) {
        //TraceManager.addDev("Must clone: " + _at);
        // We clone elements until we find a state!
        AvatarStateMachineElement tomake, current;
        AvatarStateMachineElement tmp;
        AvatarTransition at = (AvatarTransition) (_at.basicCloneMe(block));
        addElement(at);

        current = _at.getNext(0);
        tomake = at;

        while ((current != null) && !(current instanceof AvatarState)) {
            //TraceManager.addDev("Cloning: " + current);
            current.setNotCheckable();
            current.setAsVerifiable(false);
            tmp = current.basicCloneMe(block);
            addElement(tmp);
            tomake.addNext(tmp);
            tomake = tmp;
            current = current.getNext(0);
            if (current == null) {
                break;
            }
        }

        if (current == null) {
            TraceManager.addDev("NULL CURRENT !!! NULL CURRENT");
        }

        tomake.addNext(current);

        return at;

        /*if ((_at.getNbOfAction() > 0) || (_at.hasCompute())) {
          return _at.basicCloneMe();
          }

          AvatarStateMachineElement next = _at.getNext(0);

          if (next instanceof AvatarActionOnSignal) {
          AvatarTransition at = _at.
          AvatarActionOnSignal aaos = ((AvatarActionOnSignal)next).basicCloneMe();
          addElement(at);
          addElement(aaos);
          at.addNext(aaos);
          aaos.addNext(next.getNext(0)); // Shall be a state!
          return at;
          }

          if (next instanceof AvatarExpireTimer) {
          AvatarTransition at = _at.basicCloneMe();
          AvatarExpireTimer aet = ((AvatarExpireTimer)next).basicCloneMe();
          AvatarTransition
          addElement(at);
          addElement(aet);
          at.addNext(aet);
          aet.addNext(next.getNext(0)); // Shall be a state!
          return at;
          }

          if (next instanceof AvatarResetTimer) {
          AvatarTransition at = _at.basicCloneMe();
          AvatarResetTimer art = ((AvatarResetTimer)next).basicCloneMe();
          addElement(at);
          addElement(art);
          at.addNext(art);
          art.addNext(next.getNext(0)); // Shall be a state!
          return at;
          }

          if (next instanceof AvatarSetTimer) {
          AvatarTransition at = _at.basicCloneMe();
          AvatarSetTimer ast = ((AvatarSetTimer)next).basicCloneMe();
          addElement(at);
          addElement(ast);
          at.addNext(ast);
          ast.addNext(next.getNext(0)); // Shall be a state!
          return at;
          }

          return _at.basicCloneMe();*/

    }

    public void removeStopStatesOf(AvatarState _as) {
        List<AvatarStopState> ll = new LinkedList<AvatarStopState>();

        for (AvatarStateMachineElement elt : elements) {
            if (elt instanceof AvatarStopState) {
                if (elt.getState() == _as) {
                    ll.add((AvatarStopState) elt);
                }
            }
        }

        for (AvatarStopState ass : ll) {
            TraceManager.addDev("Removed a stop state");
            AvatarState astate = new AvatarState("OldStopState", ass.getReferenceObject(), block);
            astate.setState(ass.getState());
            replace(ass, astate);
        }
    }

    public String findUniqueStateName(String name) {
        int id = 0;
        boolean found;

        while (id < 10000) {
            found = false;
            for (AvatarStateMachineElement elt : elements) {
                if (elt instanceof AvatarState) {
                    if (elt.getName().compareTo(name + id) == 0) {
                        found = true;
                        break;
                    }
                }
            }
            if (!found) {
                return name + id;
            }
            id++;
        }
        return name + id;
    }

    public void handleUnfollowedStartState(AvatarStateMachineOwner _block) {
        if (startState.nbOfNexts() == 0) {
            AvatarStopState stopState = new AvatarStopState("__StopState", startState.getReferenceObject(), _block);
            AvatarTransition at = new AvatarTransition(_block, "__toStop", startState.getReferenceObject());
            addElement(stopState);
            addElement(at);
            startState.addNext(at);
            at.addNext(stopState);
        }
    }

    /**
     * Removes all function calls by inlining them.
     *
     * @param block The block from which library function calls should be removed.
     */
    public void removeLibraryFunctionCalls(AvatarBlock block) {
        /* Perform BFS for AvatarLibraryFunctionCall elements. When one is found, replace it by the state machine and fix the links */
        LinkedList<AvatarStateMachineElement> toVisit = new LinkedList<AvatarStateMachineElement>();
        toVisit.add(this.startState);
        Set<AvatarStateMachineElement> visited = new HashSet<AvatarStateMachineElement>();
        Map<AvatarLibraryFunctionCall, AvatarStateMachineElement> callsTranslated = new HashMap<AvatarLibraryFunctionCall, AvatarStateMachineElement>();

        while (!toVisit.isEmpty()) {
            /* Get the first element of the queue */
            AvatarStateMachineElement curAsme = toVisit.remove();
            if (visited.contains(curAsme))
                continue;

            if (curAsme instanceof AvatarLibraryFunctionCall) {
                AvatarLibraryFunctionCall alfc = (AvatarLibraryFunctionCall) curAsme;

                /* Create a state that will be used as an entry point for the sub-state machine */
                AvatarState firstState = new AvatarState("entry_" + alfc.getLibraryFunction().getName() + "_" + alfc.getCounter(),
                        curAsme.getReferenceObject(), block);
                elements.add(firstState);

                /* Add this state to the mapping so that future state can use it to replace their next element */
                callsTranslated.put(alfc, firstState);

                /* inline the function call */
                AvatarStateMachineElement lastState = alfc.inlineFunctionCall(block, firstState);
                TraceManager.addDev("LAST STATE IS " + lastState + " class=" + lastState.getClass().getCanonicalName());

                /* Add the next elements to the newly created last state */
                for (AvatarStateMachineElement asme : curAsme.getNexts())
                    lastState.addNext(asme);

                /* Remove the call in the list of elements */
                elements.remove(curAsme);


                /* Use the translated function call's first element as current element */
                curAsme = firstState;


            }

            /* Add current element to the visited set */
            visited.add(curAsme);

            /* Loop through the next elements */
            int i = 0;
            if (curAsme.getNexts() != null) {
                for (AvatarStateMachineElement asme : curAsme.getNexts()) {
                    /* Check if it is a function call */
                    if (asme instanceof AvatarLibraryFunctionCall) {
                        AvatarStateMachineElement replaceBy = callsTranslated.get(asme);
                        /* Check if function call has already been translated */
                        if (replaceBy != null) {
                            /* replace by the translated function call */
                            curAsme.removeNext(i);
                            curAsme.addNext(replaceBy);

                            /* new next element has been added at the end of the list so we need to fix i */
                            i--;
                        } else {
                            /* mark the function call and the current state to be visited */
                            toVisit.add(asme);
                            toVisit.add(curAsme);
                            visited.remove(curAsme);
                        }
                    } else
                        toVisit.add(asme);

                    i++;
                }
            }
        }
    }

    /**
     * Removes all empty transitions between two states.
     * This concerns also the start state, and end states.
     * DO NOT take into account code of states, and start states
     *
     * @param block        The block containing the state machine
     * @param _canOptimize boolean data
     */
    public void removeEmptyTransitions(AvatarBlock block, boolean _canOptimize) {

        //TraceManager.addDev("Remove empty transitions with optimize=" + _canOptimize);

        // Look for such a transition
        // states -> tr -> state with tr is empty
        // a tr is empty when it has no action or guard
        AvatarStateElement foundState1 = null, foundState2 = null;
        AvatarTransition foundAt = null;


        for (AvatarStateMachineElement elt : elements) {
            if ((elt instanceof AvatarStateElement) && (!(elt instanceof AvatarStartState))) {
                if (elt.getNexts().size() == 1) {
                    if ((elt.getNext(0) instanceof AvatarTransition)) {
                        AvatarTransition at = (AvatarTransition) (elt.getNext(0));
                        if (at.getNext(0) instanceof AvatarStateElement) {
                            if (at.isEmpty() && at.hasNonDeterministicGuard()) {
                                if ((_canOptimize) && (!(elt.isCheckable()))) {
                                    //TraceManager.addDev("State found:" + elt);
                                    foundState1 = (AvatarStateElement) elt;
                                    foundAt = at;
                                    foundState2 = (AvatarStateElement) (at.getNext(0));
                                    break;
                                }

                            }
                        }
                    } else {
                        TraceManager.addDev("Malformed spec: " + elt.getName() + " is followed by " + elt.getName());

                    }
                }
            }
        }

        // Found?
        if (foundState1 != null) {
            if (foundState1 == foundState2) {
                // We simply remove the transition
                //TraceManager.addDev("Found same state -> removing the transitions");
                removeElement(foundAt);
                // removing from the next of foundState1
                foundState1.removeNext(foundAt);

            } else {
                // Must remove state1 and at, and link all previous of state 1 to state2
                //TraceManager.addDev("Found 2 states state1=" + foundState1.getName() + " state2=" + foundState2.getName());
                for (AvatarStateMachineElement elt : getPreviousElementsOf(foundState1)) {
                    elt.replaceAllNext(foundState1, foundState2);
                }
                removeElement(foundAt);
                removeElement(foundState1);
                foundState2.addReferenceObjectFrom(foundState1);

            }
            removeEmptyTransitions(block, _canOptimize);
        }
    }

    public void removeEmptyTransitionsOnSameState(AvatarBlock _block) {
        ArrayList<AvatarStateMachineElement> toBeRemoved = new ArrayList<>();

        for (AvatarStateMachineElement elt : elements) {
            if (elt instanceof AvatarTransition) {
                AvatarTransition at = (AvatarTransition)elt;
                if (at.isEmpty()) {
                    AvatarStateMachineElement previous = getPreviousElementOf(at);
                    AvatarStateMachineElement next = at.getNext(0);
                    if (( previous != null) && (next != null)) {
                        if ((previous == next) && (previous instanceof AvatarState)) {
                            toBeRemoved.add(at);
                        }
                    }
                }
            }
        }

        for(AvatarStateMachineElement elt : toBeRemoved) {
            AvatarStateMachineElement previous = getPreviousElementOf(elt);
            if (previous != null) {
                previous.removeNext(elt);
            }
            elements.remove(elt);
        }
    }

    // groups together transitions to avoid extra states
    public void groupUselessTransitions(AvatarBlock _block) {
        ArrayList<AvatarState> toBeRemoved = new ArrayList<>();
        AvatarTransition atBefore, atAfter;

        for (AvatarStateMachineElement elt : elements) {
            if (elt instanceof AvatarState) {
                if ((elt.nexts.size() == 1) && (getPreviousElementsOf(elt).size() == 1)) {
                    // We need to check that the follower is only a transition with an action
                    AvatarStateMachineElement follower = elt.nexts.get(0);
                    if ((follower instanceof AvatarTransition) && (follower.nexts.size() == 1)) {
                        if (follower.nexts.get(0) instanceof AvatarState) {

                            AvatarStateMachineElement previous = getPreviousElementsOf(elt).get(0);
                            if (previous instanceof AvatarTransition) {
                                toBeRemoved.add((AvatarState) elt);
                            }
                        }
                    }
                }
            }
        }
        for (AvatarState st : toBeRemoved) {
            atBefore = (AvatarTransition) (getPreviousElementsOf(st).get(0));
            atAfter = (AvatarTransition) (st.nexts.get(0));

            // Adding guard, delays and actions from after to before
            if (atAfter.getGuard().isGuarded()) {
                if (atBefore.getGuard().isGuarded()) {
                    atBefore.setGuard("[(" + atAfter.getGuard() + ") && (" + atBefore.getGuard() + ")]", "");
                } else {
                    atBefore.setGuard(atAfter.getGuard(), "");
                }
            }

            if (atAfter.hasDelay()) {
                if (atBefore.hasDelay()) {
                    atBefore.setDelays(atAfter.getMinDelay() + "+" + atBefore.getMinDelay(),
                            atAfter.getMaxDelay() + "+" + atBefore.getMaxDelay(), "", "");

                }
            }

            for (AvatarAction a : atAfter.getActions()) {
                atBefore.addAction(a, a.getOriginalAction());
            }


            // Updating links
            atBefore.nexts.clear();
            atBefore.nexts.add(atAfter.getNext(0));

            // Removing state and next transition
            elements.remove(atAfter);
            elements.remove(st);
        }

    }

    public int getIndexOfStartState() {
        if (allStates == null) {
            return -1;
        }

        for (int i = 0; i < allStates.length; i++) {
            if (allStates[i] instanceof AvatarStartState) {
                return i;
            }
        }

        return -1;
    }

    public int getIndexOfState(AvatarStateElement _ase) {
        if (allStates == null) {
            return -1;
        }

        for (int i = 0; i < allStates.length; i++) {
            if (allStates[i] == _ase) {
                return i;
            }
        }

        return -1;
    }


    // Fills the current state machine by cloning the current one

    public void advancedClone(AvatarStateMachine _newAsm, AvatarStateMachineOwner _newBlock) {
        // Elements
        HashMap<AvatarStateMachineElement, AvatarStateMachineElement> correspondenceMap = new HashMap<AvatarStateMachineElement, AvatarStateMachineElement>();
        for (AvatarStateMachineElement elt : elements) {
            AvatarStateMachineElement ae;
            ae = elt.basicCloneMe(_newBlock);

            /*if (ae instanceof AvatarState) {
                TraceManager.addDev("New state: ");
            }
            TraceManager.addDev("elt: " +  ae.toString());*/

            if (ae == null) {
                TraceManager.addDev("Null AE");
            }

            _newAsm.addElement(ae);

            if (ae instanceof AvatarStartState) {
                _newAsm.setStartState((AvatarStartState) ae);
            }
            correspondenceMap.put(elt, ae);
        }

        // Other attributes
        for (AvatarStateMachineElement elt : elements) {
            AvatarStateMachineElement ae = correspondenceMap.get(elt);
            if (ae != null) {
                elt.fillAdvancedValues(ae, correspondenceMap, this);
            } else {
                TraceManager.addDev("Null correspondance ae");
            }
        }
    }

    public AvatarTransition findEmptyTransition(final AvatarStateMachineElement elementSource,
                                                final AvatarStateMachineElement elementTarget) {
        for (final AvatarStateMachineElement element : elements) {
            if (element instanceof AvatarTransition) {
                final AvatarTransition transition = (AvatarTransition) element;

                if (transition.isEmpty() && !transition.getNexts().isEmpty()) {
                    if (getPreviousElementOf(transition) == elementSource && transition.getNexts().get(0) == elementTarget) {
                        return transition;
                    }
                }
            }
        }

        return null;
    }


    public void removeAllDelays() {
        for (final AvatarStateMachineElement element : elements) {
            if (element instanceof AvatarTransition) {
                final AvatarTransition transition = (AvatarTransition) element;
                if (transition.hasDelay()) {
                    transition.setDelays("0", "0", transition.getOriginalMinDelay(), transition.getOriginalMaxDelay());
                }
            } else if (element instanceof AvatarSetTimer) {
                ((AvatarSetTimer) element).setTimerValue("0");
            }
        }
    }

    /**
     * Looks for all numerical values over
     * the provided max
     *
     * @param maxV Maximum value.
     */
    public ArrayList<AvatarElement> elementsWithNumericalValueOver(int maxV) {
        String val;

        ArrayList<AvatarElement> invalids = new ArrayList<AvatarElement>();

        for (AvatarStateMachineElement asme : elements) {

            // Action on signals
            if (asme instanceof AvatarActionOnSignal) {
                AvatarActionOnSignal aaos = (AvatarActionOnSignal) asme;
                for (int i = 0; i < aaos.getNbOfValues(); i++) {
                    val = aaos.getValue(i);
                    if (MyMath.hasIntegerValueOverMax(val, maxV)) {
                        invalids.add(this);
                        break;
                    }
                }

            }

            if (asme instanceof AvatarRandom) {
                AvatarRandom arand = (AvatarRandom) asme;
                val = arand.getMinValue();
                if (MyMath.hasIntegerValueOverMax(val, maxV)) {
                    invalids.add(this);
                } else {
                    val = arand.getMaxValue();
                    if (MyMath.hasIntegerValueOverMax(val, maxV)) {
                        invalids.add(this);
                    }
                }
            }

            if (asme instanceof AvatarSetTimer) {
                AvatarSetTimer atop = (AvatarSetTimer) asme;
                val = atop.getTimerValue();
                if (MyMath.hasIntegerValueOverMax(val, maxV)) {
                    invalids.add(this);
                }
            }

            if (asme instanceof AvatarTransition) {
                AvatarTransition at = (AvatarTransition) asme;

                // Guard
                if (at.getGuard() != null) {
                    val = at.getGuard().toString();
                    if (MyMath.hasIntegerValueOverMax(val, maxV)) {
                        invalids.add(this);
                    }
                }

                // Delays
                val = at.getMinDelay();
                if (MyMath.hasIntegerValueOverMax(val, maxV)) {
                    invalids.add(this);
                }
                val = at.getMaxDelay();
                if (MyMath.hasIntegerValueOverMax(val, maxV)) {
                    invalids.add(this);
                }

                // Actions
                for (AvatarAction aa : at.getActions()) {
                    val = aa.toString();
                    if (MyMath.hasIntegerValueOverMax(val, maxV)) {
                        invalids.add(this);
                    }
                }

            }


        }

        return invalids;
    }


    public List<ArrayList<AvatarTransition>> checkStaticInternalLoops() {
        if (allStates == null) {
            return null;
        }

        List<ArrayList<AvatarTransition>> loops = new ArrayList<ArrayList<AvatarTransition>>();
        List<AvatarTransition> trace = new ArrayList<AvatarTransition>();
        Set<AvatarStateMachineElement> visited = new HashSet<AvatarStateMachineElement>();

        for (AvatarStateElement state : allStates) {
            checkStaticInternalLoopsRec(state, state, trace, visited, loops, 0);
            visited.add(state); //avoid cycles permutations
        }
        return loops;
    }


    public void checkStaticInternalLoopsRec(AvatarStateMachineElement node, AvatarStateMachineElement arrival, List<AvatarTransition> trace, Set<AvatarStateMachineElement> visited, List<ArrayList<AvatarTransition>> loops, int depth) {
        if (visited.contains(node)) {
            if (node == arrival) {
                //valid loop, copy trace to loops
                loops.add(new ArrayList<AvatarTransition>(trace));
                return;
            } else {
                //not valid loop
                return;
            }
        } else if (node.nexts == null) {
            return;
        }

        visited.add(node);
        for (AvatarStateMachineElement next : node.nexts) {
            if (next instanceof AvatarTransition) {
                AvatarTransition at = (AvatarTransition) next;
                //the state machine should alternate states and transitions
                if (!(at.getNext(0) instanceof AvatarActionOnSignal)) {
                    //Choose internal action paths
                    trace.add(depth, at);
                    checkStaticInternalLoopsRec(at.getNext(0), arrival, trace, visited, loops, depth + 1);
                    trace.remove(depth);
                }
            }
        }
        visited.remove(node);

        return;
    }

    // Returns the list of elements non reachabable from the start state
    public List<AvatarStateMachineElement> getUnusedElements() {
        if (startState == null) {
            return null;
        }

        List<AvatarStateMachineElement> elts = new LinkedList<>();

        HashSet<AvatarStateMachineElement> found = new HashSet<>();
        findAvatarElements(startState, found);

        for (AvatarStateMachineElement asme : elements) {
            if (!(found.contains(asme))) {
                elts.add(asme);
            }
        }

        return elts;
    }

    private void findAvatarElements(AvatarStateMachineElement first, HashSet<AvatarStateMachineElement> found) {
        if (found.contains(first)) {
            return;
        }

        found.add(first);

        if (first instanceof AvatarState) {
            // Find all internal start states of this state
            for (AvatarStateMachineElement internal : elements) {
                if (internal instanceof AvatarStartState) {
                    if (internal.getState() == first) {
                        findAvatarElements(internal, found);
                    }
                }
            }
        }


        for (AvatarStateMachineElement asme : first.getNexts()) {
            findAvatarElements(asme, found);


        }
    }

    public boolean removeDuplicatedTransitions() {

        ArrayList<AvatarStateMachineElement> toBeRemoved = new ArrayList<>();
        for (AvatarStateMachineElement elt : elements) {
            if (elt instanceof AvatarState) {
                // We look at the nexts
                // If transition -> state duplicated, or transition -> aaos duplicated -> state : we remove  the duplicate
                toBeRemoved.addAll(removeDuplicatedTransitionsFromState((AvatarState) elt));
            }
        }

        for (AvatarStateMachineElement asme : toBeRemoved) {
            elements.remove(asme);
        }

        for (AvatarStateMachineElement elt : elements) {
            if (elt instanceof AvatarState) {
                toBeRemoved.clear();
                for (AvatarStateMachineElement asmeState: elt.getNexts()) {
                    if (!(elements.contains(asmeState))) {
                        toBeRemoved.add(asmeState);
                    }
                }
                for (AvatarStateMachineElement asme : toBeRemoved) {
                    elt.getNexts().remove(asme);
                }
            }


        }


        return toBeRemoved.size() != 0;
    }

    public ArrayList<AvatarStateMachineElement> removeDuplicatedTransitionsFromState(AvatarState _st) {
        ArrayList<AvatarStateMachineElement> toBeRemoved = new ArrayList<>();

        // At least two exiting transitions
        if (_st.getNexts().size() < 2) {
            return toBeRemoved;
        }

        // We check is the at least two transitions are equivalent

        for (int i = 0; i < _st.getNexts().size(); i++) {
            AvatarTransition at1 = (AvatarTransition) (_st.getNexts().get(i));
            for (int j = i + 1; j < _st.getNexts().size(); j++) {
                AvatarTransition at2 = (AvatarTransition) (_st.getNexts().get(j));
                if (at1.equals(at2)) {
                    //TraceManager.addDev("\tTwo equal transitions in state " + _st.getName());
                    // We have to consider the next of at1 and at2
                    AvatarStateMachineElement next1, next2;
                    next1 = at1.getNext(0);
                    next2 = at2.getNext(0);
                    if (next1 instanceof AvatarState && next1 == next2) {
                        toBeRemoved.add(at2);
                    } else if ((next1 instanceof AvatarActionOnSignal) && (next2 instanceof AvatarActionOnSignal)) {
                        //TraceManager.addDev("\tChecking for equal AAOS " + _st.getName());
                        AvatarActionOnSignal aaos1 = (AvatarActionOnSignal) next1;
                        AvatarActionOnSignal aaos2 = (AvatarActionOnSignal) next2;
                        if (next1.getNexts().get(0) instanceof AvatarTransition && next2.getNexts().get(0) instanceof AvatarTransition) {
                            AvatarTransition at11 = (AvatarTransition) next1.getNexts().get(0);
                            AvatarTransition at21 = (AvatarTransition) next2.getNexts().get(0);
                            if (at11.equals(at21)) {
                                if (at11.getNexts().get(0) instanceof AvatarState && (at11.getNexts().get(0) == at21.getNexts().get(0))) {
                                    // We need to compare the two AvatarActionOnSignal
                                    //TraceManager.addDev("\tComparing aaos1 and aaos2 in " + _st.getName());
                                    if (aaos1.equals(aaos2)) {
                                        toBeRemoved.add(at2);
                                        toBeRemoved.add(aaos2);
                                        toBeRemoved.add(at21);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        TraceManager.addDev("\ttoBeRemoved of size " + toBeRemoved.size() + " for state " + _st.getName());


        return toBeRemoved;
    }

}