/***************************************************************************
 *   This file is part of KDevelop                                         *
 *   Copyright 2008 Andreas Pakulat <apaku@gmx.de>                         *
 *   Copyright 2006 Roberto Raggi <roberto@kdevelop.org>                   *
 *   Copyright 2006-2008 Hamish Rodda <rodda@kde.org>                      *
 *                                                                         *
 *   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 Library 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_ABSTRACTABSTRACTCONTEXTBUILDER_H
#define KDEVPLATFORM_ABSTRACTABSTRACTCONTEXTBUILDER_H

#include <climits>

#include <ktexteditor/range.h>

#include "../topducontext.h"
#include "../duchainpointer.h"
#include "../duchainlock.h"
#include "../duchain.h"
#include "../ducontext.h"
#include "../identifier.h"
#include "../parsingenvironment.h"

#include <serialization/indexedstring.h>
#include <util/stack.h>

namespace KDevelop
{
/**
 * \short Abstract definition-use chain context builder class
 *
 * The AbstractContextBuilder is a convenience class template for creating customized
 * definition-use chain context builders from an AST.  It simplifies:
 * - creating or modifying an existing DUContext tree
 * - following a DUContext tree for second and subsequent passes, if required
 * - opening and closing DUContext instances
 * - tracking which DUContext instances are still present when recompiling, and removing DUContexts which no longer exist in the source code.
 *
 * \author Hamish Rodda \<rodda@kde.org\>
 */
template<typename T, typename NameT>
class AbstractContextBuilder
{
public:
  /// Constructor.
  AbstractContextBuilder() : m_compilingContexts( false )
    , m_recompiling( false )
    , m_lastContext(nullptr)
  {
  }

  virtual ~AbstractContextBuilder()
  {
  }

  /**
   * Entry point for building a definition-use chain with this builder.
   *
   * This function determines whether we are updating a chain, or creating a new one.  If we are
   * creating a new chain, a new TopDUContext is created and registered with DUChain.
   *
   * \param url Url of the document being parsed.
   * \param node AST node to start building from.
   * \param updateContext TopDUContext to update if a duchain was previously created for this url, otherwise pass a null pointer.
   *
   * \returns the newly created or updated TopDUContext pointer.
   */
  virtual ReferencedTopDUContext build( const IndexedString& url, T* node,
                              ReferencedTopDUContext updateContext
                                    = ReferencedTopDUContext() )
  {
    m_compilingContexts = true;
    m_url = url;

    ReferencedTopDUContext top;
    {
      DUChainWriteLocker lock( DUChain::lock() );
      top = updateContext.data();

      if( top )
      {
        m_recompiling = true;
        Q_ASSERT(top->type() == DUContext::Global);
        Q_ASSERT(DUChain::self()->chainForIndex(top->ownIndex()) == top);
      }
      else
      {
        top = newTopContext( RangeInRevision( CursorInRevision( 0, 0 ), CursorInRevision( INT_MAX, INT_MAX ) ) );
        DUChain::self()->addDocumentChain( top );
        top->setType( DUContext::Global );
      }

      setEncountered( top );
      setContextOnNode( node, top );
    }

    supportBuild( node, top );

    m_compilingContexts = false;
    return top;
  }

protected:
  /**
   * Support another builder by tracking the current context.
   * @param context the context to use. Must be set when the given node has no context. When it has one attached, this parameter is not needed.
   */
  virtual void supportBuild(T* node, DUContext* context = nullptr)
  {
    if (!context)
      context = contextFromNode(node);

    Q_ASSERT(context);

    openContext( context );

    startVisiting(node);

    closeContext();

    Q_ASSERT(m_contextStack.isEmpty());
  }

  /**
   * Entry point to your visitor.  Reimplement and call the appropriate visit function.
   *
   * \param node AST node to visit.
   */
  virtual void startVisiting( T* node ) = 0;

