/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** 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.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#ifndef QDOCDATABASE_H
#define QDOCDATABASE_H

#include "config.h"
#include "examplenode.h"
#include "propertynode.h"
#include "text.h"
#include "tree.h"

#include <QtCore/qdebug.h>
#include <QtCore/qmap.h>
#include <QtCore/qstring.h>

QT_BEGIN_NAMESPACE

typedef QMultiMap<Text, const Node *> TextToNodeMap;

class Atom;
class FunctionNode;
class Generator;
class QDocDatabase;

enum FindFlag {
    SearchBaseClasses = 0x1,
    SearchEnumValues = 0x2,
    TypesOnly = 0x4,
    IgnoreModules = 0x8
};

class QDocForest
{
private:
    friend class QDocDatabase;
    QDocForest(QDocDatabase *qdb) : qdb_(qdb), primaryTree_(nullptr), currentIndex_(0) {}
    ~QDocForest();

    NamespaceNode *firstRoot();
    NamespaceNode *nextRoot();
    Tree *firstTree();
    Tree *nextTree();
    Tree *primaryTree() { return primaryTree_; }
    Tree *findTree(const QString &t) { return forest_.value(t); }
    QStringList keys() { return forest_.keys(); }
    NamespaceNode *primaryTreeRoot() { return (primaryTree_ ? primaryTree_->root() : nullptr); }
    bool isEmpty() { return searchOrder().isEmpty(); }
    bool done() { return (currentIndex_ >= searchOrder().size()); }
    const QList<Tree *> &searchOrder();
    const QList<Tree *> &indexSearchOrder();
    void setSearchOrder(const QStringList &t);
    bool isLoaded(const QString &fn)
    {
        return std::any_of(searchOrder().constBegin(), searchOrder().constEnd(),
                           [fn](Tree *tree) { return fn == tree->indexFileName(); });
    }

    const Node *findNode(const QStringList &path, const Node *relative, int findFlags,
                         Node::Genus genus)
    {
        for (const auto *tree : searchOrder()) {
            const Node *n = tree->findNode(path, relative, findFlags, genus);
            if (n)
                return n;
            relative = nullptr;
        }
        return nullptr;
    }

    Node *findNodeByNameAndType(const QStringList &path, bool (Node::*isMatch)() const)
    {
        for (const auto *tree : searchOrder()) {
            Node *n = tree->findNodeByNameAndType(path, isMatch);
            if (n)
                return n;
        }
        return nullptr;
    }

    ClassNode *findClassNode(const QStringList &path)
    {
        for (const auto *tree : searchOrder()) {
            ClassNode *n = tree->findClassNode(path);
            if (n)
                return n;
        }
        return nullptr;
    }

    Node *findNodeForInclude(const QStringList &path)
    {
        for (const auto *tree : searchOrder()) {
            Node *n = tree->findNodeForInclude(path);
            if (n)
                return n;
        }
        return nullptr;
    }

    const FunctionNode *findFunctionNode(const QStringList &path, const Parameters &parameters,
                                         const Node *relative, Node::Genus genus);
    const Node *findNodeForTarget(QStringList &targetPath, const Node *relative, Node::Genus genus,
                                  QString &ref);

    const Node *findTypeNode(const QStringList &path, const Node *relative, Node::Genus genus)
    {
        int flags = SearchBaseClasses | SearchEnumValues | TypesOnly;
        if (relative && genus == Node::DontCare && relative->genus() != Node::DOC)
            genus = relative->genus();
        for (const auto *tree : searchOrder()) {
            const Node *n = tree->findNode(path, relative, flags, genus);
            if (n)
                return n;
            relative = nullptr;
        }
        return nullptr;
    }

    const PageNode *findPageNodeByTitle(const QString &title)
    {
        for (const auto *tree : searchOrder()) {
            const PageNode *n = tree->findPageNodeByTitle(title);
            if (n)
                return n;
        }
        return nullptr;
    }

    const CollectionNode *getCollectionNode(const QString &name, Node::NodeType type)
    {
        for (auto *tree : searchOrder()) {
            const CollectionNode *cn = tree->getCollection(name, type);
            if (cn)
                return cn;
        }
        return nullptr;
    }

