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

#include "cppcodeformatter.h"

#include <texteditor/textdocumentlayout.h>
#include <cplusplus/Lexer.h>

#include <utils/qtcassert.h>

#include <QDebug>
#include <QMetaEnum>
#include <QTextDocument>
#include <QTextBlock>

using namespace CPlusPlus;
using namespace TextEditor;

namespace CppEditor {

CodeFormatter::~CodeFormatter() = default;

void CodeFormatter::setTabSize(int tabSize)
{
    m_tabSize = tabSize;
}

void CodeFormatter::recalculateStateAfter(const QTextBlock &block)
{
    restoreCurrentState(block.previous());

    bool endedJoined = false;
    // Discard newline expected bit from state
    const int lexerState = tokenizeBlock(block, &endedJoined) & ~0x80;
    m_tokenIndex = 0;
    m_newStates.clear();

    if (tokenAt(0).kind() == T_POUND) {
        enter(cpp_macro_start);
        m_tokenIndex = 1;
    }

    while (m_tokenIndex < m_tokens.size()) {
        m_currentToken = tokenAt(m_tokenIndex);
        const int kind = m_currentToken.kind();

        switch (m_currentState.top().type) {
        case topmost_intro:
            tryDeclaration();
            break;

        case namespace_start:
            switch (kind) {
            case T_LBRACE:      enter(namespace_open); break;
            case T_SEMICOLON:
            case T_RBRACE:      leave(); break;
            } break;

        case namespace_open:
            if (tryDeclaration())
                break;
            switch (kind) {
            case T_RBRACE:      leave(); continue; // always nested in namespace_start
            } break;

        case extern_start:
            switch (kind) {
            case T_STRING_LITERAL: break; // continue looking for the lbrace
            case T_LBRACE:      enter(extern_open); break;
            default:            leave(); continue;
            } break;

        case extern_open:
            if (tryDeclaration())
                break;
            switch (kind) {
            case T_RBRACE:      leave(); leave(); break; // always nested in extern_start
            } break;

        case class_start:
            switch (kind) {
            case T_SEMICOLON:   leave(); break;
            case T_LPAREN:      turnInto(declaration_start); continue; // "struct Foo bar() {"
            case T_LBRACE:      enter(class_open); break;
            } break;

        case class_open:
            if (tryDeclaration())
                break;
            switch (kind) {
            case T_RBRACE:      leave(); continue; // always nested in class_start
            } break;

        case access_specifier_start:
            switch (kind) {
            case T_COLON:       leave(); break;
            } break;

        case enum_start:
            switch (kind) {
            case T_SEMICOLON:   leave(); break;
            case T_LPAREN:      turnInto(declaration_start); continue; // "enum Foo bar() {"
            case T_LBRACE:      enter(enum_open); break;
            } break;

        case enum_open:
            switch (kind) {
            case T_RBRACE:      leave(); continue; // always nested in enum_start
            case T_LBRACE:      enter(brace_list_open); break;
            } break;

        case brace_list_open:
            switch (kind) {
            case T_RBRACE:      leave(); break;
            case T_LBRACE:      enter(brace_list_open); break;
            } break;

        case using_start:
            switch (kind) {
            case T_SEMICOLON:   leave(); break;
            } break;

        case template_start:
            switch (kind) {
            case T_LESS:        turnInto(template_param); break;
            } break;

        case template_param:
            switch (kind) {
            case T_LESS:        enter(template_param); break;
            case T_GREATER:     leave(); break;
            case T_GREATER_GREATER: leave(); leave(); break; // call leave twice to pop both template_param states
            } break;

        case operator_declaration:
            switch (kind) {
            case T_LPAREN:      break;
            default:            leave(); break;
            } break;

        case declaration_start:
            switch (kind) {
            case T_CLASS:
            case T_STRUCT:      turnInto(class_start); continue;
            case T_ENUM:        turnInto(enum_start); continue;
            case T_RBRACE:      leave(true); continue;
            case T_SEMICOLON:   leave(true); break;
            case T_EQUAL:       enter(assign_open_or_initializer); break;
            case T_LBRACE:      enter(defun_open); break;
            case T_COLON:       enter(member_init_open); enter(member_init_expected); break;
            case T_OPERATOR:    enter(operator_declaration); break;
            case T_GREATER_GREATER: break;
            case T_LBRACKET: break;
            case T_NAMESPACE:   leave(); enter(namespace_start); break;
            case T_IDENTIFIER:
                if (isStatementMacroOrEquivalent()) {
                    enter(qt_like_macro);
                    break;
                }
                [[fallthrough]];
            default:            tryExpression(true); break;
            } break;

        case assign_open_or_initializer:
            switch (kind) {
            case T_LBRACE:      enter(brace_list_open); break;
            case T_RBRACE:      leave(true); continue;
            case T_SEMICOLON:
            case T_RPAREN:
            case T_COMMA:       leave(); continue;
            default:            enter(assign_open); continue;
            } break;

        case expression:
            switch (kind) {
            case T_RBRACE:      leave(true); continue;
            case T_SEMICOLON:   leave(); continue;
            case T_LBRACE:
            case T_COLON:
                if (m_currentState.at(m_currentState.size() - 2).type == declaration_start) {
                    // oops, the expression was a function declaration argument list, hand lbrace/colon to declaration_start
                    leave();
                    continue;
                } else {
                    turnInto(substatement_open);
                }
                break;
            case T_ARROW: // Trailing return type?
                if (m_currentState.at(m_currentState.size() - 2).type == declaration_start)
                    leave();
                else
                    tryExpression();
                break;
            default:            tryExpression(); break;
            } break;

        case assign_open:
            switch (kind) {
            case T_RBRACE:      leave(true); continue;
            case T_SEMICOLON:
            case T_RPAREN:
            case T_COMMA:       leave(); continue;
            default:            tryExpression(); break;
            } break;

        case lambda_instroducer_or_subscribtion:
            switch (kind) {
            case T_RBRACKET:    turnInto(lambda_declarator_expected); break; // we can't determine exact kind of expression. Try again
            case T_COMMA:
            case T_EQUAL:       turnInto(lambda_instroducer); break;              // ',' or '=' inside brackets can be only within lambda capture list
            case T_IDENTIFIER:          // '&', id, 'this' are allowed both in the capture list and subscribtion
            case T_AMPER:
            case T_THIS:        break;
            default:            tryExpression(m_currentState.at(m_currentState.size() - 2).type == declaration_start); break;
                                        // any other symbol allowed only in subscribtion operator
            } break;

        case lambda_declarator_expected:
            switch (kind) {
            case T_LPAREN:      turnInto(lambda_declarator_or_expression); break; // '(' just after ']'. We can't make decisioin here
            case T_LBRACE:      turnInto(substatement_open); break; // '{' just after ']' opens a lambda-compound statement
            default:
                if (m_currentState.size() >= 3 && m_currentState.at(m_currentState.size() - 3).type == declaration_start)
                    leave();

                leave();
                continue;
            } break;

        case lambda_instroducer:
            switch (kind) {
            case T_RBRACKET:    turnInto(lambda_declarator); break;
            } break;

        case lambda_declarator_or_expression:
            switch (kind) {
            case T_LBRACE:      turnInto(substatement_open); /*tryStatement();*/ break;
            case T_RPAREN:      turnInto(lambda_statement_expected); break;
            case T_IDENTIFIER:
            case T_SEMICOLON:   leave(); continue;
            default:
                if (tryDeclaration()) {// We found the declaration within '()' so it is lambda declarator
                    leave();
                    turnInto(lambda_declarator);
                    break;
                } else {
                    turnInto(expression);
                    enter(arglist_open);
                    continue;
                }
            } break;

        case lambda_statement_expected:
            switch (kind) {
            case T_LBRACE:      turnInto(substatement_open); /*tryStatement()*/; break;
            case T_NOEXCEPT:    // 'noexcept', 'decltype' and 'mutable' are only part of lambda declarator
            case T_DECLTYPE:
            case T_MUTABLE:     turnInto(lambda_declarator); break;
            case T_RBRACKET:    // '[', ']' and '->' can be part of lambda declarator
            case T_LBRACKET:
            case T_ARROW:       break;
            default:
                if (m_tokenIndex > 0 && tokenAt(m_tokenIndex - 1).kind() == T_ARROW) {
                    break;
                } else {
                    leave();
                    continue;
                }
            } break;

        case lambda_declarator:
            switch (kind) {
            case T_LBRACE:      turnInto(substatement_open); /*tryStatement()*/; break;
            } break;

        case arglist_open:
            switch (kind) {
            case T_SEMICOLON:   leave(true); break;
            case T_LBRACE:      enter(brace_list_open); break;
            case T_RBRACE:      leave(true); continue;
            case T_RPAREN:
                leave();
                if (m_currentState.top().type == qt_like_macro && m_currentState.size() > 1
                    && m_currentState.at(m_currentState.size() - 2).type == declaration_start) {
                    leave();
                    leave();
                } else if (m_currentState.top().type == catch_statement) {
                    turnInto(substatement);
                }
                break;
            default:            tryExpression(); break;
            } break;

        case braceinit_open:
            switch (kind) {
            case T_RBRACE:      leave(); break;
            case T_RPAREN:      leave(); continue; // recover?
            default:            tryExpression(); break;
            } break;

        case ternary_op:
            switch (kind) {
            case T_RPAREN:
            case T_COMMA:
            case T_SEMICOLON:   leave(); continue; // always nested, propagate
            default:            tryExpression(); break;
            } break;

        case stream_op:
        case stream_op_cont:
            switch (kind) {
            case T_LESS_LESS:
            case T_GREATER_GREATER:
                if (m_currentState.top().type == stream_op)
                    enter(stream_op_cont);
                else // stream_op_cont already
                    turnInto(stream_op_cont);
                break;
            case T_RPAREN:
            case T_COMMA:
            case T_SEMICOLON:   leave(); continue; // always nested, propagate
            default:            tryExpression(); break;
            } break;

        case member_init_open:
            switch (kind) {
            case T_LBRACE:      turnInto(defun_open); break;
            case T_COMMA:       enter(member_init_expected); break;
            case T_SEMICOLON:   leave(); continue; // try to recover
            } break;

        case member_init_expected:
            switch (kind) {
            case T_IDENTIFIER:  turnInto(member_init); break;
            case T_LBRACE:
            case T_SEMICOLON:   leave(); continue; // try to recover
            } break;

        case member_init:
            switch (kind) {
            case T_LBRACE:
            case T_LPAREN:      enter(member_init_nest_open); break;
            case T_RBRACE:
            case T_RPAREN:      leave(); break;
            case T_SEMICOLON:   leave(); continue; // try to recover
            } break;

        case member_init_nest_open:
            switch (kind) {
            case T_RBRACE:
            case T_RPAREN:      leave(); continue;
            case T_SEMICOLON:   leave(); continue; // try to recover
            default:            tryExpression(); break;
            } break;

        case defun_open:
            if (tryStatement())
                break;
            switch (kind) {
            case T_RBRACE:      leave(); leave(); break; // always nested in declaration_start
            } break;

        case switch_statement:
        case statement_with_condition:
        case if_statement:
            switch (kind) {
            case T_LPAREN:      enter(condition_open); break;
            default:            leave(true); continue;
            } break;

        case maybe_else:
            if (m_currentToken.isComment()) {
                break;
            } else if (kind == T_ELSE) {
                turnInto(else_clause);
                enter(substatement);
                break;
            } else {
                leave(true);
                continue;
            }

        case else_clause:
            // ### shouldn't happen
            dump();
            QTC_CHECK(false);
            leave(true);
            break;

        case do_statement:
            // ### shouldn't happen
            dump();
            QTC_CHECK(false);
            leave(true);
            break;

        case return_statement:
            switch (kind) {
            case T_RBRACE:      leave(true); continue;
            case T_SEMICOLON:   leave(true); break;
            } break;

        case substatement:
            // prefer substatement_open over block_open
            if (kind != T_LBRACE && tryStatement())
                break;
            switch (kind) {
            case T_LBRACE:      turnInto(substatement_open); break;
            case T_SEMICOLON:   leave(true); break;
            case T_RBRACE:      leave(true); continue;
            } break;

        case for_statement:
            switch (kind) {
            case T_LPAREN:      enter(for_statement_paren_open); break;
            default:            leave(true); continue;
            } break;

        case catch_statement:
            switch (kind) {
            case T_LPAREN:      enter(arglist_open); break;
            } break;

        case for_statement_paren_open:
            enter(for_statement_init); continue;

        case for_statement_init:
            switch (kind) {
            case T_SEMICOLON:   turnInto(for_statement_condition); break;
            case T_LPAREN:      enter(condition_paren_open); break;
            case T_RPAREN:      turnInto(for_statement_expression); continue;
            } break;

        case for_statement_condition:
            switch (kind) {
            case T_SEMICOLON:   turnInto(for_statement_expression); break;
            case T_LPAREN:      enter(condition_paren_open); break;
            case T_RPAREN:      turnInto(for_statement_expression); continue;
            } break;

        case for_statement_expression:
            switch (kind) {
            case T_RPAREN:      leave(); turnInto(substatement); break;
            case T_LPAREN:      enter(condition_paren_open); break;
            } break;

        case case_start:
            switch (kind) {
            case T_COLON:       turnInto(case_cont); break;
            } break;

        case case_cont:
            if (kind != T_CASE && kind != T_DEFAULT && tryStatement())
                break;
            switch (kind) {
            case T_RBRACE:
            case T_DEFAULT:
            case T_CASE:        leave(); continue;
            } break;

        case substatement_open:
            if (tryStatement())
                break;
            switch (kind) {
            case T_RBRACE:      leave(true); break;
            } break;

        case condition_open:
            switch (kind) {
            case T_RPAREN:      turnInto(substatement); break;
            case T_LPAREN:      enter(condition_paren_open); break;
            } break;

        case block_open:
            if (tryStatement())
                break;
            switch (kind) {
            case T_RBRACE:      leave(true); break;
            } break;

        // paren nesting
        case condition_paren_open:
            switch (kind) {
            case T_RPAREN:      leave(); break;
            case T_LPAREN:      enter(condition_paren_open); break;
            } break;

        case qt_like_macro:
            switch (kind) {
            case T_LPAREN:      enter(arglist_open); break;
            case T_SEMICOLON:   leave(true); break;
            default:            leave(); continue;
            } break;

        case label:
            switch (kind) {
            case T_COLON:       leave(); break;
            default:            leave(); continue; // shouldn't happen
            } break;

        case multiline_comment_start:
        case multiline_comment_cont:
            if (kind != T_COMMENT && kind != T_DOXY_COMMENT) {
                leave();
                continue;
            } else if (m_tokenIndex == m_tokens.size() - 1
                    && lexerState == 0) {
                leave();
            } else if (m_tokenIndex == 0 && m_currentToken.isComment()) {
                // to allow enter/leave to update the indentDepth
                turnInto(multiline_comment_cont);
            }
            break;

        case cpp_macro_start: {
            const int size = m_currentState.size();

            int previousMarker = -1;
            int previousPreviousMarker = -1;
            for (int i = size - 1; i >= 0; --i) {
                if (m_currentState.at(i).type == cpp_macro_conditional) {
                    if (previousMarker == -1) {
                        previousMarker = i;
                    } else {
                        previousPreviousMarker = i;
                        break;
                    }
                }
            }

            QStringView tokenText = currentTokenText();
            if (tokenText == QLatin1String("ifdef")
                    || tokenText == QLatin1String("if")
                    || tokenText == QLatin1String("ifndef")) {
                enter(cpp_macro_conditional);
                // copy everything right of previousMarker, excluding cpp_macro_conditional
                for (int i = previousMarker + 1; i < size; ++i)
                    m_currentState += m_currentState.at(i);
            }
            if (previousMarker != -1) {
                if (tokenText == QLatin1String("endif")) {
                    QStack<State>::iterator begin = m_currentState.begin() + previousPreviousMarker + 1;
                    QStack<State>::iterator end = m_currentState.begin() + previousMarker + 1;
                    m_currentState.erase(begin, end);
                } else if (tokenText == QLatin1String("else")
                        || tokenText == QLatin1String("elif")) {
                    m_currentState.resize(previousMarker + 1);
                    for (int i = previousPreviousMarker + 1; i < previousMarker; ++i)
                        m_currentState += m_currentState.at(i);
                }
            }

            turnInto(cpp_macro);
            break;
        }

        case cpp_macro:
        case cpp_macro_cont:
            break;

        case string_open:
        case raw_string_open:
            if (!m_currentToken.isStringLiteral()) {
                leave();
                continue;
            }
            break;

        default:
            qWarning() << "Unhandled state" << m_currentState.top().type;
            break;

        } // end of state switch

        ++m_tokenIndex;
    }

    int topState = m_currentState.top().type;

    if (topState != multiline_comment_start
            && topState != multiline_comment_cont
            && (lexerState == T_COMMENT
                || lexerState == T_DOXY_COMMENT)) {
        enter(multiline_comment_start);
    }

    if (topState == qt_like_macro)
        leave(true);

    if ((topState == cpp_macro_cont
            || topState == cpp_macro) && !endedJoined)
        leave();

    if (topState == cpp_macro && endedJoined)
        turnInto(cpp_macro_cont);

    saveCurrentState(block);
}

void CodeFormatter::indentFor(const QTextBlock &block, int *indent, int *padding)
{
//    qDebug() << "indenting for" << block.blockNumber() + 1;

    restoreCurrentState(block.previous());
    correctIndentation(block);
    *indent = m_indentDepth;
    *padding = m_paddingDepth;
}

void CodeFormatter::indentForNewLineAfter(const QTextBlock &block, int *indent, int *padding)
{
    restoreCurrentState(block);
    *indent = m_indentDepth;
    *padding = m_paddingDepth;

    int lexerState = loadLexerState(block);
    m_tokens.clear();
    m_currentLine.clear();
    adjustIndent(m_tokens, lexerState, indent, padding);
}

void CodeFormatter::updateStateUntil(const QTextBlock &endBlock)
{
    QStack<State> previousState = initialState();
    QTextBlock it = endBlock.document()->firstBlock();

    // find the first block that needs recalculation
    for (; it.isValid() && it != endBlock; it = it.next()) {
        BlockData blockData;
        if (!loadBlockData(it, &blockData))
            break;
        if (blockData.m_blockRevision != it.revision())
            break;
        if (previousState.isEmpty() || blockData.m_beginState.isEmpty()
                || previousState != blockData.m_beginState)
            break;
        if (loadLexerState(it) == -1)
            break;
        previousState = blockData.m_endState;
    }

    if (it == endBlock)
        return;

    // update everthing until endBlock
    for (; it.isValid() && it != endBlock; it = it.next()) {
        recalculateStateAfter(it);
    }

    // invalidate everything below by marking the state in endBlock as invalid
    if (it.isValid()) {
        BlockData invalidBlockData;
        saveBlockData(&it, invalidBlockData);
    }
}

void CodeFormatter::updateLineStateChange(const QTextBlock &block)
{
    if (!block.isValid())
        return;

    BlockData blockData;
    if (loadBlockData(block, &blockData) && blockData.m_blockRevision == block.revision())
        return;

    recalculateStateAfter(block);

    // invalidate everything below by marking the next block's state as invalid
    QTextBlock next = block.next();
    if (!next.isValid())
        return;

    saveBlockData(&next, BlockData());
}

bool CodeFormatter::isInRawStringLiteral(const QTextBlock &block) const
{
    if (!block.previous().isValid())
        return false;
    BlockData blockData;
    if (!loadBlockData(block.previous(), &blockData))
        return false;
    return !blockData.m_endState.isEmpty() && blockData.m_endState.top().type == raw_string_open;
}

CodeFormatter::State CodeFormatter::state(int belowTop) const
{
    if (belowTop < m_currentState.size())
        return m_currentState.at(m_currentState.size() - 1 - belowTop);
    else
        return {};
}

int CodeFormatter::tokenIndex() const
{
    return m_tokenIndex;
}

int CodeFormatter::tokenCount() const
{
    return m_tokens.size();
}

const Token &CodeFormatter::currentToken() const
{
    return m_currentToken;
}

void CodeFormatter::invalidateCache(QTextDocument *document)
{
    if (!document)
        return;

    BlockData invalidBlockData;
    QTextBlock it = document->firstBlock();
    for (; it.isValid(); it = it.next()) {
        saveBlockData(&it, invalidBlockData);
    }
}

void CodeFormatter::enter(int newState)
{
    int savedIndentDepth = m_indentDepth;
    int savedPaddingDepth = m_paddingDepth;
    onEnter(newState, &m_indentDepth, &savedIndentDepth, &m_paddingDepth, &savedPaddingDepth);
    State s(newState, savedIndentDepth, savedPaddingDepth);
    m_currentState.push(s);
    m_newStates.push(s);
}

void CodeFormatter::leave(bool statementDone)
{
    QTC_ASSERT(m_currentState.size() > 1, return);
    if (m_currentState.top().type == topmost_intro)
        return;

    if (m_newStates.size() > 0)
        m_newStates.pop();

    // restore indent depth
    State poppedState = m_currentState.pop();
    m_indentDepth = poppedState.savedIndentDepth;
    m_paddingDepth = poppedState.savedPaddingDepth;

    int topState = m_currentState.top().type;

    // does it suffice to check if token is T_SEMICOLON or T_RBRACE?
    // maybe distinction between leave and turnInto?
    if (statementDone) {
        if (topState == substatement
                || topState == statement_with_condition
                || topState == for_statement
                || topState == switch_statement
                || topState == do_statement) {
            leave(true);
        } else if (topState == if_statement) {
            if (poppedState.type != maybe_else)
                enter(maybe_else);
            else
                leave(true);
        } else if (topState == else_clause) {
            // leave the else *and* the surrounding if, to prevent another else
            leave();
            leave(true);
        }
    }
}

void CodeFormatter::correctIndentation(const QTextBlock &block)
{
    const int lexerState = tokenizeBlock(block);
    QTC_ASSERT(m_currentState.size() >= 1, return);

    adjustIndent(m_tokens, lexerState, &m_indentDepth, &m_paddingDepth);
}

bool CodeFormatter::isStatementMacroOrEquivalent() const
{
    const QStringView tokenText = currentTokenText();
    return tokenText.startsWith(QLatin1String("Q_"))
           || tokenText.startsWith(QLatin1String("QT_"))
           || tokenText.startsWith(QLatin1String("QML_"))
           || tokenText.startsWith(QLatin1String("QDOC_"))
           || m_statementMacros.contains(tokenText);
}

bool CodeFormatter::tryExpression(bool alsoExpression)
{
    int newState = -1;

    const int kind = m_currentToken.kind();
    switch (kind) {
    case T_LPAREN:          newState = arglist_open; break;
    case T_QUESTION:        newState = ternary_op; break;
    case T_LBRACE:          newState = braceinit_open; break;

    case T_EQUAL:
    case T_AMPER_EQUAL:
    case T_CARET_EQUAL:
    case T_SLASH_EQUAL:
    case T_EXCLAIM_EQUAL:
    case T_GREATER_GREATER_EQUAL:
    case T_LESS_LESS_EQUAL:
    case T_MINUS_EQUAL:
    case T_PERCENT_EQUAL:
    case T_PIPE_EQUAL:
    case T_PLUS_EQUAL:
    case T_STAR_EQUAL:
    case T_TILDE_EQUAL:
        newState = assign_open;
        break;

    case T_LESS_LESS:
    case T_GREATER_GREATER:
        newState = stream_op;
        for (int i = m_currentState.size() - 1; i >= 0; --i) {
            const int type = m_currentState.at(i).type;
            if (type == arglist_open || type == braceinit_open) { // likely a left-shift instead
                newState = -1;
                break;
            }
            if (type == topmost_intro
                    || type == substatement_open
                    || type == defun_open
                    || type == namespace_open
                    || type == extern_open
                    || type == class_open
                    || type == brace_list_open) {
                break;
            }
        }
        break;
    case T_LBRACKET:
        newState = lambda_instroducer_or_subscribtion;
        break;
    }

    if (m_currentToken.isStringLiteral()) {
        newState = m_currentToken.kind() >= T_FIRST_RAW_STRING_LITERAL
                && m_currentToken.kind() <= T_LAST_RAW_STRING_LITERAL ? raw_string_open
                                                                      : string_open;
    }

    if (newState != -1) {
        if (alsoExpression)
            enter(expression);
        enter(newState);
        return true;
    }

    return false;
}

bool CodeFormatter::tryDeclaration()
{
    const int kind = m_currentToken.kind();
    switch (kind) {
    case T_Q_ENUMS:
    case T_Q_PROPERTY:
    case T_Q_PRIVATE_PROPERTY:
    case T_Q_FLAGS:
    case T_Q_GADGET:
    case T_Q_OBJECT:
    case T_Q_INTERFACES:
    case T_Q_DECLARE_INTERFACE:
    case T_Q_PRIVATE_SLOT:
        enter(qt_like_macro);
        return true;
    case T_IDENTIFIER:
        if (m_tokenIndex == 0) {
            if (isStatementMacroOrEquivalent()) {
                enter(qt_like_macro);
                return true;
            }
            if (m_tokens.size() > 1 && m_tokens.at(1).kind() == T_COLON) {
                enter(label);
                return true;
            }
        }
        Q_FALLTHROUGH();
    case T_CHAR:
    case T_CHAR16_T:
    case T_CHAR32_T:
    case T_WCHAR_T:
    case T_BOOL:
    case T_SHORT:
    case T_INT:
    case T_LONG:
    case T_SIGNED:
    case T_UNSIGNED:
    case T_FLOAT:
    case T_DOUBLE:
    case T_VOID:
    case T_AUTO:
    case T___TYPEOF__:
    case T___ATTRIBUTE__:
    case T___DECLSPEC:
    case T_STATIC:
    case T_FRIEND:
    case T_CONST:
    case T_VOLATILE:
    case T_INLINE:
        enter(declaration_start);
        return true;

    case T_TEMPLATE:
        enter(template_start);
        return true;

    case T_NAMESPACE:
        enter(namespace_start);
        return true;

    case T_EXTERN:
        enter(extern_start);
        return true;

    case T_STRUCT:
    case T_UNION:
    case T_CLASS:
        enter(class_start);
        return true;

    case T_ENUM:
        enter(enum_start);
        return true;

    case T_USING:
        enter(using_start);
        return true;

    case T_PUBLIC:
    case T_PRIVATE:
    case T_PROTECTED:
    case T_Q_SIGNALS:
        if (m_currentState.top().type == class_open) {
            enter(access_specifier_start);
            return true;
        }
        return false;

    default:
        return false;
    }
}

bool CodeFormatter::tryStatement()
{
    const int kind = m_currentToken.kind();
    if (tryDeclaration())
        return true;
    switch (kind) {
    case T_RETURN:
    case T_CO_RETURN:
        enter(return_statement);
        enter(expression);
        return true;
    case T_FOR:
        enter(for_statement);
        return true;
    case T_SWITCH:
        enter(switch_statement);
        return true;
    case T_IF:
        enter(if_statement);
        return true;
    case T_WHILE:
    case T_Q_FOREACH:
        enter(statement_with_condition);
        return true;
    case T_DO:
        enter(do_statement);
        enter(substatement);
        return true;
    case T_TRY:
        enter(substatement);
        return true;
    case T_CATCH:
        enter(catch_statement);
        return true;
    case T_CASE:
    case T_DEFAULT:
        enter(case_start);
        return true;
    case T_LBRACE:
        enter(block_open);
        return true;
    default:
        return false;
    }
}

bool CodeFormatter::isBracelessState(int type) const
{
    return type == substatement
        || type == if_statement
        || type == else_clause
        || type == statement_with_condition
        || type == for_statement
        || type == do_statement;
}

const Token &CodeFormatter::tokenAt(int idx) const
{
    static const Token empty;
    if (idx < 0 || idx >= m_tokens.size())
        return empty;
    else
        return m_tokens.at(idx);
}

int CodeFormatter::column(int index) const
{
    int col = 0;
    if (index > m_currentLine.length())
        index = m_currentLine.length();

    const QChar tab = QLatin1Char('\t');

    for (int i = 0; i < index; i++) {
        if (m_currentLine[i] == tab)
            col = ((col / m_tabSize) + 1) * m_tabSize;
        else
            col++;
    }
    return col;
}

QStringView CodeFormatter::currentTokenText() const
{
    if (m_currentToken.utf16charsEnd() > m_currentLine.size())
        return QStringView(m_currentLine).mid(m_currentToken.utf16charsBegin());
    return QStringView(m_currentLine).mid(m_currentToken.utf16charsBegin(), m_currentToken.utf16chars());
}

void CodeFormatter::turnInto(int newState)
{
    leave(false);
    enter(newState);
}

void CodeFormatter::saveCurrentState(const QTextBlock &block)
{
    if (!block.isValid())
        return;

    BlockData blockData;
    blockData.m_blockRevision = block.revision();
    blockData.m_beginState = m_beginState;
    blockData.m_endState = m_currentState;
    blockData.m_indentDepth = m_indentDepth;
    blockData.m_paddingDepth = m_paddingDepth;

    QTextBlock saveableBlock(block);
    saveBlockData(&saveableBlock, blockData);
}

void CodeFormatter::restoreCurrentState(const QTextBlock &block)
{
    if (block.isValid()) {
        BlockData blockData;
        if (loadBlockData(block, &blockData)) {
            m_indentDepth = blockData.m_indentDepth;
            m_paddingDepth = blockData.m_paddingDepth;
            m_currentState = blockData.m_endState;
            m_beginState = m_currentState;
            return;
        }
    }

    m_currentState = initialState();
    m_beginState = m_currentState;
    m_indentDepth = 0;
    m_paddingDepth = 0;
}

QStack<CodeFormatter::State> CodeFormatter::initialState()
{
    static QStack<CodeFormatter::State> initialState;
    if (initialState.isEmpty())
        initialState.push(State(topmost_intro, 0, 0));
    return initialState;
}

int CodeFormatter::tokenizeBlock(const QTextBlock &block, bool *endedJoined)
{
    int startState = loadLexerState(block.previous());
    if (block.blockNumber() == 0)
        startState = 0;
    QTC_ASSERT(startState != -1, return 0);

    LanguageFeatures features;
    features.qtEnabled = true;
    features.qtMocRunEnabled = true;
    features.qtKeywordsEnabled = true;
    features.cxxEnabled = true;
    features.objCEnabled = true;
    features.cxx11Enabled = true;
    features.cxx14Enabled = true;

    SimpleLexer tokenize;
    tokenize.setLanguageFeatures(features);
    tokenize.setExpectedRawStringSuffix(
                TextDocumentLayout::expectedRawStringSuffix(block.previous()));

    m_currentLine = block.text();
    // to determine whether a line was joined, Tokenizer needs a
    // newline character at the end
    m_currentLine.append(QLatin1Char('\n'));
    m_tokens = tokenize(m_currentLine, startState);

    if (endedJoined)
        *endedJoined = tokenize.endedJoined();

    const int lexerState = tokenize.state();
    TextDocumentLayout::setLexerState(block, lexerState);
    TextDocumentLayout::setExpectedRawStringSuffix(block, tokenize.expectedRawStringSuffix());
    return lexerState;
}

void CodeFormatter::dump() const
{
    QMetaEnum metaEnum = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("StateType"));

