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

#include "codeassistant.h"
#include <interfaces/icore.h>
#include <interfaces/idocumentcontroller.h>
#include <ktexteditor/document.h>
#include <interfaces/iuicontroller.h>
#include <ktexteditor/view.h>
#include <language/duchain/duchainlock.h>
#include <language/duchain/duchain.h>
#include <interfaces/ilanguagecontroller.h>
#include <language/backgroundparser/backgroundparser.h>
#include <language/duchain/declaration.h>
#include <language/backgroundparser/parsejob.h>
#include "signatureassistant.h"
#include <language/duchain/duchainutils.h>
#include <language/duchain/duchainutils.h>

using namespace KDevelop;

namespace Cpp {

StaticCodeAssistant::StaticCodeAssistant() : m_activeProblemAssistant(false) {
  m_timer = new QTimer(this);
  m_timer->setSingleShot(true),
  m_timer->setInterval(400);
  connect(m_timer, SIGNAL(timeout()), SLOT(timeout()));
  connect(KDevelop::ICore::self()->documentController(), SIGNAL(documentLoaded(KDevelop::IDocument*)), SLOT(documentLoaded(KDevelop::IDocument*)));
  connect(KDevelop::ICore::self()->documentController(), SIGNAL(documentActivated(KDevelop::IDocument*)), SLOT(documentActivated(KDevelop::IDocument*)));
  foreach(KDevelop::IDocument* document, KDevelop::ICore::self()->documentController()->openDocuments())
    documentLoaded(document);
  connect(KDevelop::ICore::self()->languageController()->backgroundParser(), SIGNAL(parseJobFinished(KDevelop::ParseJob*)), SLOT(parseJobFinished(KDevelop::ParseJob*)));
}

void StaticCodeAssistant::documentLoaded(KDevelop::IDocument* document) {
  
  if(document->textDocument())
  {
    ///@todo Make these connections non-queued, and then reach forward using a QPointer, since else a crash may happen when the document is destroyed before the message is processed
    connect(document->textDocument(), SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)), SLOT(textInserted(KTextEditor::Document*,KTextEditor::Range)));
    connect(document->textDocument(), SIGNAL(textRemoved(KTextEditor::Document*,KTextEditor::Range,QString)), SLOT(textRemoved(KTextEditor::Document*,KTextEditor::Range,QString)));
  }
}

void StaticCodeAssistant::assistantHide() {
  m_activeAssistant = KSharedPtr<KDevelop::IAssistant>();
  m_activeProblemAssistant = false;
}

void StaticCodeAssistant::textInserted(KTextEditor::Document* document, KTextEditor::Range range) {
  m_eventualDocument = document;
  m_eventualRange = range;
  m_eventualRemovedText.clear();
  QMetaObject::invokeMethod(this, "eventuallyStartAssistant", Qt::QueuedConnection);
}

void StaticCodeAssistant::textRemoved(KTextEditor::Document* document, KTextEditor::Range range, const QString &removedText) {
  m_eventualDocument = document;
  m_eventualRange = range;
  m_eventualRemovedText = removedText;
  QMetaObject::invokeMethod(this, "eventuallyStartAssistant", Qt::QueuedConnection);
}

