/*
   Copyright 2007 David Nolden <david.nolden.kdevelop@art-master.de>

   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.
 */

#ifndef KDEVPLATFORM_PARSINGENVIRONMENT_H
#define KDEVPLATFORM_PARSINGENVIRONMENT_H

#include <serialization/indexedstring.h>

#include <language/languageexport.h>
#include "duchainbase.h"
#include "topducontext.h"
#include <language/editor/modificationrevisionset.h>

namespace KDevelop {
/**
 * Just an enumeration of a few parsing-environment types.
 * Enumerate a few possible future parsing-environment types.
 * A parsing-environment could also have a type not in this enumeration,
 * the only important thing is that it's unique for the type.
 *
 * The type is needed to match ParsingEnvironment, ParsingEnvironmentFile, and ParsingEnvironmentManager together so they fit.
 * For example the c++-versions would have their specific type.
 *
 * The type must be unique(no other language may have the same type),
 * and the type must be persistent.(it must be same after restarting kdevelop)
 *
 * */
enum ParsingEnvironmentType
{
    StandardParsingEnvironment /**< a basic standard parsing environment */,
    CppParsingEnvironment /**< a C++ parsing environment */,
    PythonParsingEnvironment /**< a python parsing environment */,
    CMakeParsingEnvironment /**< a CMake parsing environment */,
    CSharpParsingEnvironment /**< a CSharp parsing environment */,
    JavaParsingEnvironment /**< a JAva parsing environment */,
    RubyParsingEnvironment /**< a Ruby parsing environment */,
    PhpParsingEnvironment    /**< a PHP parsing environment */
};

///Internal
struct StaticParsingEnvironmentData
{
    TopDUContext::IndexedRecursiveImports simplifiedVisibleDeclarationsSatisfied;
    TopDUContext::IndexedRecursiveImports visibleDeclarationsSatisfied;
    TopDUContext::IndexedRecursiveImports allDeclarationsSatisfied;
    TopDUContext::IndexedRecursiveImports allDeclarationsAndUsesSatisfied;
    TopDUContext::IndexedRecursiveImports ASTSatisfied;
};

/**
 * Use this as base-class to define new parsing-environments.
 *
 * Parsing-environments are needed for languages that create different
 * parsing-results depending on the environment. For example in c++,
 * the environment mainly consists of macros. The include-path can
 * be considered to be a part of the parsing-environment too, because
 * different files may be included using another include-path.
 *
 * The classes have to use the serialization scheme from the duchain.
 * Each class must be registered using REGISTER_DUCHAIN_ITEM with a unique id.
 *
 * \warning Access to this class must be serialized through du-chain locking
 * */
class KDEVPLATFORMLANGUAGE_EXPORT ParsingEnvironment
{
public:
    ParsingEnvironment();
    virtual ~ParsingEnvironment();

