/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtScxml module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qscxmltabledata_p.h"
#include "qscxmlcompiler_p.h"
#include "qscxmlexecutablecontent_p.h"

QT_USE_NAMESPACE

/*!
    \class QScxmlTableData
    \since 5.8
    \inmodule QtScxml
    \brief The QScxmlTableData class is used by compiled state machines.

    QScxmlTableData is the interface to the compiled representation of SCXML
    state machines. It should only be used internally and by state machines
    compiled from SCXML documents.
 */

/*!
    \fn QScxmlTableData::string(QScxmlExecutableContent::StringId id) const
    Returns a QString for the given \a id.
 */

/*!
    \fn QScxmlTableData::instructions() const
    Returns a pointer to the instructions of executable content contained in
    the state machine.
 */

/*!
    \fn QScxmlTableData::evaluatorInfo(QScxmlExecutableContent::EvaluatorId evaluatorId) const
    Returns the QScxmlExecutableContent::EvaluatorInfo object for the given \a evaluatorId.
 */

/*!
    \fn QScxmlTableData::assignmentInfo(QScxmlExecutableContent::EvaluatorId assignmentId) const
    Returns the QScxmlExecutableContent::AssignmentInfo object for the given \a assignmentId.
 */

/*!
    \fn QScxmlTableData::foreachInfo(QScxmlExecutableContent::EvaluatorId foreachId) const
    Returns the QScxmlExecutableContent::ForeachInfo object for the given \a foreachId.
 */

/*!
    \fn QScxmlTableData::dataNames(int *count) const
    Retrieves the string IDs for the names of data items in the data model. The
    number of strings is saved into \a count and a pointer to an array of
    string IDs is returned.

    Returns a pointer to an array of string IDs.
 */

/*!
    \fn QScxmlTableData::initialSetup() const
    Initializes the table data. Returns the ID of the container with
    instructions to be executed when initializing the state machine.
 */

/*!
    \fn QScxmlTableData::name() const
    Returns the name of the state machine.
 */

/*!
    \fn QScxmlTableData::stateMachineTable() const
    Returns a pointer to the complete state table, expressed as an opaque
    sequence of integers.
 */

/*!
    \fn QScxmlTableData::serviceFactory(int id) const
    Returns the service factory that creates invokable services for the state
    with the ID \a id.
 */

using namespace QScxmlInternal;

namespace {
using namespace QScxmlExecutableContent;

class TableDataBuilder: public DocumentModel::NodeVisitor
{
public:
    TableDataBuilder(GeneratedTableData &tableData,
                     GeneratedTableData::MetaDataInfo &metaDataInfo,
                     GeneratedTableData::DataModelInfo &dataModelInfo,
                     GeneratedTableData::CreateFactoryId func)
        : createFactoryId(func)
        , m_tableData(tableData)
        , m_dataModelInfo(dataModelInfo)
        , m_stringTable(tableData.theStrings)
        , m_instructions(tableData.theInstructions)
        , m_evaluators(tableData.theEvaluators)
        , m_assignments(tableData.theAssignments)
        , m_foreaches(tableData.theForeaches)
        , m_dataIds(tableData.theDataNameIds)
        , m_stateNames(metaDataInfo.stateNames)

    {
        m_activeSequences.reserve(4);
        tableData.theInitialSetup = QScxmlExecutableContent::NoContainer;
    }

    void buildTableData(DocumentModel::ScxmlDocument *doc)
    {
        m_isCppDataModel = doc->root->dataModel == DocumentModel::Scxml::CppDataModel;
        m_parents.reserve(32);
        m_allTransitions.resize(doc->allTransitions.size());
        m_docTransitionIndices.reserve(doc->allTransitions.size());
        for (auto *t : qAsConst(doc->allTransitions)) {
            m_docTransitionIndices.insert(t, m_docTransitionIndices.size());
        }
        m_docStatesIndices.reserve(doc->allStates.size());
        m_transitionsForState.resize(doc->allStates.size());
        m_allStates.resize(doc->allStates.size());
        for (DocumentModel::AbstractState *s : qAsConst(doc->allStates)) {
            m_docStatesIndices.insert(s, m_docStatesIndices.size());
        }

        doc->root->accept(this);
        m_stateTable.version = Q_QSCXMLC_OUTPUT_REVISION;
        generateStateMachineData();

        m_tableData.theInstructions.squeeze();
    }

