// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qstatemachine.h"
#include "qstate.h"
#include "qstate_p.h"
#include "qstatemachine_p.h"
#include "qabstracttransition.h"
#include "qabstracttransition_p.h"
#include "qsignaltransition.h"
#include "qsignaltransition_p.h"
#include "qsignaleventgenerator_p.h"
#include "qabstractstate.h"
#include "qabstractstate_p.h"
#include "qfinalstate.h"
#include "qhistorystate.h"
#include "qhistorystate_p.h"

#include "private/qcoreapplication_p.h"
#include "private/qobject_p.h"
#include "private/qthread_p.h"

#if QT_CONFIG(qeventtransition)
#include "qeventtransition.h"
#include "qeventtransition_p.h"
#endif

#if QT_CONFIG(animation)
#include "qpropertyanimation.h"
#include "qanimationgroup.h"
#include <private/qvariantanimation_p.h>
#endif

#include <QtCore/qmetaobject.h>
#include <qdebug.h>

#include <algorithm>

QT_BEGIN_NAMESPACE

enum {
    Offset0 = 0x00000000,
    Offset1 = 0x00008000,
    Offset2 = 0x00080000,
    Offset3 = 0x00800000,

    Size0 = Offset1 - Offset0,
    Size1 = Offset2 - Offset1,
    Size2 = Offset3 - Offset2,
    Size3 = QStateMachinePrivate::FreeListDefaultConstants::MaxIndex - Offset3
};

const int QStateMachinePrivate::FreeListDefaultConstants::Sizes[FreeListDefaultConstants::BlockCount] = {
    Size0,
    Size1,
    Size2,
    Size3
};

/*!
    \class QStateMachine
    \inmodule QtStateMachine
    \reentrant

    \brief The QStateMachine class provides a hierarchical finite state machine.

    \since 4.6
    \ingroup statemachine

    QStateMachine is based on the concepts and notation of
    \l{http://www.wisdom.weizmann.ac.il/~dharel/SCANNED.PAPERS/Statecharts.pdf}{Statecharts}.
    QStateMachine is part of \l{Qt State Machine Overview}{Qt State Machine Framework}.

    A state machine manages a set of states (classes that inherit from
    QAbstractState) and transitions (descendants of
    QAbstractTransition) between those states; these states and
    transitions define a state graph. Once a state graph has been
    built, the state machine can execute it. QStateMachine's
    execution algorithm is based on the \l{http://www.w3.org/TR/scxml/}{State Chart XML (SCXML)}
    algorithm. The framework's \l{Qt State Machine Overview}{overview} gives several state
    graphs and the code to build them.

    Use the addState() function to add a top-level state to the state machine.
    States are removed with the removeState() function. Removing states while
    the machine is running is discouraged.

    Before the machine can be started, the \l{initialState}{initial
    state} must be set. The initial state is the state that the
    machine enters when started. You can then start() the state
    machine. The started() signal is emitted when the initial state is
    entered.

    The machine is event driven and keeps its own event loop. Events
    are posted to the machine through postEvent(). Note that this
    means that it executes asynchronously, and that it will not
    progress without a running event loop. You will normally not have
    to post events to the machine directly as Qt's transitions, e.g.,
    QEventTransition and its subclasses, handle this. But for custom
    transitions triggered by events, postEvent() is useful.

    The state machine processes events and takes transitions until a
    top-level final state is entered; the state machine then emits the
    finished() signal. You can also stop() the state machine
    explicitly. The stopped() signal is emitted in this case.

    The following snippet shows a state machine that will finish when a button
    is clicked:

    \snippet code/src_corelib_statemachine_qstatemachine.cpp simple state machine

    This code example uses QState, which inherits QAbstractState. The
    QState class provides a state that you can use to set properties
    and invoke methods on \l{QObject}s when the state is entered or
    exited. It also contains convenience functions for adding
    transitions, e.g., \l{QSignalTransition}s as in this example. See
    the QState class description for further details.

    If an error is encountered, the machine will look for an
    \l{errorState}{error state}, and if one is available, it will
    enter this state. The types of errors possible are described by the
    \l{QStateMachine::}{Error} enum. After the error state is entered,
    the type of the error can be retrieved with error(). The execution
    of the state graph will not stop when the error state is entered. If
    no error state applies to the erroneous state, the machine will stop
    executing and an error message will be printed to the console.

    \note Important: setting the \l{ChildMode} of a state machine to parallel (\l{ParallelStates})
          results in an invalid state machine. It can only be set to (or kept as)
          \l{ExclusiveStates}.

    \sa QAbstractState, QAbstractTransition, QState, {Qt State Machine Overview}
*/

/*!
    \property QStateMachine::errorString

    \brief the error string of this state machine
*/

/*!
    \property QStateMachine::globalRestorePolicy

    \brief the restore policy for states of this state machine.

    The default value of this property is
    QState::DontRestoreProperties.
*/

/*!
    \property QStateMachine::running
    \since 5.4

    \brief the running state of this state machine

    \sa start(), stop(), started(), stopped(), runningChanged()
*/

#if QT_CONFIG(animation)
/*!
    \property QStateMachine::animated

    \brief whether animations are enabled

    The default value of this property is \c true.

    \b {See also} \l [CPP] QAbstractTransition::addAnimation()
*/
#endif

// #define QSTATEMACHINE_DEBUG
// #define QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG

struct CalculationCache {
    struct TransitionInfo {
        QList<QAbstractState*> effectiveTargetStates;
        QSet<QAbstractState*> exitSet;
        QAbstractState *transitionDomain;

        bool effectiveTargetStatesIsKnown: 1;
        bool exitSetIsKnown              : 1;
        bool transitionDomainIsKnown     : 1;

        TransitionInfo()
            : transitionDomain(nullptr)
            , effectiveTargetStatesIsKnown(false)
            , exitSetIsKnown(false)
            , transitionDomainIsKnown(false)
        {}
    };

    typedef QHash<QAbstractTransition *, TransitionInfo> TransitionInfoCache;
    TransitionInfoCache cache;

    bool effectiveTargetStates(QAbstractTransition *t, QList<QAbstractState *> *targets) const
    {
        Q_ASSERT(targets);

        TransitionInfoCache::const_iterator cacheIt = cache.find(t);
        if (cacheIt == cache.end() || !cacheIt->effectiveTargetStatesIsKnown)
            return false;

        *targets = cacheIt->effectiveTargetStates;
        return true;
    }

    void insert(QAbstractTransition *t, const QList<QAbstractState *> &targets)
    {
        TransitionInfoCache::iterator cacheIt = cache.find(t);
        TransitionInfo &ti = cacheIt == cache.end()
                ? *cache.insert(t, TransitionInfo())
                : *cacheIt;

        Q_ASSERT(!ti.effectiveTargetStatesIsKnown);
        ti.effectiveTargetStates = targets;
        ti.effectiveTargetStatesIsKnown = true;
    }

    bool exitSet(QAbstractTransition *t, QSet<QAbstractState *> *exits) const
    {
        Q_ASSERT(exits);

        TransitionInfoCache::const_iterator cacheIt = cache.find(t);
        if (cacheIt == cache.end() || !cacheIt->exitSetIsKnown)
            return false;

        *exits = cacheIt->exitSet;
        return true;
    }

    void insert(QAbstractTransition *t, const QSet<QAbstractState *> &exits)
    {
        TransitionInfoCache::iterator cacheIt = cache.find(t);
        TransitionInfo &ti = cacheIt == cache.end()
                ? *cache.insert(t, TransitionInfo())
                : *cacheIt;

        Q_ASSERT(!ti.exitSetIsKnown);
        ti.exitSet = exits;
        ti.exitSetIsKnown = true;
    }

    bool transitionDomain(QAbstractTransition *t, QAbstractState **domain) const
    {
        Q_ASSERT(domain);

        TransitionInfoCache::const_iterator cacheIt = cache.find(t);
        if (cacheIt == cache.end() || !cacheIt->transitionDomainIsKnown)
            return false;

        *domain = cacheIt->transitionDomain;
        return true;
    }

    void insert(QAbstractTransition *t, QAbstractState *domain)
    {
        TransitionInfoCache::iterator cacheIt = cache.find(t);
        TransitionInfo &ti = cacheIt == cache.end()
                ? *cache.insert(t, TransitionInfo())
                : *cacheIt;

        Q_ASSERT(!ti.transitionDomainIsKnown);
        ti.transitionDomain = domain;
        ti.transitionDomainIsKnown = true;
    }
};

/* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ :

function isDescendant(state1, state2)

Returns 'true' if state1 is a descendant of state2 (a child, or a child of a child, or a child of a
child of a child, etc.) Otherwise returns 'false'.
*/
static inline bool isDescendant(const QAbstractState *state1, const QAbstractState *state2)
{
    Q_ASSERT(state1 != nullptr);

    for (QAbstractState *it = state1->parentState(); it != nullptr; it = it->parentState()) {
        if (it == state2)
            return true;
    }

    return false;
}

static bool containsDecendantOf(const QSet<QAbstractState *> &states, const QAbstractState *node)
{
    for (QAbstractState *s : states)
        if (isDescendant(s, node))
            return true;

    return false;
}

static int descendantDepth(const QAbstractState *state, const QAbstractState *ancestor)
{
    int depth = 0;
    for (const QAbstractState *it = state; it != nullptr; it = it->parentState()) {
        if (it == ancestor)
            break;
        ++depth;
    }
    return depth;
}

/* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ :

function getProperAncestors(state1, state2)

If state2 is null, returns the set of all ancestors of state1 in ancestry order (state1's parent
followed by the parent's parent, etc. up to an including the <scxml> element). If state2 is
non-null, returns in ancestry order the set of all ancestors of state1, up to but not including
state2. (A "proper ancestor" of a state is its parent, or the parent's parent, or the parent's
parent's parent, etc.))If state2 is state1's parent, or equal to state1, or a descendant of state1,
this returns the empty set.
*/
static QList<QState *> getProperAncestors(const QAbstractState *state, const QAbstractState *upperBound)
{
    Q_ASSERT(state != nullptr);
    QList<QState *> result;
    result.reserve(16);
    for (QState *it = state->parentState(); it && it != upperBound; it = it->parentState()) {
        result.append(it);
    }
    return result;
}

/* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ :

function getEffectiveTargetStates(transition)

Returns the states that will be the target when 'transition' is taken, dereferencing any history states.

function getEffectiveTargetStates(transition)
    targets = new OrderedSet()
    for s in transition.target
        if isHistoryState(s):
            if historyValue[s.id]:
                targets.union(historyValue[s.id])
            else:
                targets.union(getEffectiveTargetStates(s.transition))
        else:
            targets.add(s)
    return targets
*/
static QList<QAbstractState *> getEffectiveTargetStates(QAbstractTransition *transition, CalculationCache *cache)
{
    Q_ASSERT(cache);

    QList<QAbstractState *> targetsList;
    if (cache->effectiveTargetStates(transition, &targetsList))
        return targetsList;

    QSet<QAbstractState *> targets;
    const auto targetStates = transition->targetStates();
    for (QAbstractState *s : targetStates) {
        if (QHistoryState *historyState = QStateMachinePrivate::toHistoryState(s)) {
            QList<QAbstractState*> historyConfiguration = QHistoryStatePrivate::get(historyState)->configuration;
            if (!historyConfiguration.isEmpty()) {
                // There is a saved history, so apply that.
                targets.unite(QSet<QAbstractState *>(historyConfiguration.constBegin(), historyConfiguration.constEnd()));
            } else if (QAbstractTransition *defaultTransition = historyState->defaultTransition()) {
                // No saved history, take all default transition targets.
                const auto &targetStates = defaultTransition->targetStates();
                targets.unite(QSet<QAbstractState *>(targetStates.constBegin(), targetStates.constEnd()));
            } else {
                // Woops, we found a history state without a default state. That's not valid!
                QStateMachinePrivate *m = QStateMachinePrivate::get(historyState->machine());
                m->setError(QStateMachine::NoDefaultStateInHistoryStateError, historyState);
            }
        } else {
            targets.insert(s);
        }
    }

    targetsList = targets.values();
    cache->insert(transition, targetsList);
    return targetsList;
}

QStateMachinePrivate::QStateMachinePrivate()
{
    isMachine = true;

    state = NotRunning;
    processing = false;
    processingScheduled = false;
    stop = false;
    stopProcessingReason = EventQueueEmpty;
    error = QStateMachine::NoError;
    signalEventGenerator = nullptr;
}

QStateMachinePrivate::~QStateMachinePrivate()
{
    qDeleteAll(internalEventQueue);
    qDeleteAll(externalEventQueue);

    for (QHash<int, DelayedEvent>::const_iterator it = delayedEvents.cbegin(), eit = delayedEvents.cend(); it != eit; ++it) {
        delete it.value().event;
    }
}

QState *QStateMachinePrivate::rootState() const
{
    return const_cast<QStateMachine*>(q_func());
}

static int indexOfDescendant(QState *s, QAbstractState *desc)
{
    QList<QAbstractState*> childStates = QStatePrivate::get(s)->childStates();
    for (int i = 0; i < childStates.size(); ++i) {
        QAbstractState *c = childStates.at(i);
        if ((c == desc) || isDescendant(desc, c)) {
            return i;
        }
    }
    return -1;
}

