/*
* This file is part of KDevelop
*
* Copyright 2006 Adam Treat <treat@kde.org>
* Copyright 2006-2007 Hamish Rodda <rodda@kde.org>
* Copyright 2007-2008 David Nolden<david.nolden@kdevelop.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 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.
*/

#include "cppparsejob.h"

#include <QFile>
#include <QByteArray>
#include <QReadWriteLock>
#include <QReadLocker>

#include <kdebug.h>
#include <klocale.h>

#include <threadweaver/Thread.h>

#include <interfaces/ilanguage.h>
#include <language/interfaces/iastcontainer.h>

#include <ktexteditor/document.h>
#include <ktexteditor/smartinterface.h>

#include "cpplanguagesupport.h"
#include "cpphighlighting.h"

// #include "parser/binder.h"
#include "parser/parser.h"
#include "parser/control.h"
#include "parser/dumptree.h"
#include <language/duchain/duchain.h>
#include <language/duchain/duchainpointer.h>
#include <language/duchain/duchainlock.h>
#include <language/duchain/dumpdotgraph.h>
#include <language/duchain/dumpchain.h>
#include <language/backgroundparser/parsejob.h>
#include <language/backgroundparser/urlparselock.h>
#include "cppeditorintegrator.h"
#include "declarationbuilder.h"
#include "usebuilder.h"
#include <language/duchain/topducontext.h>
#include "preprocessjob.h"
#include "environmentmanager.h"
#include <unistd.h>
#include <qwaitcondition.h>
#include <language/duchain/duchainutils.h>
#include "includepathcomputer.h"
#include <interfaces/iuicontroller.h>
#include <cpppreprocessenvironment.h>

//#define DUMP_SMART_RANGES
//#define DUMP_AST
//#define DUMP_DUCHAIN

using namespace KDevelop;

bool importsContext(const QVector<DUContext::Import>& contexts, const DUContext* context) {
  foreach(const DUContext::Import &listCtx, contexts)
    if(listCtx.context(0) && listCtx.context(0)->imports(context))
      return true;
  return false;
}

QList<IndexedString> convertFromUrls(const QList<KUrl>& urlList) {
  QList<IndexedString> ret;
  foreach(const KUrl& url, urlList)
    ret << IndexedString(url.pathOrUrl());
  return ret;
}

bool CPPParseJob::needUpdateEverything() const {
  return m_needUpdateEverything;
}

bool CPPParseJob::wasUpdated(const KDevelop::DUContext* context) const
{
  return m_updated.contains(context);
}
void CPPParseJob::setWasUpdated(const KDevelop::DUContext* context)
{
  m_updated.insert(context);
}

const QSet<const KDevelop::DUContext*>& CPPParseJob::updated() const
{
  return m_updated;
}


void CPPParseJob::setNeedUpdateEverything(bool need) {
  m_needUpdateEverything = need;
}

CPPParseJob::CPPParseJob( const KUrl &url,
                    PreprocessJob* parentPreprocessor )
        : KDevelop::ParseJob( url ),
        m_needUpdateEverything( false ),
        m_parentPreprocessor( parentPreprocessor ),
        m_session( new ParseSession ),
        m_readFromDisk( false ),
        m_includePathsComputed( 0 ),
        m_keepDuchain( false ),
        m_parsedIncludes( 0 ),
        m_needsUpdate( true )
{
    if( !m_parentPreprocessor ) {
        addJob(m_preprocessJob = new PreprocessJob(this));
        addJob(m_parseJob = new CPPInternalParseJob(this));
    } else {
        m_preprocessJob = 0;
        m_parseJob = 0;
        //The preprocessor will call parseForeground() to preprocess & parse instantly
    }
}

void CPPParseJob::setKeepDuchain(bool b) {
    m_keepDuchain = b;
}

bool CPPParseJob::keepDuchain() const {
    return m_keepDuchain;
}

void CPPParseJob::setKeepEverything(bool b) {
    m_keepEverything = b;
}

bool CPPParseJob::keepEverything() const {
    return m_keepEverything;
}

void CPPParseJob::includedFileParsed() {
  ++m_parsedIncludes;
  const int estimateIncludes = 450;
  float _progress = ((float)m_parsedIncludes) / estimateIncludes;
  if(_progress > 0.8)
    _progress = 0.8;

  emit progress(this, _progress, i18n("Parsing included files"));
}

void CPPParseJob::setLocalProgress(float _progress, QString text) {
  emit progress(this, 0.8+_progress*0.2, text);
}

void CPPParseJob::setNeedsUpdate(bool needs) {
  m_needsUpdate = needs;
}

bool CPPParseJob::needsUpdate() const {
  return m_needsUpdate;
}

void CPPParseJob::parseForeground() {
    //Create the sub-jobs and directly execute them.
    Q_ASSERT( !m_preprocessJob && !m_parseJob );

    m_preprocessJob = new PreprocessJob(this);
    m_parseJob = new CPPInternalParseJob(this);
    m_preprocessJob->run();
    m_parseJob->run();
}