  /**
   * Associate a \a context with a given AST \a node.  Once called on a \a node, the
   * contextFromNode() function should return this \a context when called.
   *
   * \param node AST node to associate
   * \param context DUContext to associate
   */
  virtual void setContextOnNode( T* node, DUContext* context ) = 0;

  /**
   * Retrieve an associated DUContext from the given \a node.  Used on second and
   * subsequent passes of the context builder (for supporting other builds)
   *
   * \param node AST node which was previously associated
   * \returns the DUContext which was previously associated
   */
  virtual DUContext* contextFromNode( T* node ) = 0;

  /**
   * Retrieves a text range from the given nodes.
   *
   * As editor integrators have to be extended to determine ranges from AST nodes,
   * this function must be reimplemented to allow generic retrieving of rangs from nodes.
   *
   * \param fromNode the AST node to start from (on the start boundary)
   * \param toNode the AST node to end at (on the end boundary)
   *
   * \returns the text range encompassing the given AST node(s)
   */
  virtual RangeInRevision editorFindRange( T* fromNode, T* toNode ) = 0;

  /**
   * Retrieve a text range for the given nodes.  This is a special function required
   * by c++ support as a different range may need to be retrieved depending on
   * whether macros are involved.  It is not usually required to implement this
   * function separately to editorFindRange() for other languages.
   *
   * \param fromNode the AST node to start from (on the start boundary)
   * \param toNode the AST node to end at (on the end boundary)
   *
   * \returns the text range encompassing the given AST node(s)
   */
  virtual RangeInRevision editorFindRangeForContext( T* fromNode, T* toNode )
  {
    return editorFindRange(fromNode, toNode);
  }

  /**
   * Determine the QualifiedIdentifier which corresponds to the given ast \a node.
   *
   * \param node ast node which represents an identifier
   * \return the qualified identifier determined from \a node
   */
  virtual QualifiedIdentifier identifierForNode( NameT* node ) = 0;

  /**
   * Create a new DUContext from the given \a range.
   *
   * This exists so that you can create custom DUContext subclasses for your
   * language if you need to.
   *
   * \param range range for the new context to encompass
   * \returns the newly created context
   */
  virtual DUContext* newContext(const RangeInRevision& range)
  {
    return new DUContext(range, currentContext());
  }

  /**
   * Create a new TopDUContext from the given \a range.
   *
   * This exists so that you can create custom TopDUContext subclasses for your
   * language if you need to.
   *
   * \returns the newly created context
   */
  virtual TopDUContext* newTopContext(const RangeInRevision& range, ParsingEnvironmentFile* file = nullptr)
  {
    return new TopDUContext(m_url, range, file);
  }

  /// Determine the currently open context. \returns the current context.
  inline DUContext* currentContext() const { return m_contextStack.top(); }
  /// Determine the last closed context. \returns the last closed context.
  inline DUContext* lastContext() const { return m_lastContext; }
  /// Clears the last closed context.
  inline void clearLastContext() { m_lastContext = nullptr; }

  inline void setLastContext(DUContext* context) { m_lastContext = context; }

  TopDUContext* topContext() const {
    return currentContext()->topContext();
  }

  /**
   * Determine if we are recompiling an existing definition-use chain, or if
   * a new chain is being created from scratch.
   *
   * \returns true if an existing duchain is being updated, otherwise false.
   */
  inline bool recompiling() const { return m_recompiling; }

  /**
   * Tell the context builder whether we are recompiling an existing definition-use chain, or if
   * a new chain is being created from scratch.
   *
   * \param recomp set to true if an existing duchain is being updated, otherwise false.
   */
  inline void setRecompiling(bool recomp) { m_recompiling = recomp; }

  /**
   * Determine whether this pass will create DUContext instances.
   *
   * On the first pass of definition-use chain compiling, DUContext instances
   * are created to represent contexts in the source code.  These contexts are
   * associated with their AST nodes at the time (see setContextOnNode()).
   *
   * On second and subsequent passes, the contexts already exist and thus can be
   * retrieved through contextFromNode().
   *
   * \returns true if compiling contexts (ie. 1st pass), otherwise false.
   */
  inline bool compilingContexts() const { return m_compilingContexts; }