    qDebug() << "Current token index" << m_tokenIndex;
    qDebug() << "Current state:";
    for (const State &s : std::as_const(m_currentState))
        qDebug() << metaEnum.valueToKey(s.type) << s.savedIndentDepth << s.savedPaddingDepth;

    qDebug() << "Current indent depth:" << m_indentDepth;
    qDebug() << "Current padding depth:" << m_paddingDepth;
}


namespace Internal {
class CppCodeFormatterData: public CodeFormatterData
{
public:
    CodeFormatter::BlockData m_data;
};
} // namespace Internal

using namespace Internal;

QtStyleCodeFormatter::QtStyleCodeFormatter() = default;

QtStyleCodeFormatter::QtStyleCodeFormatter(const TabSettings &tabSettings,
                                           const CppCodeStyleSettings &settings)
    : m_tabSettings(tabSettings)
    , m_styleSettings(settings)
{
    setTabSize(tabSettings.m_tabSize);
    setStatementMacros(m_styleSettings.statementMacros);
}

void QtStyleCodeFormatter::setTabSettings(const TabSettings &tabSettings)
{
    m_tabSettings = tabSettings;
    setTabSize(tabSettings.m_tabSize);
}

void QtStyleCodeFormatter::setCodeStyleSettings(const CppCodeStyleSettings &settings)
{
    m_styleSettings = settings;
    setStatementMacros(m_styleSettings.statementMacros);
}