KUrl CPPParseJob::includedFromPath() const {
    return m_includedFromPath;
}

void CPPParseJob::setIncludedFromPath( const KUrl& path ) {
    m_includedFromPath = path;
}

PreprocessJob* CPPParseJob::parentPreprocessor() const {
    return m_parentPreprocessor;
}

void CPPParseJob::gotIncludePaths(IncludePathComputer* comp) {
  m_includePathsComputed = comp;
  m_waitForIncludePathsMutex.lock(); //This makes sure that the parse thread goes into the waiting state first
  m_waitForIncludePathsMutex.unlock();
  m_waitForIncludePaths.wakeAll();
}


const KUrl::List& CPPParseJob::includePathUrls() const {
  includePaths();
  return masterJob()->m_includePathUrls;
}

void CPPParseJob::mergeDefines(CppPreprocessEnvironment& env) const
{
  //m_includePathsComputed is filled when includePaths() is called
  masterJob()->includePaths();
  
  if(ICore::self()->shuttingDown())
    return; //If the system is shutting down, include-paths were not computed properly
  
  Q_ASSERT(masterJob()->m_includePathsComputed);
  
  QHash<QString, QString> defines = masterJob()->m_includePathsComputed->defines();
  
  ///@todo Most probably, the same macro-sets will be calculated again and again.
  ///           One ReferenceCountedMacroSet would be enough.
  
  kDebug() << "DEFINES:" << defines;
  
  for(QHash<QString, QString>::const_iterator it = defines.constBegin(); it != defines.constEnd(); ++it)
  {
    rpp::pp_macro* m = new rpp::pp_macro(IndexedString(it.key()));
    m->setDefinitionText( *it );
    
    //Call rpp::Environment::setMacro directly, so we don't add the macro to the environment-file.
    //It should be only part of the environment.
    env.rpp::Environment::setMacro(m);
  }
}

const QList<IndexedString>& CPPParseJob::includePaths() const {
    //If a lock was held here, we would get deadlocks
    if( ICore::self()->shuttingDown() )
      return m_includePaths;
    
    if( masterJob() == this ) {
        if( !m_includePathsComputed ) {
            Q_ASSERT(!DUChain::lock()->currentThreadHasReadLock() && !DUChain::lock()->currentThreadHasWriteLock());
            m_waitForIncludePathsMutex.lock();
            qRegisterMetaType<CPPParseJob*>("CPPParseJob*");
            QMetaObject::invokeMethod(cpp(), "findIncludePathsForJob", Qt::QueuedConnection, Q_ARG(CPPParseJob*, const_cast<CPPParseJob*>(this)));
            //Will be woken once the include-paths are computed
            while(!m_waitForIncludePaths.wait(&m_waitForIncludePathsMutex, 1000))
            {
              if(ICore::self()->shuttingDown())
              {
                return m_includePaths;
              }
            }
            m_waitForIncludePathsMutex.unlock();
            Q_ASSERT(m_includePathsComputed);
            m_includePathsComputed->computeBackground();
            m_includePathUrls = m_includePathsComputed->result();
            m_includePaths = convertFromUrls(m_includePathUrls);

        }
        return m_includePaths;
    } else {
        return masterJob()->includePaths();
    }
}

CPPParseJob::~CPPParseJob()
{
  delete m_includePathsComputed;
}

KDevelop::ModificationRevisionSet CPPParseJob::includePathDependencies() const {
  if(m_includePathsComputed)
    return m_includePathsComputed->m_includePathDependency;

  return KDevelop::ModificationRevisionSet();
}

CPPParseJob* CPPParseJob::masterJob() {
    if( parentPreprocessor() )
        return static_cast<CPPParseJob*>(parentPreprocessor()->parent())->masterJob();
    else
        return this;
}

const CPPParseJob* CPPParseJob::masterJob() const {
    if( parentPreprocessor() )
        return static_cast<CPPParseJob*>(parentPreprocessor()->parent())->masterJob();
    else
        return this;
}

void CPPParseJob::setUpdatingProxyContext( const ReferencedTopDUContext& context ) {
    m_updatingProxyContext = context;
}

ReferencedTopDUContext CPPParseJob::updatingProxyContext() const {
    return m_updatingProxyContext;
}

void CPPParseJob::setUpdatingContentContext( const ReferencedTopDUContext& context ) {
    m_updatingContentContext = context;
}

ReferencedTopDUContext CPPParseJob::updatingContentContext() const {
    return m_updatingContentContext;
}

CppLanguageSupport * CPPParseJob::cpp() const
{
  return CppLanguageSupport::self();
}

void CPPParseJob::addIncludedFile(KDevelop::ReferencedTopDUContext duChain, int sourceLine) {
    if(duChain.data()) {
      DUChainReadLocker lock(DUChain::lock());
      m_includedFiles.push_back(LineContextPair(duChain, sourceLine));
    }
}