    ///@see ParsingEnvironmentType
    virtual int type() const;
};

///The data class used for storing. Use this as base-class of your custom data classes for classes derived from
///ParsingEnvironmentFile
class KDEVPLATFORMLANGUAGE_EXPORT ParsingEnvironmentFileData
    : public DUChainBaseData
{
public:
    bool m_isProxyContext = false;
    TopDUContext::Features m_features = TopDUContext::VisibleDeclarationsAndContexts;
    KDevelop::ModificationRevision m_modificationTime;
    ModificationRevisionSet m_allModificationRevisions;
    KDevelop::IndexedString m_url;
    KDevelop::IndexedTopDUContext m_topContext;
    IndexedString m_language;

    ///If this is not empty, it means that the cache is used instead of the implicit structure.
    TopDUContext::IndexedRecursiveImports m_importsCache;
};

using ParsingEnvironmentFilePointer = QExplicitlySharedDataPointer<ParsingEnvironmentFile>;

/**
 * This represents all information about a specific parsed file that is needed
 * to match the file to a parsing-environment.
 *
 * It is QSharedData because it is embedded into top-du-contexts and at the same time
 * references may be held by ParsingEnvironmentManager.
 *
 * \warning Access to this class must be serialized through du-chain locking
 * */
class KDEVPLATFORMLANGUAGE_EXPORT ParsingEnvironmentFile
    : public DUChainBase
    , public QSharedData
{
public:
    ~ParsingEnvironmentFile() override;
    explicit ParsingEnvironmentFile(const IndexedString& url);
    ParsingEnvironmentFile(ParsingEnvironmentFileData& data, const IndexedString& url);
    explicit ParsingEnvironmentFile(ParsingEnvironmentFileData& data);

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

    ///@see ParsingEnvironmentType
    virtual int type() const;

    ///Should return whether this file matches into the given environment. The default-implementation always returns true.
    virtual bool matchEnvironment(const ParsingEnvironment* environment) const;

    ///Convenience-function that returns the top-context
    TopDUContext* topContext() const override;

    KDevelop::IndexedTopDUContext indexedTopContext() const;

    KDevelop::IndexedString url() const override;

    ///Can additionally use language-specific information to decide whether the top-context that has this data attached needs to be reparsed.
    ///The standard-implementation checks the modification-time of this file stored using setModificationRevision, and all other modification-times
    ///stored with addModificationRevision(..).
    virtual bool needsUpdate(const ParsingEnvironment* environment = nullptr) const;

    /**
     * A language-specific flag used by C++ to mark one context as a proxy of another.
     * If this flag is set on a context, the first imported context should be used for any computations
     * like searches, listing, etc. instead of using this context.
     *
     * The problems should be stored within the proxy-contexts, and optionally there may be
     * any count of imported proxy-contexts imported behind the content-context(This can be used for tracking problems)
     *
     * Note: This flag does not directly change the behavior of the language-independent du-chain.
     */
    bool isProxyContext() const;

    ///Sets the flag
    void setIsProxyContext(bool);

    ///The features of the attached top-context. They are automatically replicated here by the top-context, so they
    ///are accessible even without the top-context loaded.
    TopDUContext::Features features() const;

    ///Returns the parsing-environment information of all importers of the coupled TopDUContext. This is more efficient than
    ///loading the top-context and finding out, because when a top-context is loaded, also all its recursive imports are loaded
    QList<QExplicitlySharedDataPointer<ParsingEnvironmentFile>> importers() const;

    ///Returns the parsing-environment information of all imports of the coupled TopDUContext. This is more efficient than
    ///loading the top-context and finding out
    QList<QExplicitlySharedDataPointer<ParsingEnvironmentFile>> imports() const;

    ///Returns true if this top-context satisfies at least the given minimum features.
    ///If there is static minimum features set up in ParseJob, this also checks against those.
    ///Features like "ForceUpdate" are treated specially.
    ///@p minimumFeatures The features that must be satisfied. May be an arbitrary combination of features.
    bool featuresSatisfied(TopDUContext::Features minimumFeatures) const;

    ///Should return a correctly filled ModificationRevision of the source it was created from.
    void setModificationRevision(const KDevelop::ModificationRevision& rev);

    KDevelop::ModificationRevision modificationRevision() const;

    ///Clears the modification times of all dependencies
    void clearModificationRevisions();

    void addModificationRevision(const IndexedString& url, const ModificationRevision& revision);

    const ModificationRevisionSet& allModificationRevisions() const;

    void addModificationRevisions(const ModificationRevisionSet&);

    /// Returns the language for this top-context. If the string is empty, the language is unknown.
    IndexedString language() const;

    /// If the recursive imports of the attached TopDUContext are cached, this returns the cached imports.
    /// This works without loading the top-context.
    const TopDUContext::IndexedRecursiveImports& importsCache() const;

    ///Sets the language for this top-context. Each top-context should get the language assigned that can by used
    ///in order to load the language using ILanguageController.
    void setLanguage(const IndexedString& language);

    enum {
        Identity = 11
    };

    DUCHAIN_DECLARE_DATA(ParsingEnvironmentFile)

private:
    friend class TopDUContext;
    friend class DUChainPrivate;
    static StaticParsingEnvironmentData* m_staticData;
    ///The features are managed by TopDUContext. They are set to TopDUContext::Empty when the top-context is persistently destroyed,
    ///so the persistent feature-satisfaction cache can be cleared.
    void setFeatures(TopDUContext::Features);
    void setTopContext(KDevelop::IndexedTopDUContext context);
    bool featuresMatch(KDevelop::TopDUContext::Features minimumFeatures,
                       QSet<const KDevelop::ParsingEnvironmentFile*>& checked) const;
    void setImportsCache(const TopDUContext::IndexedRecursiveImports&);
};

// TODO: why is this needed/what is it supposed to print? just the pointer value?
inline QDebug operator<<(QDebug s, const QExplicitlySharedDataPointer<ParsingEnvironmentFile>& p)
{
    s.nospace() << p->url();
    return s.space();
}
}

#endif