bool QStateMachinePrivate::transitionStateEntryLessThan(QAbstractTransition *t1, QAbstractTransition *t2)
{
    QState *s1 = t1->sourceState(), *s2 = t2->sourceState();
    if (s1 == s2) {
        QList<QAbstractTransition*> transitions = QStatePrivate::get(s1)->transitions();
        return transitions.indexOf(t1) < transitions.indexOf(t2);
    } else if (isDescendant(s1, s2)) {
        return true;
    } else if (isDescendant(s2, s1)) {
        return false;
    } else {
        Q_ASSERT(s1->machine() != nullptr);
        QStateMachinePrivate *mach = QStateMachinePrivate::get(s1->machine());
        QState *lca = mach->findLCA(QList<QAbstractState*>() << s1 << s2);
        Q_ASSERT(lca != nullptr);
        int s1Depth = descendantDepth(s1, lca);
        int s2Depth = descendantDepth(s2, lca);
        if (s1Depth == s2Depth)
            return (indexOfDescendant(lca, s1) < indexOfDescendant(lca, s2));
        else
            return s1Depth > s2Depth;
    }
}

bool QStateMachinePrivate::stateEntryLessThan(QAbstractState *s1, QAbstractState *s2)
{
    if (s1->parent() == s2->parent()) {
        return s1->parent()->children().indexOf(s1)
            < s2->parent()->children().indexOf(s2);
    } else if (isDescendant(s1, s2)) {
        return false;
    } else if (isDescendant(s2, s1)) {
        return true;
    } else {
        Q_ASSERT(s1->machine() != nullptr);
        QStateMachinePrivate *mach = QStateMachinePrivate::get(s1->machine());
        QState *lca = mach->findLCA(QList<QAbstractState*>() << s1 << s2);
        Q_ASSERT(lca != nullptr);
        return (indexOfDescendant(lca, s1) < indexOfDescendant(lca, s2));
    }
}

bool QStateMachinePrivate::stateExitLessThan(QAbstractState *s1, QAbstractState *s2)
{
    if (s1->parent() == s2->parent()) {
        return s2->parent()->children().indexOf(s2)
            < s1->parent()->children().indexOf(s1);
    } else if (isDescendant(s1, s2)) {
        return true;
    } else if (isDescendant(s2, s1)) {
        return false;
    } else {
        Q_ASSERT(s1->machine() != nullptr);
        QStateMachinePrivate *mach = QStateMachinePrivate::get(s1->machine());
        QState *lca = mach->findLCA(QList<QAbstractState*>() << s1 << s2);
        Q_ASSERT(lca != nullptr);
        return (indexOfDescendant(lca, s2) < indexOfDescendant(lca, s1));
    }
}

QState *QStateMachinePrivate::findLCA(const QList<QAbstractState*> &states, bool onlyCompound)
{
    if (states.isEmpty())
        return nullptr;
    QList<QState *> ancestors = getProperAncestors(states.at(0), rootState()->parentState());
    for (int i = 0; i < ancestors.size(); ++i) {
        QState *anc = ancestors.at(i);
        if (onlyCompound && !isCompound(anc))
            continue;

        bool ok = true;
        for (int j = states.size() - 1; (j > 0) && ok; --j) {
            const QAbstractState *s = states.at(j);
            if (!isDescendant(s, anc))
                ok = false;
        }
        if (ok)
            return anc;
    }

    // Oops, this should never happen! The state machine itself is a common ancestor of all states,
    // no matter what. But, for the onlyCompound case: we probably have a state machine whose
    // childMode is set to parallel, which is illegal. However, we're stuck with it (and with
    // exposing this invalid/dangerous API to users), so recover in the least horrible way.
    setError(QStateMachine::StateMachineChildModeSetToParallelError, q_func());
    return q_func(); // make the statemachine the LCA/LCCA (which it should have been anyway)
}

QState *QStateMachinePrivate::findLCCA(const QList<QAbstractState*> &states)
{
    return findLCA(states, true);
}

QList<QAbstractTransition*> QStateMachinePrivate::selectTransitions(QEvent *event, CalculationCache *cache)
{
    Q_ASSERT(cache);
    Q_Q(const QStateMachine);

    QVarLengthArray<QAbstractState *> configuration_sorted;
    for (QAbstractState *s : std::as_const(configuration)) {
        if (isAtomic(s))
            configuration_sorted.append(s);
    }
    std::sort(configuration_sorted.begin(), configuration_sorted.end(), stateEntryLessThan);

    QList<QAbstractTransition*> enabledTransitions;
    const_cast<QStateMachine *>(q)->beginSelectTransitions(event);
    for (QAbstractState *state : std::as_const(configuration_sorted)) {
        QList<QState *> lst = getProperAncestors(state, nullptr);
        if (QState *grp = toStandardState(state))
            lst.prepend(grp);
        bool found = false;
        for (int j = 0; (j < lst.size()) && !found; ++j) {
            QState *s = lst.at(j);
            QList<QAbstractTransition*> transitions = QStatePrivate::get(s)->transitions();
            for (int k = 0; k < transitions.size(); ++k) {
                QAbstractTransition *t = transitions.at(k);
                if (QAbstractTransitionPrivate::get(t)->callEventTest(event)) {
#ifdef QSTATEMACHINE_DEBUG
                    qDebug() << q << ": selecting transition" << t;
#endif
                    enabledTransitions.append(t);
                    found = true;
                    break;
                }
            }
        }
    }

    if (!enabledTransitions.isEmpty()) {
        removeConflictingTransitions(enabledTransitions, cache);
#ifdef QSTATEMACHINE_DEBUG
        qDebug() << q << ": enabled transitions after removing conflicts:" << enabledTransitions;
#endif
    }
    const_cast<QStateMachine*>(q)->endSelectTransitions(event);
    return enabledTransitions;
}

/* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ :

function removeConflictingTransitions(enabledTransitions):
    filteredTransitions = new OrderedSet()
 // toList sorts the transitions in the order of the states that selected them
    for t1 in enabledTransitions.toList():
        t1Preempted = false;
        transitionsToRemove = new OrderedSet()
        for t2 in filteredTransitions.toList():
            if computeExitSet([t1]).hasIntersection(computeExitSet([t2])):
                if isDescendant(t1.source, t2.source):
                    transitionsToRemove.add(t2)
                else:
                    t1Preempted = true
                    break
        if not t1Preempted:
            for t3 in transitionsToRemove.toList():
                filteredTransitions.delete(t3)
            filteredTransitions.add(t1)

    return filteredTransitions

Note: the implementation below does not build the transitionsToRemove, but removes them in-place.
*/
void QStateMachinePrivate::removeConflictingTransitions(QList<QAbstractTransition*> &enabledTransitions, CalculationCache *cache)
{
    Q_ASSERT(cache);

    if (enabledTransitions.size() < 2)
        return; // There is no transition to conflict with.

    QList<QAbstractTransition*> filteredTransitions;
    filteredTransitions.reserve(enabledTransitions.size());
    std::sort(enabledTransitions.begin(), enabledTransitions.end(), transitionStateEntryLessThan);

    for (QAbstractTransition *t1 : std::as_const(enabledTransitions)) {
        bool t1Preempted = false;
        const QSet<QAbstractState*> exitSetT1 = computeExitSet_Unordered(t1, cache);
        QList<QAbstractTransition*>::iterator t2It = filteredTransitions.begin();
        while (t2It != filteredTransitions.end()) {
            QAbstractTransition *t2 = *t2It;
            if (t1 == t2) {
                // Special case: someone added the same transition object to a state twice. In this
                // case, t2 (which is already in the list) "preempts" t1.
                t1Preempted = true;
                break;
            }

            QSet<QAbstractState*> exitSetT2 = computeExitSet_Unordered(t2, cache);
            if (!exitSetT1.intersects(exitSetT2)) {
                // No conflict, no cry. Next patient please.
                ++t2It;
            } else {
                // Houston, we have a conflict. Check which transition can be removed.
                if (isDescendant(t1->sourceState(), t2->sourceState())) {
                    // t1 preempts t2, so we can remove t2
                    t2It = filteredTransitions.erase(t2It);
                } else {
                    // t2 preempts t1, so there's no use in looking further and we don't need to add
                    // t1 to the list.
                    t1Preempted = true;
                    break;
                }
            }
        }
        if (!t1Preempted)
            filteredTransitions.append(t1);
    }

    enabledTransitions = filteredTransitions;
}

void QStateMachinePrivate::microstep(QEvent *event, const QList<QAbstractTransition*> &enabledTransitions,
                                     CalculationCache *cache)
{
    Q_ASSERT(cache);

#ifdef QSTATEMACHINE_DEBUG
    qDebug() << q_func() << ": begin microstep( enabledTransitions:" << enabledTransitions << ')';
    qDebug() << q_func() << ": configuration before exiting states:" << configuration;
#endif
    QList<QAbstractState*> exitedStates = computeExitSet(enabledTransitions, cache);
    QHash<RestorableId, QVariant> pendingRestorables = computePendingRestorables(exitedStates);

    QSet<QAbstractState*> statesForDefaultEntry;
    QList<QAbstractState*> enteredStates = computeEntrySet(enabledTransitions, statesForDefaultEntry, cache);

#ifdef QSTATEMACHINE_DEBUG
    qDebug() << q_func() << ": computed exit set:" << exitedStates;
    qDebug() << q_func() << ": computed entry set:" << enteredStates;
#endif

    QHash<QAbstractState *, QList<QPropertyAssignment>> assignmentsForEnteredStates =
            computePropertyAssignments(enteredStates, pendingRestorables);
    if (!pendingRestorables.isEmpty()) {
        // Add "implicit" assignments for restored properties to the first
        // (outermost) entered state
        Q_ASSERT(!enteredStates.isEmpty());
        QAbstractState *s = enteredStates.constFirst();
        assignmentsForEnteredStates[s] << restorablesToPropertyList(pendingRestorables);
    }

    exitStates(event, exitedStates, assignmentsForEnteredStates);
#ifdef QSTATEMACHINE_DEBUG
    qDebug() << q_func() << ": configuration after exiting states:" << configuration;
#endif

    executeTransitionContent(event, enabledTransitions);

#if QT_CONFIG(animation)
    QList<QAbstractAnimation *> selectedAnimations = selectAnimations(enabledTransitions);
#endif

    enterStates(event, exitedStates, enteredStates, statesForDefaultEntry, assignmentsForEnteredStates
#if QT_CONFIG(animation)
                , selectedAnimations
#endif
                );
#ifdef QSTATEMACHINE_DEBUG
    qDebug() << q_func() << ": configuration after entering states:" << configuration;
    qDebug() << q_func() << ": end microstep";
#endif
}

/* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ :

procedure computeExitSet(enabledTransitions)

For each transition t in enabledTransitions, if t is targetless then do nothing, else compute the
transition's domain. (This will be the source state in the case of internal transitions) or the
least common compound ancestor state of the source state and target states of t (in the case of
external transitions. Add to the statesToExit set all states in the configuration that are
descendants of the domain.

function computeExitSet(transitions)
   statesToExit = new OrderedSet
     for t in transitions:
       if (t.target):
          domain = getTransitionDomain(t)
          for s in configuration:
             if isDescendant(s,domain):
               statesToExit.add(s)
   return statesToExit
*/
QList<QAbstractState*> QStateMachinePrivate::computeExitSet(const QList<QAbstractTransition*> &enabledTransitions,
                                                            CalculationCache *cache)
{
    Q_ASSERT(cache);

    QList<QAbstractState*> statesToExit_sorted = computeExitSet_Unordered(enabledTransitions, cache).values();
    std::sort(statesToExit_sorted.begin(), statesToExit_sorted.end(), stateExitLessThan);
    return statesToExit_sorted;
}

QSet<QAbstractState*> QStateMachinePrivate::computeExitSet_Unordered(const QList<QAbstractTransition*> &enabledTransitions,
                                                                     CalculationCache *cache)
{
    Q_ASSERT(cache);

    QSet<QAbstractState*> statesToExit;
    for (QAbstractTransition *t : enabledTransitions)
        statesToExit.unite(computeExitSet_Unordered(t, cache));
    return statesToExit;
}

QSet<QAbstractState*> QStateMachinePrivate::computeExitSet_Unordered(QAbstractTransition *t,
                                                                     CalculationCache *cache)
{
    Q_ASSERT(cache);

    QSet<QAbstractState*> statesToExit;
    if (cache->exitSet(t, &statesToExit))
        return statesToExit;

    QList<QAbstractState *> effectiveTargetStates = getEffectiveTargetStates(t, cache);
    QAbstractState *domain = getTransitionDomain(t, effectiveTargetStates, cache);
    if (domain == nullptr && !t->targetStates().isEmpty()) {
        // So we didn't find the least common ancestor for the source and target states of the
        // transition. If there were not target states, that would be fine: then the transition
        // will fire any events or signals, but not exit the state.
        //
        // However, there are target states, so it's either a node without a parent (or parent's
        // parent, etc), or the state belongs to a different state machine. Either way, this
        // makes the state machine invalid.
        if (error == QStateMachine::NoError)
            setError(QStateMachine::NoCommonAncestorForTransitionError, t->sourceState());
        QList<QAbstractState *> lst = pendingErrorStates.values();
        lst.prepend(t->sourceState());

        domain = findLCCA(lst);
        Q_ASSERT(domain != nullptr);
    }

    for (QAbstractState* s : std::as_const(configuration)) {
        if (isDescendant(s, domain))
            statesToExit.insert(s);
    }

    cache->insert(t, statesToExit);
    return statesToExit;
}