    QmlTypeNode *lookupQmlType(const QString &name)
    {
        for (const auto *tree : searchOrder()) {
            QmlTypeNode *qcn = tree->lookupQmlType(name);
            if (qcn)
                return qcn;
        }
        return nullptr;
    }

    Aggregate *lookupQmlBasicType(const QString &name)
    {
        for (const auto *tree : searchOrder()) {
            Aggregate *a = tree->lookupQmlBasicType(name);
            if (a)
                return a;
        }
        return nullptr;
    }
    void clearSearchOrder() { searchOrder_.clear(); }
    void clearLinkCounts()
    {
        for (auto *tree : searchOrder())
            tree->clearLinkCount();
    }
    void printLinkCounts(const QString &project);
    QString getLinkCounts(QStringList &strings, QList<int> &counts);
    void newPrimaryTree(const QString &module);
    void setPrimaryTree(const QString &t);
    NamespaceNode *newIndexTree(const QString &module);

private:
    QDocDatabase *qdb_;
    Tree *primaryTree_;
    int currentIndex_;
    QMap<QString, Tree *> forest_;
    QList<Tree *> searchOrder_;
    QList<Tree *> indexSearchOrder_;
    QList<QString> moduleNames_;
};

class QDocDatabase
{
public:
    static QDocDatabase *qdocDB();
    static void destroyQdocDB();
    ~QDocDatabase();

    Tree *findTree(const QString &t) { return forest_.findTree(t); }

    const CNMap &groups() { return primaryTree()->groups(); }
    const CNMap &modules() { return primaryTree()->modules(); }
    const CNMap &qmlModules() { return primaryTree()->qmlModules(); }
    const CNMap &jsModules() { return primaryTree()->jsModules(); }

    CollectionNode *addGroup(const QString &name) { return primaryTree()->addGroup(name); }
    CollectionNode *addModule(const QString &name) { return primaryTree()->addModule(name); }
    CollectionNode *addQmlModule(const QString &name) { return primaryTree()->addQmlModule(name); }
    CollectionNode *addJsModule(const QString &name) { return primaryTree()->addJsModule(name); }

    CollectionNode *addToGroup(const QString &name, Node *node)
    {
        return primaryTree()->addToGroup(name, node);
    }
    CollectionNode *addToModule(const QString &name, Node *node)
    {
        return primaryTree()->addToModule(name, node);
    }
    CollectionNode *addToQmlModule(const QString &name, Node *node)
    {
        return primaryTree()->addToQmlModule(name, node);
    }
    CollectionNode *addToJsModule(const QString &name, Node *node)
    {
        return primaryTree()->addToJsModule(name, node);
    }

    void addExampleNode(ExampleNode *n) { primaryTree()->addExampleNode(n); }
    ExampleNodeMap &exampleNodeMap() { return primaryTree()->exampleNodeMap(); }

    QmlTypeNode *findQmlType(const QString &name);
    QmlTypeNode *findQmlType(const QString &qmid, const QString &name);
    QmlTypeNode *findQmlType(const ImportRec &import, const QString &name);
    Aggregate *findQmlBasicType(const QString &qmid, const QString &name);

