/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
#ifndef LO_CLANG_SHARED_PLUGINS

#include <algorithm>
#include <cassert>
#include <limits>
#include <set>
#include <string>

#include "clang/AST/Attr.h"

#include "plugin.hxx"

namespace {

class OverrideVirtual:
    public loplugin::FilteringRewritePlugin<OverrideVirtual>
{
public:
    explicit OverrideVirtual(loplugin::InstantiationData const & data):
        FilteringRewritePlugin(data) {}

    virtual bool preRun() override;
    virtual void run() override;

    bool VisitCXXMethodDecl(CXXMethodDecl const * decl);

private:
    std::set<SourceLocation> insertions_;
};

bool OverrideVirtual::preRun() {
    return compiler.getLangOpts().CPlusPlus
        && compiler.getPreprocessor().getIdentifierInfo(
            "LIBO_INTERNAL_ONLY")->hasMacroDefinition();
}

void OverrideVirtual::run() {
    if (preRun())
        TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
}
bool OverrideVirtual::VisitCXXMethodDecl(CXXMethodDecl const * decl) {
    // As a heuristic, ignore declarations where the name is spelled out in an
    // ignored location; that e.g. handles uses of the Q_OBJECT macro from
    // external QtCore/qobjectdefs.h:
    if (ignoreLocation(decl) || !decl->isFirstDecl()
        || decl->begin_overridden_methods() == decl->end_overridden_methods()
        || decl->hasAttr<OverrideAttr>()
        || ignoreLocation(
            compiler.getSourceManager().getSpellingLoc(
                decl->getNameInfo().getLoc())))
    {
        return true;
    }
    std::string over(
        isInUnoIncludeFile(decl->getSourceRange().getBegin())
        ? "SAL_OVERRIDE" : "override");
    if (rewriter != nullptr) {
        // In  void MACRO(...);  getSourceRange().getEnd() would (erroneously?)
        // point at "MACRO" rather than ")", so make the loop always terminate
        // at the first ";" or "{" instead of getSourceRange().getEnd():
        unsigned parens = 0;
        bool seenSpace = false;
        //TODO: Whether to add a space after an inserted "SAL_OVERRIDE" should
        // depend on the following token at the spelling location where
        // "SAL_OVERRIDE" is inserted, not on the following token in the fully-
        // macro-expanded view:
        bool addSpace = bool();
        SourceLocation loc;
        for (SourceLocation l(decl->getSourceRange().getBegin());;) {
            SourceLocation sl(compiler.getSourceManager().getSpellingLoc(l));
            unsigned n = Lexer::MeasureTokenLength(
                sl, compiler.getSourceManager(), compiler.getLangOpts());
            StringRef s(compiler.getSourceManager().getCharacterData(sl), n);
            //TODO: Looks like a Clang bug that in some cases like
            // (filter/source/svg/svgexport.cxx)
            //
            // | #define TEXT_FIELD_GET_CLASS_NAME_METHOD( class_name ) \ |
            // | virtual OUString getClassName() const                  \ |
            // | {                                                      \ |
            // |     static const char className[] = #class_name;       \ |
            // |     return OUString( className );                      \ |
            // | }                                                        |
            // |                                                          |
            // | TEXT_FIELD_GET_CLASS_NAME_METHOD( TextField )            |
            //
            // where "\<NL>" is followed directly by a real token without
            // intervening whitespace, tokens "\<NL>virtual" and "\<NL>{" are
            // reported:
            if (s.startswith("\\\n")) {
                s = s.drop_front(2);
            }
            if (parens == 0) {
                if (s == "=" || s == "{") {
                    if (!seenSpace) {
                        addSpace = true;
                    }
                    break;
                }
                if (s == ";") {
                    break;
                }
            }
            if (s == "(") {
                assert(parens < std::numeric_limits<unsigned>::max());
                ++parens;
            } else if (s == ")") {
                assert(parens != 0);
                --parens;
            }
            if (s.empty()) {
                if (!seenSpace) {
                    addSpace = false;
                }
                seenSpace = true;
            } else if (s.startswith("/*") || s.startswith("//") || s == "\\") {
                if (!seenSpace) {
                    addSpace = true;
                }
                seenSpace = true;
            } else {
                seenSpace = false;
                addSpace = false;
                loc = sl;
            }
            if (l.isMacroID()
                && compiler.getSourceManager().isAtEndOfImmediateMacroExpansion(
                    l, &l))
            {
                n = Lexer::MeasureTokenLength(
                    compiler.getSourceManager().getSpellingLoc(l),
                    compiler.getSourceManager(), compiler.getLangOpts());
            }
            l = l.getLocWithOffset(std::max<unsigned>(n, 1));
        }
        assert(loc.isValid());
        if (!insertions_.insert(loc).second
            || insertTextAfterToken(
                loc,
                std::string(" ") + over + std::string(addSpace ? " " : "")))
        {
            return true;
        }
    }
    report(
        DiagnosticsEngine::Warning,
        ("overriding virtual function declaration not marked '%0'"),
        decl->getLocation())
        << over << decl->getSourceRange();
    for (auto i = decl->begin_overridden_methods();
         i != decl->end_overridden_methods(); ++i)
    {
        report(
            DiagnosticsEngine::Note, "overridden declaration is here",
            (*i)->getLocation())
            << (*i)->getSourceRange();
    }
    return true;
}

loplugin::Plugin::Registration<OverrideVirtual> overridevirtual("overridevirtual");

} // namespace

#endif // LO_CLANG_SHARED_PLUGINS

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */