/* This file is part of KDevelop
    SPDX-FileCopyrightText: 2003 Roberto Raggi <roberto@kdevelop.org>
    SPDX-FileCopyrightText: 2003 Alexander Dymo <adymo@kdevelop.org>
    SPDX-FileCopyrightText: 2004 Jonas Jacobi <j.jacobi@gmx.de>

    SPDX-License-Identifier: LGPL-2.0-or-later
*/

#include "codemodel_utils.h"

namespace CodeModelUtils
{

namespace Functions
{

void processClasses(FunctionList &list, const ClassDom dom)
{
    const ClassList cllist = dom->classList();
    for (ClassList::ConstIterator it = cllist.begin(); it != cllist.end(); ++it) {
        processClasses(list, *it);
    }

    const FunctionList fnlist = dom->functionList();
    for (FunctionList::ConstIterator it = fnlist.begin(); it != fnlist.end(); ++it) {
        list.append(*it);
    }
}

void processNamespaces(FunctionList &list, const NamespaceDom dom)
{
    const NamespaceList nslist = dom->namespaceList();
    for (NamespaceList::ConstIterator it = nslist.begin(); it != nslist.end(); ++it) {
        processNamespaces(list, *it);
    }
    const ClassList cllist = dom->classList();
    for (ClassList::ConstIterator it = cllist.begin(); it != cllist.end(); ++it) {
        processClasses(list, *it);
    }

    const FunctionList fnlist = dom->functionList();
    for (FunctionList::ConstIterator it = fnlist.begin(); it != fnlist.end(); ++it) {
        list.append(*it);
    }
}

void processNamespaces(FunctionList & list, const NamespaceDom dom, QMap< FunctionDom, Scope > & relations)
{
    const NamespaceList nslist = dom->namespaceList();
    for (NamespaceList::ConstIterator it = nslist.begin(); it != nslist.end(); ++it) {
        processNamespaces(list, *it, relations);
    }
    const ClassList cllist = dom->classList();
    for (ClassList::ConstIterator it = cllist.begin(); it != cllist.end(); ++it) {
        processClasses(list, *it, relations, dom);
    }

    const FunctionList fnlist = dom->functionList();
    for (FunctionList::ConstIterator it = fnlist.begin(); it != fnlist.end(); ++it) {
        list.append(*it);
        relations[*it].ns = dom;
    }
}

void processClasses(FunctionList & list, const ClassDom dom, QMap< FunctionDom, Scope > & relations)
{
    const ClassList cllist = dom->classList();
    for (ClassList::ConstIterator it = cllist.begin(); it != cllist.end(); ++it) {
        processClasses(list, *it, relations);
    }

    const FunctionList fnlist = dom->functionList();
    for (FunctionList::ConstIterator it = fnlist.begin(); it != fnlist.end(); ++it) {
        list.append(*it);
        relations[*it].klass = dom;
    }
}

void processClasses(FunctionList & list, const ClassDom dom, QMap< FunctionDom, Scope > & relations, const NamespaceDom & nsdom)
{
    const ClassList cllist = dom->classList();
    for (ClassList::ConstIterator it = cllist.begin(); it != cllist.end(); ++it) {
        processClasses(list, *it, relations, nsdom);
    }

    const FunctionList fnlist = dom->functionList();
    for (FunctionList::ConstIterator it = fnlist.begin(); it != fnlist.end(); ++it) {
        list.append(*it);
        relations[*it].klass = dom;
        relations[*it].ns = nsdom;
    }
}

} // end of Functions namespace



namespace FunctionDefinitions
{

void processClasses(FunctionDefinitionList &list, const ClassDom dom)
{
    const ClassList cllist = dom->classList();
    for (ClassList::ConstIterator it = cllist.begin(); it != cllist.end(); ++it) {
        processClasses(list, *it);
    }

    const FunctionDefinitionList fnlist = dom->functionDefinitionList();
    for (FunctionDefinitionList::ConstIterator it = fnlist.begin(); it != fnlist.end(); ++it) {
        list.append(*it);
    }
}

void processNamespaces(FunctionDefinitionList &list, const NamespaceDom dom)
{
    const NamespaceList nslist = dom->namespaceList();
    for (NamespaceList::ConstIterator it = nslist.begin(); it != nslist.end(); ++it) {
        processNamespaces(list, *it);
    }
    const ClassList cllist = dom->classList();
    for (ClassList::ConstIterator it = cllist.begin(); it != cllist.end(); ++it) {
        processClasses(list, *it);
    }

    const FunctionDefinitionList fnlist = dom->functionDefinitionList();
    for (FunctionDefinitionList::ConstIterator it = fnlist.begin(); it != fnlist.end(); ++it) {
        list.append(*it);
    }
}

void processNamespaces(FunctionDefinitionList & list, const NamespaceDom dom, QMap< FunctionDefinitionDom, Scope > & relations)
{
    const NamespaceList nslist = dom->namespaceList();
    for (NamespaceList::ConstIterator it = nslist.begin(); it != nslist.end(); ++it) {
        processNamespaces(list, *it, relations);
    }
    const ClassList cllist = dom->classList();
    for (ClassList::ConstIterator it = cllist.begin(); it != cllist.end(); ++it) {
        processClasses(list, *it, relations, dom);
    }

    const FunctionDefinitionList fnlist = dom->functionDefinitionList();
    for (FunctionDefinitionList::ConstIterator it = fnlist.begin(); it != fnlist.end(); ++it) {
        list.append(*it);
        relations[*it].ns = dom;
    }
}

void processClasses(FunctionDefinitionList & list, const ClassDom dom, QMap< FunctionDefinitionDom, Scope > & relations)
{
    const ClassList cllist = dom->classList();
    for (ClassList::ConstIterator it = cllist.begin(); it != cllist.end(); ++it) {
        processClasses(list, *it, relations);
    }

    const FunctionDefinitionList fnlist = dom->functionDefinitionList();
    for (FunctionDefinitionList::ConstIterator it = fnlist.begin(); it != fnlist.end(); ++it) {
        list.append(*it);
        relations[*it].klass = dom;
    }
}

void processClasses(FunctionDefinitionList & list, const ClassDom dom, QMap< FunctionDefinitionDom, Scope > & relations, const NamespaceDom & nsdom)
{
    const ClassList cllist = dom->classList();
    for (ClassList::ConstIterator it = cllist.begin(); it != cllist.end(); ++it) {
        processClasses(list, *it, relations, nsdom);
    }

    const FunctionDefinitionList fnlist = dom->functionDefinitionList();
    for (FunctionDefinitionList::ConstIterator it = fnlist.begin(); it != fnlist.end(); ++it) {
        list.append(*it);
        relations[*it].klass = dom;
        relations[*it].ns = nsdom;
    }
}

} // end of FunctionDefinitions namespace



FunctionList allFunctions(const FileDom &dom)
{
    using namespace Functions;
    FunctionList list;

    const NamespaceList nslist = dom->namespaceList();
    for (NamespaceList::ConstIterator it = nslist.begin(); it != nslist.end(); ++it) {
        processNamespaces(list, *it);
    }

    const ClassList cllist = dom->classList();
    for (ClassList::ConstIterator it = cllist.begin(); it != cllist.end(); ++it) {
        processClasses(list, *it);
    }

    const FunctionList fnlist = dom->functionList();
    for (FunctionList::ConstIterator it = fnlist.begin(); it != fnlist.end(); ++it) {
        list.append(*it);
    }

    return list;
}

AllFunctions allFunctionsDetailed(const FileDom & dom)
{
    using namespace Functions;
    AllFunctions list;

    const NamespaceList nslist = dom->namespaceList();
    for (NamespaceList::ConstIterator it = nslist.begin(); it != nslist.end(); ++it) {
        processNamespaces(list.functionList, *it, list.relations);
    }

    const ClassList cllist = dom->classList();
    for (ClassList::ConstIterator it = cllist.begin(); it != cllist.end(); ++it) {
        processClasses(list.functionList, *it, list.relations);
    }

    const FunctionList fnlist = dom->functionList();
    for (FunctionList::ConstIterator it = fnlist.begin(); it != fnlist.end(); ++it) {
        list.functionList.append(*it);
    }

    return list;
}

AllFunctionDefinitions allFunctionDefinitionsDetailed(const FileDom & dom)
{
    using namespace FunctionDefinitions;
    AllFunctionDefinitions list;

    const NamespaceList nslist = dom->namespaceList();
    for (NamespaceList::ConstIterator it = nslist.begin(); it != nslist.end(); ++it) {
        processNamespaces(list.functionList, *it, list.relations);
    }

    const ClassList cllist = dom->classList();
    for (ClassList::ConstIterator it = cllist.begin(); it != cllist.end(); ++it) {
        processClasses(list.functionList, *it, list.relations);
    }

    const FunctionDefinitionList fnlist = dom->functionDefinitionList();
    for (FunctionDefinitionList::ConstIterator it = fnlist.begin(); it != fnlist.end(); ++it) {
        list.functionList.append(*it);
    }

    return list;
}


bool resultTypesFit(const FunctionDom & dec, const FunctionDefinitionDom & def)
{
    if (!def->resultType().contains("::")) return dec->resultType() == def->resultType();
    QStringList l1 = dec->scope() + QStringList::split("::", dec->resultType());
    QStringList l2 = QStringList::split("::", def->resultType());

    if (l1.isEmpty() || l2.isEmpty() || l1.back() != l2.back()) return false;

    while (!l1.isEmpty() && !l2.isEmpty()) {
        if (l1.back() == l2.back()) {
            l1.pop_back();
            l2.pop_back();
        } else {
            l1.pop_back();
        }
    }

    if (l2.isEmpty()) return true;

    return false;
}


bool compareDeclarationToDefinition(const FunctionDom & dec, const FunctionDefinitionDom & def)
{
    if (dec->scope() == def->scope() && dec->name() == def->name() && resultTypesFit(dec, def) && dec->isConstant() == def->isConstant()) {
        const ArgumentList defList = def->argumentList(), decList = dec->argumentList();
        if (defList.size() != decList.size())
            return false;

        size_t n = defList.size();
        for (size_t i = 0; i < n; ++i)
            if (defList[i]->type() != decList[i]->type())
                return false;

        return true;
    }
    return false;
}


bool compareDeclarationToDefinition(const FunctionDom & dec, const FunctionDefinitionDom & def, const std::set<NamespaceImportModel> & nsImports)
{
    if (dec->name() == def->name() && resultTypesFit(dec, def) && dec->isConstant() == def->isConstant()) {
        const ArgumentList defList = def->argumentList(), decList = dec->argumentList();
        if (defList.size() != decList.size())
            return false;

        size_t n = defList.size();
        for (size_t i = 0; i < n; ++i)
            if (defList[i]->type() != decList[i]->type())
                return false;

        const QStringList &defScope = def->scope(), &decScope = dec->scope();
        if (defScope != decScope) {
            if (defScope.size() >= decScope.size())
                return false;

            n = decScope.size();
            for (size_t i1 = 0, i2 = 0; i1 < n; ++i1) {
                if (i2 >= defScope.size() || decScope[i1] != defScope[i2]) {
                    NamespaceImportModel model;
                    model.setName(decScope[i1]);
                    model.setFileName(def->fileName());
                    if (nsImports.find(model) == nsImports.end())
                        return false;
                } else {
                    ++i2;
                }
            }
        }

        return true;
    }
    return false;
}



FunctionList allFunctionsExhaustive(FileDom &dom)
{
    PredAmOwner<FunctionDom> ow(dom);
    FunctionList ret;

    findFunctionDeclarations(ow, dom->wholeGroup(), ret);

    return ret;
}


FunctionDefinitionList allFunctionDefinitionsExhaustive(FileDom &dom)
{
    PredAmOwner<FunctionDefinitionDom> ow(dom);
    FunctionDefinitionList ret;

    findFunctionDefinitions(ow, dom->wholeGroup(), ret);

    return ret;
}



ClassDom findClassByPosition(NamespaceModel* nameSpace, int line, int col)
{
    if (nameSpace == 0)
        return 0;

    NamespaceList nsList = nameSpace->namespaceList();
    for (NamespaceList::iterator i = nsList.begin(); i != nsList.end(); ++i) {
        ClassDom result = findClassByPosition(*i, line, col);
        if (result != 0)
            return result;
    }

    ClassList classes = nameSpace->classList();
    for (ClassList::iterator i = classes.begin(); i != classes.end(); ++i) {
        ClassDom result = findClassByPosition(*i, line, col);
        if (result != 0)
            return result;
    }

    return 0;
}

ClassDom findClassByPosition(ClassModel* aClass, int line, int col)
{
    if (aClass == 0)
        return 0;

    ClassList classes = aClass->classList();
    for (ClassList::iterator i = classes.begin(); i != classes.end(); ++i) {
        ClassDom result = findClassByPosition(*i, line, col);
        if (result != 0)
            return result;
    }

    int startLine, startCol;
    aClass->getStartPosition(&startLine, &startCol);

    if (startLine <= line) {
        int endLine, endCol;
        aClass->getEndPosition(&endLine, &endCol);
        if (endLine >= line)
            return (aClass);
    }

    return 0;
}

int findLastMethodLine(ClassDom aClass, CodeModelItem::Access access)
{
    int point = -1;

    const FunctionList functionList = aClass->functionList();
    for (FunctionList::ConstIterator it=functionList.begin(); it!=functionList.end(); ++it) {
        int funEndLine, funEndColumn;
        (*it)->getEndPosition(&funEndLine, &funEndColumn);

        if ((*it)->access() == access && point < funEndLine)
            point = funEndLine;
    }

    return point;
}

int findLastVariableLine(ClassDom aClass, CodeModelItem::Access access)
{
    int point = -1;

    const VariableList varList = aClass->variableList();
    for (VariableList::ConstIterator it= varList.begin(); it!= varList.end(); ++it) {
        int varEndLine, varEndColumn;
        (*it)->getEndPosition(&varEndLine, &varEndColumn);

        if ((*it)->access() == access && point < varEndLine)
            point = varEndLine;
    }

    return point;
}

QString accessSpecifierToString(CodeModelItem::Access access)
{
    switch (access) {
    case CodeModelItem::Public:
        return "public";
    case CodeModelItem::Protected:
        return "protected";
    case CodeModelItem::Private:
        return "private";
    default:
        return "unknown";
    }
}

FunctionDefinitionDom CodeModelHelper::functionDefinitionAt(NamespaceDom ns, int line, int column)
{
    NamespaceList namespaceList = ns->namespaceList();
    NamespaceList::iterator nslEnd = namespaceList.end();

    for (NamespaceList::iterator it=namespaceList.begin(); it!=nslEnd; ++it) {
        if (FunctionDefinitionDom def = functionDefinitionAt(*it, line, column))
            return def;
    }

    ClassList classList = ns->classList();
    ClassList::iterator clEnd = classList.end();

    for (ClassList::iterator it=classList.begin(); it!=clEnd; ++it) {
        if (FunctionDefinitionDom def = functionDefinitionAt(*it, line, column))
            return def;
    }

    FunctionDefinitionList functionDefinitionList = ns->functionDefinitionList();
    FunctionDefinitionList::iterator fdlEnd = functionDefinitionList.end();

    for (FunctionDefinitionList::iterator it=functionDefinitionList.begin();
         it!=fdlEnd; ++it) {
        if (FunctionDefinitionDom def = functionDefinitionAt(*it, line, column))
            return def;
    }

    return FunctionDefinitionDom();
}

FunctionDefinitionDom CodeModelHelper::functionDefinitionAt(ClassDom klass, int line, int column)
{
    ClassList classList = klass->classList();
    ClassList::iterator clEnd = classList.end();

    for (ClassList::iterator it=classList.begin(); it!=clEnd; ++it) {
        if (FunctionDefinitionDom def = functionDefinitionAt(*it, line, column))
            return def;
    }

    FunctionDefinitionList functionDefinitionList = klass->functionDefinitionList();
    FunctionDefinitionList::iterator fdlEnd = functionDefinitionList.end();
    for (FunctionDefinitionList::Iterator it=functionDefinitionList.begin();
         it!=fdlEnd; ++it) {
        if (FunctionDefinitionDom def = functionDefinitionAt(*it, line, column))
            return def;
    }

    return FunctionDefinitionDom();
}

FunctionDefinitionDom CodeModelHelper::functionDefinitionAt(FunctionDefinitionDom fun, int line, int // column
                                                           )
{
    int startLine, startColumn;
    int endLine, endColumn;

    fun->getStartPosition(&startLine, &startColumn);
    fun->getEndPosition(&endLine, &endColumn);

    if (!(line >= startLine && line <= endLine)  || fun->fileName() != m_fileName)
        return FunctionDefinitionDom();

    /*
    if (line == startLine && column < startColumn)
    return FunctionDefinitionDom();

    if (line == endLine && column > endColumn)
    return FunctionDefinitionDom();*/

    return fun;
}



FunctionDom CodeModelHelper::functionDeclarationAt(NamespaceDom ns, int line, int column)
{
    NamespaceList namespaceList = ns->namespaceList();
    NamespaceList::iterator nsEnd = namespaceList.end();
    for (NamespaceList::iterator it=namespaceList.begin(); it!=nsEnd; ++it) {
        if (FunctionDom def = functionDeclarationAt(*it, line, column))
            return def;
    }

    ClassList classList = ns->classList();
    ClassList::iterator clEnd = classList.end();
    for (ClassList::iterator it=classList.begin(); it!=clEnd; ++it) {
        if (FunctionDom def = functionDeclarationAt(*it, line, column))
            return def;
    }

    FunctionList functionList = ns->functionList();
    FunctionList::iterator flEnd = functionList.end();
    for (FunctionList::iterator it=functionList.begin();
         it!=flEnd; ++it) {
        if (FunctionDom def = functionDeclarationAt(*it, line, column))
            return def;
    }

    return FunctionDom();
}

FunctionDom CodeModelHelper::functionDeclarationAt(ClassDom klass, int line, int column)
{
    ClassList classList = klass->classList();
    ClassList::iterator clEnd = classList.end();
    for (ClassList::iterator it=classList.begin(); it!=clEnd; ++it) {
        if (FunctionDom def = functionDeclarationAt(*it, line, column))
            return def;
    }

    FunctionList functionList = klass->functionList();
    FunctionList::iterator flEnd = functionList.end();
    for (FunctionList::Iterator it=functionList.begin();
         it!=flEnd; ++it) {
        if (FunctionDom def = functionDeclarationAt(*it, line, column))
            return def;
    }

    return FunctionDom();
}

FunctionDom CodeModelHelper::functionDeclarationAt(FunctionDom fun, int line, int // column
                                                  )
{
    int startLine, startColumn;
    int endLine, endColumn;

    fun->getStartPosition(&startLine, &startColumn);
    fun->getEndPosition(&endLine, &endColumn);

    if (!(line >= startLine && line <= endLine) || fun->fileName() != m_fileName)
        return FunctionDom();


    /*    if (line == startLine && column < startColumn)
    return FunctionDom();

    if (line == endLine && column > endColumn)
    return FunctionDom();*/

    return fun;
}




ClassDom CodeModelHelper::classAt(NamespaceDom ns, int line, int column)
{
    NamespaceList namespaceList = ns->namespaceList();
    NamespaceList::iterator nsEnd = namespaceList.end();

    for (NamespaceList::iterator it=namespaceList.begin(); it!=nsEnd; ++it) {
        if (ClassDom def = classAt(*it, line, column))
            return def;
    }

    ClassList classList = ns->classList();
    ClassList::iterator clEnd = classList.end();
    for (ClassList::iterator it=classList.begin(); it!=clEnd; ++it) {
        if (ClassDom def = classAt(*it, line, column))
            return def;
    }

    return ClassDom();
}

ClassDom CodeModelHelper::classAt(ClassDom klass, int line, int column)
{
    ClassList classList = klass->classList();
    ClassList::iterator clEnd = classList.end();
    for (ClassList::iterator it=classList.begin(); it!=clEnd; ++it) {
        if (ClassDom def = classAt(*it, line, column))
            return def;
    }

    int startLine, startColumn;
    int endLine, endColumn;

    klass->getStartPosition(&startLine, &startColumn);
    klass->getEndPosition(&endLine, &endColumn);

    if (!(line >= startLine && line <= endLine)  || klass->fileName() != m_fileName)
        return ClassDom();

    return klass;
}


CodeModelHelper::CodeModelHelper(CodeModel* model, FileDom file) : m_model(model)
{
    if (!file) return;
    m_files = file->wholeGroup();
    m_fileName = file->name();
}


FunctionDom CodeModelHelper::functionAt(int line, int column, FunctionTypes types)
{
    if (m_files.isEmpty()) return FunctionDom();


    FunctionDom ret;
    FileList::iterator it = m_files.begin();
    while (it != m_files.end()) {
        if (types & Declaration) {
            ret = functionDeclarationAt(model_cast<NamespaceDom>(*it), line, column);
            if (ret) return ret;
        }
        if (types & Definition) {
            FunctionDefinitionDom r  = functionDefinitionAt(model_cast<NamespaceDom>(*it), line, column);
            if (r) {
                ret = model_cast<FunctionDom>(r);
                return ret;
            }
        }
        ++it;
    }

    return ret;
}

ClassDom CodeModelHelper::classAt(int line, int column)
{
    if (m_files.isEmpty()) return ClassDom();

    ClassDom ret;
    FileList::iterator it = m_files.begin();
    while (it != m_files.end()) {
        ret = classAt(model_cast<NamespaceDom>(*it), line, column);
        if (ret) return ret;
        ++it;
    }

    return ret;
}

}//end of namespace CodeModeUtils