void CPPParseJob::setProxyEnvironmentFile( Cpp::EnvironmentFile* file ) {
    m_proxyEnvironmentFile = KSharedPtr<Cpp::EnvironmentFile>(file);
}

Cpp::EnvironmentFile* CPPParseJob::proxyEnvironmentFile() {
    return m_proxyEnvironmentFile.data();
}

void CPPParseJob::setContentEnvironmentFile( Cpp::EnvironmentFile* file ) {
    //Q_ASSERT(!file || file->identity().flags() & IdentifiedFile::Content);
    m_contentEnvironmentFile = KSharedPtr<Cpp::EnvironmentFile>(file);
}

void CPPParseJob::addPreprocessorProblem(const ProblemPointer problem) {
  m_preprocessorProblems << problem;
}

QList<ProblemPointer> CPPParseJob::preprocessorProblems() const {
  return m_preprocessorProblems;
}

QList<ProblemPointer>* CPPParseJob::preprocessorProblemsPointer() {
  return &m_preprocessorProblems;
}

Cpp::EnvironmentFile* CPPParseJob::contentEnvironmentFile() {
    return m_contentEnvironmentFile.data();
}

const IncludeFileList& CPPParseJob::includedFiles() const {
    return m_includedFiles;
}

CPPParseJob * CPPInternalParseJob::parentJob() const
{
    Q_ASSERT(parent());
    return static_cast<CPPParseJob*>(const_cast<QObject*>(parent()));
}

const KTextEditor::Range& CPPParseJob::textRangeToParse() const
{
    return m_textRangeToParse;
}

CPPInternalParseJob::CPPInternalParseJob(CPPParseJob * parent)
    : ThreadWeaver::Job(parent)
    , m_initialized(false)
    , m_priority(0)
{
}

///If @param ctx is a proxy-context, returns the target-context. Else returns ctx. @warning du-chain must be locked
LineContextPair contentFromProxy(LineContextPair ctx) {
    if( ctx.context->parsingEnvironmentFile() && ctx.context->parsingEnvironmentFile()->isProxyContext() ) {
        {
          ReferencedTopDUContext ref(ctx.context);
        }
        if(ctx.context->importedParentContexts().isEmpty()) {
          kWarning() << "proxy-context for" << ctx.context->url().str() << "has no imports!" << ctx.context->ownIndex();
          ///@todo Find out how this can happen
//           Q_ASSERT(0);
          return LineContextPair(0, 0);
        }

        Q_ASSERT(!ctx.context->importedParentContexts().isEmpty());
        return LineContextPair( dynamic_cast<TopDUContext*>(ctx.context->importedParentContexts().first().context(0)), ctx.sourceLine );
    }else{
        return ctx;
    }
}

void CPPInternalParseJob::initialize() {
    if(m_initialized)
      return;
    m_initialized = true;

    updatingProxyContext = parentJob()->updatingProxyContext().data();
    updatingContentContext = parentJob()->updatingContentContext().data();

    proxyContext = updatingProxyContext;
    contentContext = updatingContentContext;
/*    if(!contentContext)
      contentContext = ContextBuilder::createEmptyTopContext(parentJob()->document());

    if(!proxyContext && Cpp::EnvironmentManager::self()->isSimplifiedMatching())
      proxyContext = ContextBuilder::createEmptyTopContext(parentJob()->document());*/
}

void CPPInternalParseJob::highlightIfNeeded()
{
    if(!KDevelop::EditorIntegrator::documentForUrl(parentJob()->document()))
      return;

    DUChainReadLocker l(DUChain::lock());
    ReferencedTopDUContext standardContext = DUChainUtils::standardContextForUrl(parentJob()->document().toUrl());

    if(standardContext && standardContext->smartRange()) {
      kDebug( 9007 ) << "Highlighting" << parentJob()->document().str();
      //If the document has a smart-range, at least re-do the highlighting
      l.unlock();
      if ( parentJob()->cpp() && parentJob()->cpp()->codeHighlighting() )
        parentJob()->cpp()->codeHighlighting()->highlightDUChain( standardContext.data() );
    }
}