void QStateMachinePrivate::exitStates(QEvent *event, const QList<QAbstractState *> &statesToExit_sorted,
                                      const QHash<QAbstractState *, QList<QPropertyAssignment>> &assignmentsForEnteredStates)
{
    for (int i = 0; i < statesToExit_sorted.size(); ++i) {
        QAbstractState *s = statesToExit_sorted.at(i);
        if (QState *grp = toStandardState(s)) {
            QList<QHistoryState*> hlst = QStatePrivate::get(grp)->historyStates();
            for (int j = 0; j < hlst.size(); ++j) {
                QHistoryState *h = hlst.at(j);
                QHistoryStatePrivate::get(h)->configuration.clear();
                QSet<QAbstractState*>::const_iterator it;
                for (it = configuration.constBegin(); it != configuration.constEnd(); ++it) {
                    QAbstractState *s0 = *it;
                    if (QHistoryStatePrivate::get(h)->historyType == QHistoryState::DeepHistory) {
                        if (isAtomic(s0) && isDescendant(s0, s))
                            QHistoryStatePrivate::get(h)->configuration.append(s0);
                    } else if (s0->parentState() == s) {
                        QHistoryStatePrivate::get(h)->configuration.append(s0);
                    }
                }
#ifdef QSTATEMACHINE_DEBUG
                qDebug() << q_func() << ": recorded" << ((QHistoryStatePrivate::get(h)->historyType == QHistoryState::DeepHistory) ? "deep" : "shallow")
                         << "history for" << s << "in" << h << ':' << QHistoryStatePrivate::get(h)->configuration;
#endif
            }
        }
    }
    for (int i = 0; i < statesToExit_sorted.size(); ++i) {
        QAbstractState *s = statesToExit_sorted.at(i);
#ifdef QSTATEMACHINE_DEBUG
        qDebug() << q_func() << ": exiting" << s;
#endif
        QAbstractStatePrivate::get(s)->callOnExit(event);

#if QT_CONFIG(animation)
        terminateActiveAnimations(s, assignmentsForEnteredStates);
#else
        Q_UNUSED(assignmentsForEnteredStates);
#endif

        configuration.remove(s);
        QAbstractStatePrivate::get(s)->emitExited();
    }
}

void QStateMachinePrivate::executeTransitionContent(QEvent *event, const QList<QAbstractTransition*> &enabledTransitions)
{
    for (int i = 0; i < enabledTransitions.size(); ++i) {
        QAbstractTransition *t = enabledTransitions.at(i);
#ifdef QSTATEMACHINE_DEBUG
        qDebug() << q_func() << ": triggering" << t;
#endif
        QAbstractTransitionPrivate::get(t)->callOnTransition(event);
        QAbstractTransitionPrivate::get(t)->emitTriggered();
    }
}

QList<QAbstractState*> QStateMachinePrivate::computeEntrySet(const QList<QAbstractTransition *> &enabledTransitions,
                                                             QSet<QAbstractState *> &statesForDefaultEntry,
                                                             CalculationCache *cache)
{
    Q_ASSERT(cache);

    QSet<QAbstractState*> statesToEnter;
    if (pendingErrorStates.isEmpty()) {
        for (QAbstractTransition *t : enabledTransitions) {
            const auto targetStates = t->targetStates();
            for (QAbstractState *s : targetStates)
                addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry);

            const QList<QAbstractState *> effectiveTargetStates = getEffectiveTargetStates(t, cache);
            QAbstractState *ancestor = getTransitionDomain(t, effectiveTargetStates, cache);
            for (QAbstractState *s : effectiveTargetStates)
                addAncestorStatesToEnter(s, ancestor, statesToEnter, statesForDefaultEntry);
        }
    }

    // Did an error occur while selecting transitions? Then we enter the error state.
    if (!pendingErrorStates.isEmpty()) {
        statesToEnter.clear();
        statesToEnter = pendingErrorStates;
        statesForDefaultEntry = pendingErrorStatesForDefaultEntry;
        pendingErrorStates.clear();
        pendingErrorStatesForDefaultEntry.clear();
    }

    QList<QAbstractState*> statesToEnter_sorted = statesToEnter.values();
    std::sort(statesToEnter_sorted.begin(), statesToEnter_sorted.end(), stateEntryLessThan);
    return statesToEnter_sorted;
}

/* The algorithm as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ :

function getTransitionDomain(transition)

Return the compound state such that 1) all states that are exited or entered as a result of taking
'transition' are descendants of it 2) no descendant of it has this property.

function getTransitionDomain(t)
  tstates = getEffectiveTargetStates(t)
  if not tstates:
      return null
  elif t.type == "internal" and isCompoundState(t.source) and tstates.every(lambda s: isDescendant(s,t.source)):
      return t.source
  else:
      return findLCCA([t.source].append(tstates))
*/
QAbstractState *QStateMachinePrivate::getTransitionDomain(QAbstractTransition *t,
                                                          const QList<QAbstractState *> &effectiveTargetStates,
                                                          CalculationCache *cache)
{
    Q_ASSERT(cache);

    if (effectiveTargetStates.isEmpty())
        return nullptr;

    QAbstractState *domain = nullptr;
    if (cache->transitionDomain(t, &domain))
        return domain;

    if (t->transitionType() == QAbstractTransition::InternalTransition) {
        if (QState *tSource = t->sourceState()) {
            if (isCompound(tSource)) {
                bool allDescendants = true;
                for (QAbstractState *s : effectiveTargetStates) {
                    if (!isDescendant(s, tSource)) {
                        allDescendants = false;
                        break;
                    }
                }

                if (allDescendants)
                    return tSource;
            }
        }
    }

    QList<QAbstractState *> states(effectiveTargetStates);
    if (QAbstractState *src = t->sourceState())
        states.prepend(src);
    domain = findLCCA(states);
    cache->insert(t, domain);
    return domain;
}

void QStateMachinePrivate::enterStates(QEvent *event, const QList<QAbstractState *> &exitedStates_sorted,
                                       const QList<QAbstractState *> &statesToEnter_sorted,
                                       const QSet<QAbstractState *> &statesForDefaultEntry,
                                       QHash<QAbstractState *, QList<QPropertyAssignment>> &propertyAssignmentsForState
#if QT_CONFIG(animation)
                                       , const QList<QAbstractAnimation *> &selectedAnimations
#endif
                                       )
{
#ifdef QSTATEMACHINE_DEBUG
    Q_Q(QStateMachine);
#endif
    for (int i = 0; i < statesToEnter_sorted.size(); ++i) {
        QAbstractState *s = statesToEnter_sorted.at(i);
#ifdef QSTATEMACHINE_DEBUG
        qDebug() << q << ": entering" << s;
#endif
        configuration.insert(s);
        registerTransitions(s);

#if QT_CONFIG(animation)
        initializeAnimations(s, selectedAnimations, exitedStates_sorted, propertyAssignmentsForState);
#endif

        // Immediately set the properties that are not animated.
        {
            const auto assignments = propertyAssignmentsForState.value(s);
            for (const auto &assn : assignments) {
                if (globalRestorePolicy == QState::RestoreProperties) {
                    if (assn.explicitlySet) {
                        if (!hasRestorable(s, assn.object, assn.propertyName)) {
                            QVariant value = savedValueForRestorable(exitedStates_sorted, assn.object, assn.propertyName);
                            unregisterRestorables(exitedStates_sorted, assn.object, assn.propertyName);
                            registerRestorable(s, assn.object, assn.propertyName, value);
                        }
                    } else {
                        // The property is being restored, hence no need to
                        // save the current value. Discard any saved values in
                        // exited states, since those are now stale.
                        unregisterRestorables(exitedStates_sorted, assn.object, assn.propertyName);
                    }
                }
                assn.write();
            }
        }

        QAbstractStatePrivate::get(s)->callOnEntry(event);
        QAbstractStatePrivate::get(s)->emitEntered();

        // FIXME:
        // See the "initial transitions" comment in addDescendantStatesToEnter first, then implement:
//        if (statesForDefaultEntry.contains(s)) {
//            // ### executeContent(s.initial.transition.children())
//        }
        Q_UNUSED(statesForDefaultEntry);

        if (QHistoryState *h = toHistoryState(s))
            QAbstractTransitionPrivate::get(h->defaultTransition())->callOnTransition(event);

        // Emit propertiesAssigned signal if the state has no animated properties.
        {
            QState *ss = toStandardState(s);
            if (ss
    #if QT_CONFIG(animation)
                && !animationsForState.contains(s)
    #endif
                )
                QStatePrivate::get(ss)->emitPropertiesAssigned();
        }

        if (isFinal(s)) {
            QState *parent = s->parentState();
            if (parent) {
                if (parent != rootState()) {
                    QFinalState *finalState = qobject_cast<QFinalState *>(s);
                    Q_ASSERT(finalState);
                    emitStateFinished(parent, finalState);
                }
                QState *grandparent = parent->parentState();
                if (grandparent && isParallel(grandparent)) {
                    bool allChildStatesFinal = true;
                    QList<QAbstractState*> childStates = QStatePrivate::get(grandparent)->childStates();
                    for (int j = 0; j < childStates.size(); ++j) {
                        QAbstractState *cs = childStates.at(j);
                        if (!isInFinalState(cs)) {
                            allChildStatesFinal = false;
                            break;
                        }
                    }
                    if (allChildStatesFinal && (grandparent != rootState())) {
                        QFinalState *finalState = qobject_cast<QFinalState *>(s);
                        Q_ASSERT(finalState);
                        emitStateFinished(grandparent, finalState);
                    }
                }
            }
        }
    }
    {
        QSet<QAbstractState*>::const_iterator it;
        for (it = configuration.constBegin(); it != configuration.constEnd(); ++it) {
            if (isFinal(*it)) {
                QState *parent = (*it)->parentState();
                if (((parent == rootState())
                     && (rootState()->childMode() == QState::ExclusiveStates))
                    || ((parent->parentState() == rootState())
                        && (rootState()->childMode() == QState::ParallelStates)
                        && isInFinalState(rootState()))) {
                    processing = false;
                    stopProcessingReason = Finished;
                    break;
                }
            }
        }
    }
//    qDebug() << "configuration:" << configuration.toList();
}

/* The algorithm as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ has a bug. See
 * QTBUG-44963 for details. The algorithm here is as described in
 * http://www.w3.org/Voice/2013/scxml-irp/SCXML.htm as of Friday March 13, 2015.

procedure addDescendantStatesToEnter(state,statesToEnter,statesForDefaultEntry, defaultHistoryContent):
    if isHistoryState(state):
        if historyValue[state.id]:
            for s in historyValue[state.id]:
                addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent)
            for s in historyValue[state.id]:
                addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry, defaultHistoryContent)
        else:
            defaultHistoryContent[state.parent.id] = state.transition.content
            for s in state.transition.target:
                addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent)
            for s in state.transition.target:
                addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry, defaultHistoryContent)
    else:
        statesToEnter.add(state)
        if isCompoundState(state):
            statesForDefaultEntry.add(state)
            for s in state.initial.transition.target:
                addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent)
            for s in state.initial.transition.target:
                addAncestorStatesToEnter(s, state, statesToEnter, statesForDefaultEntry, defaultHistoryContent)
        else:
            if isParallelState(state):
                for child in getChildStates(state):
                    if not statesToEnter.some(lambda s: isDescendant(s,child)):
                        addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry, defaultHistoryContent)
*/
void QStateMachinePrivate::addDescendantStatesToEnter(QAbstractState *state,
                                                      QSet<QAbstractState*> &statesToEnter,
                                                      QSet<QAbstractState*> &statesForDefaultEntry)
{
    if (QHistoryState *h = toHistoryState(state)) {
        const QList<QAbstractState*> historyConfiguration = QHistoryStatePrivate::get(h)->configuration;
        if (!historyConfiguration.isEmpty()) {
            for (QAbstractState *s : historyConfiguration)
                addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry);
            for (QAbstractState *s : historyConfiguration)
                addAncestorStatesToEnter(s, state->parentState(), statesToEnter, statesForDefaultEntry);

#ifdef QSTATEMACHINE_DEBUG
            qDebug() << q_func() << ": restoring"
                     << ((QHistoryStatePrivate::get(h)->historyType == QHistoryState::DeepHistory) ? "deep" : "shallow")
                     << "history from" << state << ':' << historyConfiguration;
#endif
        } else {
            QList<QAbstractState*> defaultHistoryContent;
            if (QAbstractTransition *t = QHistoryStatePrivate::get(h)->defaultTransition)
                defaultHistoryContent = t->targetStates();

            if (defaultHistoryContent.isEmpty()) {
                setError(QStateMachine::NoDefaultStateInHistoryStateError, h);
            } else {
                for (QAbstractState *s : std::as_const(defaultHistoryContent))
                    addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry);
                for (QAbstractState *s : std::as_const(defaultHistoryContent))
                    addAncestorStatesToEnter(s, state->parentState(), statesToEnter, statesForDefaultEntry);
#ifdef QSTATEMACHINE_DEBUG
                qDebug() << q_func() << ": initial history targets for" << state << ':' << defaultHistoryContent;
#endif
           }
        }
    } else {
        if (state == rootState()) {
            // Error has already been set by exitStates().
            Q_ASSERT(error != QStateMachine::NoError);
            return;
        }
        statesToEnter.insert(state);
        if (isCompound(state)) {
            statesForDefaultEntry.insert(state);
            if (QAbstractState *initial = toStandardState(state)->initialState()) {
                Q_ASSERT(initial->machine() == q_func());

                // FIXME:
                // Qt does not support initial transitions (which is a problem for parallel states).
                // The way it simulates this for other states, is by having a single initial state.
                // See also the FIXME in enterStates.
                statesForDefaultEntry.insert(initial);

                addDescendantStatesToEnter(initial, statesToEnter, statesForDefaultEntry);
                addAncestorStatesToEnter(initial, state, statesToEnter, statesForDefaultEntry);
            } else {
                setError(QStateMachine::NoInitialStateError, state);
                return;
            }
        } else if (isParallel(state)) {
            QState *grp = toStandardState(state);
            const auto childStates = QStatePrivate::get(grp)->childStates();
            for (QAbstractState *child : childStates) {
                if (!containsDecendantOf(statesToEnter, child))
                    addDescendantStatesToEnter(child, statesToEnter, statesForDefaultEntry);
            }
        }
    }
}