void QtStyleCodeFormatter::saveBlockData(QTextBlock *block, const BlockData &data) const
{
    TextBlockUserData *userData = TextDocumentLayout::userData(*block);
    auto cppData = static_cast<CppCodeFormatterData *>(userData->codeFormatterData());
    if (!cppData) {
        cppData = new CppCodeFormatterData;
        userData->setCodeFormatterData(cppData);
    }
    cppData->m_data = data;
}

bool QtStyleCodeFormatter::loadBlockData(const QTextBlock &block, BlockData *data) const
{
    TextBlockUserData *userData = TextDocumentLayout::textUserData(block);
    if (!userData)
        return false;
    auto cppData = static_cast<const CppCodeFormatterData *>(userData->codeFormatterData());
    if (!cppData)
        return false;

    *data = cppData->m_data;
    return true;
}

void QtStyleCodeFormatter::saveLexerState(QTextBlock *block, int state) const
{
    TextDocumentLayout::setLexerState(*block, state);
}

int QtStyleCodeFormatter::loadLexerState(const QTextBlock &block) const
{
    return TextDocumentLayout::lexerState(block);
}

void QtStyleCodeFormatter::addContinuationIndent(int *paddingDepth) const
{
    if (*paddingDepth == 0)
        *paddingDepth = 2*m_tabSettings.m_indentSize;
    else
        *paddingDepth += m_tabSettings.m_indentSize;
}