void CPPInternalParseJob::run()
{
    //Happens during shutdown
    if(!ICore::self()->languageController()->language("C++")->languageSupport() || !parentJob()->cpp())
      return;
    //If we have a parent, the parse-mutex is already locked
    QReadLocker lock(parentJob()->parentPreprocessor() ? 0 : parentJob()->cpp()->language()->parseLock());
    if(!ICore::self()->languageController()->language("C++")->languageSupport() || !parentJob()->cpp())
      return;

    UrlParseLock urlLock(parentJob()->document());

    if(!parentJob()->needsUpdate()) {
      parentJob()->processDelayedImports();
      kDebug( 9007 ) << "===-- ALREADY UP TO DATE --===> " << parentJob()->document().str();
      highlightIfNeeded();
      return;
    }
    if(!parentJob()->contentEnvironmentFile()) {
      //May happen when the file could not be opened or similar
      kDebug( 9007 ) << "===-- Problem: Preprocessor did not create environment-file, skipping --===> " << parentJob()->document().str();
      return;
    }
    kDebug( 9007 ) << "===-- PARSING --===> "
    << parentJob()->document().str();


    if (parentJob()->abortRequested())
        return /*parentJob()->abortJob()*/;

    parentJob()->setLocalProgress(0, i18n("Parsing actual file"));

    initialize();

    Cpp::EnvironmentFilePointer proxyEnvironmentFile(parentJob()->proxyEnvironmentFile());
    Cpp::EnvironmentFilePointer contentEnvironmentFile(parentJob()->contentEnvironmentFile());

    //Eventually remove old imports
    if(contentContext) {
      /**
      * If we are updating everything, and the context has not been updated yet in this run,
       * remove all contexts previously imported into the content-context,
      * because they may contain badly parsed data, that might later override new parsed data.
      * If simplified environment-matching is disabled, always remove the imports if the file is reparsed,
      * their new versions will be re-added.
      * */
        DUChainWriteLocker lock(DUChain::lock());
      if(!parentJob()->keepDuchain() &&
        ((!parentJob()->masterJob()->wasUpdated(contentContext) && parentJob()->needUpdateEverything())
          || !proxyContext)) {

          foreach(const DUContext::Import& ctx, contentContext->importedParentContexts())
            contentContext->removeImportedParentContext(ctx.context(0));
          }
          ///@todo Make this possible without a permanent write-lock
          contentContext->updateImportsCache();
    }

    QList<LineContextPair> importedContentChains; //All content-chains imported while this parse-run. Also contains the temporary ones.
    QList<ReferencedTopDUContext> importedTemporaryChains; //All imported content-chains that were imported temporarily from parents.
    QSet<KDevelop::IndexedString> encounteredIncludeUrls; //All imported file-urls that were encountered this run.

    {
        DUChainReadLocker lock(DUChain::lock());
        foreach ( const LineContextPair &context, parentJob()->includedFiles() ) {
            LineContextPair pair = contentFromProxy(context);
            if(!pair.context)
              continue;

            importedContentChains << pair;
            encounteredIncludeUrls << context.context->url();
        }
    }

    //Temporarily import contexts imported by parents, because in C++ those are visible from here too
    if( parentJob()->parentPreprocessor() ) {
        DUChainReadLocker lock(DUChain::lock());
        //In theory, we would need to insert the whole until-now parsed part of the parent, but that's not possible because
        //we mix preprocessing and parsing, by triggering the parsing of included files by the preprocessor of the including file
        //So we simply assume that the includes are at the top of the file.
        CPPParseJob* currentJob = parentJob()->parentPreprocessor()->parentJob();

        while(currentJob) {
          foreach ( const LineContextPair &topContext, currentJob->includedFiles() ) {
              //As above, we work with the content-contexts.
              LineContextPair context = contentFromProxy(topContext);
              if(!context.context)
                continue;
              DUContextPointer ptr(context.context);
              if( !importsContext(importedContentChains, context.context)  && (!updatingContentContext ||       !importsContext(updatingContentContext->importedParentContexts(), context.context)) ) {
                if(!updatingContentContext || !updatingContentContext->imports(context.context, updatingContentContext->range().end)) {
                    //This must be conditional, else we might remove needed includes later because we think they were purely temporary
                    importedContentChains << context;
                    importedContentChains.back().temporary = true;
                    importedTemporaryChains << context.context;
                }
              }
          }
          if(currentJob->parentPreprocessor())
            currentJob = currentJob->parentPreprocessor()->parentJob();
          else
            currentJob = 0;
        }
    }

    ///Now we build/update the content-context

    bool doNotChangeDUChain = false;

    if(!parentJob()->keepDuchain()) {

      TopDUContext::Features newFeatures = parentJob()->minimumFeatures();
      if(contentContext)
        newFeatures = (TopDUContext::Features)(newFeatures | contentContext->features());

      if(newFeatures & TopDUContext::ForceUpdate)
        kDebug() << "update enforced";

      ///At some point, we have to give up on features again, else processing will be just too slow.
      ///Simple solution for now: Always go down to the minimum required level.
      if(!contentContext || !contentContext->smartRange())
        newFeatures = parentJob()->minimumFeatures();

      bool keepAST = newFeatures & TopDUContext::AST;
      
      //Remove update-flags like 'Recursive' or 'ForceUpdate', and the AST flag
      newFeatures = (TopDUContext::Features)(newFeatures & TopDUContext::AllDeclarationsContextsAndUses);
      
      TranslationUnitAST* ast = 0L;

      Control control;
      Parser parser(&control);

      if(newFeatures != TopDUContext::Empty)
      {
        ast = parser.parse( parentJob()->parseSession().data() );

        //This will be set to true if the duchain data should be left untouched
        if((ast->hadMissingCompoundTokens || control.hasProblem(KDevelop::ProblemData::Lexer)) && updatingContentContext) {
          //Make sure we don't update into a completely invalid state where everything is invalidated temporarily.
          DUChainWriteLocker l(DUChain::lock());

          if((updatingContentContext->features() & parentJob()->minimumFeatures()) ==  parentJob()->minimumFeatures() &&
              updatingContentContext->smartRange() &&
              updatingContentContext->parsingEnvironmentFile()->modificationRevision().modificationTime == ModificationRevision::revisionForFile(updatingContentContext->url()).modificationTime) {
            kDebug() << "not processing" << updatingContentContext->url().str() << "because of missing compound tokens";
            ICore::self()->uiController()->showErrorMessage(i18n("Not updating duchain for %1", parentJob()->document().toUrl().fileName()), 1);
            l.unlock();
            doNotChangeDUChain = true;
            ProblemPointer problem(new Problem);
            problem->setSource(ProblemData::Parser);
            problem->setDescription("Not updating the DUChain because of serious document inconsistency");
            control.reportProblem(problem);
          }
        }

        if (parentJob()->abortRequested())
            return /*parentJob()->abortJob()*/;

        if ( ast ) {
            ast->session = parentJob()->parseSession().data();
#ifdef DUMP_AST
            DumpTree dump;
            dump.dump(ast, parentJob()->parseSession()->token_stream);
#endif
        }
      }

      CppEditorIntegrator editor(parentJob()->parseSession().data());
      bool isStandardContext = false;
      {
        DUChainWriteLocker l(DUChain::lock());
        TopDUContext* knownStandardContext = DUChainUtils::standardContextForUrl(parentJob()->document().toUrl());

        isStandardContext = (parentJob()->masterJob() == parentJob() || knownStandardContext == updatingContentContext || !knownStandardContext);

        if(isStandardContext) {
          //Delete the smart-ranges of all other contexts, so we never get problems with smart uses/highlighting
          foreach(TopDUContext* context, DUChain::self()->chainsForDocument(parentJob()->document())) {
            if(context != updatingContentContext && context->smartRange()) {
              SmartConverter sc(&editor);
              sc.deconvertDUChain(context);
              kDebug() << "smart-deconverting a non-standard context";
            }
          }

          editor.setCurrentUrl(parentJob()->document(), true);

          if(updatingContentContext && !updatingContentContext->smartRange() && editor.smart()) {
            kWarning() << "updated context has not been smartened yet! Smartening it";
            SmartConverter sc(&editor);
            sc.convertDUChain(updatingContentContext);
            Q_ASSERT(updatingContentContext->smartRange());
            ICore::self()->languageController()->backgroundParser()->addManagedTopRange(updatingContentContext->smartRange()->document()->url(), updatingContentContext->smartRange());
          }
        }
      }

      editor.setCurrentUrl(parentJob()->document(), isStandardContext);

      kDebug( 9007 ) << (contentContext ? "updating" : "building") << "duchain for" << parentJob()->document().str();

      uint oldItemCount = 0;
      if(contentContext) {
        DUChainWriteLocker l(DUChain::lock());
        contentContext->clearProblems();
        oldItemCount = contentContext->childContexts().size() + contentContext->localDeclarations().size();
      }

      DeclarationBuilder declarationBuilder(&editor);

      if(newFeatures == TopDUContext::VisibleDeclarationsAndContexts) {
        declarationBuilder.setOnlyComputeVisible(true); //Only visible declarations/contexts need to be built.
      }
      else if(newFeatures == TopDUContext::SimplifiedVisibleDeclarationsAndContexts)
      {
        declarationBuilder.setOnlyComputeVisible(true);
        declarationBuilder.setComputeSimplified(true);
        kDebug() << "computing simplified";
      }else if(newFeatures == TopDUContext::Empty) {
        kDebug() << "computing empty";
        declarationBuilder.setComputeEmpty(true);
      }


      if(keepAST)
        declarationBuilder.setMapAst(true); //Set the property to map the AST & DUChain

      if(!doNotChangeDUChain) {

        if(Cpp::EnvironmentManager::self()->matchingLevel() == Cpp::EnvironmentManager::Disabled) {
            DUChainWriteLocker lock(DUChain::lock());
            if(contentContext)
              contentContext->clearImportedParentContexts();
        }

        contentContext = declarationBuilder.buildDeclarations(contentEnvironmentFile, ast, &importedContentChains, contentContext, false);

        //If publically visible declarations were added/removed, all following parsed files need to be updated
        if(declarationBuilder.changeWasSignificant()) {
          ///@todo The right solution to the whole problem: Do not put any imports into the content-contexts. Instead, Represent the complete import-structure in the proxy-contexts.
          ///      While searching, always use the perspective of the proxy. Even better: Change the context-system so proxy-contexts become completely valid contexts from the outside perspective,
          ///      that import all their imports, and that share all their content except the imports/environment-information with all the other proxy contexts for that file, and with one content-context.
          ///      Main problem: Contained Declarations/DUContexts point at their parent top-context. Which proxy-context should they point at?
          //kDebug() << "A significant change was recorded, all following contexts will be updated";
          //parentJob()->masterJob()->setNeedUpdateEverything(true);
        }

        {
          DUChainReadLocker l(DUChain::lock());
          Q_ASSERT(!updatingContentContext || updatingContentContext == contentContext);
          Q_ASSERT(contentContext->parsingEnvironmentFile().data() == contentEnvironmentFile.data());

          if(contentContext->childContexts().size() + contentContext->localDeclarations().size() == 0) {
            if(oldItemCount != 0) {
              //To catch some problems
              kDebug(9007) << "All items in" << parentJob()->document().str() << "have been extincted, previous count:" << oldItemCount << "current identity offset:" << contentEnvironmentFile->identityOffset();
            }
          }
        }
      }

      {
        DUChainWriteLocker l(DUChain::lock());

        foreach( const ProblemPointer& problem, parentJob()->preprocessorProblems() ) {
          contentContext->addProblem(problem);
        }

        foreach( KDevelop::ProblemPointer p, control.problems() ) {
          p->setLocationStack(parentJob()->includeStack());
          contentContext->addProblem(p);
        }
      }

      if(!doNotChangeDUChain) {
        if(contentContext) {
            DUChainWriteLocker l(DUChain::lock());
            QList<TopDUContext*> remove;
            foreach(const ReferencedTopDUContext &ctx, importedTemporaryChains)
                remove << ctx.data();
            contentContext->removeImportedParentContexts(remove);
        }

        ///When simplified environment-matching is enabled, we will accumulate many different
        ///versions of imports into a single top-context. To reduce that a little, we remove all
        ///with urls we didn't encounter.
        if(updatingContentContext) {
            DUChainWriteLocker l(DUChain::lock());

            //Remove the temporary chains first, so we don't get warnings from them

            QVector<DUContext::Import> imports = contentContext->importedParentContexts();
            foreach(const DUContext::Import &ctx, imports) {
                if(ctx.context(0) && !encounteredIncludeUrls.contains(ctx.context(0)->url()) && contentEnvironmentFile->missingIncludeFiles().set().count() == 0 && (!proxyEnvironmentFile || proxyEnvironmentFile->missingIncludeFiles().set().count() == 0)) {
                    contentContext->removeImportedParentContext(ctx.context(0));
                    kDebug( 9007 ) << "removing not encountered import " << ctx.context(0)->url().str();
                }
            }
        }

        if(contentContext) {
          DUChainWriteLocker l(DUChain::lock());
          contentContext->updateImportsCache();
        }

        if (!parentJob()->abortRequested()) {
          if ((newFeatures & TopDUContext::AllDeclarationsContextsAndUses) == TopDUContext::AllDeclarationsContextsAndUses) {
              parentJob()->setLocalProgress(0.5, i18n("Building uses"));

              UseBuilder useBuilder(&editor);
              useBuilder.setMapAst(keepAST);
              useBuilder.buildUses(ast);
              DUChainWriteLocker l(DUChain::lock());
              foreach(KDevelop::ProblemPointer problem, useBuilder.problems())
                contentContext->addProblem(problem);
          }else{
              //Delete existing uses
              DUChainWriteLocker lock( DUChain::lock() );
              contentContext->deleteUsesRecursively();
          }
          
          if (!parentJob()->abortRequested() && editor.smart()) {

            if ( parentJob()->cpp() && parentJob()->cpp()->codeHighlighting() )
            {
                parentJob()->cpp()->codeHighlighting()->highlightDUChain( contentContext );
            }
          }
        }
      }

      ///Now mark the context as not being updated. This MUST be done or we will be waiting forever in a loop
      {
        DUChainWriteLocker l(DUChain::lock());
        contentContext->setFeatures(newFeatures);
        if(proxyContext)
          proxyContext->setFeatures(newFeatures);
        contentContext->setFlags( (TopDUContext::Flags)(contentContext->flags() & (~TopDUContext::UpdatingContext)) );

        //Now that the Ast is fully built, add it to the TopDUContext if requested
        if(keepAST)
        {
          kDebug() << "AST Is being kept for" << parentJob()->document().toUrl();
          contentContext->setAst(IAstContainer::Ptr( parentJob()->parseSession().data() ));
          parentJob()->parseSession()->setASTNodeParents();
        }

        else
          contentContext->clearAst();
      }

      if (parentJob()->abortRequested())
        return /*parentJob()->abortJob()*/;

    }else{
      {
        DUChainWriteLocker l(DUChain::lock());
        if(proxyContext && contentContext)
          proxyContext->setFeatures(contentContext->features());
      }
      kDebug() << "keeping duchain";
      highlightIfNeeded();
    }

    //Even if doNotChangeDUChain is enabled, add new imported contexts.
    //This is very useful so new added includes always work.
    if(parentJob()->keepDuchain() || doNotChangeDUChain) {
      DUChainWriteLocker l(DUChain::lock());
      ///Add all our imports to the re-used context, just to make sure they are there.
      foreach( const LineContextPair& import, importedContentChains )
          if(!import.temporary)
            contentContext->addImportedParentContext(import.context, SimpleCursor(import.sourceLine, 0));
      contentContext->updateImportsCache();
    }

    if(!doNotChangeDUChain) {
      DUChainReadLocker lock(DUChain::lock());
      foreach(const LineContextPair& import, parentJob()->includedFiles()) {
        if(import.temporary)
          continue;
        LineContextPair context = contentFromProxy(import);
        if(!context.context)
          continue;
        Q_ASSERT(context.context);
        if(!contentContext->imports(context.context.data(), SimpleCursor::invalid())) {
          kWarning() << "Context should be imported, but is not:" << contentContext->url().str() << " <- " << context.context->url().str();
        }
      }
    }

    ///Build/update the proxy-context

    if( proxyEnvironmentFile ) {
        ContextBuilder builder(parentJob()->parseSession().data());
        if(Cpp::EnvironmentManager::self()->matchingLevel() == Cpp::EnvironmentManager::Disabled) {
            DUChainWriteLocker lock(DUChain::lock());
            if(updatingProxyContext)
              updatingProxyContext->clearImportedParentContexts();
        }

        proxyContext = builder.buildProxyContextFromContent(proxyEnvironmentFile, TopDUContextPointer(contentContext), TopDUContextPointer(updatingProxyContext));

        DUChainWriteLocker lock(DUChain::lock());

        Q_ASSERT(!updatingProxyContext || updatingProxyContext == proxyContext);

        if(proxyContext->importedParentContexts().isEmpty()) {
          Q_ASSERT(0); //Failure
        }

        Q_ASSERT(proxyContext->importedParentContexts()[0].context(0) == contentContext);

        //Make sure the imported contextsa re added
        foreach ( const LineContextPair &context, parentJob()->includedFiles() )
          proxyContext->addImportedParentContext(context.context, SimpleCursor(context.sourceLine, 0));

        proxyContext->updateImportsCache();

        proxyContext->clearProblems();

        //Put the problems into the proxy-context
        foreach( const ProblemPointer& problem, parentJob()->preprocessorProblems() ) {
          if(problem->finalLocation().start().line() < proxyEnvironmentFile->contentStartLine())
            proxyContext->addProblem(problem);
        }

        //Copy all problems into the proxy, because it's used to represent them.
        foreach( const ProblemPointer& problem, contentContext->problems() )
          proxyContext->addProblem(problem);
    }

    if(proxyContext) {
      DUChainReadLocker lock(DUChain::lock());
      Q_ASSERT(!proxyContext->importedParentContexts().isEmpty());
    }

    {
      //Update include-path dependencies
      DUChainWriteLocker lock(DUChain::lock());
      if(proxyEnvironmentFile)
        proxyEnvironmentFile->setIncludePathDependencies(parentJob()->includePathDependencies());

      if(contentEnvironmentFile)
        contentEnvironmentFile->setIncludePathDependencies(parentJob()->includePathDependencies());
    }

    ///In the end, mark the contexts as updated.
    if(contentContext)
      parentJob()->masterJob()->setWasUpdated(contentContext);
    if(proxyContext)
      parentJob()->masterJob()->setWasUpdated(proxyContext);

    parentJob()->setDuChain(proxyContext ? proxyContext : contentContext);

    //Indicate progress
    parentJob()->setLocalProgress(1, i18n("Ready"));

    if(parentJob()->masterJob() != parentJob())
      parentJob()->masterJob()->includedFileParsed();

    // Debug output...

    if ( !parentJob()->parentPreprocessor() ) {
            DUChainReadLocker lock(DUChain::lock());
#ifdef DUMP_DUCHAIN
        kDebug( 9007 ) << "================== duchain ==================";
        KDevelop::DumpChain dump;
        dump.dump(contentContext);
#endif
#ifdef DUMP_SMART_RANGES
        if(contentContext->smartRange()) {
          kDebug() << "dumping smart range";
          KDevelop::DumpChain dump;
          kDebug() << dump.dumpRanges(contentContext->smartRange());
        }

#endif

        //KDevelop::DumpDotGraph dumpGraph;
        //kDebug(9007) << "Dot-graph:\n" << dumpGraph.dotGraph(topContext, true);
    }

    //DumpTree dumpTree;

    kDebug( 9007 ) << "===-- Parsing finished --===>" << parentJob()->document().str();

    parentJob()->processDelayedImports();

    //Check the import structure
/*    if(parentJob()->masterJob() == parentJob()) {
      DUChainReadLocker l(DUChain::lock());
      foreach(const DUContext* context, parentJob()->updated()) {
        if(context == contentContext || context == proxyContext)
          continue;
        if(static_cast<const TopDUContext*>(context)->flags() & TopDUContext::ProxyContextFlag){
//           Q_ASSERT(proxyContext->imports(context, proxyContext->range().end));
        }else{
          kDebug( 9007 ) << "lost an instance of" << context->url().str() << "with" << context->childContexts().count() << "child-contexts and " << context->localDeclarations().count() << "local declarations";
//          Q_ASSERT(contentContext->imports(context, contentContext->range().end));
        }
      }
    }*/

   parentJob()->cleanupSmartRevision();
}