/* The algorithm as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ :

procedure addAncestorStatesToEnter(state, ancestor, statesToEnter, statesForDefaultEntry, defaultHistoryContent)
   for anc in getProperAncestors(state,ancestor):
       statesToEnter.add(anc)
       if isParallelState(anc):
           for child in getChildStates(anc):
               if not statesToEnter.some(lambda s: isDescendant(s,child)):
                  addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry, defaultHistoryContent)
*/
void QStateMachinePrivate::addAncestorStatesToEnter(QAbstractState *s, QAbstractState *ancestor,
                                                    QSet<QAbstractState*> &statesToEnter,
                                                    QSet<QAbstractState*> &statesForDefaultEntry)
{
    const auto properAncestors = getProperAncestors(s, ancestor);
    for (QState *anc : properAncestors) {
        if (!anc->parentState())
            continue;
        statesToEnter.insert(anc);
        if (isParallel(anc)) {
            const auto childStates = QStatePrivate::get(anc)->childStates();
            for (QAbstractState *child : childStates) {
                if (!containsDecendantOf(statesToEnter, child))
                    addDescendantStatesToEnter(child, statesToEnter, statesForDefaultEntry);
            }
        }
    }
}

bool QStateMachinePrivate::isFinal(const QAbstractState *s)
{
    return s && (QAbstractStatePrivate::get(s)->stateType == QAbstractStatePrivate::FinalState);
}

bool QStateMachinePrivate::isParallel(const QAbstractState *s)
{
    const QState *ss = toStandardState(s);
    return ss && (QStatePrivate::get(ss)->childMode == QState::ParallelStates);
}

bool QStateMachinePrivate::isCompound(const QAbstractState *s) const
{
    const QState *group = toStandardState(s);
    if (!group)
        return false;
    bool isMachine = QStatePrivate::get(group)->isMachine;
    // Don't treat the machine as compound if it's a sub-state of this machine
    if (isMachine && (group != rootState()))
        return false;
    return (!isParallel(group) && !QStatePrivate::get(group)->childStates().isEmpty());
}

bool QStateMachinePrivate::isAtomic(const QAbstractState *s) const
{
    const QState *ss = toStandardState(s);
    return (ss && QStatePrivate::get(ss)->childStates().isEmpty())
        || isFinal(s)
        // Treat the machine as atomic if it's a sub-state of this machine
        || (ss && QStatePrivate::get(ss)->isMachine && (ss != rootState()));
}

QState *QStateMachinePrivate::toStandardState(QAbstractState *state)
{
    if (state && (QAbstractStatePrivate::get(state)->stateType == QAbstractStatePrivate::StandardState))
        return static_cast<QState*>(state);
    return nullptr;
}

const QState *QStateMachinePrivate::toStandardState(const QAbstractState *state)
{
    if (state && (QAbstractStatePrivate::get(state)->stateType == QAbstractStatePrivate::StandardState))
        return static_cast<const QState*>(state);
    return nullptr;
}

QFinalState *QStateMachinePrivate::toFinalState(QAbstractState *state)
{
    if (state && (QAbstractStatePrivate::get(state)->stateType == QAbstractStatePrivate::FinalState))
        return static_cast<QFinalState*>(state);
    return nullptr;
}

QHistoryState *QStateMachinePrivate::toHistoryState(QAbstractState *state)
{
    if (state && (QAbstractStatePrivate::get(state)->stateType == QAbstractStatePrivate::HistoryState))
        return static_cast<QHistoryState*>(state);
    return nullptr;
}

bool QStateMachinePrivate::isInFinalState(QAbstractState* s) const
{
    if (isCompound(s)) {
        QState *grp = toStandardState(s);
        QList<QAbstractState*> lst = QStatePrivate::get(grp)->childStates();
        for (int i = 0; i < lst.size(); ++i) {
            QAbstractState *cs = lst.at(i);
            if (isFinal(cs) && configuration.contains(cs))
                return true;
        }
        return false;
    } else if (isParallel(s)) {
        QState *grp = toStandardState(s);
        QList<QAbstractState*> lst = QStatePrivate::get(grp)->childStates();
        for (int i = 0; i < lst.size(); ++i) {
            QAbstractState *cs = lst.at(i);
            if (!isInFinalState(cs))
                return false;
        }
        return true;
    } else {
        // we don't treat the machine as compound if it's a sub-state of this machine
        // see isCompound() implementation
        // but that machine still can be in a final state (Finished)
        QState *grp = toStandardState(s);
        if (QStatePrivate::get(grp)->isMachine && (grp != rootState())) {
            QStateMachine *stateMachine = static_cast<QStateMachine*>(grp);
            if (stateMachine->d_func()->stopProcessingReason == Finished) {
                return true;
            }
        }
        return false;
    }
}

#ifndef QT_NO_PROPERTIES

/*!
  \internal
  Returns \c true if the given state has saved the value of the given property,
  otherwise returns \c false.
*/
bool QStateMachinePrivate::hasRestorable(QAbstractState *state, QObject *object,
                                         const QByteArray &propertyName) const
{
    RestorableId id(object, propertyName);
    return registeredRestorablesForState.value(state).contains(id);
}

/*!
  \internal
  Returns the value to save for the property identified by \a id.
  If an exited state (member of \a exitedStates_sorted) has saved a value for
  the property, the saved value from the last (outermost) state that will be
  exited is returned (in practice carrying the saved value on to the next
  state). Otherwise, the current value of the property is returned.
*/
QVariant QStateMachinePrivate::savedValueForRestorable(const QList<QAbstractState*> &exitedStates_sorted,
                                                       QObject *object, const QByteArray &propertyName) const
{
#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
    qDebug() << q_func() << ": savedValueForRestorable(" << exitedStates_sorted << object << propertyName << ')';
#endif
    for (int i = exitedStates_sorted.size() - 1; i >= 0; --i) {
        QAbstractState *s = exitedStates_sorted.at(i);
        QHash<RestorableId, QVariant> restorables = registeredRestorablesForState.value(s);
        QHash<RestorableId, QVariant>::const_iterator it = restorables.constFind(RestorableId(object, propertyName));
        if (it != restorables.constEnd()) {
#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
            qDebug() << q_func() << ":   using" << it.value() << "from" << s;
#endif
            return it.value();
        }
    }
#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
    qDebug() << q_func() << ":   falling back to current value";
#endif
    return object->property(propertyName);
}

void QStateMachinePrivate::registerRestorable(QAbstractState *state, QObject *object, const QByteArray &propertyName,
                                              const QVariant &value)
{
#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
    qDebug() << q_func() << ": registerRestorable(" << state << object << propertyName << value << ')';
#endif
    RestorableId id(object, propertyName);
    QHash<RestorableId, QVariant> &restorables = registeredRestorablesForState[state];
    if (!restorables.contains(id))
        restorables.insert(id, value);
#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
    else
        qDebug() << q_func() << ":   (already registered)";
#endif
}

void QStateMachinePrivate::unregisterRestorables(const QList<QAbstractState *> &states, QObject *object,
                                                 const QByteArray &propertyName)
{
#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
    qDebug() << q_func() << ": unregisterRestorables(" << states << object << propertyName << ')';
#endif
    RestorableId id(object, propertyName);
    for (int i = 0; i < states.size(); ++i) {
        QAbstractState *s = states.at(i);
        QHash<QAbstractState*, QHash<RestorableId, QVariant> >::iterator it;
        it = registeredRestorablesForState.find(s);
        if (it == registeredRestorablesForState.end())
            continue;
        QHash<RestorableId, QVariant> &restorables = it.value();
        const auto it2 = restorables.constFind(id);
        if (it2 == restorables.cend())
            continue;
#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
        qDebug() << q_func() << ":   unregistered for" << s;
#endif
        restorables.erase(it2);
        if (restorables.isEmpty())
            registeredRestorablesForState.erase(it);
    }
}

QList<QPropertyAssignment> QStateMachinePrivate::restorablesToPropertyList(const QHash<RestorableId, QVariant> &restorables) const
{
    QList<QPropertyAssignment> result;
    QHash<RestorableId, QVariant>::const_iterator it;
    for (it = restorables.constBegin(); it != restorables.constEnd(); ++it) {
        const RestorableId &id = it.key();
        if (!id.object()) {
            // Property object was deleted
            continue;
        }
#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
        qDebug() << q_func() << ": restoring" << id.object() << id.proertyName() << "to" << it.value();
#endif
        result.append(QPropertyAssignment(id.object(), id.propertyName(), it.value(), /*explicitlySet=*/false));
    }
    return result;
}

/*!
  \internal
  Computes the set of properties whose values should be restored given that
  the states \a statesToExit_sorted will be exited.

  If a particular (object, propertyName) pair occurs more than once (i.e.,
  because nested states are being exited), the value from the last (outermost)
  exited state takes precedence.

  The result of this function must be filtered according to the explicit
  property assignments (QState::assignProperty()) of the entered states
  before the property restoration is actually performed; i.e., if an entered
  state assigns to a property that would otherwise be restored, that property
  should not be restored after all, but the saved value from the exited state
  should be remembered by the entered state (see registerRestorable()).
*/
QHash<QStateMachinePrivate::RestorableId, QVariant> QStateMachinePrivate::computePendingRestorables(
        const QList<QAbstractState*> &statesToExit_sorted) const
{
    QHash<QStateMachinePrivate::RestorableId, QVariant> restorables;
    for (int i = statesToExit_sorted.size() - 1; i >= 0; --i) {
        QAbstractState *s = statesToExit_sorted.at(i);
        QHash<QStateMachinePrivate::RestorableId, QVariant> rs = registeredRestorablesForState.value(s);
        QHash<QStateMachinePrivate::RestorableId, QVariant>::const_iterator it;
        for (it = rs.constBegin(); it != rs.constEnd(); ++it) {
            if (!restorables.contains(it.key()))
                restorables.insert(it.key(), it.value());
        }
    }
    return restorables;
}

/*!
  \internal
  Computes the ordered sets of property assignments for the states to be
  entered, \a statesToEnter_sorted. Also filters \a pendingRestorables (removes
  properties that should not be restored because they are assigned by an
  entered state).
*/
QHash<QAbstractState *, QList<QPropertyAssignment>> QStateMachinePrivate::computePropertyAssignments(
        const QList<QAbstractState*> &statesToEnter_sorted, QHash<RestorableId, QVariant> &pendingRestorables) const
{
    QHash<QAbstractState *, QList<QPropertyAssignment>> assignmentsForState;
    for (int i = 0; i < statesToEnter_sorted.size(); ++i) {
        QState *s = toStandardState(statesToEnter_sorted.at(i));
        if (!s)
            continue;

        QList<QPropertyAssignment> &assignments = QStatePrivate::get(s)->propertyAssignments;
        for (int j = 0; j < assignments.size(); ++j) {
            const QPropertyAssignment &assn = assignments.at(j);
            if (assn.objectDeleted()) {
                assignments.removeAt(j--);
            } else {
                pendingRestorables.remove(RestorableId(assn.object, assn.propertyName));
                assignmentsForState[s].append(assn);
            }
        }
    }
    return assignmentsForState;
}

#endif // QT_NO_PROPERTIES

QAbstractState *QStateMachinePrivate::findErrorState(QAbstractState *context)
{
    // Find error state recursively in parent hierarchy if not set explicitly for context state
    QAbstractState *errorState = nullptr;
    if (context != nullptr) {
        QState *s = toStandardState(context);
        if (s != nullptr)
            errorState = s->errorState();

        if (errorState == nullptr)
            errorState = findErrorState(context->parentState());
    }

    return errorState;
}

void QStateMachinePrivate::setError(QStateMachine::Error errorCode, QAbstractState *currentContext)
{
    Q_Q(QStateMachine);

    error = errorCode;
    switch (errorCode) {
    case QStateMachine::NoInitialStateError:
        Q_ASSERT(currentContext != nullptr);

        errorString = QStateMachine::tr("Missing initial state in compound state '%1'")
                        .arg(currentContext->objectName());

        break;
    case QStateMachine::NoDefaultStateInHistoryStateError:
        Q_ASSERT(currentContext != nullptr);

        errorString = QStateMachine::tr("Missing default state in history state '%1'")
                        .arg(currentContext->objectName());
        break;

    case QStateMachine::NoCommonAncestorForTransitionError:
        Q_ASSERT(currentContext != nullptr);

        errorString = QStateMachine::tr("No common ancestor for targets and source of transition from state '%1'")
                        .arg(currentContext->objectName());
        break;

    case QStateMachine::StateMachineChildModeSetToParallelError:
        Q_ASSERT(currentContext != nullptr);

        errorString = QStateMachine::tr("Child mode of state machine '%1' is not 'ExclusiveStates'.")
                        .arg(currentContext->objectName());
        break;

    default:
        errorString = QStateMachine::tr("Unknown error");
    };

    pendingErrorStates.clear();
    pendingErrorStatesForDefaultEntry.clear();

    QAbstractState *currentErrorState = findErrorState(currentContext);

    // Avoid infinite loop if the error state itself has an error
    if (currentContext == currentErrorState)
        currentErrorState = nullptr;

    Q_ASSERT(currentErrorState != rootState());

    if (currentErrorState != nullptr) {
#ifdef QSTATEMACHINE_DEBUG
        qDebug() << q << ": entering error state" << currentErrorState << "from" << currentContext;
#endif
        pendingErrorStates.insert(currentErrorState);
        addDescendantStatesToEnter(currentErrorState, pendingErrorStates, pendingErrorStatesForDefaultEntry);
        addAncestorStatesToEnter(currentErrorState, rootState(), pendingErrorStates, pendingErrorStatesForDefaultEntry);
        pendingErrorStates -= configuration;
    } else {
        qWarning("Unrecoverable error detected in running state machine: %ls",
                 qUtf16Printable(errorString));
        q->stop();
    }
}