  /**
   * Sets whether we need to create ducontexts, ie. if this is the first pass.
   *
   * \sa compilingContexts()
   */
  inline void setCompilingContexts(bool compilingContexts) { m_compilingContexts = compilingContexts; }

  /**
   * Create child contexts for only a portion of the document.
   *
   * \param node The AST node which corresponds to the context to parse
   * \param parent The DUContext which encompasses the \a node.
   * \returns The DUContext which was reparsed, ie. \a parent.
   */
  DUContext* buildSubContexts( T *node, DUContext* parent )
  {
  //     m_compilingContexts = true;
  //     m_recompiling = false;
      setContextOnNode( node, parent );
      {
          openContext( contextFromNode( node ) );
          startVisiting( node );
          closeContext();
      }

      m_compilingContexts = false;

      if ( contextFromNode( node ) == parent )
      {
          qDebug() << "Error in AbstractContextBuilder::buildSubContexts(...): du-context was not replaced with new one";
          DUChainWriteLocker lock( DUChain::lock() );
          deleteContextOnNode( node );
      }

      return contextFromNode( node );
  }

  /**
   * Delete the DUContext which is associated with the given \a node,
   * and remove the association.
   *
   * \param node Node which is associated with the context to delete.
   */
  void deleteContextOnNode( T* node )
  {
    delete contextFromNode( node );
    setContextOnNode(node, nullptr);
  }

  /**
   * Open a context, and create / update it if necessary.
   *
   * \param rangeNode The range which encompasses the context.
   * \param type The type of context to open.
   * \param identifier The range which encompasses the name of this context, if one exists.
   * \returns the opened context.
   */
  DUContext* openContext(T* rangeNode, DUContext::ContextType type, NameT* identifier = nullptr)
  {
    if ( m_compilingContexts )
    {
      DUContext* ret = openContextInternal( editorFindRangeForContext( rangeNode, rangeNode ), type, identifier ? identifierForNode( identifier ) : QualifiedIdentifier() );
      setContextOnNode( rangeNode, ret );
      return ret;
    }
    else
    {
      openContext( contextFromNode(rangeNode) );
      return currentContext();
    }
  }

  /**
   * Open a context, and create / update it if necessary.
   *
   * \param node The range to associate with the context.
   * \param range A custom range which the context should encompass.
   * \param type The type of context to open.
   * \param identifier The range which encompasses the name of this context, if one exists.
   * \returns the opened context.
   */
  DUContext* openContext(T* node, const RangeInRevision& range, DUContext::ContextType type, NameT* identifier = nullptr)
  {
    if (m_compilingContexts) {
      DUContext* ret = openContextInternal(range, type, identifier ? identifierForNode(identifier) : QualifiedIdentifier());
      setContextOnNode( node, ret );
      return ret;

    } else {
      openContext( contextFromNode(node) );
      return currentContext();
    }
  }

  /**
   * Open a context, and create / update it if necessary.
   *
   * \param node The range to associate with the context.
   * \param range A custom range which the context should encompass.
   * \param type The type of context to open.
   * \param id The identifier for this context
   * \returns the opened context.
   */
  DUContext* openContext(T* node, const RangeInRevision& range, DUContext::ContextType type, const QualifiedIdentifier& id)
  {
    if (m_compilingContexts) {
      DUContext* ret = openContextInternal(range, type, id);
      setContextOnNode( node, ret );
      return ret;

    } else {
      openContext( contextFromNode(node) );
      return currentContext();
    }
  }