    void generateStateMachineData()
    {
        const int tableSize = sizeof(StateTable) / sizeof(qint32);
        const int stateSize = qint32(sizeof(StateTable::State) / sizeof(qint32));
        const int transitionSize = qint32(sizeof(StateTable::Transition) / sizeof(qint32));

        m_stateTable.stateOffset = tableSize;
        m_stateTable.stateCount = m_allStates.size();
        m_stateTable.transitionOffset = m_stateTable.stateOffset +
                m_stateTable.stateCount * stateSize;
        m_stateTable.transitionCount = m_allTransitions.size();
        m_stateTable.arrayOffset = m_stateTable.transitionOffset +
                m_stateTable.transitionCount * transitionSize;
        m_stateTable.arraySize = m_arrays.size();

        const qint32 dataSize = qint32(tableSize)
                + (m_allStates.size() * stateSize)
                + (m_allTransitions.size() * transitionSize)
                + m_arrays.size()
                + 1;
        QList<qint32> data(dataSize, -1);
        qint32 *ptr = data.data();

        memcpy(ptr, &m_stateTable, sizeof(m_stateTable));
        ptr += tableSize;

        Q_ASSERT(ptr == data.constData() + m_stateTable.stateOffset);
        memcpy(ptr, m_allStates.constData(),
               sizeof(StateTable::State) * size_t(m_allStates.size()));
        ptr += stateSize * size_t(m_allStates.size());

        Q_ASSERT(ptr == data.constData() + m_stateTable.transitionOffset);
        memcpy(ptr, m_allTransitions.constData(),
               sizeof(StateTable::Transition) * size_t(m_allTransitions.size()));
        ptr += transitionSize * size_t(m_allTransitions.size());

        Q_ASSERT(ptr == data.constData() + m_stateTable.arrayOffset);
        memcpy(ptr, m_arrays.constData(), sizeof(qint32) * size_t(m_arrays.size()));
        ptr += m_arrays.size();

        *ptr++ = StateTable::terminator;

        Q_ASSERT(ptr == data.constData() + dataSize);

        m_tableData.theStateMachineTable = data;
    }

protected: // visitor
    using NodeVisitor::visit;

    bool visit(DocumentModel::Scxml *node) override final
    {
        setName(node->name);

        switch (node->dataModel) {
        case DocumentModel::Scxml::NullDataModel:
            m_stateTable.dataModel = StateTable::NullDataModel;
            break;
        case DocumentModel::Scxml::JSDataModel:
            m_stateTable.dataModel = StateTable::EcmaScriptDataModel;
            break;
        case DocumentModel::Scxml::CppDataModel:
            m_stateTable.dataModel = StateTable::CppDataModel;
            break;
        default:
            m_stateTable.dataModel = StateTable::InvalidDataModel;
            break;
        }

        switch (node->binding) {
        case DocumentModel::Scxml::EarlyBinding:
            m_stateTable.binding = StateTable::EarlyBinding;
            break;
        case DocumentModel::Scxml::LateBinding:
            m_stateTable.binding = StateTable::LateBinding;
            m_bindLate = true;
            break;
        default:
            Q_UNREACHABLE();
        }

        m_stateTable.name = addString(node->name);

        m_parents.append(-1);
        visit(node->children);

        m_dataElements.append(node->dataElements);
        if (node->script || !m_dataElements.isEmpty() || !node->initialSetup.isEmpty()) {
            setInitialSetup(startNewSequence());
            generate(m_dataElements);
            if (node->script) {
                node->script->accept(this);
            }
            visit(&node->initialSetup);
            endSequence();
        }

        QList<DocumentModel::AbstractState *> childStates;
        for (DocumentModel::StateOrTransition *sot : qAsConst(node->children)) {
            if (DocumentModel::AbstractState *s = sot->asAbstractState()) {
                childStates.append(s);
            }
        }
        m_stateTable.childStates = addStates(childStates);
        if (node->initialTransition) {
            visit(node->initialTransition);
            const int transitionIndex = m_docTransitionIndices.value(node->initialTransition, -1);
            Q_ASSERT(transitionIndex != -1);
            m_stateTable.initialTransition = transitionIndex;
        }
        m_parents.removeLast();

        return false;
    }