#if QT_CONFIG(animation)

QStateMachinePrivate::InitializeAnimationResult
QStateMachinePrivate::initializeAnimation(QAbstractAnimation *abstractAnimation,
                                          const QPropertyAssignment &prop)
{
    InitializeAnimationResult result;
    QAnimationGroup *group = qobject_cast<QAnimationGroup*>(abstractAnimation);
    if (group) {
        for (int i = 0; i < group->animationCount(); ++i) {
            QAbstractAnimation *animationChild = group->animationAt(i);
            const auto ret = initializeAnimation(animationChild, prop);
            result.handledAnimations << ret.handledAnimations;
            result.localResetEndValues << ret.localResetEndValues;
        }
    } else {
        QPropertyAnimation *animation = qobject_cast<QPropertyAnimation *>(abstractAnimation);
        if (animation != nullptr
            && prop.object == animation->targetObject()
            && prop.propertyName == animation->propertyName()) {

            // Only change end value if it is undefined
            if (!animation->endValue().isValid()) {
                animation->setEndValue(prop.value);
                result.localResetEndValues.append(animation);
            }
            result.handledAnimations.append(animation);
        }
    }
    return result;
}

void QStateMachinePrivate::_q_animationFinished()
{
    Q_Q(QStateMachine);
    QAbstractAnimation *anim = qobject_cast<QAbstractAnimation*>(q->sender());
    Q_ASSERT(anim != nullptr);
    QObject::disconnect(anim, SIGNAL(finished()), q, SLOT(_q_animationFinished()));
    if (resetAnimationEndValues.contains(anim)) {
        qobject_cast<QVariantAnimation*>(anim)->setEndValue(QVariant()); // ### generalize
        resetAnimationEndValues.remove(anim);
    }

    QAbstractState *state = stateForAnimation.take(anim);
    Q_ASSERT(state != nullptr);

#ifndef QT_NO_PROPERTIES
    // Set the final property value.
    QPropertyAssignment assn = propertyForAnimation.take(anim);
    assn.write();
    if (!assn.explicitlySet)
        unregisterRestorables(QList<QAbstractState*>() << state, assn.object, assn.propertyName);
#endif

    QHash<QAbstractState*, QList<QAbstractAnimation*> >::iterator it;
    it = animationsForState.find(state);
    Q_ASSERT(it != animationsForState.end());
    QList<QAbstractAnimation*> &animations = it.value();
    animations.removeOne(anim);
    if (animations.isEmpty()) {
        animationsForState.erase(it);
        QStatePrivate::get(toStandardState(state))->emitPropertiesAssigned();
    }
}

QList<QAbstractAnimation *> QStateMachinePrivate::selectAnimations(const QList<QAbstractTransition *> &transitionList) const
{
    QList<QAbstractAnimation *> selectedAnimations;
    if (animated) {
        for (int i = 0; i < transitionList.size(); ++i) {
            QAbstractTransition *transition = transitionList.at(i);

            selectedAnimations << transition->animations();
            selectedAnimations << defaultAnimationsForSource.values(transition->sourceState());

            QList<QAbstractState *> targetStates = transition->targetStates();
            for (int j=0; j<targetStates.size(); ++j)
                selectedAnimations << defaultAnimationsForTarget.values(targetStates.at(j));
        }
        selectedAnimations << defaultAnimations;
    }
    return selectedAnimations;
}

void QStateMachinePrivate::terminateActiveAnimations(QAbstractState *state,
    const QHash<QAbstractState *, QList<QPropertyAssignment>> &assignmentsForEnteredStates)
{
    Q_Q(QStateMachine);
    QList<QAbstractAnimation*> animations = animationsForState.take(state);
    for (int i = 0; i < animations.size(); ++i) {
        QAbstractAnimation *anim = animations.at(i);
        QObject::disconnect(anim, SIGNAL(finished()), q, SLOT(_q_animationFinished()));
        stateForAnimation.remove(anim);

        // Stop the (top-level) animation.
        // ### Stopping nested animation has weird behavior.
        QAbstractAnimation *topLevelAnim = anim;
        while (QAnimationGroup *group = topLevelAnim->group())
            topLevelAnim = group;
        topLevelAnim->stop();

        if (resetAnimationEndValues.contains(anim)) {
            qobject_cast<QVariantAnimation*>(anim)->setEndValue(QVariant()); // ### generalize
            resetAnimationEndValues.remove(anim);
        }
        QPropertyAssignment assn = propertyForAnimation.take(anim);
        Q_ASSERT(assn.object != nullptr);
        // If there is no property assignment that sets this property,
        // set the property to its target value.
        bool found = false;
        for (auto it = assignmentsForEnteredStates.constBegin(); it != assignmentsForEnteredStates.constEnd(); ++it) {
            const QList<QPropertyAssignment> &assignments = it.value();
            for (int j = 0; j < assignments.size(); ++j) {
                if (assignments.at(j).hasTarget(assn.object, assn.propertyName)) {
                    found = true;
                    break;
                }
            }
        }
        if (!found) {
            assn.write();
            if (!assn.explicitlySet)
                unregisterRestorables(QList<QAbstractState*>() << state, assn.object, assn.propertyName);
        }
    }
}

void QStateMachinePrivate::initializeAnimations(QAbstractState *state, const QList<QAbstractAnimation *> &selectedAnimations,
                                                const QList<QAbstractState*> &exitedStates_sorted,
                                                QHash<QAbstractState *, QList<QPropertyAssignment>> &assignmentsForEnteredStates)
{
    Q_Q(QStateMachine);
    if (!assignmentsForEnteredStates.contains(state))
        return;
    QList<QPropertyAssignment> &assignments = assignmentsForEnteredStates[state];
    for (int i = 0; i < selectedAnimations.size(); ++i) {
        QAbstractAnimation *anim = selectedAnimations.at(i);
        for (auto it = assignments.begin(); it != assignments.end(); ) {
            const QPropertyAssignment &assn = *it;
            const auto ret = initializeAnimation(anim, assn);
            if (!ret.handledAnimations.isEmpty()) {
                for (int j = 0; j < ret.handledAnimations.size(); ++j) {
                    QAbstractAnimation *a = ret.handledAnimations.at(j);
                    propertyForAnimation.insert(a, assn);
                    stateForAnimation.insert(a, state);
                    animationsForState[state].append(a);
                    // ### connect to just the top-level animation?
                    QObject::connect(a, SIGNAL(finished()), q, SLOT(_q_animationFinished()), Qt::UniqueConnection);
                }
                if ((globalRestorePolicy == QState::RestoreProperties)
                        && !hasRestorable(state, assn.object, assn.propertyName)) {
                    QVariant value = savedValueForRestorable(exitedStates_sorted, assn.object, assn.propertyName);
                    unregisterRestorables(exitedStates_sorted, assn.object, assn.propertyName);
                    registerRestorable(state, assn.object, assn.propertyName, value);
                }
                it = assignments.erase(it);
            } else {
                ++it;
            }
            for (int j = 0; j < ret.localResetEndValues.size(); ++j)
                resetAnimationEndValues.insert(ret.localResetEndValues.at(j));
        }
        // We require that at least one animation is valid.
        // ### generalize
        QList<QVariantAnimation*> variantAnims = anim->findChildren<QVariantAnimation*>();
        if (QVariantAnimation *va = qobject_cast<QVariantAnimation*>(anim))
            variantAnims.append(va);

        bool hasValidEndValue = false;
        for (int j = 0; j < variantAnims.size(); ++j) {
            if (variantAnims.at(j)->endValue().isValid()) {
                hasValidEndValue = true;
                break;
            }
        }

        if (hasValidEndValue) {
            if (anim->state() == QAbstractAnimation::Running) {
                // The animation is still running. This can happen if the
                // animation is a group, and one of its children just finished,
                // and that caused a state to emit its propertiesAssigned() signal, and
                // that triggered a transition in the machine.
                // Just stop the animation so it is correctly restarted again.
                anim->stop();
            }
            anim->start();
        }

        if (assignments.isEmpty()) {
            assignmentsForEnteredStates.remove(state);
            break;
        }
    }
}

#endif // animation

QAbstractTransition *QStateMachinePrivate::createInitialTransition() const
{
    class InitialTransition : public QAbstractTransition
    {
    public:
        InitialTransition(const QList<QAbstractState *> &targets)
            : QAbstractTransition()
        { setTargetStates(targets); }
    protected:
        bool eventTest(QEvent *) override { return true; }
        void onTransition(QEvent *) override {}
    };

    QState *root = rootState();
    Q_ASSERT(root != nullptr);
    QList<QAbstractState *> targets;
    switch (root->childMode()) {
    case QState::ExclusiveStates:
        targets.append(root->initialState());
        break;
    case QState::ParallelStates:
        targets = QStatePrivate::get(root)->childStates();
        break;
    }
    return new InitialTransition(targets);
}

void QStateMachinePrivate::clearHistory()
{
    Q_Q(QStateMachine);
    QList<QHistoryState*> historyStates = q->findChildren<QHistoryState*>();
    for (int i = 0; i < historyStates.size(); ++i) {
        QHistoryState *h = historyStates.at(i);
        QHistoryStatePrivate::get(h)->configuration.clear();
    }
}

/*!
  \internal

  Registers all signal transitions whose sender object lives in another thread.

  Normally, signal transitions are lazily registered (when a state becomes
  active). But if the sender is in a different thread, the transition must be
  registered early to keep the state machine from "dropping" signals; e.g.,
  a second (transition-bound) signal could be emitted on the sender thread
  before the state machine gets to process the first signal.
*/
void QStateMachinePrivate::registerMultiThreadedSignalTransitions()
{
    Q_Q(QStateMachine);
    QList<QSignalTransition*> transitions = rootState()->findChildren<QSignalTransition*>();
    for (int i = 0; i < transitions.size(); ++i) {
        QSignalTransition *t = transitions.at(i);
        if ((t->machine() == q) && t->senderObject() && (t->senderObject()->thread() != q->thread()))
            registerSignalTransition(t);
    }
}

void QStateMachinePrivate::_q_start()
{
    Q_Q(QStateMachine);
    Q_ASSERT(state == Starting);
    // iterate over a copy, since we emit signals which may cause
    // 'configuration' to change, resulting in undefined behavior when
    // iterating at the same time:
    const auto config = configuration;
    for (QAbstractState *state : config) {
        QAbstractStatePrivate *abstractStatePrivate = QAbstractStatePrivate::get(state);
        abstractStatePrivate->active.setValue(false);
    }
    configuration.clear();
    qDeleteAll(internalEventQueue);
    internalEventQueue.clear();
    qDeleteAll(externalEventQueue);
    externalEventQueue.clear();
    clearHistory();

    registerMultiThreadedSignalTransitions();

    startupHook();

#ifdef QSTATEMACHINE_DEBUG
    qDebug() << q << ": starting";
#endif
    state = Running;
    processingScheduled = true; // we call _q_process() below

    QList<QAbstractTransition*> transitions;
    CalculationCache calculationCache;
    QAbstractTransition *initialTransition = createInitialTransition();
    transitions.append(initialTransition);

    QEvent nullEvent(QEvent::None);
    executeTransitionContent(&nullEvent, transitions);
    QList<QAbstractState*> exitedStates = QList<QAbstractState*>();
    QSet<QAbstractState*> statesForDefaultEntry;
    QList<QAbstractState*> enteredStates = computeEntrySet(transitions, statesForDefaultEntry, &calculationCache);
    QHash<RestorableId, QVariant> pendingRestorables;
    QHash<QAbstractState *, QList<QPropertyAssignment>> assignmentsForEnteredStates =
            computePropertyAssignments(enteredStates, pendingRestorables);
#if QT_CONFIG(animation)
    QList<QAbstractAnimation *> selectedAnimations = selectAnimations(transitions);
#endif
    // enterStates() will set stopProcessingReason to Finished if a final
    // state is entered.
    stopProcessingReason = EventQueueEmpty;
    enterStates(&nullEvent, exitedStates, enteredStates, statesForDefaultEntry,
                assignmentsForEnteredStates
#if QT_CONFIG(animation)
                , selectedAnimations
#endif
                );
    delete initialTransition;

#ifdef QSTATEMACHINE_DEBUG
    qDebug() << q << ": initial configuration:" << configuration;
#endif

    emit q->started(QStateMachine::QPrivateSignal());
    emit q->runningChanged(true);

    if (stopProcessingReason == Finished) {
        // The state machine immediately reached a final state.
        processingScheduled = false;
        state = NotRunning;
        unregisterAllTransitions();
        emitFinished();
        emit q->runningChanged(false);
        exitInterpreter();
    } else {
        _q_process();
    }
}