void CPPParseJob::processDelayedImports() {
  if(!m_delayedImports.isEmpty()) {
    foreach(const LineJobPair& job, m_delayedImports)
      job.first->addDelayedImporter(LineContextPair(m_parseJob->proxyContext ? m_parseJob->proxyContext : m_parseJob->contentContext, job.second));
    m_delayedImports.clear();
  }
  if(!m_delayedImporters.isEmpty()) {
    DUChainWriteLocker l(DUChain::lock());
    foreach(const LineContextPair& context, m_delayedImporters) {
      Q_ASSERT(context.context->parsingEnvironmentFile());
      if(context.context->parsingEnvironmentFile()->isProxyContext()) {
        Q_ASSERT(m_parseJob->proxyContext);
        context.context->addImportedParentContext(m_parseJob->proxyContext.data(), SimpleCursor(context.sourceLine, 0));
        Cpp::EnvironmentFile* cppEnvFile = dynamic_cast<Cpp::EnvironmentFile*>(context.context->parsingEnvironmentFile().data());
        Q_ASSERT(cppEnvFile);
        cppEnvFile->merge(dynamic_cast<Cpp::EnvironmentFile&>(*m_parseJob->proxyContext->parsingEnvironmentFile().data()));
        context.context->updateImportsCache();
      }
      Q_ASSERT(m_parseJob->contentContext);
      LineContextPair content = contentFromProxy(context);
      if(!content.context)
        continue;
      Q_ASSERT(content.context);
      content.context->addImportedParentContext(m_parseJob->proxyContext.data(), SimpleCursor(content.sourceLine, 0));
      content.context->updateImportsCache();
      Cpp::EnvironmentFile* cppEnvFile = dynamic_cast<Cpp::EnvironmentFile*>(content.context->parsingEnvironmentFile().data());
      Q_ASSERT(cppEnvFile);
      cppEnvFile->merge(dynamic_cast<Cpp::EnvironmentFile&>(*m_parseJob->contentContext->parsingEnvironmentFile().data()));
    }
  }
}