    bool visit(DocumentModel::State *state) override final
    {
        m_stateNames.add(state->id);
        const int stateIndex = m_docStatesIndices.value(state, -1);
        Q_ASSERT(stateIndex != -1);
        StateTable::State &newState = m_allStates[stateIndex];
        newState.name = addString(state->id);
        newState.parent = currentParent();

        switch (state->type) {
        case DocumentModel::State::Normal:
            newState.type = StateTable::State::Normal;
            break;
        case DocumentModel::State::Parallel:
            newState.type = StateTable::State::Parallel;
            break;
        case DocumentModel::State::Final:
            newState.type = StateTable::State::Final;
            newState.doneData = generate(state->doneData);
            break;
        default:
            Q_UNREACHABLE();
        }

        m_parents.append(stateIndex);

        if (!state->dataElements.isEmpty()) {
            if (m_bindLate) {
                newState.initInstructions = startNewSequence();
                generate(state->dataElements);
                endSequence();
            } else {
                m_dataElements.append(state->dataElements);
            }
        }

        newState.entryInstructions = generate(state->onEntry);
        newState.exitInstructions = generate(state->onExit);
        if (!state->invokes.isEmpty()) {
            QList<int> factoryIds;
            for (DocumentModel::Invoke *invoke : qAsConst(state->invokes)) {
                auto ctxt = createContext(QStringLiteral("invoke"));
                QList<QScxmlExecutableContent::StringId> namelist;
                for (const QString &name : qAsConst(invoke->namelist))
                    namelist += addString(name);
                QList<QScxmlExecutableContent::ParameterInfo> params;
                for (DocumentModel::Param *param : qAsConst(invoke->params)) {
                    QScxmlExecutableContent::ParameterInfo p;
                    p.name = addString(param->name);
                    p.expr = createEvaluatorVariant(QStringLiteral("param"), QStringLiteral("expr"),
                                                    param->expr);
                    p.location = addString(param->location);
                    params.append(p);
                }
                QScxmlExecutableContent::ContainerId finalize =
                        QScxmlExecutableContent::NoContainer;
                if (!invoke->finalize.isEmpty()) {
                    finalize = startNewSequence();
                    visit(&invoke->finalize);
                    endSequence();
                }
                auto srcexpr = createEvaluatorString(QStringLiteral("invoke"),
                                                     QStringLiteral("srcexpr"),
                                                     invoke->srcexpr);
                QScxmlExecutableContent::InvokeInfo invokeInfo;
                invokeInfo.id = addString(invoke->id);
                invokeInfo.prefix = addString(state->id + QStringLiteral(".session-"));
                invokeInfo.location = addString(invoke->idLocation);
                invokeInfo.context = ctxt;
                invokeInfo.expr = srcexpr;
                invokeInfo.finalize = finalize;
                invokeInfo.autoforward = invoke->autoforward;
                const int factoryId = createFactoryId(invokeInfo, namelist, params,
                                                      invoke->content);
                Q_ASSERT(factoryId >= 0);
                factoryIds.append(factoryId);
                m_stateTable.maxServiceId = std::max(m_stateTable.maxServiceId, factoryId);
            }
            newState.serviceFactoryIds = addArray(factoryIds);
        }

        visit(state->children);

        QList<DocumentModel::AbstractState *> childStates;
        for (DocumentModel::StateOrTransition *sot : qAsConst(state->children)) {
            if (auto s = sot->asAbstractState()) {
                childStates.append(s);
            }
        }
        newState.childStates = addStates(childStates);
        newState.transitions = addArray(m_transitionsForState.at(stateIndex));
        if (state->initialTransition) {
            visit(state->initialTransition);
            newState.initialTransition = m_transitionsForState.at(stateIndex).last();
        }
        m_parents.removeLast();

        return false;
    }

    bool visit(DocumentModel::Transition *transition) override final
    {
        const int transitionIndex = m_docTransitionIndices.value(transition, -1);
        Q_ASSERT(transitionIndex != -1);
        StateTable::Transition &newTransition = m_allTransitions[transitionIndex];
        const int parentIndex = currentParent();
        if (parentIndex != -1) {
            m_transitionsForState[parentIndex].append(transitionIndex);
        }
        newTransition.source = parentIndex;

        if (transition->condition) {
            newTransition.condition = createEvaluatorBool(QStringLiteral("transition"),
                                                          QStringLiteral("cond"),
                                                          *transition->condition.data());
        }

        switch (transition->type) {
        case DocumentModel::Transition::External:
            newTransition.type = StateTable::Transition::External;
            break;
        case DocumentModel::Transition::Internal:
            newTransition.type = StateTable::Transition::Internal;
            break;
        case DocumentModel::Transition::Synthetic:
            newTransition.type = StateTable::Transition::Synthetic;
            break;
        default:
            Q_UNREACHABLE();
        }

        if (!transition->instructionsOnTransition.isEmpty()) {
            m_currentTransition = transitionIndex;
            newTransition.transitionInstructions = startNewSequence();
            visit(&transition->instructionsOnTransition);
            endSequence();
            m_currentTransition = -1;
        }

        newTransition.targets = addStates(transition->targetStates);

        QList<int> eventIds;
        for (const QString &event : qAsConst(transition->events))
            eventIds.push_back(addString(event));

        newTransition.events = addArray(eventIds);

        return false;
    }