void QStateMachinePrivate::_q_process()
{
    Q_Q(QStateMachine);
    Q_ASSERT(state == Running);
    Q_ASSERT(!processing);
    processing = true;
    processingScheduled = false;
    beginMacrostep();
#ifdef QSTATEMACHINE_DEBUG
    qDebug() << q << ": starting the event processing loop";
#endif
    bool didChange = false;
    while (processing) {
        if (stop) {
            processing = false;
            break;
        }
        QList<QAbstractTransition*> enabledTransitions;
        CalculationCache calculationCache;

        QEvent *e = new QEvent(QEvent::None);
        enabledTransitions = selectTransitions(e, &calculationCache);
        if (enabledTransitions.isEmpty()) {
            delete e;
            e = nullptr;
        }
        while (enabledTransitions.isEmpty() && ((e = dequeueInternalEvent()) != nullptr)) {
#ifdef QSTATEMACHINE_DEBUG
            qDebug() << q << ": dequeued internal event" << e << "of type" << e->type();
#endif
            enabledTransitions = selectTransitions(e, &calculationCache);
            if (enabledTransitions.isEmpty()) {
                delete e;
                e = nullptr;
            }
        }
        while (enabledTransitions.isEmpty() && ((e = dequeueExternalEvent()) != nullptr)) {
#ifdef QSTATEMACHINE_DEBUG
                qDebug() << q << ": dequeued external event" << e << "of type" << e->type();
#endif
                enabledTransitions = selectTransitions(e, &calculationCache);
                if (enabledTransitions.isEmpty()) {
                    delete e;
                    e = nullptr;
                }
        }
        if (enabledTransitions.isEmpty()) {
            if (isInternalEventQueueEmpty()) {
                processing = false;
                stopProcessingReason = EventQueueEmpty;
                noMicrostep();
#ifdef QSTATEMACHINE_DEBUG
                qDebug() << q << ": no transitions enabled";
#endif
            }
        } else {
            didChange = true;
            q->beginMicrostep(e);
            microstep(e, enabledTransitions, &calculationCache);
            q->endMicrostep(e);
        }
        delete e;
    }
#ifdef QSTATEMACHINE_DEBUG
    qDebug() << q << ": finished the event processing loop";
#endif
    if (stop) {
        stop = false;
        stopProcessingReason = Stopped;
    }

    switch (stopProcessingReason) {
    case EventQueueEmpty:
        processedPendingEvents(didChange);
        break;
    case Finished:
        state = NotRunning;
        cancelAllDelayedEvents();
        unregisterAllTransitions();
        emitFinished();
        emit q->runningChanged(false);
        break;
    case Stopped:
        state = NotRunning;
        cancelAllDelayedEvents();
        unregisterAllTransitions();
        emit q->stopped(QStateMachine::QPrivateSignal());
        emit q->runningChanged(false);
        break;
    }
    endMacrostep(didChange);
    if (stopProcessingReason == Finished)
        exitInterpreter();
}

void QStateMachinePrivate::_q_startDelayedEventTimer(int id, int delay)
{
    Q_Q(QStateMachine);
    QMutexLocker locker(&delayedEventsMutex);
    QHash<int, DelayedEvent>::iterator it = delayedEvents.find(id);
    if (it != delayedEvents.end()) {
        DelayedEvent &e = it.value();
        Q_ASSERT(!e.timerId);
        e.timerId = q->startTimer(delay);
        if (!e.timerId) {
            qWarning("QStateMachine::postDelayedEvent: failed to start timer (id=%d, delay=%d)", id, delay);
            delete e.event;
            delayedEvents.erase(it);
            delayedEventIdFreeList.release(id);
        } else {
            timerIdToDelayedEventId.insert(e.timerId, id);
        }
    } else {
        // It's been cancelled already
        delayedEventIdFreeList.release(id);
    }
}

void QStateMachinePrivate::_q_killDelayedEventTimer(int id, int timerId)
{
    Q_Q(QStateMachine);
    q->killTimer(timerId);
    QMutexLocker locker(&delayedEventsMutex);
    delayedEventIdFreeList.release(id);
}

void QStateMachinePrivate::postInternalEvent(QEvent *e)
{
    QMutexLocker locker(&internalEventMutex);
    internalEventQueue.append(e);
}

void QStateMachinePrivate::postExternalEvent(QEvent *e)
{
    QMutexLocker locker(&externalEventMutex);
    externalEventQueue.append(e);
}

QEvent *QStateMachinePrivate::dequeueInternalEvent()
{
    QMutexLocker locker(&internalEventMutex);
    if (internalEventQueue.isEmpty())
        return nullptr;
    return internalEventQueue.takeFirst();
}

QEvent *QStateMachinePrivate::dequeueExternalEvent()
{
    QMutexLocker locker(&externalEventMutex);
    if (externalEventQueue.isEmpty())
        return nullptr;
    return externalEventQueue.takeFirst();
}

bool QStateMachinePrivate::isInternalEventQueueEmpty()
{
    QMutexLocker locker(&internalEventMutex);
    return internalEventQueue.isEmpty();
}

bool QStateMachinePrivate::isExternalEventQueueEmpty()
{
    QMutexLocker locker(&externalEventMutex);
    return externalEventQueue.isEmpty();
}

void QStateMachinePrivate::processEvents(EventProcessingMode processingMode)
{
    Q_Q(QStateMachine);
    if ((state != Running) || processing || processingScheduled)
        return;
    switch (processingMode) {
    case DirectProcessing:
        if (QThread::currentThread() == q->thread()) {
            _q_process();
            break;
        }
        // processing must be done in the machine thread, so:
        Q_FALLTHROUGH();
    case QueuedProcessing:
        processingScheduled = true;
        QMetaObject::invokeMethod(q, "_q_process", Qt::QueuedConnection);
        break;
    }
}

void QStateMachinePrivate::cancelAllDelayedEvents()
{
    Q_Q(QStateMachine);
    QMutexLocker locker(&delayedEventsMutex);
    QHash<int, DelayedEvent>::const_iterator it;
    for (it = delayedEvents.constBegin(); it != delayedEvents.constEnd(); ++it) {
        const DelayedEvent &e = it.value();
        if (e.timerId) {
            timerIdToDelayedEventId.remove(e.timerId);
            q->killTimer(e.timerId);
            delayedEventIdFreeList.release(it.key());
        } else {
            // Cancellation will be detected in pending _q_startDelayedEventTimer() call
        }
        delete e.event;
    }
    delayedEvents.clear();
}

/*
  This function is called when the state machine is performing no
  microstep because no transition is enabled (i.e. an event is ignored).

  The default implementation does nothing.
*/
void QStateMachinePrivate::noMicrostep()
{ }

/*
  This function is called when the state machine has reached a stable
  state (no pending events), and has not finished yet.
  For each event the state machine receives it is guaranteed that
  1) beginMacrostep is called
  2) selectTransition is called at least once
  3) begin/endMicrostep is called at least once or noMicrostep is called
     at least once (possibly both, but at least one)
  4) the state machine either enters an infinite loop, or stops (runningChanged(false),
     and either finished or stopped are emitted), or processedPendingEvents() is called.
  5) if the machine is not in an infinite loop endMacrostep is called
  6) when the machine is finished and all processing (like signal emission) is done,
     exitInterpreter() is called. (This is the same name as the SCXML specification uses.)

  didChange is set to true if at least one microstep was performed, it is possible
  that the machine returned to exactly the same state as before, but some transitions
  were triggered.

  The default implementation does nothing.
*/
void QStateMachinePrivate::processedPendingEvents(bool didChange)
{
    Q_UNUSED(didChange);
}

void QStateMachinePrivate::beginMacrostep()
{ }

void QStateMachinePrivate::endMacrostep(bool didChange)
{
    Q_UNUSED(didChange);
}

void QStateMachinePrivate::exitInterpreter()
{
}

void QStateMachinePrivate::emitStateFinished(QState *forState, QFinalState *guiltyState)
{
    Q_UNUSED(guiltyState);
    Q_ASSERT(guiltyState);

#ifdef QSTATEMACHINE_DEBUG
    Q_Q(QStateMachine);
    qDebug() << q << ": emitting finished signal for" << forState;
#endif

    QStatePrivate::get(forState)->emitFinished();
}

void QStateMachinePrivate::startupHook()
{
}

namespace _QStateMachine_Internal{

class GoToStateTransition : public QAbstractTransition
{
    Q_OBJECT
public:
    GoToStateTransition(QAbstractState *target)
        : QAbstractTransition()
    { setTargetState(target); }
protected:
    void onTransition(QEvent *) override { deleteLater(); }
    bool eventTest(QEvent *) override { return true; }
};

} // namespace
// mingw compiler tries to export QObject::findChild<GoToStateTransition>(),
// which doesn't work if its in an anonymous namespace.
using namespace _QStateMachine_Internal;
/*!
  \internal

  Causes this state machine to unconditionally transition to the given
  \a targetState.

  Provides a backdoor for using the state machine "imperatively"; i.e.  rather
  than defining explicit transitions, you drive the machine's execution by
  calling this function. It breaks the whole integrity of the
  transition-driven model, but is provided for pragmatic reasons.
*/
void QStateMachinePrivate::goToState(QAbstractState *targetState)
{
    if (!targetState) {
        qWarning("QStateMachine::goToState(): cannot go to null state");
        return;
    }

    if (configuration.contains(targetState))
        return;

    Q_ASSERT(state == Running);
    QState *sourceState = nullptr;
    QSet<QAbstractState*>::const_iterator it;
    for (it = configuration.constBegin(); it != configuration.constEnd(); ++it) {
        sourceState = toStandardState(*it);
        if (sourceState != nullptr)
            break;
    }

    Q_ASSERT(sourceState != nullptr);
    // Reuse previous GoToStateTransition in case of several calls to
    // goToState() in a row.
    GoToStateTransition *trans = sourceState->findChild<GoToStateTransition*>();
    if (!trans) {
        trans = new GoToStateTransition(targetState);
        sourceState->addTransition(trans);
    } else {
        trans->setTargetState(targetState);
    }

    processEvents(QueuedProcessing);
}

void QStateMachinePrivate::registerTransitions(QAbstractState *state)
{
    QState *group = toStandardState(state);
    if (!group)
        return;
    QList<QAbstractTransition*> transitions = QStatePrivate::get(group)->transitions();
    for (int i = 0; i < transitions.size(); ++i) {
        QAbstractTransition *t = transitions.at(i);
        registerTransition(t);
    }
}

void QStateMachinePrivate::maybeRegisterTransition(QAbstractTransition *transition)
{
    if (QSignalTransition *st = qobject_cast<QSignalTransition*>(transition)) {
        maybeRegisterSignalTransition(st);
    }
#if QT_CONFIG(qeventtransition)
    else if (QEventTransition *et = qobject_cast<QEventTransition*>(transition)) {
        maybeRegisterEventTransition(et);
    }
#endif
}

void QStateMachinePrivate::registerTransition(QAbstractTransition *transition)
{
    if (QSignalTransition *st = qobject_cast<QSignalTransition*>(transition)) {
        registerSignalTransition(st);
    }
#if QT_CONFIG(qeventtransition)
    else if (QEventTransition *oet = qobject_cast<QEventTransition*>(transition)) {
        registerEventTransition(oet);
    }
#endif
}

void QStateMachinePrivate::unregisterTransition(QAbstractTransition *transition)
{
    if (QSignalTransition *st = qobject_cast<QSignalTransition*>(transition)) {
        unregisterSignalTransition(st);
    }
#if QT_CONFIG(qeventtransition)
    else if (QEventTransition *oet = qobject_cast<QEventTransition*>(transition)) {
        unregisterEventTransition(oet);
    }
#endif
}

void QStateMachinePrivate::maybeRegisterSignalTransition(QSignalTransition *transition)
{
    Q_Q(QStateMachine);
    const QObject *senderObject =
            QSignalTransitionPrivate::get(transition)->senderObject.valueBypassingBindings();
    if ((state == Running) && (configuration.contains(transition->sourceState())
            || (senderObject && (senderObject->thread() != q->thread())))) {
        registerSignalTransition(transition);
    }
}

void QStateMachinePrivate::registerSignalTransition(QSignalTransition *transition)
{
    Q_Q(QStateMachine);
    if (QSignalTransitionPrivate::get(transition)->signalIndex != -1)
        return; // already registered
    const QObject *sender =
            QSignalTransitionPrivate::get(transition)->senderObject.valueBypassingBindings();
    if (!sender)
        return;
    QByteArray signal = QSignalTransitionPrivate::get(transition)->signal.valueBypassingBindings();
    if (signal.isEmpty())
        return;
    if (signal.startsWith('0'+QSIGNAL_CODE))
        signal.remove(0, 1);
    const QMetaObject *meta = sender->metaObject();
    int signalIndex = meta->indexOfSignal(signal);
    int originalSignalIndex = signalIndex;
    if (signalIndex == -1) {
        signalIndex = meta->indexOfSignal(QMetaObject::normalizedSignature(signal));
        if (signalIndex == -1) {
            qWarning("QSignalTransition: no such signal: %s::%s",
                     meta->className(), signal.constData());
            return;
        }
        originalSignalIndex = signalIndex;
    }
    // The signal index we actually want to connect to is the one
    // that is going to be sent, i.e. the non-cloned original index.
    while (meta->method(signalIndex).attributes() & QMetaMethod::Cloned)
        --signalIndex;

    connectionsMutex.lock();
    QList<int> &connectedSignalIndexes = connections[sender];
    if (connectedSignalIndexes.size() <= signalIndex)
        connectedSignalIndexes.resize(signalIndex+1);
    if (connectedSignalIndexes.at(signalIndex) == 0) {
        if (!signalEventGenerator)
            signalEventGenerator = new QSignalEventGenerator(q);
        static const int generatorMethodOffset = QSignalEventGenerator::staticMetaObject.methodOffset();
        bool ok = QMetaObject::connect(sender, signalIndex, signalEventGenerator, generatorMethodOffset);
        if (!ok) {
#ifdef QSTATEMACHINE_DEBUG
            qDebug() << q << ": FAILED to add signal transition from" << transition->sourceState()
                     << ": ( sender =" << sender << ", signal =" << signal
                     << ", targets =" << transition->targetStates() << ')';
#endif
            return;
        }
    }
    ++connectedSignalIndexes[signalIndex];
    connectionsMutex.unlock();

    QSignalTransitionPrivate::get(transition)->signalIndex = signalIndex;
    QSignalTransitionPrivate::get(transition)->originalSignalIndex = originalSignalIndex;
#ifdef QSTATEMACHINE_DEBUG
    qDebug() << q << ": added signal transition from" << transition->sourceState()
             << ": ( sender =" << sender << ", signal =" << signal
             << ", targets =" << transition->targetStates() << ')';
#endif
}