void StaticCodeAssistant::eventuallyStartAssistant() {
  if(!m_eventualDocument)
    return;

  KTextEditor::View* view = m_eventualDocument.data()->activeView();
  //Eventually pop up an assistant
  if(!view)
    return;

  //FIXME: update signature assistant to play well with the rename assistant
  KTextEditor::Range sigAssistRange = m_eventualRange;
  if (!m_eventualRemovedText.isEmpty()) {
    sigAssistRange.setRange(sigAssistRange.start(), sigAssistRange.start());
  }

  KSharedPtr<AdaptDefinitionSignatureAssistant> signatureAssistant(
    new AdaptDefinitionSignatureAssistant(view, sigAssistRange)
  );

  if(signatureAssistant->isUseful()) {
    startAssistant(KSharedPtr<IAssistant>(signatureAssistant.data()));
  }

  RenameAssistant* renameAssistant = m_renameAssistants[view].data();
  if (!renameAssistant) {
    renameAssistant =  new RenameAssistant(view);
    m_renameAssistants[view].attach(renameAssistant);
    connect(m_eventualDocument.data(), SIGNAL(aboutToClose(KTextEditor::Document*)),
            SLOT(deleteRenameAssistantsForDocument(KTextEditor::Document*)));
  }

  renameAssistant->textChanged(m_eventualRange, m_eventualRemovedText);

  if(renameAssistant->isUseful()) {
    startAssistant(KSharedPtr<IAssistant>(renameAssistant));
  }

  // optimize, esp. for setText() calls as done in e.g. reformat source
  // only start the assitant once for multiple textRemoved/textInserted signals
  m_eventualDocument.clear();
  m_eventualRange = KTextEditor::Range::invalid();
  m_eventualRemovedText.clear();
}

void StaticCodeAssistant::deleteRenameAssistantsForDocument(KTextEditor::Document* document)
{
  foreach(KTextEditor::View *view, document->views()) {
    if (m_renameAssistants.contains(view))
      m_renameAssistants.remove(view);
  }
}

void StaticCodeAssistant::startAssistant(KSharedPtr< KDevelop::IAssistant > assistant) {
  if(m_activeAssistant)
    m_activeAssistant->doHide();
  
  if(!m_currentView)
    return;
  
  m_activeAssistant = assistant;
  if(m_activeAssistant) {
    connect(m_activeAssistant.data(), SIGNAL(hide()), SLOT(assistantHide()), Qt::DirectConnection);
    ICore::self()->uiController()->popUpAssistant(IAssistant::Ptr(m_activeAssistant.data()));
    
    m_assistantStartedAt =  m_currentView.data()->cursorPosition();
  }
}

void StaticCodeAssistant::parseJobFinished(KDevelop::ParseJob* job) {
  if(job->document() == m_currentDocument) {
    if(m_activeAssistant) {
      if(m_activeProblemAssistant)
        m_activeAssistant->doHide(); //Hide the assistant, as we will create a new one if the problem is still there
      else
        return;
    }
    KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock(), 300);
    if(!lock.locked())
      return;
    if(job->duChain()) {
      checkAssistantForProblems(job->duChain());
    }
  }
}

void StaticCodeAssistant::cursorPositionChanged(KTextEditor::View* , KTextEditor::Cursor pos ) {
  if(m_activeAssistant && m_assistantStartedAt.isValid())
    if(abs(m_assistantStartedAt.line() - pos.line()) >= 1)
      m_activeAssistant->doHide();
    
  m_timer->start();
}

void StaticCodeAssistant::documentActivated(KDevelop::IDocument* doc) {
  if(doc)
    m_currentDocument = KDevelop::IndexedString(doc->url());
  
  if(m_currentView) {
    disconnect(m_currentView.data(), SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)));
    m_currentView.clear();
  }
  
  if(doc->textDocument()) {
    m_currentView = doc->textDocument()->activeView();
    if(m_currentView)
      connect(m_currentView.data(), SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)));
  }
}

void StaticCodeAssistant::checkAssistantForProblems(KDevelop::TopDUContext* top) {
    foreach(KDevelop::ProblemPointer problem, top->problems()) {
      if(m_currentView && m_currentView.data()->cursorPosition().line() == problem->range().start.line) {
        IAssistant::Ptr solution = problem->solutionAssistant();
        if(solution) {
          startAssistant(solution);
          m_activeProblemAssistant = true;
          break;
        }
      }
    }
}

void StaticCodeAssistant::timeout() {
  if(!m_activeAssistant && m_currentView) {
    KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock(), 300);
    if(!lock.locked())
      return;
    
    TopDUContext* top = KDevelop::DUChainUtils::standardContextForUrl(m_currentDocument.toUrl());
    if(top)
      checkAssistantForProblems(top);
  }
}

}

#include "codeassistant.moc"
