/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
 * 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 <functional>

#include "clang/AST/Comment.h"

#include "compat.hxx"
#include "plugin.hxx"

// Remove dynamic exception specifications.  See the mail thread starting at
// <https://lists.freedesktop.org/archives/libreoffice/2017-January/076665.html>
// "Dynamic Exception Specifications" for details.

namespace {

bool isOverriding(FunctionDecl const * decl) {
    if (decl->hasAttr<OverrideAttr>()) {
        return true;
    }
    auto m = dyn_cast<CXXMethodDecl>(decl);
    return m != nullptr
        && m->begin_overridden_methods() != m->end_overridden_methods();
}

bool isDtorOrDealloc(FunctionDecl const * decl) {
    if (isa<CXXDestructorDecl>(decl)) {
        return true;
    }
    switch (decl->getOverloadedOperator()) {
    case OO_Delete:
    case OO_Array_Delete:
        return true;
    default:
        return false;
    }
}

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

    bool preRun() override {
        return compiler.getLangOpts().CPlusPlus;
    }

    void run() override {
        if (preRun()) {
            TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
        }
    }

    bool VisitFunctionDecl(FunctionDecl const * decl) {
        if (ignoreLocation(decl)) {
            return true;
        }
        auto proto = decl->getType()->getAs<FunctionProtoType>();
        if (proto == nullptr || proto->getExceptionSpecType() != EST_Dynamic) {
            return true;
        }
        if (decl->isCanonicalDecl() && !isOverriding(decl)
            && !anyRedeclHasThrowsDocumentation(decl))
        {
            report(
                DiagnosticsEngine::Warning,
                ("function declaration has dynamic exception specification but"
                 " no corresponding documentation comment"),
                decl->getLocation())
                << decl->getSourceRange();
        }
        if (rewriter != nullptr) {
            if (!(decl->isDefined() || compat::isPureVirtual(decl))) {
                return true;
            }
            if (auto m = dyn_cast<CXXMethodDecl>(decl)) {
                for (auto i = m->begin_overridden_methods();
                     i != m->end_overridden_methods(); ++i)
                {
                    auto proto2 = (*i)->getType()->getAs<FunctionProtoType>();
                    assert(proto2 != nullptr);
                    if (proto2->getExceptionSpecType() == EST_Dynamic) {
                        return true;
                    }
                }
            }
        }
        bool dtorOrDealloc = isDtorOrDealloc(decl);
        auto const source = decl->getExceptionSpecSourceRange();
        if (rewriter != nullptr && source.isValid()) {
            if (dtorOrDealloc) {
                if (replaceText(source, "noexcept(false)")) {
                    return true;
                }
            } else {
                auto beg = source.getBegin();
                if (beg.isFileID()) {
                    for (;;) {
                        auto prev = Lexer::GetBeginningOfToken(
                            beg.getLocWithOffset(-1),
                            compiler.getSourceManager(),
                            compiler.getLangOpts());
                        auto n = Lexer::MeasureTokenLength(
                            prev, compiler.getSourceManager(),
                            compiler.getLangOpts());
                        auto s = StringRef(
                            compiler.getSourceManager().getCharacterData(prev),
                            n);
                        while (compat::starts_with(s, "\\\n")) {
                            s = s.drop_front(2);
                            while (!s.empty()
                                   && (s.front() == ' ' || s.front() == '\t'
                                       || s.front() == '\n' || s.front() == '\v'
                                       || s.front() == '\f'))
                            {
                                s = s.drop_front(1);
                            }
                        }
                        if (!s.empty() && s != "\\") {
                            if (compat::starts_with(s, "//")) {
                                beg = source.getBegin();
                            }
                            break;
                        }
                        beg = prev;
                    }
                }
                if (removeText(SourceRange(beg, source.getEnd()))) {
                    return true;
                }
            }
        }
        report(
            DiagnosticsEngine::Warning,
            (dtorOrDealloc
             ? "replace dynamic exception specification with 'noexcept(false)'"
             : "remove dynamic exception specification"),
            source.isValid() ? source.getBegin() : decl->getLocation())
            << (source.isValid() ? source : decl->getSourceRange());
        return true;
    }

private:
    bool hasThrowsDocumentation(FunctionDecl const * decl) {
        if (auto cmt = compiler.getASTContext().getCommentForDecl(
            decl, &compiler.getPreprocessor()))
        {
            for (auto i = cmt->child_begin(); i != cmt->child_end(); ++i) {
                if (auto bcc = dyn_cast<comments::BlockCommandComment>(*i)) {
                    if (compiler.getASTContext().getCommentCommandTraits()
                        .getCommandInfo(bcc->getCommandID())->IsThrowsCommand)
                    {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    bool anyRedeclHasThrowsDocumentation(FunctionDecl const * decl) {
        return std::any_of(
            decl->redecls_begin(), decl->redecls_end(),
            [this](FunctionDecl * d) { return hasThrowsDocumentation(d); });
            // std::bind(
            //     &DynExcSpec::hasThrowsDocumentation, this,
            //     std::placeholders::_1));
    }
};

loplugin::Plugin::Registration<DynExcSpec> dynexcspec("dynexcspec");

} // namespace

#endif // LO_CLANG_SHARED_PLUGINS

/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