void QStateMachinePrivate::unregisterSignalTransition(QSignalTransition *transition)
{
    int signalIndex = QSignalTransitionPrivate::get(transition)->signalIndex;
    if (signalIndex == -1)
        return; // not registered
    const QObject *sender =
            QSignalTransitionPrivate::get(transition)->senderObject.valueBypassingBindings();
    QSignalTransitionPrivate::get(transition)->signalIndex = -1;

    connectionsMutex.lock();
    QList<int> &connectedSignalIndexes = connections[sender];
    Q_ASSERT(connectedSignalIndexes.size() > signalIndex);
    Q_ASSERT(connectedSignalIndexes.at(signalIndex) != 0);
    if (--connectedSignalIndexes[signalIndex] == 0) {
        Q_ASSERT(signalEventGenerator != nullptr);
        static const int generatorMethodOffset = QSignalEventGenerator::staticMetaObject.methodOffset();
        QMetaObject::disconnect(sender, signalIndex, signalEventGenerator, generatorMethodOffset);
        int sum = 0;
        for (int i = 0; i < connectedSignalIndexes.size(); ++i)
            sum += connectedSignalIndexes.at(i);
        if (sum == 0)
            connections.remove(sender);
    }
    connectionsMutex.unlock();
}

void QStateMachinePrivate::unregisterAllTransitions()
{
    Q_Q(QStateMachine);
    {
        QList<QSignalTransition*> transitions = rootState()->findChildren<QSignalTransition*>();
        for (int i = 0; i < transitions.size(); ++i) {
            QSignalTransition *t = transitions.at(i);
            if (t->machine() == q)
                unregisterSignalTransition(t);
        }
    }
#if QT_CONFIG(qeventtransition)
    {
        QList<QEventTransition*> transitions = rootState()->findChildren<QEventTransition*>();
        for (int i = 0; i < transitions.size(); ++i) {
            QEventTransition *t = transitions.at(i);
            if (t->machine() == q)
                unregisterEventTransition(t);
        }
    }
#endif
}

#if QT_CONFIG(qeventtransition)
void QStateMachinePrivate::maybeRegisterEventTransition(QEventTransition *transition)
{
    if ((state == Running) && configuration.contains(transition->sourceState()))
        registerEventTransition(transition);
}

void QStateMachinePrivate::registerEventTransition(QEventTransition *transition)
{
    Q_Q(QStateMachine);
    if (QEventTransitionPrivate::get(transition)->registered)
        return;
    const QEvent::Type eventType =
            QEventTransitionPrivate::get(transition)->eventType.valueBypassingBindings();
    if (eventType >= QEvent::User) {
        qWarning("QObject event transitions are not supported for custom types");
        return;
    }
    QObject *object = QEventTransitionPrivate::get(transition)->object.valueBypassingBindings();
    if (!object)
        return;
    QObjectPrivate *od = QObjectPrivate::get(object);
    if (!od->extraData || !od->extraData->eventFilters.contains(q))
        object->installEventFilter(q);
    ++qobjectEvents[object][eventType];
    QEventTransitionPrivate::get(transition)->registered = true;
#ifdef QSTATEMACHINE_DEBUG
    qDebug() << q << ": added event transition from" << transition->sourceState()
             << ": ( object =" << object << ", event =" << eventType
             << ", targets =" << transition->targetStates() << ')';
#endif
}

void QStateMachinePrivate::unregisterEventTransition(QEventTransition *transition)
{
    Q_Q(QStateMachine);
    if (!QEventTransitionPrivate::get(transition)->registered)
        return;
    QObject *object = QEventTransitionPrivate::get(transition)->object.valueBypassingBindings();
    QHash<QEvent::Type, int> &events = qobjectEvents[object];
    const QEvent::Type eventType =
            QEventTransitionPrivate::get(transition)->eventType.valueBypassingBindings();
    Q_ASSERT(events.value(eventType) > 0);
    if (--events[eventType] == 0) {
        events.remove(eventType);
        int sum = 0;
        QHash<QEvent::Type, int>::const_iterator it;
        for (it = events.constBegin(); it != events.constEnd(); ++it)
            sum += it.value();
        if (sum == 0) {
            qobjectEvents.remove(object);
            object->removeEventFilter(q);
        }
    }
    QEventTransitionPrivate::get(transition)->registered = false;
}

void QStateMachinePrivate::handleFilteredEvent(QObject *watched, QEvent *event)
{
    if (qobjectEvents.value(watched).contains(event->type())) {
        postInternalEvent(new QStateMachine::WrappedEvent(watched, event->clone()));
        processEvents(DirectProcessing);
    }
}
#endif

void QStateMachinePrivate::handleTransitionSignal(QObject *sender, int signalIndex,
                                                  void **argv)
{
#ifndef QT_NO_DEBUG
    connectionsMutex.lock();
    Q_ASSERT(connections[sender].at(signalIndex) != 0);
    connectionsMutex.unlock();
#endif
    const QMetaObject *meta = sender->metaObject();
    QMetaMethod method = meta->method(signalIndex);
    int argc = method.parameterCount();
    QList<QVariant> vargs;
    vargs.reserve(argc);
    for (int i = 0; i < argc; ++i) {
        auto type = method.parameterMetaType(i);
        vargs.append(QVariant(type, argv[i+1]));
    }

#ifdef QSTATEMACHINE_DEBUG
    qDebug() << q_func() << ": sending signal event ( sender =" << sender
             << ", signal =" << method.methodSignature().constData() << ')';
#endif
    postInternalEvent(new QStateMachine::SignalEvent(sender, signalIndex, vargs));
    processEvents(DirectProcessing);
}

/*!
  Constructs a new state machine with the given \a parent.
*/
QStateMachine::QStateMachine(QObject *parent)
    : QState(*new QStateMachinePrivate, /*parentState=*/nullptr)
{
    // Can't pass the parent to the QState constructor, as it expects a QState
    // But this works as expected regardless of whether parent is a QState or not
    setParent(parent);
}

/*!
  \since 5.0
  \deprecated

  Constructs a new state machine with the given \a childMode
  and \a parent.

  \warning Do not set the \a childMode to anything else than \l{ExclusiveStates}, otherwise the
           state machine is invalid, and might work incorrectly.
*/
QStateMachine::QStateMachine(QState::ChildMode childMode, QObject *parent)
    : QState(*new QStateMachinePrivate, /*parentState=*/nullptr)
{
    Q_D(QStateMachine);
    d->childMode = childMode;
    setParent(parent); // See comment in constructor above

    if (childMode != ExclusiveStates) {
        //### FIXME for Qt6: remove this constructor completely, and hide the childMode property.
        // Yes, the StateMachine itself is conceptually a state, but it should only expose a limited
        // number of properties. The execution algorithm (in the URL below) treats a state machine
        // as a state, but from an API point of view, it's questionable if the QStateMachine should
        // inherit from QState.
        //
        // See function findLCCA in https://www.w3.org/TR/2014/WD-scxml-20140529/#AlgorithmforSCXMLInterpretation
        // to see where setting childMode to parallel will break down.
        qWarning() << "Invalid childMode for QStateMachine" << this;
    }
}

/*!
  \internal
*/
QStateMachine::QStateMachine(QStateMachinePrivate &dd, QObject *parent)
    : QState(dd, /*parentState=*/nullptr)
{
    setParent(parent);
}

/*!
  Destroys this state machine.
*/
QStateMachine::~QStateMachine()
{
}

/*!
  \enum QStateMachine::EventPriority

  This enum type specifies the priority of an event posted to the state
  machine using postEvent().

  Events of high priority are processed before events of normal priority.

  \value NormalPriority The event has normal priority.
  \value HighPriority The event has high priority.
*/

/*! \enum QStateMachine::Error

    This enum type defines errors that can occur in the state machine at run time. When the state
    machine encounters an unrecoverable error at run time, it will set the error code returned
    by error(), the error message returned by errorString(), and enter an error state based on
    the context of the error.

    \value NoError No error has occurred.
    \value NoInitialStateError The machine has entered a QState with children which does not have an
           initial state set. The context of this error is the state which is missing an initial
           state.
    \value NoDefaultStateInHistoryStateError The machine has entered a QHistoryState which does not have
           a default state set. The context of this error is the QHistoryState which is missing a
           default state.
    \value NoCommonAncestorForTransitionError The machine has selected a transition whose source
           and targets are not part of the same tree of states, and thus are not part of the same
           state machine. Commonly, this could mean that one of the states has not been given
           any parent or added to any machine. The context of this error is the source state of
           the transition.
    \value StateMachineChildModeSetToParallelError The machine's \l childMode
           property was set to \l{QState::ParallelStates}. This is illegal.
           Only states may be declared as parallel, not the state machine
           itself. This enum value was added in Qt 5.14.

    \sa setErrorState()
*/

/*!
  Returns the error code of the last error that occurred in the state machine.
*/
QStateMachine::Error QStateMachine::error() const
{
    Q_D(const QStateMachine);
    return d->error;
}

/*!
  Returns the error string of the last error that occurred in the state machine.
*/
QString QStateMachine::errorString() const
{
    Q_D(const QStateMachine);
    return d->errorString;
}

/*!
  Clears the error string and error code of the state machine.
*/
void QStateMachine::clearError()
{
    Q_D(QStateMachine);
    d->error = NoError;
    d->errorString = QString();
}

QBindable<QString> QStateMachine::bindableErrorString() const
{
    Q_D(const QStateMachine);
    return &d->errorString;
}

/*!
   Returns the restore policy of the state machine.

   \sa setGlobalRestorePolicy()
*/
QState::RestorePolicy QStateMachine::globalRestorePolicy() const
{
    Q_D(const QStateMachine);
    return d->globalRestorePolicy;
}

/*!
   Sets the restore policy of the state machine to \a restorePolicy. The default
   restore policy is QState::DontRestoreProperties.

   \sa globalRestorePolicy()
*/
void QStateMachine::setGlobalRestorePolicy(QState::RestorePolicy restorePolicy)
{
    Q_D(QStateMachine);
    d->globalRestorePolicy = restorePolicy;
}

QBindable<QState::RestorePolicy> QStateMachine::bindableGlobalRestorePolicy()
{
    Q_D(QStateMachine);
    return &d->globalRestorePolicy;
}

/*!
  Adds the given \a state to this state machine. The state becomes a top-level
  state and the state machine takes ownership of the state.

  If the state is already in a different machine, it will first be removed
  from its old machine, and then added to this machine.

  \sa removeState(), setInitialState()
*/
void QStateMachine::addState(QAbstractState *state)
{
    if (!state) {
        qWarning("QStateMachine::addState: cannot add null state");
        return;
    }
    if (QAbstractStatePrivate::get(state)->machine() == this) {
        qWarning("QStateMachine::addState: state has already been added to this machine");
        return;
    }
    state->setParent(this);
}

/*!
  Removes the given \a state from this state machine.  The state machine
  releases ownership of the state.

  \sa addState()
*/
void QStateMachine::removeState(QAbstractState *state)
{
    if (!state) {
        qWarning("QStateMachine::removeState: cannot remove null state");
        return;
    }
    if (QAbstractStatePrivate::get(state)->machine() != this) {
        qWarning("QStateMachine::removeState: state %p's machine (%p)"
                 " is different from this machine (%p)",
                 state, QAbstractStatePrivate::get(state)->machine(), this);
        return;
    }
    state->setParent(nullptr);
}

bool QStateMachine::isRunning() const
{
    Q_D(const QStateMachine);
    return (d->state == QStateMachinePrivate::Running);
}

/*!
  Starts this state machine.  The machine will reset its configuration and
  transition to the initial state.  When a final top-level state (QFinalState)
  is entered, the machine will emit the finished() signal.

  \note A state machine will not run without a running event loop, such as
  the main application event loop started with QCoreApplication::exec() or
  QApplication::exec().

  \sa started(), finished(), stop(), initialState(), setRunning()
*/
void QStateMachine::start()
{
    Q_D(QStateMachine);

    if ((childMode() == QState::ExclusiveStates) && (initialState() == nullptr)) {
        qWarning("QStateMachine::start: No initial state set for machine. Refusing to start.");
        return;
    }

    switch (d->state) {
    case QStateMachinePrivate::NotRunning:
        d->state = QStateMachinePrivate::Starting;
        QMetaObject::invokeMethod(this, "_q_start", Qt::QueuedConnection);
        break;
    case QStateMachinePrivate::Starting:
        break;
    case QStateMachinePrivate::Running:
        qWarning("QStateMachine::start(): already running");
        break;
    }
}

/*!
  Stops this state machine. The state machine will stop processing events and
  then emit the stopped() signal.

  \sa stopped(), start(), setRunning()
*/
void QStateMachine::stop()
{
    Q_D(QStateMachine);
    switch (d->state) {
    case QStateMachinePrivate::NotRunning:
        break;
    case QStateMachinePrivate::Starting:
        // the machine will exit as soon as it enters the event processing loop
        d->stop = true;
        break;
    case QStateMachinePrivate::Running:
        d->stop = true;
        d->processEvents(QStateMachinePrivate::QueuedProcessing);
        break;
    }
}