    static NodeMultiMap &obsoleteClasses() { return obsoleteClasses_; }
    static NodeMultiMap &obsoleteQmlTypes() { return obsoleteQmlTypes_; }
    static NodeMultiMap &classesWithObsoleteMembers() { return classesWithObsoleteMembers_; }
    static NodeMultiMap &qmlTypesWithObsoleteMembers() { return qmlTypesWithObsoleteMembers_; }
    static NodeMultiMap &cppClasses() { return cppClasses_; }
    static NodeMultiMap &qmlBasicTypes() { return qmlBasicTypes_; }
    static NodeMultiMap &qmlTypes() { return qmlTypes_; }
    static NodeMultiMap &examples() { return examples_; }
    static NodeMultiMapMap &newClassMaps() { return newClassMaps_; }
    static NodeMultiMapMap &newQmlTypeMaps() { return newQmlTypeMaps_; }
    static NodeMultiMapMap &newSinceMaps() { return newSinceMaps_; }

private:
    void findAllClasses(Aggregate *node) { node->findAllClasses(); }
    void findAllFunctions(Aggregate *node) { node->findAllFunctions(functionIndex_); }
    void findAllAttributions(Aggregate *node) { node->findAllAttributions(attributions_); }
    void findAllLegaleseTexts(Aggregate *node);
    void findAllObsoleteThings(Aggregate *node) { node->findAllObsoleteThings(); }
    void findAllSince(Aggregate *node) { node->findAllSince(); }

public:
    /*******************************************************************
     special collection access functions
    ********************************************************************/
    NodeMultiMap &getCppClasses();
    NodeMultiMap &getObsoleteClasses();
    NodeMultiMap &getClassesWithObsoleteMembers();
    NodeMultiMap &getObsoleteQmlTypes();
    NodeMultiMap &getQmlTypesWithObsoleteMembers();
    NodeMultiMap &getNamespaces();
    NodeMultiMap &getQmlBasicTypes();
    NodeMultiMap &getQmlTypes();
    NodeMultiMap &getExamples();
    NodeMultiMap &getAttributions();
    NodeMapMap &getFunctionIndex();
    TextToNodeMap &getLegaleseTexts();
    const NodeMultiMap &getClassMap(const QString &key);
    const NodeMultiMap &getQmlTypeMap(const QString &key);
    const NodeMultiMap &getSinceMap(const QString &key);

    /*******************************************************************
      Many of these will be either eliminated or replaced.
    ********************************************************************/
    void resolveStuff();
    void insertTarget(const QString &name, const QString &title, TargetRec::TargetType type,
                      Node *node, int priority)
    {
        primaryTree()->insertTarget(name, title, type, node, priority);
    }

    /*******************************************************************
      The functions declared below are called for the current tree only.
    ********************************************************************/
    Aggregate *findRelatesNode(const QStringList &path)
    {
        return primaryTree()->findRelatesNode(path);
    }
    Node *findNodeInOpenNamespace(QStringList &path, bool (Node::*)() const);
    /*******************************************************************/

    /*****************************************************************************
      This function can handle parameters enclosed in '[' ']' (domain and genus).
    ******************************************************************************/
    const Node *findNodeForAtom(const Atom *atom, const Node *relative, QString &ref,
                                Node::Genus genus = Node::DontCare);
    /*******************************************************************/

    /*******************************************************************
      The functions declared below are called for all trees.
    ********************************************************************/
    ClassNode *findClassNode(const QStringList &path) { return forest_.findClassNode(path); }
    Node *findNodeForInclude(const QStringList &path) { return forest_.findNodeForInclude(path); }
    const FunctionNode *findFunctionNode(const QString &target, const Node *relative,
                                         Node::Genus genus);
    const Node *findTypeNode(const QString &type, const Node *relative, Node::Genus genus);
    const Node *findNodeForTarget(const QString &target, const Node *relative);
    const PageNode *findPageNodeByTitle(const QString &title)
    {
        return forest_.findPageNodeByTitle(title);
    }
    Node *findNodeByNameAndType(const QStringList &path, bool (Node::*isMatch)() const)
    {
        return forest_.findNodeByNameAndType(path, isMatch);
    }
    const CollectionNode *getCollectionNode(const QString &name, Node::NodeType type)
    {
        return forest_.getCollectionNode(name, type);
    }
    FunctionNode *findFunctionNodeForTag(QString tag)
    {
        return primaryTree()->findFunctionNodeForTag(tag);
    }
    FunctionNode *findMacroNode(const QString &t) { return primaryTree()->findMacroNode(t); }

    QStringList groupNamesForNode(Node *node);

private:
    const Node *findNodeForTarget(QStringList &targetPath, const Node *relative, Node::Genus genus,
                                  QString &ref)
    {
        return forest_.findNodeForTarget(targetPath, relative, genus, ref);
    }
    const FunctionNode *findFunctionNode(const QStringList &path, const Parameters &parameters,
                                         const Node *relative, Node::Genus genus)
    {
        return forest_.findFunctionNode(path, parameters, relative, genus);
    }

