/* This file is part of KDevelop
   Copyright 2008 David Nolden <david.nolden.kdevelop@art-master.de>
   Copyright 2014 Kevin Funk <kfunk@kde.org>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License version 2 as published by the Free Software Foundation.

   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 "definitions.h"

#include "appendedlist.h"
#include "declaration.h"
#include "declarationid.h"
#include "duchainpointer.h"
#include <serialization/indexedstring.h>
#include "serialization/itemrepository.h"

namespace KDevelop {

using TextStreamFunction = QTextStream& (*)(QTextStream&);
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
constexpr TextStreamFunction endl = Qt::endl;
#else
constexpr TextStreamFunction endl = ::endl;
#endif



DEFINE_LIST_MEMBER_HASH(DefinitionsItem, definitions, IndexedDeclaration)

class DefinitionsItem
{
public:
    DefinitionsItem()
    {
        initializeAppendedLists();
    }
    DefinitionsItem(const DefinitionsItem& rhs, bool dynamic = true) : declaration(rhs.declaration)
    {
        initializeAppendedLists(dynamic);
        copyListsFrom(rhs);
    }

    ~DefinitionsItem()
    {
        freeAppendedLists();
    }

    DefinitionsItem& operator=(const DefinitionsItem& rhs) = delete;

    unsigned int hash() const
    {
        //We only compare the declaration. This allows us implementing a map, although the item-repository
        //originally represents a set.
        return declaration.hash();
    }

    unsigned int itemSize() const
    {
        return dynamicSize();
    }

    uint classSize() const
    {
        return sizeof(DefinitionsItem);
    }

    DeclarationId declaration;

    START_APPENDED_LISTS(DefinitionsItem);
    APPENDED_LIST_FIRST(DefinitionsItem, IndexedDeclaration, definitions);
    END_APPENDED_LISTS(DefinitionsItem, definitions);
};

class DefinitionsRequestItem
{
public:

    DefinitionsRequestItem(const DefinitionsItem& item) : m_item(item)
    {
    }
    enum {
        AverageSize = 30 //This should be the approximate average size of an Item
    };

    unsigned int hash() const
    {
        return m_item.hash();
    }

    uint itemSize() const
    {
        return m_item.itemSize();
    }

    void createItem(DefinitionsItem* item) const
    {
        new (item) DefinitionsItem(m_item, false);
    }

    static void destroy(DefinitionsItem* item, KDevelop::AbstractItemRepository&)
    {
        item->~DefinitionsItem();
    }

    static bool persistent(const DefinitionsItem*)
    {
        return true;
    }

    bool equals(const DefinitionsItem* item) const
    {
        return m_item.declaration == item->declaration;
    }

    const DefinitionsItem& m_item;
};

class DefinitionsVisitor
{
public:
    DefinitionsVisitor(Definitions* _definitions, const QTextStream& _out)
        : definitions(_definitions)
        , out(_out)
    {
    }

    bool operator()(const DefinitionsItem* item)
    {
        QDebug qout(out.device());
        auto id = item->declaration;
        const auto allDefinitions = definitions->definitions(id);

        qout << "Definitions for" << id.qualifiedIdentifier() << endl;
        for (const IndexedDeclaration& decl : allDefinitions) {
            if (decl.data()) {
                qout << " " << decl.data()->qualifiedIdentifier() << "in" << decl.data()->url().byteArray() << "at" <<
                    decl.data()->rangeInCurrentRevision() << endl;
            }
        }

        return true;
    }

private:
    const Definitions* definitions;
    const QTextStream& out;
};

class DefinitionsPrivate
{
public:

    DefinitionsPrivate() : m_definitions(QStringLiteral("Definition Map"))
    {
    }
    //Maps declaration-ids to definitions
    // mutable as things like findIndex are not const
    mutable ItemRepository<DefinitionsItem, DefinitionsRequestItem> m_definitions;
};

Definitions::Definitions()
    : d_ptr(new DefinitionsPrivate())
{
}

Definitions::~Definitions() = default;

void Definitions::addDefinition(const DeclarationId& id, const IndexedDeclaration& definition)
{
    Q_D(Definitions);

    DefinitionsItem item;
    item.declaration = id;
    item.definitionsList().append(definition);
    DefinitionsRequestItem request(item);

    uint index = d->m_definitions.findIndex(item);

    if (index) {
        //Check whether the item is already in the mapped list, else copy the list into the new created item
        const DefinitionsItem* oldItem = d->m_definitions.itemFromIndex(index);
        for (unsigned int a = 0; a < oldItem->definitionsSize(); ++a) {
            if (oldItem->definitions()[a] == definition)
                return; //Already there
            item.definitionsList().append(oldItem->definitions()[a]);
        }

        d->m_definitions.deleteItem(index);
    }

    //This inserts the changed item
    d->m_definitions.index(request);
}

void Definitions::removeDefinition(const DeclarationId& id, const IndexedDeclaration& definition)
{
    Q_D(Definitions);

    DefinitionsItem item;
    item.declaration = id;
    DefinitionsRequestItem request(item);

    uint index = d->m_definitions.findIndex(item);

    if (index) {
        //Check whether the item is already in the mapped list, else copy the list into the new created item
        const DefinitionsItem* oldItem = d->m_definitions.itemFromIndex(index);
        for (unsigned int a = 0; a < oldItem->definitionsSize(); ++a)
            if (!(oldItem->definitions()[a] == definition))
                item.definitionsList().append(oldItem->definitions()[a]);

        d->m_definitions.deleteItem(index);
        Q_ASSERT(d->m_definitions.findIndex(item) == 0);

        //This inserts the changed item
        if (item.definitionsSize() != 0)
            d->m_definitions.index(request);
    }
}

KDevVarLengthArray<IndexedDeclaration> Definitions::definitions(const DeclarationId& id) const
{
    Q_D(const Definitions);

    KDevVarLengthArray<IndexedDeclaration> ret;

    DefinitionsItem item;
    item.declaration = id;
    DefinitionsRequestItem request(item);

    uint index = d->m_definitions.findIndex(item);

    if (index) {
        const DefinitionsItem* repositoryItem = d->m_definitions.itemFromIndex(index);
        FOREACH_FUNCTION(const IndexedDeclaration &decl, repositoryItem->definitions)
        ret.append(decl);
    }

    return ret;
}

void Definitions::dump(const QTextStream& out)
{
    Q_D(Definitions);

    QMutexLocker lock(d->m_definitions.mutex());
    DefinitionsVisitor v(this, out);
    d->m_definitions.visitAllItems(v);
}
}