void QStateMachine::setRunning(bool running)
{
    if (running)
        start();
    else
        stop();
}

/*!
  \threadsafe

  Posts the given \a event of the given \a priority for processing by this
  state machine.

  This function returns immediately. The event is added to the state machine's
  event queue. Events are processed in the order posted. The state machine
  takes ownership of the event and deletes it once it has been processed.

  You can only post events when the state machine is running or when it is starting up.

  \sa postDelayedEvent()
*/
void QStateMachine::postEvent(QEvent *event, EventPriority priority)
{
    Q_D(QStateMachine);
    switch (d->state) {
    case QStateMachinePrivate::Running:
    case QStateMachinePrivate::Starting:
        break;
    default:
        qWarning("QStateMachine::postEvent: cannot post event when the state machine is not running");
        return;
    }
    if (!event) {
        qWarning("QStateMachine::postEvent: cannot post null event");
        return;
    }
#ifdef QSTATEMACHINE_DEBUG
    qDebug() << this << ": posting event" << event;
#endif
    switch (priority) {
    case NormalPriority:
        d->postExternalEvent(event);
        break;
    case HighPriority:
        d->postInternalEvent(event);
        break;
    }
    d->processEvents(QStateMachinePrivate::QueuedProcessing);
}

/*!
  \threadsafe

  Posts the given \a event for processing by this state machine, with the
  given \a delay in milliseconds. Returns an identifier associated with the
  delayed event, or -1 if the event could not be posted.

  This function returns immediately. When the delay has expired, the event
  will be added to the state machine's event queue for processing. The state
  machine takes ownership of the event and deletes it once it has been
  processed.

  You can only post events when the state machine is running.

  \sa cancelDelayedEvent(), postEvent()
*/
int QStateMachine::postDelayedEvent(QEvent *event, int delay)
{
    Q_D(QStateMachine);
    if (d->state != QStateMachinePrivate::Running) {
        qWarning("QStateMachine::postDelayedEvent: cannot post event when the state machine is not running");
        return -1;
    }
    if (!event) {
        qWarning("QStateMachine::postDelayedEvent: cannot post null event");
        return -1;
    }
    if (delay < 0) {
        qWarning("QStateMachine::postDelayedEvent: delay cannot be negative");
        return -1;
    }
#ifdef QSTATEMACHINE_DEBUG
    qDebug() << this << ": posting event" << event << "with delay" << delay;
#endif
    QMutexLocker locker(&d->delayedEventsMutex);
    int id = d->delayedEventIdFreeList.next();
    bool inMachineThread = (QThread::currentThread() == thread());
    int timerId = inMachineThread ? startTimer(delay) : 0;
    if (inMachineThread && !timerId) {
        qWarning("QStateMachine::postDelayedEvent: failed to start timer with interval %d", delay);
        d->delayedEventIdFreeList.release(id);
        return -1;
    }
    QStateMachinePrivate::DelayedEvent delayedEvent(event, timerId);
    d->delayedEvents.insert(id, delayedEvent);
    if (timerId) {
        d->timerIdToDelayedEventId.insert(timerId, id);
    } else {
        Q_ASSERT(!inMachineThread);
        QMetaObject::invokeMethod(this, "_q_startDelayedEventTimer",
                                  Qt::QueuedConnection,
                                  Q_ARG(int, id),
                                  Q_ARG(int, delay));
    }
    return id;
}

/*!
  \threadsafe

  Cancels the delayed event identified by the given \a id. The id should be a
  value returned by a call to postDelayedEvent(). Returns \c true if the event
  was successfully cancelled, otherwise returns \c false.

  \sa postDelayedEvent()
*/
bool QStateMachine::cancelDelayedEvent(int id)
{
    Q_D(QStateMachine);
    if (d->state != QStateMachinePrivate::Running) {
        qWarning("QStateMachine::cancelDelayedEvent: the machine is not running");
        return false;
    }
    QMutexLocker locker(&d->delayedEventsMutex);
    QStateMachinePrivate::DelayedEvent e = d->delayedEvents.take(id);
    if (!e.event)
        return false;
    if (e.timerId) {
        d->timerIdToDelayedEventId.remove(e.timerId);
        bool inMachineThread = (QThread::currentThread() == thread());
        if (inMachineThread) {
            killTimer(e.timerId);
            d->delayedEventIdFreeList.release(id);
        } else {
            QMetaObject::invokeMethod(this, "_q_killDelayedEventTimer",
                                      Qt::QueuedConnection,
                                      Q_ARG(int, id),
                                      Q_ARG(int, e.timerId));
        }
    } else {
        // Cancellation will be detected in pending _q_startDelayedEventTimer() call
    }
    delete e.event;
    return true;
}

/*!
   Returns the maximal consistent set of states (including parallel and final
   states) that this state machine is currently in. If a state \c s is in the
   configuration, it is always the case that the parent of \c s is also in
   c. Note, however, that the machine itself is not an explicit member of the
   configuration.
*/
QSet<QAbstractState*> QStateMachine::configuration() const
{
    Q_D(const QStateMachine);
    return d->configuration;
}

/*!
  \fn QStateMachine::started()

  This signal is emitted when the state machine has entered its initial state
  (QStateMachine::initialState).

  \sa QStateMachine::finished(), QStateMachine::start()
*/

/*!
  \fn QStateMachine::stopped()

  This signal is emitted when the state machine has stopped.

  \sa QStateMachine::stop(), QStateMachine::finished()
*/

/*!
  \reimp
*/
bool QStateMachine::event(QEvent *e)
{
    Q_D(QStateMachine);
    if (e->type() == QEvent::Timer) {
        QTimerEvent *te = static_cast<QTimerEvent*>(e);
        int tid = te->timerId();
        if (d->state != QStateMachinePrivate::Running) {
            // This event has been cancelled already
            QMutexLocker locker(&d->delayedEventsMutex);
            Q_ASSERT(!d->timerIdToDelayedEventId.contains(tid));
            return true;
        }
        d->delayedEventsMutex.lock();
        int id = d->timerIdToDelayedEventId.take(tid);
        QStateMachinePrivate::DelayedEvent ee = d->delayedEvents.take(id);
        if (ee.event != nullptr) {
            Q_ASSERT(ee.timerId == tid);
            killTimer(tid);
            d->delayedEventIdFreeList.release(id);
            d->delayedEventsMutex.unlock();
            d->postExternalEvent(ee.event);
            d->processEvents(QStateMachinePrivate::DirectProcessing);
            return true;
        } else {
            d->delayedEventsMutex.unlock();
        }
    }
    return QState::event(e);
}

/*!
  \reimp
*/
bool QStateMachine::eventFilter(QObject *watched, QEvent *event)
{
#if QT_CONFIG(qeventtransition)
    Q_D(QStateMachine);
    d->handleFilteredEvent(watched, event);
    return false;
#else
    return QState::eventFilter(watched, event);
#endif
}

/*!
  \internal

  This function is called when the state machine is about to select
  transitions based on the given \a event.

  The default implementation does nothing.
*/
void QStateMachine::beginSelectTransitions(QEvent *event)
{
    Q_UNUSED(event);
}

/*!
  \internal

  This function is called when the state machine has finished selecting
  transitions based on the given \a event.

  The default implementation does nothing.
*/
void QStateMachine::endSelectTransitions(QEvent *event)
{
    Q_UNUSED(event);
}

/*!
  \internal

  This function is called when the state machine is about to do a microstep.

  The default implementation does nothing.
*/
void QStateMachine::beginMicrostep(QEvent *event)
{
    Q_UNUSED(event);
}

/*!
  \internal

  This function is called when the state machine has finished doing a
  microstep.

  The default implementation does nothing.
*/
void QStateMachine::endMicrostep(QEvent *event)
{
    Q_UNUSED(event);
}

/*!
  \reimp
    This function will call start() to start the state machine.
*/
void QStateMachine::onEntry(QEvent *event)
{
    start();
    QState::onEntry(event);
}

/*!
  \reimp
    This function will call stop() to stop the state machine and
    subsequently emit the stopped() signal.
*/
void QStateMachine::onExit(QEvent *event)
{
    stop();
    QState::onExit(event);
}

#if QT_CONFIG(animation)

/*!
  Returns whether animations are enabled for this state machine.
*/
bool QStateMachine::isAnimated() const
{
    Q_D(const QStateMachine);
    return d->animated;
}

/*!
  Sets whether animations are \a enabled for this state machine.
*/
void QStateMachine::setAnimated(bool enabled)
{
    Q_D(QStateMachine);
    d->animated = enabled;
}

QBindable<bool> QStateMachine::bindableAnimated()
{
    Q_D(QStateMachine);
    return &d->animated;
}

/*!
    Adds a default \a animation to be considered for any transition.
*/
void QStateMachine::addDefaultAnimation(QAbstractAnimation *animation)
{
    Q_D(QStateMachine);
    d->defaultAnimations.append(animation);
}

/*!
    Returns the list of default animations that will be considered for any transition.
*/
QList<QAbstractAnimation*> QStateMachine::defaultAnimations() const
{
    Q_D(const QStateMachine);
    return d->defaultAnimations;
}

/*!
    Removes \a animation from the list of default animations.
*/
void QStateMachine::removeDefaultAnimation(QAbstractAnimation *animation)
{
    Q_D(QStateMachine);
    d->defaultAnimations.removeAll(animation);
}

#endif // animation

void QSignalEventGenerator::execute(QMethodRawArguments a)
{
    auto machinePrivate = QStateMachinePrivate::get(qobject_cast<QStateMachine*>(parent()));
    if (machinePrivate->state != QStateMachinePrivate::Running)
        return;
    int signalIndex = senderSignalIndex();
    Q_ASSERT(signalIndex != -1);
    machinePrivate->handleTransitionSignal(sender(), signalIndex, a.arguments);
}

QSignalEventGenerator::QSignalEventGenerator(QStateMachine *parent)
    : QObject(parent)
{
}

/*!
  \class QStateMachine::SignalEvent
  \inmodule QtStateMachine

  \brief The SignalEvent class represents a Qt signal event.

  \since 4.6
  \ingroup statemachine

  A signal event is generated by a QStateMachine in response to a Qt
  signal. The QSignalTransition class provides a transition associated with a
  signal event. QStateMachine::SignalEvent is part of \l{Qt State Machine Overview}
  {Qt State Machine Framework}.

  The sender() function returns the object that generated the signal. The
  signalIndex() function returns the index of the signal. The arguments()
  function returns the arguments of the signal.

  \sa QSignalTransition
*/

/*!
  \internal

  Constructs a new SignalEvent object with the given \a sender, \a
  signalIndex and \a arguments.
*/
QStateMachine::SignalEvent::SignalEvent(QObject *sender, int signalIndex,
                                        const QList<QVariant> &arguments)
    : QEvent(QEvent::StateMachineSignal), m_sender(sender),
      m_signalIndex(signalIndex), m_arguments(arguments)
{
}

/*!
  Destroys this SignalEvent.
*/
QStateMachine::SignalEvent::~SignalEvent()
{
}

/*!
  \fn QStateMachine::SignalEvent::sender() const

  Returns the object that emitted the signal.

  \sa QObject::sender()
*/

/*!
  \fn QStateMachine::SignalEvent::signalIndex() const

  Returns the index of the signal.

  \sa QMetaObject::indexOfSignal(), QMetaObject::method()
*/

/*!
  \fn QStateMachine::SignalEvent::arguments() const

  Returns the arguments of the signal.
*/


/*!
  \class QStateMachine::WrappedEvent
  \inmodule QtStateMachine

  \brief The WrappedEvent class inherits QEvent and holds a clone of an event associated with a QObject.

  \since 4.6
  \ingroup statemachine

  A wrapped event is generated by a QStateMachine in response to a Qt
  event. The QEventTransition class provides a transition associated with a
  such an event. QStateMachine::WrappedEvent is part of \l{Qt State Machine Overview}.

  The object() function returns the object that generated the event. The
  event() function returns a clone of the original event.

  \sa QEventTransition
*/

/*!
  \internal

  Constructs a new WrappedEvent object with the given \a object
  and \a event.

  The WrappedEvent object takes ownership of \a event.
*/
QStateMachine::WrappedEvent::WrappedEvent(QObject *object, QEvent *event)
    : QEvent(QEvent::StateMachineWrapped), m_object(object), m_event(event)
{
}

/*!
  Destroys this WrappedEvent.
*/
QStateMachine::WrappedEvent::~WrappedEvent()
{
    delete m_event;
}

/*!
  \fn QStateMachine::WrappedEvent::object() const

  Returns the object that the event is associated with.
*/

/*!
  \fn QStateMachine::WrappedEvent::event() const

  Returns a clone of the original event.
*/

/*!
  \fn QStateMachine::runningChanged(bool running)
  \since 5.4

  This signal is emitted when the running property is changed with \a running as argument.

  \sa QStateMachine::running
*/

/*!
  \fn QStateMachine::postDelayedEvent(QEvent *event, std::chrono::milliseconds delay)
  \since 5.15
  \overload
  \threadsafe

  Posts the given \a event for processing by this state machine, with the
  given \a delay in milliseconds. Returns an identifier associated with the
  delayed event, or -1 if the event could not be posted.

  This function returns immediately. When the delay has expired, the event
  will be added to the state machine's event queue for processing. The state
  machine takes ownership of the event and deletes it once it has been
  processed.

  You can only post events when the state machine is running.

  \sa cancelDelayedEvent(), postEvent()
*/

QT_END_NAMESPACE

#include "qstatemachine.moc"
#include "moc_qstatemachine.cpp"