void CPPParseJob::addDelayedImport(LineJobPair job) {
  m_delayedImports << job;
}

void CPPParseJob::addDelayedImporter(LineContextPair duChain) {
  m_delayedImporters << duChain;
}

void CPPParseJob::requestDependancies()
{
}

ParseSession::Ptr CPPParseJob::parseSession() const
{
    return m_session;
}

void CPPParseJob::setReadFromDisk(bool readFromDisk)
{
    m_readFromDisk = readFromDisk;
}

bool CPPParseJob::wasReadFromDisk() const
{
    return m_readFromDisk;
}

CPPInternalParseJob * CPPParseJob::parseJob() const
{
    return m_parseJob;
}

int CPPInternalParseJob::priority() const
{
    return m_priority;
}

void CPPInternalParseJob::setPriority(int priority)
{
    m_priority = priority;
}

const QStack< DocumentCursor > & CPPParseJob::includeStack() const
{
  return m_includeStack;
}

void CPPParseJob::setIncludeStack(const QStack< DocumentCursor > & includeStack)
{
  m_includeStack = includeStack;
}

TopDUContext::Features CPPParseJob::standardMinimumFeatures() const
{
    return TopDUContext::SimplifiedVisibleDeclarationsAndContexts;
}

TopDUContext::Features CPPParseJob::slaveMinimumFeatures() const
{
    TopDUContext::Features slaveMinimumFeatures = standardMinimumFeatures();
    
    if(minimumFeatures() & TopDUContext::Recursive)
      slaveMinimumFeatures = (TopDUContext::Features)(minimumFeatures() & (~TopDUContext::ForceUpdate));
    else if((minimumFeatures() & TopDUContext::VisibleDeclarationsAndContexts) == TopDUContext::VisibleDeclarationsAndContexts)
      slaveMinimumFeatures = TopDUContext::VisibleDeclarationsAndContexts;
    
    if((minimumFeatures() & TopDUContext::ForceUpdateRecursive) == TopDUContext::ForceUpdateRecursive)
      slaveMinimumFeatures = (TopDUContext::Features)(slaveMinimumFeatures | TopDUContext::ForceUpdateRecursive);

    //The selected minimum features are required on all imported contexts recursively
    return (TopDUContext::Features)(slaveMinimumFeatures | TopDUContext::Recursive);
}

#include "cppparsejob.moc"

