/*
 * This file is part of KDevelop
 *
 * Copyright 2007-2010 David Nolden <david.nolden.kdevelop@art-master.de>
 * Copyright 2006 Hamish Rodda <rodda@kde.org>
 * Copyright 2009 Milian Wolff <mail@milianw.de>
 *
 * This program 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 program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#ifndef KDEVPLATFORM_CODEHIGHLIGHTING_H
#define KDEVPLATFORM_CODEHIGHLIGHTING_H

#include <QObject>
#include <QHash>

#include <serialization/indexedstring.h>
#include <language/duchain/ducontext.h>
#include <language/interfaces/icodehighlighting.h>
#include <language/backgroundparser/documentchangetracker.h>

#include <KTextEditor/Attribute>
#include <KTextEditor/MovingRange>

namespace KDevelop {
class DUContext;
class Declaration;

using ColorMap = QVector<KDevelop::Declaration*>;

class CodeHighlighting;

struct HighlightingEnumContainer
{
    enum Types {
        UnknownType,
        //Primary highlighting:
        LocalClassMemberType,
        InheritedClassMemberType,
        LocalVariableType,

        //Other highlighting:
        ClassType,
        FunctionType,
        ForwardDeclarationType,
        EnumType,
        EnumeratorType,
        TypeAliasType,
        MacroType, /// Declaration of a macro such as "#define FOO"
        MacroFunctionLikeType, /// Declaration of a function like macro such as "#define FOO()"

        //If none of the above match:
        MemberVariableType,
        NamespaceVariableType,
        GlobalVariableType,

        //Most of these are currently not used:
        ArgumentType,
        CodeType,
        FileType,
        NamespaceType,
        ScopeType,
        TemplateType,
        TemplateParameterType,
        FunctionVariableType,
        ErrorVariableType
    };

    enum Contexts {
        DefinitionContext,
        DeclarationContext,
        ReferenceContext
    };
};

struct HighlightedRange
{
    RangeInRevision range;
    KTextEditor::Attribute::Ptr attribute;
    bool operator<(const HighlightedRange& rhs) const
    {
        return range.start < rhs.range.start;
    }
};

/**
 * Code highlighting instance that is used to apply code highlighting to one specific top context
 * */

class KDEVPLATFORMLANGUAGE_EXPORT CodeHighlightingInstance
    : public HighlightingEnumContainer
{
public:
    explicit CodeHighlightingInstance(const CodeHighlighting* highlighting) : m_useClassCache(false)
        , m_highlighting(highlighting)
    {
    }
    virtual ~CodeHighlightingInstance()
    {
    }

    virtual void highlightDeclaration(KDevelop::Declaration* declaration, const QColor& color);
    virtual void highlightUse(KDevelop::DUContext* context, int index, const QColor& color);
    virtual void highlightUses(KDevelop::DUContext* context);

    void highlightDUChain(KDevelop::TopDUContext* context);
    void highlightDUChain(KDevelop::DUContext* context, QHash<KDevelop::Declaration*, uint> colorsForDeclarations,
        ColorMap);

    KDevelop::Declaration* localClassFromCodeContext(KDevelop::DUContext* context) const;
    /**
     * @param context Should be the context from where the declaration is used, if a use is highlighted.
     * */
    virtual Types typeForDeclaration(KDevelop::Declaration* dec, KDevelop::DUContext* context) const;
    /**
     * Decides whether to apply auto-generated rainbow colors to @p dec.
     * Default implementation only applies that to local variables in functions.
     */
    virtual bool useRainbowColor(KDevelop::Declaration* dec) const;

    //A temporary hash for speedup
    mutable QHash<KDevelop::DUContext*, KDevelop::Declaration*> m_contextClasses;

    //Here the colors of function context are stored until they are merged into the function body
    mutable QMap<KDevelop::IndexedDUContext, QHash<KDevelop::Declaration*, uint>> m_functionColorsForDeclarations;
    mutable QMap<KDevelop::IndexedDUContext, ColorMap> m_functionDeclarationsForColors;

    mutable bool m_useClassCache;
    const CodeHighlighting* m_highlighting;

    QVector<HighlightedRange> m_highlight;
};

/**
 * General class representing the code highlighting for one language
 * */
class KDEVPLATFORMLANGUAGE_EXPORT CodeHighlighting
    : public QObject
    , public KDevelop::ICodeHighlighting
    , public HighlightingEnumContainer
{
    Q_OBJECT
    Q_INTERFACES(KDevelop::ICodeHighlighting)

public:

    explicit CodeHighlighting(QObject* parent);
    ~CodeHighlighting() override;

    /// This function is thread-safe
    /// @warning The duchain must not be locked when this is called (->possible deadlock)
    void highlightDUChain(ReferencedTopDUContext context) override;

    //color should be zero when undecided
    KTextEditor::Attribute::Ptr attributeForType(Types type, Contexts context, const QColor& color) const;
    KTextEditor::Attribute::Ptr attributeForDepth(int depth) const;

    /// This function is thread-safe
    /// Returns whether a highlighting is already given for the given url
    bool hasHighlighting(IndexedString url) const override;

private:
    //Returns whether the given attribute was set by the code highlighting, and not by something else
    //Always returns true when the attribute is zero
    bool isCodeHighlight(KTextEditor::Attribute::Ptr attr) const;

protected:
    //Can be overridden to create an own instance type
    virtual CodeHighlightingInstance* createInstance() const;

private:

    /// Highlighting of one specific document
    struct DocumentHighlighting
    {
        IndexedString m_document;
        qint64 m_waitingRevision;
        // The ranges are sorted by range start, so they can easily be matched
        QVector<HighlightedRange> m_waiting;
        QVector<KTextEditor::MovingRange*> m_highlightedRanges;
    };

    QMap<DocumentChangeTracker*, DocumentHighlighting*> m_highlights;

    friend class CodeHighlightingInstance;

    mutable QHash<Types, KTextEditor::Attribute::Ptr> m_definitionAttributes;
    mutable QHash<Types, KTextEditor::Attribute::Ptr> m_declarationAttributes;
    mutable QHash<Types, KTextEditor::Attribute::Ptr> m_referenceAttributes;
    mutable QList<KTextEditor::Attribute::Ptr> m_depthAttributes;
    // Should be used to enable/disable the colorization of local variables and their uses
    bool m_localColorization;
    // Should be used to enable/disable the colorization of global types and their uses
    bool m_globalColorization;

    mutable QMutex m_dataMutex;

private Q_SLOTS:
    void clearHighlightingForDocument(const KDevelop::IndexedString& document);
    void applyHighlighting(void* highlighting);

    void trackerDestroyed(QObject* object);

    /// when the colors change we must invalidate our local caches
    void adaptToColorChanges();

    void aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*);
    void aboutToRemoveText(const KTextEditor::Range&);
};
}

Q_DECLARE_TYPEINFO(KDevelop::HighlightedRange, Q_MOVABLE_TYPE);

#endif
