/*
    This file is part of the clazy static checker.

    Copyright (C) 2020 The Qt Company Ltd.
    Copyright (C) 2020 Lucie Gerard <lucie.gerard@qt.io>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include "qt6-deprecated-api-fixes.h"
#include "ContextUtils.h"
#include "ClazyContext.h"
#include "Utils.h"
#include "StringUtils.h"
#include "FixItUtils.h"
#include "HierarchyUtils.h"
#include "SourceCompatibilityHelpers.h"
#include "clazy_stl.h"

#include <clang/Lex/Lexer.h>
#include <clang/AST/Decl.h>
#include <clang/AST/DeclCXX.h>
#include <clang/AST/Expr.h>
#include <clang/AST/ExprCXX.h>
#include <clang/AST/Stmt.h>
#include <clang/AST/Type.h>
#include <clang/Basic/Diagnostic.h>
#include <clang/Basic/LLVM.h>
#include <clang/Basic/SourceLocation.h>
#include <llvm/ADT/ArrayRef.h>
#include <llvm/ADT/StringRef.h>
#include <llvm/Support/Casting.h>

using namespace clang;
using namespace std;

Qt6DeprecatedAPIFixes::Qt6DeprecatedAPIFixes(const std::string &name, ClazyContext *context)
    : CheckBase(name, context, Option_CanIgnoreIncludes)
{
    enablePreProcessorCallbacks();
}

void replacementForQWizard(string functionName, string &message, string &replacement) {
    message = "call function QProcess::";
    message += functionName;
    message += "(). Use function QProcess::visitedIds() instead";

    replacement = "visitedIds";
}

bool replacementForQDate(clang::Stmt *parent, string &message, string &replacement, SourceLocation &warningLocation, SourceRange &fixitRange) {

    // The one with two arguments: Qt::DateFormat format, QCalendar cal
    CXXMemberCallExpr *callExp = dyn_cast<CXXMemberCallExpr>(parent);
    if (!callExp)
        return false;
    auto func = callExp->getDirectCallee();
    if (!func)
        return false;
    int i = 1;
    if (func->getNumParams() != 2)
        return false;
    for (auto it = func->param_begin(); it !=func->param_end(); it++) {
        ParmVarDecl *param = *it;
        if (i == 1 && param->getType().getAsString() != "Qt::DateFormat")
            return false;
        if (i == 2 && param->getType().getAsString() != "class QCalendar")
            return false;
        i++;
    }
    Stmt *firstArg = clazy::childAt(parent, 1);
    Stmt *secondArg = clazy::childAt(parent, 2);
    DeclRefExpr *declFirstArg = dyn_cast<DeclRefExpr>(firstArg);
    if (!firstArg || !secondArg || !declFirstArg)
        return false;
    fixitRange = SourceRange(firstArg->getEndLoc(), secondArg->getEndLoc());
    message = "replacing with function omitting the calendar. Change manually and use QLocale if you want to keep the calendar.";
    warningLocation = secondArg->getBeginLoc();
    replacement  = declFirstArg->getNameInfo().getAsString();
    return true;
}

static std::set<std::string> qButtonGroupDeprecatedFunctions = {"buttonClicked", "buttonPressed", "buttonReleased", "buttonToggled"};

bool replacementForQButtonGroup(clang::MemberExpr * membExpr, string &message, string &replacement) {

    auto declfunc = membExpr->getReferencedDeclOfCallee()->getAsFunction();
    string paramType;
    for (auto param : Utils::functionParameters(declfunc)) {
        paramType = param->getType().getAsString();
        break;
    }
    // only the function with "int" as first argument are deprecated
    if (paramType != "int")
        return false;

    string functionName = membExpr->getMemberNameInfo().getAsString();
    string newFunctionName = "id";
    newFunctionName += functionName.substr(6,8);

    message = "call function QButtonGroup::";
    message += functionName;
    message += "(int";
    if (declfunc->param_size() > 1)
        message += ", bool";
    message +="). Use function QButtonGroup";
    message += newFunctionName;
    message += " instead.";

    replacement = newFunctionName;
    return true;
}

bool warningForQTextBrowser(clang::MemberExpr * membExpr, string &message) {

    auto declfunc = membExpr->getReferencedDeclOfCallee()->getAsFunction();
    string paramType;
    for (auto param : Utils::functionParameters(declfunc)) {
        paramType = param->getType().getAsString();
        break;
    }
    if (paramType != "const class QString &")
        return false;

    message = "Using QTextBrowser::highlighted(const QString &). Use QTextBrowser::highlighted(const QUrl &) instead.";
    return true;
}

bool warningForQComboBox(clang::MemberExpr * membExpr, string &message) {

    auto declfunc = membExpr->getReferencedDeclOfCallee()->getAsFunction();
    string paramType;
    for (auto param : Utils::functionParameters(declfunc)) {
        paramType = param->getType().getAsString();
        break;
    }
    // only the function with "const QString &" as first argument are deprecated
    if (paramType != "const class QString &")
        return false;

    message = "Use currentIndexChanged(int) instead, and get the text using itemText(index).";
    return true;
}

bool replacementForQComboBox(clang::MemberExpr * membExpr, string functionName, string &message, string &replacement) {

    auto declfunc = membExpr->getReferencedDeclOfCallee()->getAsFunction();
    string paramType;
    for (auto param : Utils::functionParameters(declfunc)) {
        paramType = param->getType().getAsString();
        break;
    }
    // only the function with "const QString &" as first argument are deprecated
    if (paramType != "const class QString &")
        return false;

    if (functionName == "activated") {
        message = "Using QComboBox::activated(const QString &). Use textActiated() instead";
        replacement = "textActivated";
    } else if (functionName == "highlighted") {
        message = "Using QComboBox::hilighted(const QString &). Use textHighlighted() instead";
        replacement = "textHighlighted";
    } else {
        return false;
    }
    return true;
}

static std::set<std::string> qProcessDeprecatedFunctions = {"start"};

void replacementForQProcess(string functionName, string &message, string &replacement) {
    message = "call function QProcess::";
    message += functionName;
    message += "(). Use function QProcess::";
    message += functionName;
    message += "Command() instead";

    replacement = functionName;
    replacement += "Command";
}

void replacementForQSignalMapper(clang::MemberExpr * membExpr, string &message, string &replacement) {

    auto declfunc = membExpr->getReferencedDeclOfCallee()->getAsFunction();
    string paramType;
    for (auto param : Utils::functionParameters(declfunc)) {
        paramType = param->getType().getAsString();
    }

    string functionNameExtention;
    string paramTypeCor;
    if (paramType == "int") {
        functionNameExtention = "Int";
        paramTypeCor = "int";
    } else if (paramType == "const class QString &") {
        functionNameExtention = "String";
        paramTypeCor = "const QString &";
    } else if (paramType == "class QWidget *") {
        functionNameExtention = "Object";
        paramTypeCor = "QWidget *";
    } else if (paramType == "class QObject *") {
        functionNameExtention = "Object";
        paramTypeCor = "QObject *";
    }

    message = "call function QSignalMapper::mapped(";
    message += paramTypeCor;
    message += "). Use function QSignalMapper::mapped";
    message += functionNameExtention;
    message += "(";
    message += paramTypeCor;
    message += ") instead.";

    replacement = "mapped";
    replacement += functionNameExtention;
}

void replacementForQResource(string functionName, string &message, string &replacement) {
    message = "call function QRessource::isCompressed(). Use function QProcess::compressionAlgorithm() instead.";
    replacement = "compressionAlgorithm";
}

static std::set<std::string> qSetDeprecatedOperators = {"operator--", "operator+", "operator-", "operator+=", "operator-="};
static std::set<std::string> qSetDeprecatedFunctions = {"rbegin", "rend", "crbegin", "crend", "hasPrevious", "previous",
                                                       "peekPrevious", "findPrevious"};
static std::set<std::string> qHashDeprecatedFunctions = {"hasPrevious", "previous",
                                                       "peekPrevious", "findPrevious"};

bool isQSetDepreprecatedOperator(string functionName, string contextName, string &message)
{
    if (qSetDeprecatedOperators.find(functionName) == qSetDeprecatedOperators.end())
        return false;
    if ((clazy::startsWith(contextName, "QSet<") || clazy::startsWith(contextName, "QHash<")) &&
            clazy::endsWith(contextName, "iterator")) {

        if (clazy::startsWith(contextName, "QSet<"))
            message = "QSet iterator categories changed from bidirectional to forward. Please port your code manually";
        else
            message = "QHash iterator categories changed from bidirectional to forward. Please port your code manually";

        return true;
    }
    return false;
}

static std::set<std::string> qGraphicsViewFunctions = {"matrix", "setMatrix", "resetMatrix"};

void warningForGraphicsViews(string functionName, string &message) {

    if (functionName == "matrix") {
        message = "Using QGraphicsView::matrix. Use transform() instead";
        return;
    } else if (functionName == "setMatrix") {
        message = "Using QGraphicsView::setMatrix(const QMatrix &). Use setTransform(const QTransform &) instead";
        return;
    } else if (functionName == "resetMatrix") {
        message = "Using QGraphicsView::resetMatrix(). Use resetTransform() instead";
        return;
    }
    return;
}

static std::set<std::string> qStylePixelMetrix = {"PM_DefaultTopLevelMargin", "PM_DefaultChildMargin", "PM_DefaultLayoutSpacing"};

static std::set<std::string> qMapFunctions = {"insertMulti", "uniqueKeys", "values", "unite"};

static std::set<std::string> qTextStreamFunctions = {
    "bin", "oct", "dec", "hex", "showbase", "forcesign", "forcepoint", "noshowbase", "noforcesign", "noforcepoint",
    "uppercasebase", "uppercasedigits", "lowercasebase", "lowercasedigits", "fixed", "scientific", "left", "right",
    "center", "endl", "flush", "reset", "bom", "ws"};

void replacementForQTextStreamFunctions(string functionName, string &message, string &replacement, bool explicitQtNamespace) {
    if (qTextStreamFunctions.find(functionName) == qTextStreamFunctions.end())
        return;
    message = "call function QTextStream::";
    message += functionName;
    message += ". Use function Qt::";
    message += functionName;
    message += " instead";

    if (!explicitQtNamespace)
        replacement = "Qt::";
    replacement += functionName;
}

void replacementForQStringSplitBehavior(string functionName, string &message, string &replacement, bool explicitQtNamespace) {
    message = "Use Qt::SplitBehavior variant instead";
    if (!explicitQtNamespace)
        replacement = "Qt::";
    replacement += functionName;
}

bool getMessageForDeclWarning(string type, string &message)
{
    if (clazy::contains(type, "QLinkedList")) {
        message = "Using QLinkedList. Use std::list instead";
        return true;
    } else if (clazy::contains(type, "QMacCocoaViewContainer")) {
        message =  "Using QMacCocoaViewContainer."
               " Use QWindow::fromWinId and QWidget::createWindowContainer instead";
        return true;
    } else if (clazy::contains(type, "QMacNativeWidget")) {
        message =  "Using QMacNativeWidget."
               " Use QWidget::winId instead";
        return true;
    } else if (clazy::contains(type, "QDirModel")) {
        message =  "Using QDirModel."
               " Use QFileSystemModel instead";
        return true;
    } else if (clazy::contains(type, "QString::SplitBehavior")) {
        message = "Using QString::SplitBehavior. Use Qt::SplitBehavior variant instead";
        return true;
    }else {
        return false;
    }

}

void Qt6DeprecatedAPIFixes::VisitDecl(clang::Decl *decl)
{
    auto funcDecl = decl->getAsFunction();
    auto varDecl = dyn_cast<VarDecl>(decl);
    auto fieldDecl = dyn_cast<FieldDecl>(decl);

    if (!funcDecl && !varDecl && !fieldDecl)
        return;

    DeclaratorDecl *declaratorDecl = nullptr;
    QualType qualType;
    if (funcDecl) {
        declaratorDecl = funcDecl;
        qualType = funcDecl->getReturnType();
    } else if (varDecl) {
        declaratorDecl = varDecl;
        qualType = varDecl->getType();
    } else if (fieldDecl) {
        declaratorDecl = fieldDecl;
        qualType = fieldDecl->getType();
    }

    string message;
    if (!getMessageForDeclWarning(qualType.getAsString(), message))
        return;

    vector<FixItHint> fixits;
#if LLVM_VERSION_MAJOR >= 10
    const string type = qualType.getAsString();

    if (clazy::endsWith(type, "QString::SplitBehavior")) {
        bool isQtNamespaceExplicit = false;
        DeclContext *newcontext =  clazy::contextForDecl(m_context->lastDecl);
        while (newcontext) {
            if (!newcontext)
                break;
            if (clang::isa<NamespaceDecl>(newcontext)) {
                clang::NamespaceDecl *namesdecl = dyn_cast<clang::NamespaceDecl>(newcontext);
                if (namesdecl->getNameAsString() == "Qt")
                    isQtNamespaceExplicit = true;
             }
            newcontext = newcontext->getParent();
        }
        string replacement;
        if (!isQtNamespaceExplicit)
            replacement = "Qt::";
        replacement += "SplitBehavior";
        SourceRange sourceRange(declaratorDecl->getTypeSpecStartLoc(), declaratorDecl->getTypeSpecEndLoc());
        fixits.push_back(FixItHint::CreateReplacement(sourceRange, replacement));
    }
#endif

    emitWarning(decl->getBeginLoc(), message, fixits);
    return;
}

string Qt6DeprecatedAPIFixes::buildReplacementforQDir(DeclRefExpr *decl_operator, bool isPointer, string replacement, string replacement_var2)
{
    if (isPointer)
        replacement += "->";
    else
        replacement += ".";
    replacement += "setPath(";
    replacement += replacement_var2;
    replacement += ")";
    return replacement;
}

string Qt6DeprecatedAPIFixes::buildReplacementForQVariant(DeclRefExpr* decl_operator, string replacement_var1, string replacement_var2)
{
    string replacement = "QVariant::compare(";
    replacement += replacement_var1;
    replacement += ", ";
    replacement += replacement_var2;
    replacement += ") ";
    replacement += decl_operator->getNameInfo().getAsString().substr(8,2);
    replacement += " 0" ;
    return replacement;
}

bool foundQDirDeprecatedOperator(DeclRefExpr *decl)
{
    return decl->getNameInfo().getAsString() == "operator=";
}

static std::set<std::string> qVariantDeprecatedOperator = {"operator<", "operator<=", "operator>", "operator>="};

bool foundQVariantDeprecatedOperator(DeclRefExpr *decl)
{
    return qVariantDeprecatedOperator.find(decl->getNameInfo().getAsString()) != qVariantDeprecatedOperator.end();
}

void Qt6DeprecatedAPIFixes::fixForDeprecatedOperator(Stmt* stmt, string className)
{
    // only interested in '=' operator for QDir
    vector<FixItHint> fixits;
    string message;
    string replacement;
    SourceLocation warningLocation;
    SourceRange fixitRange;
    Stmt *child = clazy::childAt(stmt, 0);
    bool foundOperator = false;
    DeclRefExpr *decl = nullptr;
    while (child) {
        decl = dyn_cast<DeclRefExpr>(child);
        if ( !decl ) {
            child = clazy::childAt(child, 0);
            continue;
        }

        if (className == "QDir") {
            foundOperator = foundQDirDeprecatedOperator(decl);
        } else if (className == "QVariant") {
            foundOperator = foundQVariantDeprecatedOperator(decl);
        }

        if (foundOperator) {
            warningLocation = decl->getLocation();
            break;
        }
        child = clazy::childAt(child, 0);
    }

    if (!foundOperator)
        return;

    // Getting the two arguments of the operator to build the replacement
    CXXOperatorCallExpr *oppCallExpr = dyn_cast<CXXOperatorCallExpr>(stmt);
    auto * arg0Size = oppCallExpr->getArg(0);
    auto * arg1Size = oppCallExpr->getArg(1);
    auto charRange = Lexer::getAsCharRange(arg0Size->getSourceRange(), m_sm, lo());
    auto replacementVar1 = Lexer::getSourceText(charRange, m_sm, lo());
    charRange = Lexer::getAsCharRange(arg1Size->getSourceRange(), m_sm, lo());
    auto replacementVar2 = Lexer::getSourceText(charRange, m_sm, lo());

    replacementVar1 = replacementVar1.rtrim(' ');
    replacementVar2 = replacementVar2.ltrim(' ');

    if (className == "QDir") {
        message = " function setPath() has to be used in Qt6";
        // Get the quality type of the operator first argument.
        // qdir_var1 = var2 => qdir_var1->setPath(var2) or qdir_var1.setPath(var2)
        // the qdir_var1 correspond to second child of the QDir operator
        child = clazy::childAt(stmt, 1);
        bool isPointer = false;
        while (child) {
            auto *castExpr = dyn_cast<ImplicitCastExpr>(child);
            auto *parent = dyn_cast<ParenExpr>(child);
            if (castExpr || parent) {
                child = clazy::childAt(child, 0);
                continue;
            }
            clang::UnaryOperator *uni = dyn_cast<UnaryOperator>(child);
            if (uni) {
                if (uni->getOpcodeStr(uni->getOpcode()).equals("*"))
                    isPointer = true;
            }
            break;
        }
        if (isPointer) {
            while(replacementVar1.consume_front("("))
                replacementVar1.consume_back(")");
            replacementVar1.consume_front("*");
        }
            replacement = buildReplacementforQDir(decl, isPointer, replacementVar1.str(), replacementVar2.str());
     } else if (className == "QVariant") {
            message = " operator does not exist in Qt6. Using QVariant::compare() instead.";
            replacement = buildReplacementForQVariant(decl, replacementVar1.str(), replacementVar2.str());
    }

    // If a macro is present in the stmt range the spelling location is used
    // This is producing a wrong fix. So we're forcing the use of expansion location
    FullSourceLoc endLoc(stmt->getEndLoc(), m_sm);
    SourceRange range(stmt->getBeginLoc(), endLoc.getExpansionLoc());
    fixitRange = range;
    fixits.push_back(FixItHint::CreateReplacement(fixitRange, replacement));
    emitWarning(warningLocation, message, fixits);

    return;

}

void Qt6DeprecatedAPIFixes::VisitStmt(clang::Stmt *stmt)
{
    auto oppCallExpr = dyn_cast<CXXOperatorCallExpr>(stmt);
    auto declRefExp = dyn_cast<DeclRefExpr>(stmt);
    auto membExpr = dyn_cast<MemberExpr>(stmt);
    auto consExpr = dyn_cast<CXXConstructExpr>(stmt);

    SourceLocation warningLocation;
    std::string replacement;
    std::string message;
    SourceRange fixitRange;

    vector<FixItHint> fixits;

    if (consExpr) {
        auto constructor = consExpr->getConstructor();
        if (!constructor)
            return;
        if (constructor->getDeclName().getAsString() == "QSplashScreen") {
            if (consExpr->getNumArgs() == 0)
                return;
            if (consExpr->getArg(0)->getType().getAsString() != "class QWidget *")
                return;
            message = "Use the constructor taking a QScreen * instead.";
            warningLocation = stmt->getBeginLoc();
            emitWarning(warningLocation, message, fixits);
            return;
        }
        if (constructor->getDeclName().getAsString() != "QDateTime")
            return;
        if ( consExpr->getNumArgs() != 1)
            return;
        if ( consExpr->getArg(0)->getType().getAsString() != "const class QDate")
            return;
        Stmt *child = clazy::childAt(stmt, 0);
        DeclRefExpr *decl;
        while(child) {
            decl = dyn_cast<DeclRefExpr>(child);
            if ( !decl ) {
                child = clazy::childAt(child, 0);
                continue;
            } else {
                break;
            }
        }
        if (!decl)
            return;

        replacement = decl->getNameInfo().getAsString();
        QualType qualtype = decl->getType();
        if (qualtype->isPointerType())
            replacement += "->";
        else
        replacement += ".";
        replacement += "startOfDay()";

        warningLocation = stmt->getBeginLoc();
        fixitRange = stmt->getSourceRange();
        message = "deprecated constructor. Use QDate::startOfDay() instead.";
    } else if (oppCallExpr) {

        if (clazy::isOfClass(oppCallExpr, "QDir")) {
            fixForDeprecatedOperator(stmt, "QDir");
            return;
        } else if (clazy::isOfClass(oppCallExpr, "QVariant")) {
            fixForDeprecatedOperator(stmt, "QVariant");
            return;
        }
        return;

    } else if (declRefExp) {

        warningLocation = declRefExp->getBeginLoc();
        auto decl = declRefExp->getDecl();
        if (!decl)
            return;
        auto *declContext = declRefExp->getDecl()->getDeclContext();
        if (!declContext)
            return;
        string functionName = declRefExp->getNameInfo().getAsString();
        string enclosingNameSpace;
        auto *enclNsContext = declContext->getEnclosingNamespaceContext();
        if (enclNsContext) {
            if (auto *ns = dyn_cast<NamespaceDecl>(declContext->getEnclosingNamespaceContext()))
                enclosingNameSpace = ns->getNameAsString();
        }

        // To catch QDir and QSet
        string contextName;
        if (declContext) {
            if (clang::isa<clang::CXXRecordDecl>(declContext)) {
                clang::CXXRecordDecl *recordDecl = llvm::dyn_cast<clang::CXXRecordDecl>(declContext);
                contextName = recordDecl->getQualifiedNameAsString();
            }
        }
        if (isQSetDepreprecatedOperator(functionName, contextName, message)) {
            emitWarning(warningLocation, message, fixits);
            return;
        }
        if (functionName == "addResourceSearchPath" && contextName == "QDir") {
            message = "call function QDir::addResourceSearchPath(). Use function QDir::addSearchPath() with prefix instead";
            emitWarning(warningLocation, message, fixits);
            return;
        }
        if (functionName == "qrand" || functionName == "qsrand") {
            // To catch qrand and qsrand from qglobal.
            message = "use QRandomGenerator instead";
            emitWarning(warningLocation, message, fixits);
            return;
        }

        string declType;
        declType = decl->getType().getAsString();
        if (functionName == "AdjustToMinimumContentsLength" && declType == "enum QComboBox::SizeAdjustPolicy") {
            message = "Use QComboBox::SizeAdjustPolicy::AdjustToContents or AdjustToContentsOnFirstShow instead";
            emitWarning(warningLocation, message, fixits);
            return;
        }

        if (functionName == "AllDockWidgetFeatures" && declType == "enum QDockWidget::DockWidgetFeature") {
            message = "Use QComboBox::DockWidgetClosable|DockWidgetMovable|DockWidgetFloatable explicitly instead.";
            emitWarning(warningLocation, message, fixits);
            return;
        }

        if ((qStylePixelMetrix.find(functionName) != qStylePixelMetrix.end() && declType == "enum QStyle::PixelMetric") ||
            (functionName == "SE_DialogButtonBoxLayoutItem" && declType == "enum QStyle::SubElement")) {
            message = "this enum has been removed in Qt6";
            emitWarning(warningLocation, message, fixits);
            return;
        }


        if (functionName != "MatchRegExp" && enclosingNameSpace != "QTextStreamFunctions" &&
            functionName != "KeepEmptyParts" && functionName != "SkipEmptyParts")
            return;

        // To catch enum Qt::MatchFlag and enum QString::SplitBehavior
        // Get out of this DeclRefExp to catch the potential Qt namespace surrounding it
        bool isQtNamespaceExplicit = false;
        DeclContext *newcontext =  clazy::contextForDecl(m_context->lastDecl);
        while (newcontext) {
            if (!newcontext)
                break;
            if (clang::isa<NamespaceDecl>(newcontext)) {
                clang::NamespaceDecl *namesdecl = dyn_cast<clang::NamespaceDecl>(newcontext);
                if (namesdecl->getNameAsString() == "Qt")
                    isQtNamespaceExplicit = true;
             }
            newcontext = newcontext->getParent();
        }

        if (enclosingNameSpace == "QTextStreamFunctions") {
            replacementForQTextStreamFunctions(functionName, message, replacement, isQtNamespaceExplicit);
        } else if ((functionName == "KeepEmptyParts" || functionName == "SkipEmptyParts") &&
                   declType == "enum QString::SplitBehavior") {
            replacementForQStringSplitBehavior(functionName, message, replacement, isQtNamespaceExplicit);
        } else if (functionName == "MatchRegExp" && declType == "enum Qt::MatchFlag") {
            message = "call Qt::MatchRegExp. Use Qt::MatchRegularExpression instead.";
            if (isQtNamespaceExplicit)
                replacement = "MatchRegularExpression";
            else
                replacement = "Qt::MatchRegularExpression";
        }else {
            return;
        }
        fixitRange = declRefExp->getSourceRange();

    } else if (membExpr) {
        Stmt *child = clazy::childAt(stmt, 0);
        DeclRefExpr *decl = nullptr;
        while (child) {
            decl = dyn_cast<DeclRefExpr>(child);
            if (decl)
                break;
            child = clazy::childAt(child, 0);
        }

        if (!decl)
            return;
        string functionName = membExpr->getMemberNameInfo().getAsString();
        string className = decl->getType().getAsString();
        warningLocation = membExpr->getEndLoc();

        if (clazy::startsWith(className, "QMap<") && qMapFunctions.find(functionName) != qMapFunctions.end()) {
            message = "Use QMultiMap for maps storing multiple values with the same key.";
            emitWarning(warningLocation, message, fixits);
            return;
        } else if (clazy::startsWith(className, "QHash<") && qMapFunctions.find(functionName) != qMapFunctions.end()) {
          // the name of the deprecated function are the same
            message = "Use QMultiHash for maps storing multiple values with the same key.";
            emitWarning(warningLocation, message, fixits);
            return;
        } else if (clazy::startsWith(className, "class QDir") && functionName == "addResourceSearchPath") {
            message = "call function QDir::addResourceSearchPath(). Use function QDir::addSearchPath() with prefix instead";
            emitWarning(warningLocation, message, fixits);
            return;
        } else if (clazy::startsWith(className, "class QTimeLine") &&
                   (functionName == "curveShape" || functionName == "setCurveShape")) {
            if (functionName == "curveShape") {
                message = "call QTimeLine::curveShape. Use QTimeLine::easingCurve instead";
            } else {
                message = "call QTimeLine::setCurveShape. Use QTimeLine::setEasingCurve instead";
            }
            emitWarning(warningLocation, message, fixits);
            return;
        } else if (clazy::startsWith(className, "QSet") && qSetDeprecatedFunctions.find(functionName) != qSetDeprecatedFunctions.end()) {
            message = "QSet iterator categories changed from bidirectional to forward. Please port your code manually";
            emitWarning(warningLocation, message, fixits);
            return;
        } else if (clazy::startsWith(className, "QHash") && qHashDeprecatedFunctions.find(functionName) != qHashDeprecatedFunctions.end()) {
            message = "QHash iterator categories changed from bidirectional to forward. Please port your code manually";
            emitWarning(warningLocation, message, fixits);
            return;
        } else if (clazy::startsWith(className, "class QComboBox") && functionName == "currentIndexChanged") {
            if (!warningForQComboBox(membExpr, message))
                return;
            emitWarning(warningLocation, message, fixits);
            return;
        }  else if (clazy::startsWith(className, "class QTextBrowser") && functionName == "highlighted") {
            if (!warningForQTextBrowser(membExpr, message))
                return;
            emitWarning(warningLocation, message, fixits);
            return;
        } else if (clazy::startsWith(className, "class QGraphicsView") &&
                   qGraphicsViewFunctions.find(functionName) != qMapFunctions.end()) {
            warningForGraphicsViews(functionName, message);
            emitWarning(warningLocation, message, fixits);
            return;
        } else if (className == "class QDate"  && functionName == "toString") {
            Stmt *parent = clazy::parent(m_context->parentMap, stmt);
            if (!replacementForQDate(parent, message, replacement, warningLocation, fixitRange))
                return;
            fixits.push_back(FixItHint::CreateReplacement(fixitRange, replacement));
            emitWarning(warningLocation, message, fixits);
            return;
        }else if (clazy::startsWith(className, "class QProcess") &&
                   qProcessDeprecatedFunctions.find(functionName) != qProcessDeprecatedFunctions.end()) {
            replacementForQProcess(functionName, message, replacement);
        } else if (clazy::startsWith(className, "class QResource") && functionName == "isCompressed") {
            replacementForQResource(functionName, message, replacement);
        } else if (clazy::startsWith(className, "class QSignalMapper") && functionName == "mapped") {
            replacementForQSignalMapper(membExpr, message, replacement);
        } else if (clazy::startsWith(className, "class QWizard") && functionName == "visitedPages") {
            replacementForQWizard(functionName, message, replacement);
        } else if (clazy::startsWith(className, "class QButtonGroup") &&
                   qButtonGroupDeprecatedFunctions.find(functionName) != qButtonGroupDeprecatedFunctions.end()) {
            if (!replacementForQButtonGroup(membExpr, message, replacement))
                return;
        } else if (clazy::startsWith(className, "class QComboBox") &&
                   (functionName == "activated" || functionName == "highlighted")) {
            if (!replacementForQComboBox(membExpr, functionName, message, replacement))
                return;
        } else {
            return;
        }
        fixitRange = SourceRange(membExpr->getEndLoc());
    }else {
        return;
    }

    fixits.push_back(FixItHint::CreateReplacement(fixitRange, replacement));
    emitWarning(warningLocation, message, fixits);

    return;
}

void Qt6DeprecatedAPIFixes::VisitMacroExpands(const clang::Token &MacroNameTok,
                                              const clang::SourceRange &range, const MacroInfo *)
{
    m_listingMacroExpand.push_back(range.getBegin());
    return;
}