    bool visit(DocumentModel::HistoryState *historyState) override final
    {
        const int stateIndex = m_docStatesIndices.value(historyState, -1);
        Q_ASSERT(stateIndex != -1);
        StateTable::State &newState = m_allStates[stateIndex];
        newState.name = addString(historyState->id);
        newState.parent = currentParent();

        switch (historyState->type) {
        case DocumentModel::HistoryState::Shallow:
            newState.type = StateTable::State::ShallowHistory;
            break;
        case DocumentModel::HistoryState::Deep:
            newState.type = StateTable::State::DeepHistory;
            break;
        default:
            Q_UNREACHABLE();
        }

        m_parents.append(stateIndex);
        visit(historyState->children);
        m_parents.removeLast();
        newState.transitions = addArray(m_transitionsForState.at(stateIndex));
        return false;
    }

    bool visit(DocumentModel::Send *node) override final
    {
        auto instr = m_instructions.add<Send>(Send::calculateExtraSize(node->params.size(),
                                                                       node->namelist.size()));
        instr->instructionLocation = createContext(QStringLiteral("send"));
        instr->event = addString(node->event);
        instr->eventexpr = createEvaluatorString(QStringLiteral("send"),
                                                 QStringLiteral("eventexpr"),
                                                 node->eventexpr);
        instr->type = addString(node->type);
        instr->typeexpr = createEvaluatorString(QStringLiteral("send"),
                                                QStringLiteral("typeexpr"),
                                                node->typeexpr);
        instr->target = addString(node->target);
        instr->targetexpr = createEvaluatorString(QStringLiteral("send"),
                                                  QStringLiteral("targetexpr"),
                                                  node->targetexpr);
        instr->id = addString(node->id);
        instr->idLocation = addString(node->idLocation);
        instr->delay = addString(node->delay);
        instr->delayexpr = createEvaluatorString(QStringLiteral("send"),
                                                 QStringLiteral("delayexpr"),
                                                 node->delayexpr);
        instr->content = addString(node->content);
        instr->contentexpr = createEvaluatorString(QStringLiteral("send"),
                                                   QStringLiteral("contentexpr"),
                                                   node->contentexpr);
        generate(&instr->namelist, node->namelist);
        generate(instr->params(), node->params);
        return false;
    }

    void visit(DocumentModel::Raise *node) override final
    {
        auto instr = m_instructions.add<Raise>();
        instr->event = addString(node->event);
    }

    void visit(DocumentModel::Log *node) override final
    {
        auto instr = m_instructions.add<Log>();
        instr->label = addString(node->label);
        instr->expr = createEvaluatorString(QStringLiteral("log"),
                                            QStringLiteral("expr"),
                                            node->expr);
    }

    void visit(DocumentModel::Script *node) override final
    {
        auto instr = m_instructions.add<JavaScript>();
        instr->go = createEvaluatorVoid(QStringLiteral("script"),
                                        QStringLiteral("source"),
                                        node->content);
    }

    void visit(DocumentModel::Assign *node) override final
    {
        auto instr = m_instructions.add<Assign>();
        auto ctxt = createContext(QStringLiteral("assign"), QStringLiteral("expr"), node->expr);
        instr->expression = addAssignment(node->location, node->expr, ctxt);
    }

    bool visit(DocumentModel::If *node) override final
    {
        auto instr = m_instructions.add<If>(node->conditions.size());
        instr->conditions.count = node->conditions.size();
        auto it = instr->conditions.data();
        QString tag = QStringLiteral("if");
        for (int i = 0, ei = node->conditions.size(); i != ei; ++i) {
            *it++ = createEvaluatorBool(tag, QStringLiteral("cond"), node->conditions.at(i));
            if (i == 0) {
                tag = QStringLiteral("elif");
            }
        }
        auto outSequences = m_instructions.add<InstructionSequences>();
        generate(outSequences, node->blocks);
        return false;
    }

