/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** 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 General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** 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-3.0.html.
**
****************************************************************************/

#include "clanghighlightingresultreporter.h"

#include <clangsupport/tokeninfocontainer.h>
#include <cppeditor/semantichighlighter.h>
#include <texteditor/semantichighlighter.h>
#include <texteditor/textstyles.h>
#include <utils/runextensions.h>
#include <utils/qtcassert.h>

namespace {

TextEditor::TextStyle toTextStyle(ClangBackEnd::HighlightingType type)
{
    using ClangBackEnd::HighlightingType;

    switch (type) {
    case HighlightingType::Keyword:
        return TextEditor::C_KEYWORD;
    case HighlightingType::Function:
        return TextEditor::C_FUNCTION;
    case HighlightingType::VirtualFunction:
        return TextEditor::C_VIRTUAL_METHOD;
    case HighlightingType::Type:
        return TextEditor::C_TYPE;
    case HighlightingType::PrimitiveType:
        return TextEditor::C_PRIMITIVE_TYPE;
    case HighlightingType::LocalVariable:
        return TextEditor::C_LOCAL;
    case HighlightingType::Parameter:
        return TextEditor::C_PARAMETER;
    case HighlightingType::Field:
    case HighlightingType::QtProperty:
        return TextEditor::C_FIELD;
    case HighlightingType::GlobalVariable:
        return TextEditor::C_GLOBAL;
    case HighlightingType::Enumeration:
        return TextEditor::C_ENUMERATION;
    case HighlightingType::Label:
        return TextEditor::C_LABEL;
    case HighlightingType::Preprocessor:
    case HighlightingType::PreprocessorDefinition:
    case HighlightingType::PreprocessorExpansion:
        return TextEditor::C_PREPROCESSOR;
    case HighlightingType::Punctuation:
        return TextEditor::C_PUNCTUATION;
    case HighlightingType::Declaration:
        return TextEditor::C_DECLARATION;
    case HighlightingType::FunctionDefinition:
        return TextEditor::C_FUNCTION_DEFINITION;
    case HighlightingType::OutputArgument:
        return TextEditor::C_OUTPUT_ARGUMENT;
    case HighlightingType::Operator:
        return TextEditor::C_OPERATOR;
    case HighlightingType::OverloadedOperator:
        return TextEditor::C_OVERLOADED_OPERATOR;
    case HighlightingType::Comment:
        return TextEditor::C_COMMENT;
    case HighlightingType::StringLiteral:
        return TextEditor::C_STRING;
    case HighlightingType::NumberLiteral:
        return TextEditor::C_NUMBER;
    case HighlightingType::Invalid:
        QTC_CHECK(false); // never called
        return TextEditor::C_TEXT;
    default:
        break;
    }
    Q_UNREACHABLE();
}

bool ignore(ClangBackEnd::HighlightingType type)
{
    using ClangBackEnd::HighlightingType;

    switch (type) {
    default:
        break;
    case HighlightingType::Namespace:
    case HighlightingType::Class:
    case HighlightingType::Struct:
    case HighlightingType::Enum:
    case HighlightingType::Union:
    case HighlightingType::TypeAlias:
    case HighlightingType::Typedef:
    case HighlightingType::ObjectiveCClass:
    case HighlightingType::ObjectiveCCategory:
    case HighlightingType::ObjectiveCProtocol:
    case HighlightingType::ObjectiveCInterface:
    case HighlightingType::ObjectiveCImplementation:
    case HighlightingType::ObjectiveCProperty:
    case HighlightingType::ObjectiveCMethod:
    case HighlightingType::TemplateTypeParameter:
    case HighlightingType::TemplateTemplateParameter:
    case HighlightingType::AngleBracketOpen:
    case HighlightingType::AngleBracketClose:
    case HighlightingType::DoubleAngleBracketClose:
    case HighlightingType::TernaryIf:
    case HighlightingType::TernaryElse:
        return true;
    }

    return false;
}

TextEditor::TextStyles toTextStyles(ClangBackEnd::HighlightingTypes types)
{
    TextEditor::TextStyles textStyles;
    textStyles.mixinStyles.initializeElements();

    textStyles.mainStyle = toTextStyle(types.mainHighlightingType);

    for (ClangBackEnd::HighlightingType type : types.mixinHighlightingTypes) {
        if (!ignore(type))
            textStyles.mixinStyles.push_back(toTextStyle(type));
    }

    return textStyles;
}

TextEditor::HighlightingResult toHighlightingResult(
        const ClangBackEnd::TokenInfoContainer &tokenInfo)
{
    using ClangBackEnd::HighlightingType;
    const auto textStyles = toTextStyles(tokenInfo.types);
    TextEditor::HighlightingResult result(tokenInfo.line, tokenInfo.column, tokenInfo.length,
                                          textStyles);
    if (tokenInfo.types.mixinHighlightingTypes.contains(HighlightingType::AngleBracketOpen))
        result.kind = CppEditor::SemanticHighlighter::AngleBracketOpen;
    else if (tokenInfo.types.mixinHighlightingTypes.contains(HighlightingType::AngleBracketClose))
        result.kind = CppEditor::SemanticHighlighter::AngleBracketClose;
    else if (tokenInfo.types.mixinHighlightingTypes.contains(HighlightingType::DoubleAngleBracketClose))
        result.kind = CppEditor::SemanticHighlighter::DoubleAngleBracketClose;
    else if (tokenInfo.types.mixinHighlightingTypes.contains(HighlightingType::TernaryIf))
        result.kind = CppEditor::SemanticHighlighter::TernaryIf;
    else if (tokenInfo.types.mixinHighlightingTypes.contains(HighlightingType::TernaryElse))
        result.kind = CppEditor::SemanticHighlighter::TernaryElse;
    return result;
}

void highlightResultsImpl(QFutureInterface<TextEditor::HighlightingResult> &fi,
                          const QVector<ClangBackEnd::TokenInfoContainer> &tokenInfos,
                          int chunkSize)
{
    if (fi.isCanceled())
        return;

    QVector<TextEditor::HighlightingResult> chunksToReport;
    chunksToReport.reserve(chunkSize + 1);

    using ClangBackEnd::HighlightingType;
    bool flushRequested = false;
    int flushLine = 0;

    auto reportAndClearCurrentChunks = [&] {
        flushRequested = false;
        flushLine = 0;

        if (!chunksToReport.isEmpty()) {
            fi.reportResults(chunksToReport);
            chunksToReport.erase(chunksToReport.begin(), chunksToReport.end());
        }
    };

    auto reportChunkWise = [&](const TextEditor::HighlightingResult &highlightingResult) {
        if (chunksToReport.size() >= chunkSize) {
            if (flushRequested && highlightingResult.line != flushLine) {
                reportAndClearCurrentChunks();
            } else if (!flushRequested) {
                flushRequested = true;
                flushLine = highlightingResult.line;
            }
        }

        chunksToReport.append(highlightingResult);
    };

    for (const auto &tokenInfo : tokenInfos)
        reportChunkWise(toHighlightingResult(tokenInfo));

    if (fi.isCanceled())
        return;

    reportAndClearCurrentChunks();
}

} // anonymous

namespace ClangCodeModel {
namespace Internal {

QFuture<TextEditor::HighlightingResult> highlightResults(
                      const QVector<ClangBackEnd::TokenInfoContainer> &tokenInfos,
                      int chunkSize)
{
    return Utils::runAsync(highlightResultsImpl, tokenInfos, chunkSize);
}

} // namespace Internal
} // namespace ClangCodeModel
