/*
 * KDevelop C++ Code Completion Support
 *
 * Copyright 2006-2007 Hamish Rodda <rodda@kde.org>
 * Copyright 2007-2008 David Nolden <david.nolden.kdevelop@art-master.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.
 */

#include "model.h"

#include <kdebug.h>

#include <ktexteditor/view.h>
#include <ktexteditor/document.h>
#include <ktexteditor/codecompletioninterface.h>

#include <language/codecompletion/codecompletioncontext.h>
#include <language/backgroundparser/backgroundparser.h>

#include <interfaces/icore.h>
#include <interfaces/ilanguagecontroller.h>
#include <interfaces/idocumentcontroller.h>

#include "worker.h"
#include "context.h"
#include "../cppduchain/typeconversion.h"

using namespace KTextEditor;
using namespace KDevelop;

namespace Cpp {

CodeCompletionModel* CodeCompletionModel::s_self = nullptr;

bool useArgumentHintInAutomaticCompletion() {
  return false;
}

CodeCompletionModel::CodeCompletionModel( QObject * parent )
  : KDevelop::CodeCompletionModel(parent)
{
  setForceWaitForModel(true);
  Q_ASSERT(!s_self);
  s_self = this;

  connect(ICore::self()->languageController()->backgroundParser(), SIGNAL(parseJobFinished(KDevelop::ParseJob*)),
          this, SLOT(parseJobFinished(KDevelop::ParseJob*)));
}

CodeCompletionModel::~CodeCompletionModel()
{
  s_self = nullptr;
}

CodeCompletionModel* CodeCompletionModel::self()
{
  return s_self;
}

void CodeCompletionModel::startCompletionAfterParsing(const IndexedString& path)
{
  m_awaitDocument = path;
}

void CodeCompletionModel::parseJobFinished(ParseJob* job)
{
  if (job->document() != m_awaitDocument || ICore::self()->languageController()->backgroundParser()->isQueued(m_awaitDocument)) {
    return;
  }

  IDocument* doc = ICore::self()->documentController()->documentForUrl(m_awaitDocument.toUrl());
  m_awaitDocument = {};

  if (!doc || !doc->textDocument()) {
    return;
  }

  auto view = doc->textDocument()->activeView();
  if (!view || !view->hasFocus()) {
    return;
  }

  auto iface = dynamic_cast<KTextEditor::CodeCompletionInterface*>(view);
  if (iface) {
    ///@todo 1. This is a non-public interface, and 2. Completion should be started in "automatic invocation" mode
    QMetaObject::invokeMethod(view, "userInvokedCompletion");
  }
}

bool CodeCompletionModel::shouldStartCompletion(KTextEditor::View* view, const QString& inserted, bool userInsertion, const KTextEditor::Cursor& position) {
  kDebug() << inserted;
  QString insertedTrimmed = inserted.trimmed();
  
  TypeConversion::startCache();

  QString lineText = view->document()->text(KTextEditor::Range(position.line(), 0, position.line(), position.column()));
  
  if(lineText.startsWith("#") && lineText.contains("include") && inserted.endsWith("/"))
    return true; //Directory-content completion
  
  if(insertedTrimmed.endsWith('\"'))
    return false; //Never start completion behind a string literal
    
  if(useArgumentHintInAutomaticCompletion())
    if(insertedTrimmed.endsWith( '(' ) || insertedTrimmed.endsWith(',') || insertedTrimmed.endsWith('<') || insertedTrimmed.endsWith(":") )
      return true;
  
  //Start automatic completion behind '::'
  if(insertedTrimmed.endsWith(":") && position.column() > 1 && lineText.right(2) == "::")
    return true;
  
  return KDevelop::CodeCompletionModel::shouldStartCompletion(view, inserted, userInsertion, position);
}

void CodeCompletionModel::aborted(KTextEditor::View* view) {
    kDebug() << "aborting";
    worker()->abortCurrentCompletion();
    TypeConversion::stopCache();
    
    KDevelop::CodeCompletionModel::aborted(view);
}

bool isValidIncludeDirectiveCharacter(QChar character) {
  return character.isLetterOrNumber() || character == '_' || character == '-' || character == '.';
}

bool CodeCompletionModel::shouldAbortCompletion(KTextEditor::View* view, const KTextEditor::Range& range, const QString& currentCompletion)
{
  if(view->cursorPosition() < range.start() || view->cursorPosition() > range.end())
    return true; //Always abort when the completion-range has been left
  //Do not abort completions when the text has been empty already before and a newline has been entered

  QString line = view->document()->line(range.start().line()).trimmed();
  if(line.startsWith("#include")) {
    //Do custom check for include directives, since we allow more character then during usual completion
    QString text = view->document()->text(range);
    for(int a = 0; a < text.length(); ++a) {
      if(!isValidIncludeDirectiveCharacter(text[a]))
        return true;
    }
    return false;
  }
  
  static const QRegExp allowedText("^\\~?(\\w*)");
  return !allowedText.exactMatch(currentCompletion);
}

KDevelop::CodeCompletionWorker* CodeCompletionModel::createCompletionWorker() {
  return new CodeCompletionWorker(this);
}

Range CodeCompletionModel::updateCompletionRange(View* view, const KTextEditor::Range& range) {
  if(completionContext()) {
    Cpp::CodeCompletionContext* cppContext = dynamic_cast<Cpp::CodeCompletionContext*>(completionContext().data());
    Q_ASSERT(cppContext);
    cppContext->setFollowingText(view->document()->text(range));
    bool didReset = false;
    if(completionContext()->ungroupedElements().size()) {
      //Update the ungrouped elements, since they may have changed their text
      int row = rowCount() - completionContext()->ungroupedElements().size();
      
      foreach(KDevelop::CompletionTreeElementPointer item, completionContext()->ungroupedElements()) {
        
        QModelIndex parent = index(row, 0);
        
        KDevelop::CompletionCustomGroupNode* group = dynamic_cast<KDevelop::CompletionCustomGroupNode*>(item.data());
        if(group) {
          int subRow = 0;
          foreach(KDevelop::CompletionTreeElementPointer item, group->children) {
            if(item->asItem() && item->asItem()->dataChangedWithInput()) {
//               dataChanged(index(subRow, Name, parent), index(subRow, Name, parent));
              kDebug() << "doing dataChanged";
              reset(); ///@todo This is very expensive, but kate doesn't listen for dataChanged(..). Find a cheaper way to achieve this.
              didReset = true;
              break;
            }
            ++subRow;
          }
        }
        
        if(didReset)
          break;
        
        if(item->asItem() && item->asItem()->dataChangedWithInput()) {
          reset();
          didReset = true;
          break;
        }
        ++row;
      }
//       dataChanged(index(rowCount() - completionContext()->ungroupedElements().size(), 0), index(rowCount()-1, columnCount()-1 ));
    }
  }
  
  QString line = view->document()->line(range.start().line()).trimmed();
  if(line.startsWith("#include")) {
    //Skip over all characters that are allowed in a filename but usually not in code-completion
    KTextEditor::Range newRange = range;
    while(newRange.start().column() > 0) {
      KTextEditor::Cursor newStart = newRange.start();
      newStart.setColumn(newStart.column()-1);
      QChar character = view->document()->character(newStart);
      if(isValidIncludeDirectiveCharacter(character)) {
        newRange.start() = newStart; //Skip
      }else{
        break;
      }
    }
    kDebug() << "new range:" << newRange;
    return newRange;
  }

  return KDevelop::CodeCompletionModel::updateCompletionRange(view, range);
}

Range CodeCompletionModel::completionRange(View* view, const KTextEditor::Cursor& position)
{
    Range range = KDevelop::CodeCompletionModel::completionRange(view, position);
    if (range.start().column() > 0) {
        KTextEditor::Range preRange(Cursor(range.start().line(), range.start().column() - 1),
                                    Cursor(range.start().line(), range.start().column()));
        const QString contents = view->document()->text(preRange);
        if ( contents == "~" ) {
            range.expandToRange(preRange);
        }
    }
    return range;
}

void CodeCompletionModel::foundDeclarations(QList<CompletionTreeElementPointer> item, KDevelop::CodeCompletionContext::Ptr completionContext)
{
  TypeConversion::startCache();
  KDevelop::CodeCompletionModel::foundDeclarations(item, completionContext);
}

}

#include "model.moc"