    bool visit(DocumentModel::Foreach *node) override final
    {
        auto instr = m_instructions.add<Foreach>();
        auto ctxt = createContextString(QStringLiteral("foreach"));
        instr->doIt = addForeach(node->array, node->item, node->index, ctxt);
        startSequence(&instr->block);
        visit(&node->block);
        endSequence();
        return false;
    }

    void visit(DocumentModel::Cancel *node) override final
    {
        auto instr = m_instructions.add<Cancel>();
        instr->sendid = addString(node->sendid);
        instr->sendidexpr = createEvaluatorString(QStringLiteral("cancel"),
                                                  QStringLiteral("sendidexpr"),
                                                  node->sendidexpr);
    }

protected:
    static int paramSize() { return sizeof(ParameterInfo) / sizeof(qint32); }

    ContainerId generate(const DocumentModel::DoneData *node)
    {
        auto id = m_instructions.newContainerId();
        DoneData *doneData;
        if (node) {
            doneData = m_instructions.add<DoneData>(node->params.size() * paramSize());
            doneData->contents = addString(node->contents);
            doneData->expr = createEvaluatorString(QStringLiteral("donedata"),
                                                   QStringLiteral("expr"),
                                                   node->expr);
            generate(&doneData->params, node->params);
        } else {
            doneData = m_instructions.add<DoneData>();
            doneData->contents = NoString;
            doneData->expr = NoEvaluator;
            doneData->params.count = 0;
        }
        doneData->location = createContext(QStringLiteral("final"));
        return id;
    }

    StringId createContext(const QString &instrName)
    {
        return addString(createContextString(instrName));
    }

    void generate(const QList<DocumentModel::DataElement *> &dataElements)
    {
        for (DocumentModel::DataElement *el : dataElements) {
            auto ctxt = createContext(QStringLiteral("data"), QStringLiteral("expr"), el->expr);
            auto evaluator = addDataElement(el->id, el->expr, ctxt);
            if (evaluator != NoEvaluator) {
                auto instr = m_instructions.add<QScxmlExecutableContent::Initialize>();
                instr->expression = evaluator;
            }
        }
    }

    ContainerId generate(const DocumentModel::InstructionSequences &inSequences)
    {
        if (inSequences.isEmpty())
            return NoContainer;

        auto id = m_instructions.newContainerId();
        auto outSequences = m_instructions.add<InstructionSequences>();
        generate(outSequences, inSequences);
        return id;
    }

    void generate(Array<ParameterInfo> *out, const QList<DocumentModel::Param *> &in)
    {
        out->count = in.size();
        ParameterInfo *it = out->data();
        for (DocumentModel::Param *f : in) {
            it->name = addString(f->name);
            it->expr = createEvaluatorVariant(QStringLiteral("param"), QStringLiteral("expr"),
                                              f->expr);
            it->location = addString(f->location);
            ++it;
        }
    }

    void generate(InstructionSequences *outSequences,
                  const DocumentModel::InstructionSequences &inSequences)
    {
        int sequencesOffset = m_instructions.offset(outSequences);
        int sequenceCount = 0;
        int entryCount = 0;
        for (DocumentModel::InstructionSequence *sequence : inSequences) {
            ++sequenceCount;
            startNewSequence();
            visit(sequence);
            entryCount += endSequence()->size();
        }
        outSequences = m_instructions.at<InstructionSequences>(sequencesOffset);
        outSequences->sequenceCount = sequenceCount;
        outSequences->entryCount = entryCount;
    }

    void generate(Array<StringId> *out, const QStringList &in)
    {
        out->count = in.size();
        StringId *it = out->data();
        for (const QString &str : in) {
            *it++ = addString(str);
        }
    }

    ContainerId startNewSequence()
    {
        auto id = m_instructions.newContainerId();
        auto sequence = m_instructions.add<InstructionSequence>();
        startSequence(sequence);
        return id;
    }

    void startSequence(InstructionSequence *sequence)
    {
        SequenceInfo info;
        info.location = m_instructions.offset(sequence);
        info.entryCount = 0;
        m_activeSequences.push_back(info);
        m_instructions.setSequenceInfo(&m_activeSequences.last());
        sequence->instructionType = Instruction::Sequence;
        sequence->entryCount = -1; // checked in endSequence
    }