  /**
   * Open a context, and create / update it if necessary.
   *
   * \param rangeNode The range which encompasses the context.
   * \param type The type of context to open.
   * \param identifier The identifier which corresponds to the context.
   * \returns the opened context.
   */
  DUContext* openContext( T* rangeNode, DUContext::ContextType type, const QualifiedIdentifier& identifier )
  {
    if ( m_compilingContexts )
    {
      DUContext* ret = openContextInternal( editorFindRangeForContext( rangeNode, rangeNode ), type, identifier );
      setContextOnNode( rangeNode, ret );
      return ret;
    }
    else
    {
      openContext( contextFromNode(rangeNode) );
      return currentContext();
    }
  }

  /**
   * Open a context, and create / update it if necessary.
   *
   * \param fromRange The range which starts the context.
   * \param toRange The range which ends the context.
   * \param type The type of context to open.
   * \param identifier The identifier which corresponds to the context.
   * \returns the opened context.
   */
  DUContext* openContext( T* fromRange, T* toRange, DUContext::ContextType type, const QualifiedIdentifier& identifier = QualifiedIdentifier() )
  {
    if ( m_compilingContexts )
    {
      DUContext* ret = openContextInternal( editorFindRangeForContext( fromRange, toRange ), type, identifier );
      setContextOnNode( fromRange, ret );
      return ret;
    }
    else
    {
      openContext( contextFromNode(fromRange) );
      return currentContext();
    }
  }

  /**
   * Open a newly created or previously existing context.
   *
   * The open context is put on the context stack, and becomes the new
   * currentContext().
   *
   * \warning When you call this, you also have to open a range! If you want to re-use
   * the range associated to the context, use injectContext
   *
   * \param newContext Context to open.
   */
  virtual void openContext( DUContext* newContext )
  {
    m_contextStack.push( newContext );
    m_nextContextStack.push( 0 );
  }

  /**
   * This can be used to temporarily change the current context.
   * \param ctx The context to be injected
   * */
  void injectContext( DUContext* ctx ) {
    openContext( ctx );
  }

  /**
   * Use this to close the context previously injected with injectContext.
   * */
  void closeInjectedContext() {
    m_contextStack.pop();
    m_nextContextStack.pop();
  }

  /**
   * Close the current DUContext.  When recompiling, this function will remove any
   * contexts that were not encountered in this passing run.
   * \note The DUChain write lock is already held here.
   */
  virtual void closeContext()
  {
    {
      DUChainWriteLocker lock( DUChain::lock() );
      //Remove all slaves that were not encountered while parsing
      if(m_compilingContexts)
        currentContext()->cleanIfNotEncountered( m_encountered );
      setEncountered( currentContext() );

      m_lastContext = currentContext();
    }

    m_contextStack.pop();
    m_nextContextStack.pop();
  }

  /**
   * Remember that a specific item has been encoutered while parsing.
   * All items that are not encountered will be deleted at some stage.
   *
   * \param item duchain item that was encountered.
   * */
  void setEncountered( DUChainBase* item )
  {
    m_encountered.insert( item );
  }

  /**
   * Determine whether the given \a item is in the set of encountered items.
   *
   * @return true if the \a item has been encountered, otherwise false.
   * */
  bool wasEncountered( DUChainBase* item )
  {
    return m_encountered.contains( item );
  }

  /**
   * Set the current identifier to \a id.
   *
   * \param id the new current identifier.
   */
  void setIdentifier( const QString& id )
  {
    m_identifier = Identifier( id );
    m_qIdentifier.push( m_identifier );
  }

  /**
   * Determine the current identifier.
   * \returns the current identifier.
   */
  QualifiedIdentifier qualifiedIdentifier() const
  {
    return m_qIdentifier;
  }

  /**
   * Clears the current identifier.
   */
  void clearQualifiedIdentifier()
  {
    m_qIdentifier.clear();
  }

  /**
   * Retrieve the current context stack.  This function is not expected
   * to be used often and may be phased out.
   *
   * \todo Audit whether access to the context stack is still required, and provide
   *       replacement functionality if possible.
   */
  const Stack<DUContext*>& contextStack() const
  {
    return m_contextStack;
  }