void QtStyleCodeFormatter::onEnter(int newState, int *indentDepth, int *savedIndentDepth, int *paddingDepth, int *savedPaddingDepth) const
{
    const State &parentState = state();
    const Token &tk = currentToken();
    const bool firstToken = (tokenIndex() == 0);
    const bool lastToken = (tokenIndex() == tokenCount() - 1);
    const int tokenPosition = column(tk.utf16charsBegin());
    const int nextTokenPosition = lastToken ? tokenPosition + tk.utf16chars()
                                            : column(tokenAt(tokenIndex() + 1).utf16charsBegin());
    const int spaceOrNextTokenPosition = lastToken ? tokenPosition + tk.utf16chars() + 1
                                                   : nextTokenPosition;

    if (shouldClearPaddingOnEnter(newState))
        *paddingDepth = 0;

    switch (newState) {
    case extern_start:
    case namespace_start:
        if (firstToken) {
            *savedIndentDepth = tokenPosition;
            *indentDepth = tokenPosition;
        }
        break;

    case enum_start:
    case class_start:
        if (firstToken) {
            *savedIndentDepth = tokenPosition;
            *indentDepth = tokenPosition;
        }
        *paddingDepth = 2*m_tabSettings.m_indentSize;
        break;

    case template_param:
        if (!lastToken)
            *paddingDepth = nextTokenPosition-*indentDepth;
        else
            addContinuationIndent(paddingDepth);
        break;

    case statement_with_condition:
    case for_statement:
    case switch_statement:
    case if_statement:
    case return_statement:
        if (firstToken)
            *indentDepth = *savedIndentDepth = tokenPosition;
        *paddingDepth = 2*m_tabSettings.m_indentSize;
        break;

    case declaration_start:
        if (firstToken) {
            *savedIndentDepth = tokenPosition;
            *indentDepth = *savedIndentDepth;
        }
        // continuation indent in function bodies only, to not indent
        // after the return type in "void\nfoo() {}"
        for (int i = 0; state(i).type != topmost_intro; ++i) {
            if (state(i).type == defun_open) {
                *paddingDepth = 2*m_tabSettings.m_indentSize;
                break;
            }
        }
        break;

    case assign_open:
        if (parentState.type == assign_open_or_initializer)
            break;
        Q_FALLTHROUGH();
    case assign_open_or_initializer:
        if (!lastToken && m_styleSettings.alignAssignments)
            *paddingDepth = nextTokenPosition-*indentDepth;
        else
            *paddingDepth = 2*m_tabSettings.m_indentSize;
        break;

    case arglist_open:
    case condition_paren_open:
    case member_init_nest_open:
        if (!lastToken)
            *paddingDepth = nextTokenPosition-*indentDepth;
        else
            addContinuationIndent(paddingDepth);
        break;

    case ternary_op:
        if (!lastToken)
            *paddingDepth = spaceOrNextTokenPosition-*indentDepth;
        else
            addContinuationIndent(paddingDepth);
        break;

    case stream_op:
        *paddingDepth = spaceOrNextTokenPosition-*indentDepth;
        break;
    case stream_op_cont:
        if (firstToken)
            *savedPaddingDepth = *paddingDepth = spaceOrNextTokenPosition-*indentDepth;
        break;

    case member_init_open:
        // undo the continuation indent of the parent
        *savedPaddingDepth = 0;

        // The paddingDepth is the expected location of the ',' and
        // identifiers are padded +2 from that in member_init_expected.
        if (firstToken)
            *paddingDepth = tokenPosition-*indentDepth;
        else
            *paddingDepth = m_tabSettings.m_indentSize - 2;
        break;

    case member_init_expected:
        *paddingDepth += 2;
        break;

    case member_init:
        // make continuation indents relative to identifier start
        *paddingDepth = tokenPosition - *indentDepth;
        if (firstToken) {
            // see comment in member_init_open
            *savedPaddingDepth = *paddingDepth - 2;
        }
        break;

    case case_cont:
        if (m_styleSettings.indentStatementsRelativeToSwitchLabels)
            *indentDepth += m_tabSettings.m_indentSize;
        break;

    case namespace_open:
    case class_open:
    case enum_open:
    case defun_open: {
        // undo the continuation indent of the parent
        *savedPaddingDepth = 0;

        // whether the { is followed by a non-comment token
        bool followedByData = (!lastToken && !tokenAt(tokenIndex() + 1).isComment());
        if (followedByData)
            *savedPaddingDepth = tokenPosition-*indentDepth; // pad the } to align with the {

        if (newState == class_open) {
            if (m_styleSettings.indentAccessSpecifiers
                    || m_styleSettings.indentDeclarationsRelativeToAccessSpecifiers)
                *indentDepth += m_tabSettings.m_indentSize;
            if (m_styleSettings.indentAccessSpecifiers && m_styleSettings.indentDeclarationsRelativeToAccessSpecifiers)
                *indentDepth += m_tabSettings.m_indentSize;
        } else if (newState == defun_open) {
            if (m_styleSettings.indentFunctionBody || m_styleSettings.indentFunctionBraces)
                *indentDepth += m_tabSettings.m_indentSize;
            if (m_styleSettings.indentFunctionBody && m_styleSettings.indentFunctionBraces)
                *indentDepth += m_tabSettings.m_indentSize;
        } else if (newState == namespace_open) {
            if (m_styleSettings.indentNamespaceBody || m_styleSettings.indentNamespaceBraces)
                *indentDepth += m_tabSettings.m_indentSize;
            if (m_styleSettings.indentNamespaceBody && m_styleSettings.indentNamespaceBraces)
                *indentDepth += m_tabSettings.m_indentSize;
        } else {
            *indentDepth += m_tabSettings.m_indentSize;
        }

        if (followedByData)
            *paddingDepth = nextTokenPosition-*indentDepth;
        break;
    }

    case substatement_open:
        // undo parent continuation indent
        *savedPaddingDepth = 0;

        if (parentState.type == switch_statement) {
            if (m_styleSettings.indentSwitchLabels)
                *indentDepth += m_tabSettings.m_indentSize;
        } else {
            if (m_styleSettings.indentBlockBody || m_styleSettings.indentBlockBraces)
                *indentDepth += m_tabSettings.m_indentSize;
            if (m_styleSettings.indentBlockBody && m_styleSettings.indentBlockBraces)
                *indentDepth += m_tabSettings.m_indentSize;
        }
        break;

    case brace_list_open:
        if (!lastToken) {
            if (parentState.type == assign_open_or_initializer)
                *savedPaddingDepth = tokenPosition-*indentDepth;
            *paddingDepth = nextTokenPosition-*indentDepth;
        } else {
            // avoid existing continuation indents
            if (parentState.type == assign_open_or_initializer)
                *savedPaddingDepth = state(1).savedPaddingDepth;
            *paddingDepth = *savedPaddingDepth + m_tabSettings.m_indentSize;
        }
        break;

    case block_open:
        // case_cont already adds some indent, revert it for a block
        if (parentState.type == case_cont) {
            *indentDepth = parentState.savedIndentDepth;
            if (m_styleSettings.indentBlocksRelativeToSwitchLabels)
                *indentDepth += m_tabSettings.m_indentSize;
        }

        if (m_styleSettings.indentBlockBody)
            *indentDepth += m_tabSettings.m_indentSize;
        break;

    case condition_open:
        // undo the continuation indent of the parent
        *paddingDepth = parentState.savedPaddingDepth;
        *savedPaddingDepth = *paddingDepth;

        // fixed extra indent when continuing 'if (', but not for 'else if ('
        if (m_styleSettings.extraPaddingForConditionsIfConfusingAlign
                && nextTokenPosition-*indentDepth <= m_tabSettings.m_indentSize)
            *paddingDepth = 2*m_tabSettings.m_indentSize;
        else
            *paddingDepth = nextTokenPosition-*indentDepth;
        break;

    case substatement:
        // undo the continuation indent of the parent
        *savedPaddingDepth = 0;

        break;

    case maybe_else: {
        // set indent to outermost braceless savedIndent
        int outermostBraceless = 0;
        while (isBracelessState(state(outermostBraceless).type))
            ++outermostBraceless;
        *indentDepth = state(outermostBraceless - 1).savedIndentDepth;
        // this is where the else should go, if one appears - aligned to if_statement
        *savedIndentDepth = state().savedIndentDepth;
    }   break;

    case for_statement_paren_open:
        *paddingDepth = nextTokenPosition - *indentDepth;
        break;

    case multiline_comment_start:
        *indentDepth = tokenPosition + 2; // nextTokenPosition won't work
        break;

    case multiline_comment_cont:
        *indentDepth = tokenPosition;
        break;

    case cpp_macro:
    case cpp_macro_cont:
        *indentDepth = m_tabSettings.m_indentSize;
        break;

    case string_open:
    case raw_string_open:
        *paddingDepth = tokenPosition - *indentDepth;
        break;
    }

    // ensure padding and indent are >= 0
    *indentDepth = qMax(0, *indentDepth);
    *savedIndentDepth = qMax(0, *savedIndentDepth);
    *paddingDepth = qMax(0, *paddingDepth);
    *savedPaddingDepth = qMax(0, *savedPaddingDepth);
}