    InstructionSequence *endSequence()
    {
        SequenceInfo info = m_activeSequences.back();
        m_activeSequences.pop_back();
        m_instructions.setSequenceInfo(m_activeSequences.isEmpty() ? nullptr :
                                                                     &m_activeSequences.last());

        auto sequence = m_instructions.at<InstructionSequence>(info.location);
        Q_ASSERT(sequence->entryCount == -1); // set in startSequence
        sequence->entryCount = info.entryCount;
        if (!m_activeSequences.isEmpty())
            m_activeSequences.last().entryCount += info.entryCount;
        return sequence;
    }

    EvaluatorId createEvaluatorString(const QString &instrName, const QString &attrName,
                                      const QString &expr)
    {
        if (!expr.isEmpty()) {
            if (isCppDataModel()) {
                auto id = m_evaluators.add(EvaluatorInfo(), false);
                m_dataModelInfo.stringEvaluators.insert(id, expr);
                return id;
            } else {
                return addEvaluator(expr, createContext(instrName, attrName, expr));
            }
        }

        return NoEvaluator;
    }

    EvaluatorId createEvaluatorBool(const QString &instrName, const QString &attrName,
                                    const QString &cond)
    {
        if (!cond.isEmpty()) {
            if (isCppDataModel()) {
                auto id = m_evaluators.add(EvaluatorInfo(), false);
                m_dataModelInfo.boolEvaluators.insert(id, cond);
                return id;
            } else {
                return addEvaluator(cond, createContext(instrName, attrName, cond));
            }
        }

        return NoEvaluator;
    }

    EvaluatorId createEvaluatorVariant(const QString &instrName, const QString &attrName,
                                       const QString &expr)
    {
        if (!expr.isEmpty()) {
            if (isCppDataModel()) {
                auto id = m_evaluators.add(EvaluatorInfo(), false);
                m_dataModelInfo.variantEvaluators.insert(id, expr);
                return id;
            } else {
                return addEvaluator(expr, createContext(instrName, attrName, expr));
            }
        }

        return NoEvaluator;
    }

    EvaluatorId createEvaluatorVoid(const QString &instrName, const QString &attrName,
                                    const QString &stuff)
    {
        if (!stuff.isEmpty()) {
            if (isCppDataModel()) {
                auto id = m_evaluators.add(EvaluatorInfo(), false);
                m_dataModelInfo.voidEvaluators.insert(id, stuff);
                return id;
            } else {
                return addEvaluator(stuff, createContext(instrName, attrName, stuff));
            }
        }

        return NoEvaluator;
    }

    GeneratedTableData *tableData(const QList<int> &stateMachineTable);

    StringId addString(const QString &str)
    { return str.isEmpty() ? NoString : m_stringTable.add(str); }

    void setInitialSetup(ContainerId id)
    { m_tableData.theInitialSetup = id; }

    void setName(const QString &name)
    { m_tableData.theName = addString(name); }

    bool isCppDataModel() const
    { return m_isCppDataModel; }

    int addStates(const QList<DocumentModel::AbstractState *> &states)
    {
        QList<int> array;
        for (auto *s : states) {
            int si = m_docStatesIndices.value(s, -1);
            Q_ASSERT(si != -1);
            array.push_back(si);
        }

        return addArray(array);
    }

    int addArray(const QList<int> &array)
    {
        if (array.isEmpty())
            return -1;

        const int res = m_arrays.size();
        m_arrays.push_back(array.size());
        m_arrays.append(array);
        return res;
    }

    int currentParent() const
    {
        return m_parents.last();
    }

    QString createContextString(const QString &instrName) const
    {
        if (m_currentTransition != -1) {
            QString state;
            int parent = m_allTransitions.at(m_currentTransition).source;
            if (parent != -1) {
                QString parentName = QStringLiteral("(none)");
                int name = m_allStates.at(parent).name;
                if (name != -1) {
                    parentName = m_stringTable.item(name);
                }
                state = QStringLiteral(" of state '%1'").arg(parentName);
            }
            return QStringLiteral("%1 instruction in transition %3").arg(instrName, state);
        } else {
            QString parentName = QStringLiteral("(none)");
            const int parent = currentParent();
            if (parent != -1) {
                const int name = m_allStates.at(parent).name;
                if (name != -1) {
                    parentName = m_stringTable.item(name);
                }
            }
            return QStringLiteral("%1 instruction in state %2").arg(instrName, parentName);
        }
    }

    QString createContext(const QString &instrName, const QString &attrName,
                          const QString &attrValue) const
    {
        const QString location = createContextString(instrName);
        return QStringLiteral("%1 with %2=\"%3\"").arg(location, attrName, attrValue);
    }