  /**
   * Access the index of the child context which has been encountered.
   *
   * \todo further delineate the role of this function and rename / document better.
   * \todo make private again?
   */
  int& nextContextIndex()
  {
    return m_nextContextStack.top();
  }

  /**
   * Open a context, either creating it if it does not exist, or referencing a previously existing
   * context if already encountered in a previous duchain parse run (when recompiling()).
   *
   * \param range The range of the context.
   * \param type The type of context to create.
   * \param identifier The identifier which corresponds to the context.
   * \returns the opened context.
   */

  virtual DUContext* openContextInternal( const RangeInRevision& range, DUContext::ContextType type, const QualifiedIdentifier& identifier )
  {
    Q_ASSERT( m_compilingContexts );
    DUContext* ret = nullptr;

    {
      if ( recompiling() )
      {
        DUChainReadLocker readLock( DUChain::lock() );
        const QVector<DUContext*>& childContexts = currentContext()->childContexts();

        int currentIndex = nextContextIndex();
        const auto indexedIdentifier = IndexedQualifiedIdentifier(identifier);

        for ( ; currentIndex < childContexts.count(); ++currentIndex )
        {
          DUContext* child = childContexts.at( currentIndex );
          RangeInRevision childRange = child->range();

          if (child->type() != type) {
            continue;
          }
          // We cannot update a contexts local scope identifier, that will break many other parts, like e.g.
          // the CodeModel of child contexts or declarations.
          // For unnamed child-ranges, we still do range-comparison, because we cannot distinguish them in other ways
          if ((!identifier.isEmpty() && child->indexedLocalScopeIdentifier() == indexedIdentifier)
              || (identifier.isEmpty() && child->indexedLocalScopeIdentifier().isEmpty() && !childRange.isEmpty() && childRange == range))
          {
            // Match
            ret = child;
            readLock.unlock();
            DUChainWriteLocker writeLock( DUChain::lock() );

            ret->clearImportedParentContexts();
            ++currentIndex;
            break;
          }
        }
        if(ret)
          nextContextIndex() = currentIndex; //If we had a match, jump forward to that position
          ///@todo We should also somehow make sure we don't get quadratic worst-case effort while updating.
      }

      if ( !ret )
      {
        DUChainWriteLocker writeLock( DUChain::lock() );

        ret = newContext( range );
        ret->setType( type );

        if ( !identifier.isEmpty() )
          ret->setLocalScopeIdentifier( identifier );

        setInSymbolTable( ret );
      }else{
        DUChainWriteLocker writeLock( DUChain::lock() );
        Q_ASSERT(ret->localScopeIdentifier() == identifier);
        if(ret->parentContext())
          ret->setRange( range );
      }
    }

    m_encountered.insert( ret );
    openContext( ret );
    return ret;
  }

  ///This function should call context->setInSymbolTable(..) with an appropriate decision. The duchain is write-locked when this is called.
  virtual void setInSymbolTable(DUContext* context) {
    if(!context->parentContext()->inSymbolTable()) {
      context->setInSymbolTable(false);
      return;
    }
    DUContext::ContextType type = context->type();
    context->setInSymbolTable(type == DUContext::Class || type == DUContext::Namespace || type == DUContext::Global || type == DUContext::Helper || type == DUContext::Enum);
  }

  /// @returns the current url/path ot the document we are parsing
  IndexedString document() const {
    return m_url;
  }

private:

  Identifier m_identifier;
  IndexedString m_url;
  QualifiedIdentifier m_qIdentifier;
  bool m_compilingContexts : 1;
  bool m_recompiling : 1;
  Stack<int> m_nextContextStack;
  DUContext* m_lastContext;
  //Here all valid declarations/uses/... will be collected
  QSet<DUChainBase*> m_encountered;
  Stack<DUContext*> m_contextStack;
};

}

#endif