void QtStyleCodeFormatter::adjustIndent(const Tokens &tokens, int lexerState, int *indentDepth, int *paddingDepth) const
{
    State topState = state();
    State previousState = state(1);

    const bool topWasMaybeElse = (topState.type == maybe_else);
    if (topWasMaybeElse) {
        int outermostBraceless = 1;
        while (state(outermostBraceless).type != invalid && isBracelessState(state(outermostBraceless).type))
            ++outermostBraceless;

        topState = state(outermostBraceless);
        previousState = state(outermostBraceless + 1);
    }


    // adjusting the indentDepth here instead of in enter() gives 'else if' the correct indentation
    // ### could be moved?
    switch (topState.type) {
    case substatement:
        *indentDepth += m_tabSettings.m_indentSize;
        break;
    // keep user-adjusted indent in multiline comments
    case multiline_comment_start:
    case multiline_comment_cont:
        if (!tokens.isEmpty()) {
            *indentDepth = column(tokens.at(0).utf16charsBegin());
            return;
        }
        break;
    case string_open:
    case raw_string_open:
        if (!tokenAt(0).isStringLiteral()) {
            *paddingDepth = topState.savedPaddingDepth;
            topState = previousState;
            previousState = state(2);
        }
        break;
    }

    const int kind = tokenAt(0).kind();
    switch (kind) {
    case T_POUND: *indentDepth = 0; break;
    case T_COLON:
        // ### ok for constructor initializer lists - what about ? and bitfields?
        if (topState.type == expression && previousState.type == declaration_start) {
            *paddingDepth = m_tabSettings.m_indentSize;
        } else if (topState.type == ternary_op) {
            if (*paddingDepth >= 2)
                *paddingDepth -= 2;
            else
                *paddingDepth = 0;
        }
        break;
    case T_LBRACE: {
        if (topState.type == case_cont) {
            *indentDepth = topState.savedIndentDepth;
            if (m_styleSettings.indentBlocksRelativeToSwitchLabels)
                *indentDepth += m_tabSettings.m_indentSize;
            *paddingDepth = 0;
        // function definition - argument list is expression state
        // or constructor
        } else if ((topState.type == expression && previousState.type == declaration_start)
                   || topState.type == member_init || topState.type == member_init_open) {
            // the declaration_start indent is the base
            if (topState.type == member_init)
                *indentDepth = state(2).savedIndentDepth;
            else
                *indentDepth = previousState.savedIndentDepth;
            if (m_styleSettings.indentFunctionBraces)
                *indentDepth += m_tabSettings.m_indentSize;
            *paddingDepth = 0;
        } else if (topState.type == class_start) {
            *indentDepth = topState.savedIndentDepth;
            if (m_styleSettings.indentClassBraces)
                *indentDepth += m_tabSettings.m_indentSize;
            *paddingDepth = 0;
        } else if (topState.type == enum_start) {
            *indentDepth = topState.savedIndentDepth;
            if (m_styleSettings.indentEnumBraces)
                *indentDepth += m_tabSettings.m_indentSize;
            *paddingDepth = 0;
        } else if (topState.type == namespace_start) {
            *indentDepth = topState.savedIndentDepth;
            if (m_styleSettings.indentNamespaceBraces)
                *indentDepth += m_tabSettings.m_indentSize;
            *paddingDepth = 0;
        } else if (topState.type == substatement) {
            *indentDepth = topState.savedIndentDepth;
            if (m_styleSettings.indentBlockBraces)
                *indentDepth += m_tabSettings.m_indentSize;
            *paddingDepth = 0;
        } else if (topState.type != defun_open
                && topState.type != block_open
                && topState.type != substatement_open
                && topState.type != brace_list_open
                && topState.type != arglist_open
                && !topWasMaybeElse) {
            *indentDepth = topState.savedIndentDepth;
            *paddingDepth = 0;
        }

        break;
    }
    case T_RBRACE: {
        if (topState.type == block_open && previousState.type == case_cont) {
            *indentDepth = previousState.savedIndentDepth;
            *paddingDepth = previousState.savedPaddingDepth;
            if (m_styleSettings.indentBlocksRelativeToSwitchLabels)
                *indentDepth += m_tabSettings.m_indentSize;
            break;
        }
        for (int i = 0; state(i).type != topmost_intro; ++i) {
            const int type = state(i).type;
            if (type == class_open
                    || type == namespace_open
                    || type == extern_open
                    || type == enum_open
                    || type == defun_open
                    || type == substatement_open
                    || type == brace_list_open
                    || type == block_open) {
                *indentDepth = state(i).savedIndentDepth;
                *paddingDepth = state(i).savedPaddingDepth;
                if ((type == defun_open && m_styleSettings.indentFunctionBraces)
                        || (type == class_open && m_styleSettings.indentClassBraces)
                        || (type == namespace_open && m_styleSettings.indentNamespaceBraces)
                        || (type == enum_open && m_styleSettings.indentEnumBraces)
                        || (type == substatement_open && m_styleSettings.indentBlockBraces))
                    *indentDepth += m_tabSettings.m_indentSize;
                break;
            }
        }
        break;
    }
    // Disabled for now, see QTCREATORBUG-1825. It makes extending if conditions
    // awkward: inserting a newline just before the ) shouldn't align to 'if'.
    //case T_RPAREN:
    //    if (topState.type == condition_open) {
    //        *indentDepth = previousState.savedIndentDepth;
    //    }
    //    break;
    case T_DEFAULT:
    case T_CASE: {
        for (int i = 0; state(i).type != topmost_intro; ++i) {
            const int type = state(i).type;
            if (type == switch_statement) {
                *indentDepth = state(i).savedIndentDepth;
                if (m_styleSettings.indentSwitchLabels)
                    *indentDepth += m_tabSettings.m_indentSize;
                break;
            } else if (type == case_cont) {
                *indentDepth = state(i).savedIndentDepth;
                break;
            }
        }
        break;
    }
    case T_PUBLIC:
    case T_PRIVATE:
    case T_PROTECTED:
    case T_Q_SIGNALS:
        if (m_styleSettings.indentDeclarationsRelativeToAccessSpecifiers
                && topState.type == class_open) {
            if (tokenAt(1).is(T_COLON) || tokenAt(2).is(T_COLON)
                || (tokenAt(tokenCount() - 1).is(T_COLON) && (tokenAt(1).is(T___ATTRIBUTE__) || tokenAt(1).is(T___DECLSPEC)))) {
                *indentDepth = topState.savedIndentDepth;
                if (m_styleSettings.indentAccessSpecifiers)
                    *indentDepth += m_tabSettings.m_indentSize;
            }
        }
        break;
    case T_ELSE:
        if (topWasMaybeElse)
            *indentDepth = state().savedIndentDepth; // topSavedIndent is actually the previous
        break;
    case T_LESS_LESS:
    case T_GREATER_GREATER:
        if (topState.type == stream_op || topState.type == stream_op_cont) {
            if (*paddingDepth >= 3)
                *paddingDepth -= 3; // to align << with <<
            else
                *paddingDepth = 0;
        }
        break;
    case T_COMMENT:
    case T_DOXY_COMMENT:
    case T_CPP_COMMENT:
    case T_CPP_DOXY_COMMENT:
        // unindent the last line of a comment
        if ((topState.type == multiline_comment_cont
             || topState.type == multiline_comment_start)
                && (kind == T_COMMENT || kind == T_DOXY_COMMENT)
                && (lexerState == T_EOF_SYMBOL
                    || tokens.size() != 1)) {
            if (*indentDepth >= m_tabSettings.m_indentSize)
                *indentDepth -= m_tabSettings.m_indentSize;
            else
                *indentDepth = 0;
        }
        break;
    case T_IDENTIFIER:
        if (topState.type == substatement
                || topState.type == substatement_open
                || topState.type == case_cont
                || topState.type == block_open
                || topState.type == defun_open) {
            if (tokens.size() > 1 && tokens.at(1).kind() == T_COLON) // label?
                *indentDepth = 0;
        }
        break;
    case T_BREAK:
    case T_CONTINUE:
    case T_RETURN:
    case T_CO_RETURN:
        if (topState.type == case_cont) {
            *indentDepth = topState.savedIndentDepth;
            if (m_styleSettings.indentControlFlowRelativeToSwitchLabels)
                *indentDepth += m_tabSettings.m_indentSize;
        }
        break;
    }
    // ensure padding and indent are >= 0
    *indentDepth = qMax(0, *indentDepth);
    *paddingDepth = qMax(0, *paddingDepth);
}

bool QtStyleCodeFormatter::shouldClearPaddingOnEnter(int state)
{
    switch (state) {
    case defun_open:
    case class_start:
    case class_open:
    case enum_start:
    case enum_open:
    case namespace_start:
    case namespace_open:
    case extern_start:
    case extern_open:
    case template_start:
    case if_statement:
    case else_clause:
    case for_statement:
    case switch_statement:
    case statement_with_condition:
    case do_statement:
    case return_statement:
    case block_open:
    case substatement_open:
    case substatement:
        return true;
    }
    return false;
}

} // namespace CppEditor