    EvaluatorId addEvaluator(const QString &expr, const QString &context)
    {
        EvaluatorInfo ei;
        ei.expr = addString(expr);
        ei.context = addString(context);
        return m_evaluators.add(ei);
    }

    EvaluatorId addAssignment(const QString &dest, const QString &expr, const QString &context)
    {
        AssignmentInfo ai;
        ai.dest = addString(dest);
        ai.expr = addString(expr);
        ai.context = addString(context);
        return m_assignments.add(ai);
    }

    EvaluatorId addForeach(const QString &array, const QString &item, const QString &index,
                           const QString &context)
    {
        ForeachInfo fi;
        fi.array = addString(array);
        fi.item = addString(item);
        fi.index = addString(index);
        fi.context = addString(context);
        return m_foreaches.add(fi);
    }

    EvaluatorId addDataElement(const QString &id, const QString &expr, const QString &context)
    {
        auto str = addString(id);
        if (!m_dataIds.contains(str))
            m_dataIds.append(str);
        if (expr.isEmpty())
            return NoEvaluator;

        return addAssignment(id, expr, context);
    }

private:
    template <class Container, typename T, typename U>
    class Table {
        Container &elements;
        QMap<T, int> indexForElement;

    public:
        Table(Container &storage)
            : elements(storage)
        {}

        U add(const T &s, bool uniqueOnly = true) {
            int pos = uniqueOnly ? indexForElement.value(s, -1) : -1;
            if (pos == -1) {
                pos = elements.size();
                elements.append(s);
                indexForElement.insert(s, pos);
            }
            return pos;
        }

        Container data() {
            return elements;
        }

        const T &item(U pos) const {
            return elements.at(pos);
        }
    };

    struct SequenceInfo {
        int location;
        qint32 entryCount; // the amount of qint32's that the instructions take up
    };

    class InstructionStorage {
    public:
        InstructionStorage(QList<qint32> &storage)
            : m_instr(storage)
            , m_info(nullptr)
        {}

        ContainerId newContainerId() const { return m_instr.size(); }

        template <typename T>
        T *add(int extra = 0)
        {
            const int pos = m_instr.size();
            const int size = sizeof(T) / sizeof(qint32) + extra;
            if (m_info)
                m_info->entryCount += size;
            m_instr.resize(pos + size);
            T *instr = at<T>(pos);
            Q_ASSERT(instr->instructionType == 0);
            instr->instructionType = T::kind();
            return instr;
        }

        int offset(Instruction *instr) const
        {
            return reinterpret_cast<qint32 *>(instr) - m_instr.data();
        }

        template <typename T>
        T *at(int offset)
        {
            return reinterpret_cast<T *>(&m_instr[offset]);
        }

        void setSequenceInfo(SequenceInfo *info)
        {
            m_info = info;
        }

    private:
        QList<qint32> &m_instr;
        SequenceInfo *m_info;
    };

    QList<SequenceInfo> m_activeSequences;

    GeneratedTableData::CreateFactoryId createFactoryId;
    GeneratedTableData &m_tableData;
    GeneratedTableData::DataModelInfo &m_dataModelInfo;
    Table<QStringList, QString, StringId> m_stringTable;
    InstructionStorage m_instructions;
    Table<QList<EvaluatorInfo>, EvaluatorInfo, EvaluatorId> m_evaluators;
    Table<QList<AssignmentInfo>, AssignmentInfo, EvaluatorId> m_assignments;
    Table<QList<ForeachInfo>, ForeachInfo, EvaluatorId> m_foreaches;
    QList<StringId> &m_dataIds;
    bool m_isCppDataModel = false;

    StateTable m_stateTable;
    QList<int> m_parents;
    QList<qint32> m_arrays;

    QList<StateTable::Transition> m_allTransitions;
    QHash<DocumentModel::Transition *, int> m_docTransitionIndices;
    QList<StateTable::State> m_allStates;
    QHash<DocumentModel::AbstractState *, int> m_docStatesIndices;
    QList<QList<int>> m_transitionsForState;

    int m_currentTransition = StateTable::InvalidIndex;
    bool m_bindLate = false;
    QList<DocumentModel::DataElement *> m_dataElements;
    Table<QStringList, QString, int> m_stateNames;
};

} // anonymous namespace

/*!
    \fn QScxmlTableData::~QScxmlTableData()
    Destroys the SXCML table data.
 */
QScxmlTableData::~QScxmlTableData()
{}