    /*******************************************************************/
public:
    void addPropertyFunction(PropertyNode *property, const QString &funcName,
                             PropertyNode::FunctionRole funcRole)
    {
        primaryTree()->addPropertyFunction(property, funcName, funcRole);
    }

    void setVersion(const QString &v) { version_ = v; }
    QString version() const { return version_; }

    void readIndexes(const QStringList &indexFiles);
    void generateIndex(const QString &fileName, const QString &url, const QString &title,
                       Generator *g);

    void clearOpenNamespaces() { openNamespaces_.clear(); }
    void insertOpenNamespace(const QString &path) { openNamespaces_.insert(path); }
    void setShowInternal(bool value) { showInternal_ = value; }
    void setSingleExec(bool value) { singleExec_ = value; }
    void processForest();

    // Try to make this function private.
    QDocForest &forest() { return forest_; }
    NamespaceNode *primaryTreeRoot() { return forest_.primaryTreeRoot(); }
    void newPrimaryTree(const QString &module) { forest_.newPrimaryTree(module); }
    void setPrimaryTree(const QString &t) { forest_.setPrimaryTree(t); }
    NamespaceNode *newIndexTree(const QString &module) { return forest_.newIndexTree(module); }
    const QList<Tree *> &searchOrder() { return forest_.searchOrder(); }
    void setLocalSearch() { forest_.searchOrder_ = QList<Tree *>(1, primaryTree()); }
    void setSearchOrder(const QList<Tree *> &searchOrder) { forest_.searchOrder_ = searchOrder; }
    void setSearchOrder(QStringList &t) { forest_.setSearchOrder(t); }
    void mergeCollections(Node::NodeType type, CNMap &cnm, const Node *relative);
    void mergeCollections(CollectionNode *c);
    void clearSearchOrder() { forest_.clearSearchOrder(); }
    void incrementLinkCount(const Node *t) { t->tree()->incrementLinkCount(); }
    void clearLinkCounts() { forest_.clearLinkCounts(); }
    void printLinkCounts(const QString &t) { forest_.printLinkCounts(t); }
    QString getLinkCounts(QStringList &strings, QList<int> &counts)
    {
        return forest_.getLinkCounts(strings, counts);
    }
    QStringList keys() { return forest_.keys(); }
    void resolveNamespaces();
    void resolveProxies();
    void resolveBaseClasses();
    void updateNavigation();

private:
    friend class Tree;

    const Node *findNode(const QStringList &path, const Node *relative, int findFlags,
                         Node::Genus genus)
    {
        return forest_.findNode(path, relative, findFlags, genus);
    }
    void processForest(void (QDocDatabase::*)(Aggregate *));
    bool isLoaded(const QString &t) { return forest_.isLoaded(t); }
    static void initializeDB();

private:
    QDocDatabase();
    QDocDatabase(QDocDatabase const &) : showInternal_(false), forest_(this) {}
    QDocDatabase &operator=(QDocDatabase const &);

public:
    static bool debug;
    Tree *primaryTree() { return forest_.primaryTree(); }

private:
    static QDocDatabase *qdocDB_;
    static NodeMap typeNodeMap_;
    static NodeMultiMap obsoleteClasses_;
    static NodeMultiMap classesWithObsoleteMembers_;
    static NodeMultiMap obsoleteQmlTypes_;
    static NodeMultiMap qmlTypesWithObsoleteMembers_;
    static NodeMultiMap cppClasses_;
    static NodeMultiMap qmlBasicTypes_;
    static NodeMultiMap qmlTypes_;
    static NodeMultiMap examples_;
    static NodeMultiMapMap newClassMaps_;
    static NodeMultiMapMap newQmlTypeMaps_;
    static NodeMultiMapMap newSinceMaps_;

    bool showInternal_;
    bool singleExec_;
    QString version_;
    QDocForest forest_;

    NodeMultiMap namespaceIndex_;
    NodeMultiMap attributions_;
    NodeMapMap functionIndex_;
    TextToNodeMap legaleseTexts_;
    QSet<QString> openNamespaces_;
};

QT_END_NAMESPACE

#endif