void GeneratedTableData::build(DocumentModel::ScxmlDocument *doc,
                               GeneratedTableData *table,
                               MetaDataInfo *metaDataInfo,
                               DataModelInfo *dataModelInfo,
                               GeneratedTableData::CreateFactoryId func)
{
    TableDataBuilder builder(*table, *metaDataInfo, *dataModelInfo, func);
    builder.buildTableData(doc);
}

QString GeneratedTableData::toString(const int *stateMachineTable)
{
    QString result;
    QTextStream out(&result);

    const StateTable *st = reinterpret_cast<const StateTable *>(stateMachineTable);

    out << "{" << Qt::endl
        << "\t0x" << Qt::hex << st->version << Qt::dec << ", // version" << Qt::endl
        << "\t" << st->name << ", // name" << Qt::endl
        << "\t" << st->dataModel << ", // data-model" << Qt::endl
        << "\t" << st->childStates << ", // child states array offset" << Qt::endl
        << "\t" << st->initialTransition << ", // transition to initial states" << Qt::endl
        << "\t" << st->initialSetup << ", // initial setup" << Qt::endl
        << "\t" << st->binding << ", // binding" << Qt::endl
        << "\t" << st->maxServiceId << ", // maxServiceId" << Qt::endl
        << "\t" << st->stateOffset << ", " << st->stateCount
                                           << ", // state offset and count" << Qt::endl
        << "\t" << st->transitionOffset << ", " << st->transitionCount
                                                << ", // transition offset and count" << Qt::endl
        << "\t" << st->arrayOffset << ", " << st->arraySize << ", // array offset and size" << Qt::endl
        << Qt::endl;

    out << "\t// States:" << Qt::endl;
    for (int i = 0; i < st->stateCount; ++i) {
        const StateTable::State &s = st->state(i);
        out << "\t"
            << s.name << ", "
            << s.parent << ", "
            << s.type << ", "
            << s.initialTransition << ", "
            << s.initInstructions << ", "
            << s.entryInstructions << ", "
            << s.exitInstructions << ", "
            << s.doneData << ", "
            << s.childStates << ", "
            << s.transitions << ", "
            << s.serviceFactoryIds << ","
            << Qt::endl;
    }

    out << Qt::endl
        << "\t// Transitions:" << Qt::endl;
    for (int i = 0; i < st->transitionCount; ++i) {
        auto t = st->transition(i);
        out << "\t"
            << t.events << ", "
            << t.condition << ", "
            << t.type << ", "
            << t.source << ", "
            << t.targets << ", "
            << t.transitionInstructions << ", "
            << Qt::endl ;
    }

    out << Qt::endl
        << "\t// Arrays:" << Qt::endl;
    int nextStart = 0;
    while (nextStart < st->arraySize) {
        const StateTable::Array a = st->array(nextStart);
        out << "\t" << a.size() << ", ";
        for (int j = 0; j < a.size(); ++j) {
            out << a[j] << ", ";
        }
        out << Qt::endl;
        nextStart += a.size() + 1;
    }

    out << Qt::hex;
    out << Qt::endl
        << "\t0x" << StateTable::terminator << " // terminator" << Qt::endl
        << "}";

    return result;
}

QString GeneratedTableData::string(StringId id) const
{
    return id == NoString ? QString() : theStrings.at(id);
}

InstructionId *GeneratedTableData::instructions() const
{
    return const_cast<InstructionId *>(theInstructions.data());
}

EvaluatorInfo GeneratedTableData::evaluatorInfo(EvaluatorId evaluatorId) const
{
    return theEvaluators[evaluatorId];
}

AssignmentInfo GeneratedTableData::assignmentInfo(EvaluatorId assignmentId) const
{
    return theAssignments[assignmentId];
}

ForeachInfo GeneratedTableData::foreachInfo(EvaluatorId foreachId) const
{
    return theForeaches[foreachId];
}

StringId *GeneratedTableData::dataNames(int *count) const
{
    Q_ASSERT(count);
    *count = theDataNameIds.size();
    return const_cast<StringId *>(theDataNameIds.data());
}

ContainerId GeneratedTableData::initialSetup() const
{
    return theInitialSetup;
}

QString GeneratedTableData::name() const
{
    return string(theName);
}

const qint32 *GeneratedTableData::stateMachineTable() const
{
    return theStateMachineTable.constData();
}

QScxmlInvokableServiceFactory *GeneratedTableData::serviceFactory(int id) const
{
    Q_UNUSED(id);
    return nullptr;
}
