/* This file is part of the KDE project
Copyright 2002 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
Copyright 2002 Bernd Gehrmann <bernd@kdevelop.org>
Copyright 2003 Roberto Raggi <roberto@kdevelop.org>
Copyright 2003-2008 Hamish Rodda <rodda@kde.org>
Copyright 2003 Harald Fernengel <harry@kdevelop.org>
Copyright 2003 Jens Dagerbo <jens.dagerbo@swipnet.se>
Copyright 2005 Adam Treat <treat@kde.org>
Copyright 2004-2007 Alexander Dymo <adymo@kdevelop.org>
Copyright 2007 Andreas Pakulat <apaku@gmx.de>

This library 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 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 "documentcontroller.h"

#include <QFileInfo>
#include <QtDBus/QtDBus>
#include <QApplication>

#include <kio/netaccess.h>
#include <kencodingfiledialog.h>
#include <kactioncollection.h>
#include <klocale.h>
#include <krecentfilesaction.h>
#include <ktemporaryfile.h>
#include <kplugininfo.h>
#include <ktexteditor/document.h>
#include <ktexteditor/view.h>
#include <ktexteditor/annotationinterface.h>

#include <sublime/area.h>
#include <sublime/view.h>
#include <interfaces/iplugincontroller.h>
#include <interfaces/iprojectcontroller.h>
#include <interfaces/ibuddydocumentfinder.h>
#include <interfaces/iproject.h>
#include <interfaces/iselectioncontroller.h>
#include <interfaces/context.h>
#include <interfaces/ilanguagecontroller.h>
#include <project/projectmodel.h>
#include <util/path.h>

#include "core.h"
#include "mainwindow.h"
#include "textdocument.h"
#include "uicontroller.h"
#include "partcontroller.h"
#include "savedialog.h"
#include <kmessagebox.h>
#include <KIO/Job>
#include "workingsetcontroller.h"
#include <vcs/interfaces/ibasicversioncontrol.h>
#include <vcs/models/vcsannotationmodel.h>
#include <vcs/vcsjob.h>
#include <vcs/vcspluginhelper.h>

#include <language/backgroundparser/backgroundparser.h>

#define EMPTY_DOCUMENT_URL i18n("Untitled")

namespace KDevelop
{


struct DocumentControllerPrivate {
    DocumentControllerPrivate(DocumentController* c)
        : controller(c)
        , fileOpenRecent(0)
        , globalTextEditorInstance(0)
    {
    }

    ~DocumentControllerPrivate()
    {
        //delete temporary files so they are removed from disk
        foreach (KTemporaryFile *temp, tempFiles)
            delete temp;
    }

    QString presetEncoding;

    // used to map urls to open docs
    QHash< KUrl, IDocument* > documents;

    QHash< QString, IDocumentFactory* > factories;
    QList<KTemporaryFile*> tempFiles;

    struct HistoryEntry
    {
        HistoryEntry() {}
        HistoryEntry( const KUrl & u, const KTextEditor::Cursor& cursor );

        KUrl url;
        KTextEditor::Cursor cursor;
        int id;
    };

    void removeDocument(Sublime::Document *doc)
    {
        QList<KUrl> urlsForDoc = documents.keys(dynamic_cast<KDevelop::IDocument*>(doc));
        foreach (const KUrl &url, urlsForDoc)
        {
            kDebug() << "destroying document" << doc;
            documents.remove(url);
        }
    }
    void chooseDocument()
    {

        KUrl dir;
        if( controller->activeDocument() )
        {
            dir = KUrl( controller->activeDocument()->url() );
            dir.setFileName(QString());
        }else
        {
            dir = KGlobal::config()->group("Open File").readEntry( "Last Open File Directory", Core::self()->projectController()->projectsBaseDirectory() );
        }

        KEncodingFileDialog::Result res = KEncodingFileDialog::getOpenUrlsAndEncoding( controller->encoding(), dir.url(), i18n( "*|Text File\n" ),
                                    Core::self()->uiControllerInternal()->defaultMainWindow(),
                                    i18n( "Open File" ) );
        if( !res.URLs.isEmpty() ) {
            QString encoding = res.encoding;
            foreach( const KUrl& u, res.URLs ) {
                openDocumentInternal(u, QString(), KTextEditor::Range::invalid(), encoding  );
            }
        }
        
    }

    void changeDocumentUrl(KDevelop::IDocument* document)
    {
        QMutableHashIterator<KUrl, IDocument*> it = documents;
        while (it.hasNext()) {
            if (it.next().value() == document) {
                if (documents.contains(document->url())) {
                    // Weird situation (saving as a file that is aready open)
                    IDocument* origDoc = documents[document->url()];
                    if (origDoc->state() & IDocument::Modified) {
                        // given that the file has been saved, close the saved file as the other instance will become conflicted on disk
                        document->close();
                        controller->activateDocument( origDoc );
                        break;
                    }
                    // Otherwise close the original document
                    origDoc->close();
                } else {
                    // Remove the original document
                    it.remove();
                }

                documents.insert(document->url(), document);

                if (!controller->isEmptyDocumentUrl(document->url()))
                {
                    fileOpenRecent->addUrl(document->url());
                }

                break;
            }
        }
    }

    KDevelop::IDocument* findBuddyDocument(const KUrl &url, IBuddyDocumentFinder* finder)
    {
        QList<KDevelop::IDocument*> allDocs = controller->openDocuments();
        foreach( KDevelop::IDocument* doc, allDocs ) {
            if(finder->areBuddies(url, doc->url())) {
                return doc;
            }
        }
        return 0;
    }


    IDocument* openDocumentInternal( const KUrl & inputUrl, const QString& prefName = QString(),
        const KTextEditor::Range& range = KTextEditor::Range::invalid(), const QString& encoding = "",
        DocumentController::DocumentActivationParams activationParams = 0,
        IDocument* buddy = 0)
    {
        IDocument* previousActiveDocument = controller->activeDocument();
        KTextEditor::Cursor previousActivePosition;
        if(previousActiveDocument && previousActiveDocument->textDocument() && previousActiveDocument->textDocument()->activeView())
            previousActivePosition = previousActiveDocument->textDocument()->activeView()->cursorPosition();
        

        QString _encoding = encoding;
        
        KUrl url = inputUrl;

        if ( url.isEmpty() && (!activationParams.testFlag(IDocumentController::DoNotCreateView)) )
        {
            KUrl dir;
            if( controller->activeDocument() )
            {
                dir = controller->activeDocument()->url().upUrl();
            }else
            {
                dir = KGlobal::config()->group("Open File").readEntry( "Last Open File Directory", Core::self()->projectController()->projectsBaseDirectory() );
            }

            KEncodingFileDialog::Result res = KEncodingFileDialog::getOpenUrlAndEncoding( "", dir.url(), i18n( "*|Text File\n" ),
                                        Core::self()->uiControllerInternal()->defaultMainWindow(),
                                        i18n( "Open File" ) );
            if( !res.URLs.isEmpty() )
                url = res.URLs.first();
            _encoding = res.encoding;
        }
        if ( url.isEmpty() )
            //still no url
            return 0;

        KGlobal::config()->group("Open File").writeEntry( "Last Open File Directory", url.upUrl() );

        // clean it and resolve possible symlink
        url.cleanPath( KUrl::SimplifyDirSeparators );
        if ( url.isLocalFile() )
        {
            QString path = QFileInfo( url.toLocalFile() ).canonicalFilePath();
            if ( !path.isEmpty() )
                url.setPath( path );
        }
        
        //get a part document
        IDocument* doc=0;
        if (documents.contains(url))
            doc=documents.value(url);
        else
        {
            KMimeType::Ptr mimeType;

            if (DocumentController::isEmptyDocumentUrl(url))
            {
                mimeType = KMimeType::mimeType("text/plain");
            }
            else if (!url.isValid())
            {
                // Exit if the url is invalid (should not happen)
                // If the url is valid and the file does not already exist,
                // kate creates the file and gives a message saying so
                kDebug() << "invalid URL:" << url.url();
                return 0;
            }
            else if (!KIO::NetAccess::exists( url, KIO::NetAccess::SourceSide, ICore::self()->uiController()->activeMainWindow() ))
            {
                //Don't create a new file if we are not in the code mode.
                if (static_cast<KDevelop::MainWindow*>(ICore::self()->uiController()->activeMainWindow())->area()->objectName() != "code") {
                    return 0;
                }
                // enfore text mime type in order to create a kate part editor which then can be used to create the file
                // otherwise we could end up opening e.g. okteta which then crashes, see: https://bugs.kde.org/id=326434
                mimeType = KMimeType::mimeType("text/plain");
            }
            else
            {
                mimeType = KMimeType::findByUrl( url );
                
                if( !url.isLocalFile() && mimeType->isDefault() )
                {
                    // fall back to text/plain, for remote files without extension, i.e. COPYING, LICENSE, ...
                    // using a synchronous KIO::MimetypeJob is hazardous and may lead to repeated calls to
                    // this function without it having returned in the first place
                    // and this function is *not* reentrant, see assert below:
                    // Q_ASSERT(!documents.contains(url) || documents[url]==doc);
                    mimeType = KMimeType::mimeType("text/plain");
                }
            }

            // is the URL pointing to a directory?
            if ( mimeType->is( "inode/directory" ) )
            {
                kDebug() << "cannot open directory:" << url.url();
                return 0;
            }

            if( prefName.isEmpty() )
            {
                // Try to find a plugin that handles this mimetype
                QVariantMap constraints;
                constraints.insert("X-KDevelop-SupportedMimeTypes", mimeType->name());
                Core::self()->pluginController()->pluginForExtension(QString(), QString(), constraints);
            }
            
            if( factories.contains( mimeType->name() ) )
            {
                doc = factories[mimeType->name()]->create(url, Core::self());
            }
            
            if(!doc) {
                if( !prefName.isEmpty() ) 
                {
                    doc = new PartDocument(url, Core::self(), prefName);
                } else  if ( Core::self()->partControllerInternal()->isTextType(mimeType)) 
                {
                    doc = new TextDocument(url, Core::self(), _encoding);
                } else if( Core::self()->partControllerInternal()->canCreatePart(url) ) 
                {
                    doc = new PartDocument(url, Core::self());
                } else
                {
                    int openAsText = KMessageBox::questionYesNo(0, i18n("KDevelop could not find the editor for file '%1' of type %2.\nDo you want to open it as plain text?", url.fileName(), mimeType->name()), i18nc("@title:window", "Could Not Find Editor"),
                                                                KStandardGuiItem::yes(), KStandardGuiItem::no(), "AskOpenWithTextEditor");
                    if (openAsText == KMessageBox::Yes)
                        doc = new TextDocument(url, Core::self(), _encoding);
                    else
                        return 0;
                }
            }
        }
        
        // The url in the document must equal the current url, else the housekeeping will get broken
        Q_ASSERT(!doc || doc->url() == url);
        
        if(doc && openDocumentInternal(doc, range, activationParams, buddy))
            return doc;
        else
            return 0;
        
    }
    
    bool openDocumentInternal(IDocument* doc,
                                const KTextEditor::Range& range,
                                DocumentController::DocumentActivationParams activationParams,
                                IDocument* buddy = 0)
    {
        IDocument* previousActiveDocument = controller->activeDocument();
        KTextEditor::Cursor previousActivePosition;
        if(previousActiveDocument && previousActiveDocument->textDocument() && previousActiveDocument->textDocument()->activeView())
            previousActivePosition = previousActiveDocument->textDocument()->activeView()->cursorPosition();
        
        KUrl url=doc->url();
        UiController *uiController = Core::self()->uiControllerInternal();
        Sublime::Area *area = uiController->activeArea();
        
        //We can't have the same url in many documents
        //so we check it's already the same if it exists
        //contains=>it's the same
        Q_ASSERT(!documents.contains(url) || documents[url]==doc);

        Sublime::Document *sdoc = dynamic_cast<Sublime::Document*>(doc);
        if( !sdoc )
        {
            documents.remove(url);
            delete doc;
            return false;
        }
        //react on document deletion - we need to cleanup controller structures
        
        QObject::connect(sdoc, SIGNAL(aboutToDelete(Sublime::Document*)), controller, SLOT(notifyDocumentClosed(Sublime::Document*)));
        //We check if it was already opened before
        bool emitOpened = !documents.contains(url);
        if(emitOpened)
            documents[url]=doc;
        
        if (!activationParams.testFlag(IDocumentController::DoNotCreateView))
        {
            //find a view if there's one already opened in this area
            Sublime::View *partView = 0;
            Sublime::AreaIndex* activeViewIdx = area->indexOf(uiController->activeSublimeWindow()->activeView());
            foreach (Sublime::View *view, sdoc->views())
            {
                Sublime::AreaIndex* areaIdx = area->indexOf(view);
                if (areaIdx && areaIdx == activeViewIdx)
                {
                    partView = view;
                    break;
                }
            }
            bool addView = false, applyRange = true;
            if (!partView)
            {
                //no view currently shown for this url
                partView = sdoc->createView();
                addView = true;
            }
            
            KDevelop::TextView* textView = dynamic_cast<KDevelop::TextView*>(partView);
            if(textView) {
                applyRange = false;
                textView->setInitialRange(range);
            }
            
            if(addView) {
                // This code is never executed when restoring session on startup,
                // only when opening a file manually

                Sublime::View* buddyView = 0;
                bool placeAfterBuddy = true;
                if(Core::self()->uiControllerInternal()->arrangeBuddies()) {
                    // If buddy is not set, look for a (usually) plugin which handles this URL's mimetype
                    // and use its IBuddyDocumentFinder, if available, to find a buddy document
                    if(!buddy && doc->mimeType()) {
                        QString mime = doc->mimeType()->name();
                        IBuddyDocumentFinder* buddyFinder = IBuddyDocumentFinder::finderForMimeType(mime);
                        if(buddyFinder) {
                            buddy = findBuddyDocument(url, buddyFinder);
                            if(buddy) {
                                placeAfterBuddy = buddyFinder->buddyOrder(buddy->url(), doc->url());
                            }
                        }
                    }

                    if(buddy) {
                        Sublime::Document* sublimeDocBuddy = dynamic_cast<Sublime::Document*>(buddy);

                        if(sublimeDocBuddy) {
                            Sublime::AreaIndex *pActiveViewIndex = area->indexOf(uiController->activeSublimeWindow()->activeView());
                            if(pActiveViewIndex) {
                                // try to find existing View of buddy document in current active view's tab
                                foreach (Sublime::View *pView, pActiveViewIndex->views()) {
                                    if(sublimeDocBuddy->views().contains(pView)) {
                                        buddyView = pView;
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }

                // add view to the area
                if(buddyView && area->indexOf(buddyView)) {
                    if(placeAfterBuddy) {
                        // Adding new view after buddy view, simple case
                        area->addView(partView, area->indexOf(buddyView), buddyView);
                    }
                    else {
                        // First new view, then buddy view
                        area->addView(partView, area->indexOf(buddyView), buddyView);
                        // move buddyView tab after the new document
                        area->removeView(buddyView);
                        area->addView(buddyView, area->indexOf(partView), partView);
                    }
                }
                else {
                    // no buddy found for new document / plugin does not support buddies / buddy feature disabled
                    Sublime::View *activeView = uiController->activeSublimeWindow()->activeView();
                    Sublime::UrlDocument *activeDoc = 0;
                    IBuddyDocumentFinder *buddyFinder = 0;
                    if(activeView)
                        activeDoc = dynamic_cast<Sublime::UrlDocument *>(activeView->document());
                    if(activeDoc && Core::self()->uiControllerInternal()->arrangeBuddies()) {
                        QString mime = KMimeType::findByUrl(activeDoc->url())->name();
                        buddyFinder = IBuddyDocumentFinder::finderForMimeType(mime);
                    }

                    if(Core::self()->uiControllerInternal()->openAfterCurrent() &&
                       Core::self()->uiControllerInternal()->arrangeBuddies() &&
                       buddyFinder)
                    {
                        // Check if active document's buddy is directly next to it.
                        // For example, we have the already-open tabs | *foo.h* | foo.cpp | , foo.h is active.
                        // When we open a new document here (and the buddy feature is enabled),
                        // we do not want to separate foo.h and foo.cpp, so we take care and avoid this.
                        Sublime::AreaIndex *activeAreaIndex = area->indexOf(activeView);
                        int pos = activeAreaIndex->views().indexOf(activeView);
                        Sublime::View *afterActiveView = activeAreaIndex->views().value(pos+1, 0);

                        Sublime::UrlDocument *activeDoc = 0, *afterActiveDoc = 0;
                        if(activeView && afterActiveView) {
                            activeDoc = dynamic_cast<Sublime::UrlDocument *>(activeView->document());
                            afterActiveDoc = dynamic_cast<Sublime::UrlDocument *>(afterActiveView->document());
                        }
                        if(activeDoc && afterActiveDoc &&
                           buddyFinder->areBuddies(activeDoc->url(), afterActiveDoc->url()))
                        {
                            // don't insert in between of two buddies, but after them
                            area->addView(partView, activeAreaIndex, afterActiveView);
                        }
                        else {
                            // The active document's buddy is not directly after it
                            // => no ploblem, insert after active document
                            area->addView(partView, activeView);
                        }
                    }
                    else {
                        // Opening as last tab won't disturb our buddies
                        // Same, if buddies are disabled, we needn't care about them.

                        // this method places the tab according to openAfterCurrent()
                        area->addView(partView, activeView);
                    }
                }
            }
            
            if (!activationParams.testFlag(IDocumentController::DoNotActivate))
            {
                uiController->activeSublimeWindow()->activateView(
                    partView, !activationParams.testFlag(IDocumentController::DoNotFocus));
            }
            if (!controller->isEmptyDocumentUrl(url))
            {
                fileOpenRecent->addUrl( url );
            }

            if( applyRange && range.isValid() )
            {
                if (range.isEmpty())
                    doc->setCursorPosition( range.start() );
                else
                    doc->setTextSelection( range );
            }
        }

        // Deferred signals, wait until it's all ready first
        if( emitOpened ) {
            emit controller->documentOpened( doc );
        }

        if (!activationParams.testFlag(IDocumentController::DoNotActivate) && doc != controller->activeDocument())
            emit controller->documentActivated( doc );

        saveAll->setEnabled(true);
        revertAll->setEnabled(true);
        close->setEnabled(true);
        closeAll->setEnabled(true);
        closeAllOthers->setEnabled(true);

        if(doc) {
            KTextEditor::Cursor activePosition;
            if(range.isValid())
                activePosition = range.start();
            else if(doc->textDocument() && doc->textDocument()->activeView())
                activePosition = doc->textDocument()->activeView()->cursorPosition();

            if (doc != previousActiveDocument || activePosition != previousActivePosition)
                emit controller->documentJumpPerformed(doc, activePosition, previousActiveDocument, previousActivePosition);
        }

        if ( doc->textDocument() ) {
            QObject::connect(doc->textDocument(), SIGNAL(reloaded(KTextEditor::Document*)), controller,
                             SLOT(reloaded(KTextEditor::Document*)));
        }

        return true;
    }

    DocumentController* controller;

    QList<HistoryEntry> backHistory;
    QList<HistoryEntry> forwardHistory;
    bool isJumping;

    QPointer<KAction> saveAll;
    QPointer<KAction> revertAll;
    QPointer<KAction> close;
    QPointer<KAction> closeAll;
    QPointer<KAction> closeAllOthers;
    KRecentFilesAction* fileOpenRecent;
    KTextEditor::Document* globalTextEditorInstance;
};

void DocumentController::reloaded(KTextEditor::Document* doc)
{
    ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url()),
            (TopDUContext::Features) ( TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate ),
            BackgroundParser::BestPriority, 0);
}

DocumentController::DocumentController( QObject *parent )
        : IDocumentController( parent )
{
    setObjectName("DocumentController");
    d = new DocumentControllerPrivate(this);
    QDBusConnection::sessionBus().registerObject( "/org/kdevelop/DocumentController",
        this, QDBusConnection::ExportScriptableSlots );

    connect(this, SIGNAL(documentUrlChanged(KDevelop::IDocument*)), this, SLOT(changeDocumentUrl(KDevelop::IDocument*)));

    if(!(Core::self()->setupFlags() & Core::NoUi)) setupActions();
}

void DocumentController::initialize()
{
}

void DocumentController::cleanup()
{
    if (d->fileOpenRecent)
        d->fileOpenRecent->saveEntries( KConfigGroup(KGlobal::config(), "Recent Files" ) );

    // Close all documents without checking if they should be saved.
    // This is because the user gets a chance to save them during MainWindow::queryClose.
    foreach (IDocument* doc, openDocuments())
        doc->close(IDocument::Discard);
}

DocumentController::~DocumentController()
{
    delete d;
}

void DocumentController::setupActions()
{
    KActionCollection * ac =
        Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection();

    KAction *action;

    action = ac->addAction( "file_open" );
    action->setIcon(KIcon("document-open"));
    action->setShortcut( Qt::CTRL + Qt::Key_O );
    action->setText(i18n( "&Open..." ) );
    connect( action, SIGNAL(triggered(bool)), SLOT(chooseDocument()) );
    action->setToolTip( i18n( "Open file" ) );
    action->setWhatsThis( i18n( "Opens a file for editing." ) );

    d->fileOpenRecent = KStandardAction::openRecent(this,
                    SLOT(slotOpenDocument(KUrl)), ac);
    d->fileOpenRecent->setWhatsThis(i18n("This lists files which you have opened recently, and allows you to easily open them again."));
    d->fileOpenRecent->loadEntries( KConfigGroup(KGlobal::config(), "Recent Files" ) );

    action = d->saveAll = ac->addAction( "file_save_all" );
    action->setIcon(KIcon("document-save"));
    action->setText(i18n( "Save Al&l" ) );
    connect( action, SIGNAL(triggered(bool)), SLOT(slotSaveAllDocuments()) );
    action->setToolTip( i18n( "Save all open documents" ) );
    action->setWhatsThis( i18n( "Save all open documents, prompting for additional information when necessary." ) );
    action->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_L) );
    action->setEnabled(false);

    action = d->revertAll = ac->addAction( "file_revert_all" );
    action->setIcon(KIcon("document-revert"));
    action->setText(i18n( "Reload All" ) );
    connect( action, SIGNAL(triggered(bool)), SLOT(reloadAllDocuments()) );
    action->setToolTip( i18n( "Revert all open documents" ) );
    action->setWhatsThis( i18n( "Revert all open documents, returning to the previously saved state." ) );
    action->setEnabled(false);

    action = d->close = ac->addAction( "file_close" );
    action->setIcon(KIcon("window-close"));
    action->setShortcut( Qt::CTRL + Qt::Key_W );
    action->setText( i18n( "&Close" ) );
    connect( action, SIGNAL(triggered(bool)), SLOT(fileClose()) );
    action->setToolTip( i18n( "Close file" ) );
    action->setWhatsThis( i18n( "Closes current file." ) );
    action->setEnabled(false);

    action = d->closeAll = ac->addAction( "file_close_all" );
    action->setIcon(KIcon("window-close"));
    action->setText(i18n( "Clos&e All" ) );
    connect( action, SIGNAL(triggered(bool)), SLOT(closeAllDocuments()) );
    action->setToolTip( i18n( "Close all open documents" ) );
    action->setWhatsThis( i18n( "Close all open documents, prompting for additional information when necessary." ) );
    action->setEnabled(false);

    action = d->closeAllOthers = ac->addAction( "file_closeother" );
    action->setIcon(KIcon("window-close"));
    action->setShortcut( Qt::CTRL + Qt::SHIFT + Qt::Key_W );
    action->setText(i18n( "Close All Ot&hers" ) );
    connect( action, SIGNAL(triggered(bool)), SLOT(closeAllOtherDocuments()) );
    action->setToolTip( i18n( "Close all other documents" ) );
    action->setWhatsThis( i18n( "Close all open documents, with the exception of the currently active document." ) );
    action->setEnabled(false);

    action = ac->addAction( "vcsannotate_current_document" );
    connect( action, SIGNAL(triggered(bool)), SLOT(vcsAnnotateCurrentDocument()) );
    action->setText( i18n( "Show Annotate on current document") );
    action->setIconText( i18n( "Annotate" ) );
    action->setIcon( KIcon("user-properties") );
}

void DocumentController::setEncoding( const QString &encoding )
{
    d->presetEncoding = encoding;
}

QString KDevelop::DocumentController::encoding() const
{
    return d->presetEncoding;
}

void DocumentController::slotOpenDocument(const KUrl &url)
{
    openDocument(url);
}

IDocument* DocumentController::openDocumentFromText( const QString& data )
{
    IDocument* d = openDocument(nextEmptyDocumentUrl());
    Q_ASSERT(d->textDocument());
    d->textDocument()->setText( data );
    return d;
}

bool DocumentController::openDocumentFromTextSimple( QString text )
{
    return (bool)openDocumentFromText( text );
}

bool DocumentController::openDocumentSimple( QString url )
{
    return (bool)openDocument( KUrl(url) );
}

IDocument* DocumentController::openDocument( const KUrl& inputUrl, const QString& prefName )
{
    return d->openDocumentInternal( inputUrl, prefName );
}

IDocument* DocumentController::openDocument( const KUrl & inputUrl,
        const KTextEditor::Range& range,
        DocumentActivationParams activationParams,
        const QString& encoding, IDocument* buddy)
{
    return d->openDocumentInternal( inputUrl, "", range, encoding, activationParams, buddy);
}


bool DocumentController::openDocument(IDocument* doc,
                                      const KTextEditor::Range& range,
                                      DocumentActivationParams activationParams,
                                      IDocument* buddy)
{
    return d->openDocumentInternal( doc, range, activationParams, buddy);
}


void DocumentController::fileClose()
{
    IDocument *activeDoc = activeDocument();
    if (activeDoc)
    {
        UiController *uiController = Core::self()->uiControllerInternal();
        Sublime::View *activeView = uiController->activeSublimeWindow()->activeView();
        
        uiController->activeArea()->closeView(activeView);
    }
}

void DocumentController::closeDocument( const KUrl &url )
{
    if( !d->documents.contains(url) )
        return;

    //this will remove all views and after the last view is removed, the
    //document will be self-destructed and removeDocument() slot will catch that
    //and clean up internal data structures
    d->documents[url]->close();
}

void DocumentController::notifyDocumentClosed(Sublime::Document* doc_)
{
    IDocument* doc = dynamic_cast<IDocument*>(doc_);
    Q_ASSERT(doc);
    
    d->removeDocument(doc_);
    
    if (d->documents.isEmpty()) {
        if (d->saveAll)
            d->saveAll->setEnabled(false);
        if (d->revertAll)
            d->revertAll->setEnabled(false);
        if (d->close)
            d->close->setEnabled(false);
        if (d->closeAll)
            d->closeAll->setEnabled(false);
        if (d->closeAllOthers)
            d->closeAllOthers->setEnabled(false);
    }

    emit documentClosed(doc);
}

IDocument * DocumentController::documentForUrl( const KUrl & dirtyUrl ) const
{
    //Fix urls that might not be absolute
    KUrl url(dirtyUrl);
    url.cleanPath();
    return d->documents.value( url, 0 );
}

QList<IDocument*> DocumentController::openDocuments() const
{
    QList<IDocument*> opened;
    foreach (IDocument *doc, d->documents)
    {
        Sublime::Document *sdoc = dynamic_cast<Sublime::Document*>(doc);
        if( !sdoc )
        {
            continue;
        }
        if (!sdoc->views().isEmpty())
            opened << doc;
    }
    return opened;
}

void DocumentController::activateDocument( IDocument * document, const KTextEditor::Range& range )
{
    // TODO avoid some code in openDocument?
    Q_ASSERT(document);
    openDocument(document->url(), range);
}

void DocumentController::slotSaveAllDocuments()
{
    saveAllDocuments(IDocument::Silent);
}

bool DocumentController::saveAllDocuments(IDocument::DocumentSaveMode mode)
{
    return saveSomeDocuments(openDocuments(), mode);
}

bool KDevelop::DocumentController::saveSomeDocuments(const QList< IDocument * > & list, IDocument::DocumentSaveMode mode)
{
    if (mode & IDocument::Silent) {
        foreach (IDocument* doc, modifiedDocuments(list)) {
            if( !DocumentController::isEmptyDocumentUrl(doc->url()) && !doc->save(mode) )
            {
                if( doc )
                    qWarning() << "!! Could not save document:" << doc->url();
                else
                    qWarning() << "!! Could not save document as its NULL";
            }
            // TODO if (!ret) showErrorDialog() ?
        }

    } else {
        // Ask the user which documents to save
        QList<IDocument*> checkSave = modifiedDocuments(list);

        if (!checkSave.isEmpty()) {
            KSaveSelectDialog dialog(checkSave, qApp->activeWindow());
            if (dialog.exec() == QDialog::Rejected)
                return false;
        }
    }

    return true;
}

QList< IDocument * > KDevelop::DocumentController::visibleDocumentsInWindow(MainWindow * mw) const
{
    // Gather a list of all documents which do have a view in the given main window
    // Does not find documents which are open in inactive areas
    QList<IDocument*> list;
    foreach (IDocument* doc, openDocuments()) {
        if (Sublime::Document* sdoc = dynamic_cast<Sublime::Document*>(doc)) {
            foreach (Sublime::View* view, sdoc->views()) {
                if (view->hasWidget() && view->widget()->window() == mw) {
                    list.append(doc);
                    break;
                }
            }
        }
    }
    return list;
}

QList< IDocument * > KDevelop::DocumentController::documentsExclusivelyInWindow(MainWindow * mw, bool currentAreaOnly) const
{
    // Gather a list of all documents which have views only in the given main window
    QList<IDocument*> checkSave;
    
    foreach (IDocument* doc, openDocuments()) {
        if (Sublime::Document* sdoc = dynamic_cast<Sublime::Document*>(doc)) {
            bool inOtherWindow = false;

            foreach (Sublime::View* view, sdoc->views()) {
                foreach(Sublime::MainWindow* window, Core::self()->uiControllerInternal()->mainWindows())
                    if(window->containsView(view) && (window != mw || (currentAreaOnly && window == mw && !mw->area()->views().contains(view))))
                        inOtherWindow = true;
            }

            if (!inOtherWindow)
                checkSave.append(doc);
        }
    }
    return checkSave;
}

QList< IDocument * > KDevelop::DocumentController::modifiedDocuments(const QList< IDocument * > & list) const
{
    QList< IDocument * > ret;
    foreach (IDocument* doc, list)
        if (doc->state() == IDocument::Modified || doc->state() == IDocument::DirtyAndModified)
            ret.append(doc);
    return ret;
}

bool DocumentController::saveAllDocumentsForWindow(KParts::MainWindow* mw, KDevelop::IDocument::DocumentSaveMode mode, bool currentAreaOnly)
{
    QList<IDocument*> checkSave = documentsExclusivelyInWindow(dynamic_cast<KDevelop::MainWindow*>(mw), currentAreaOnly);

    return saveSomeDocuments(checkSave, mode);
}

void DocumentController::reloadAllDocuments()
{
    if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) {
        QList<IDocument*> views = visibleDocumentsInWindow(dynamic_cast<KDevelop::MainWindow*>(mw));

        if (!saveSomeDocuments(views, IDocument::Default))
            // User cancelled or other error
            return;

        foreach (IDocument* doc, views)
            if(!isEmptyDocumentUrl(doc->url()))
                doc->reload();
    }
}

void DocumentController::closeAllDocuments()
{
    if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) {
        QList<IDocument*> views = visibleDocumentsInWindow(dynamic_cast<KDevelop::MainWindow*>(mw));

        if (!saveSomeDocuments(views, IDocument::Default))
            // User cancelled or other error
            return;

        foreach (IDocument* doc, views)
            doc->close(IDocument::Discard);
    }
}

void DocumentController::closeAllOtherDocuments()
{
    if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) {
        Sublime::View* activeView = mw->activeView();

        if (!activeView) {
            kWarning() << "Shouldn't there always be an active view when this function is called?";
            return;
        }

        // Deal with saving unsaved solo views
        QList<IDocument*> soloViews = documentsExclusivelyInWindow(dynamic_cast<KDevelop::MainWindow*>(mw));
        soloViews.removeAll(dynamic_cast<IDocument*>(activeView->document()));

        if (!saveSomeDocuments(soloViews, IDocument::Default))
            // User cancelled or other error
            return;

        foreach (Sublime::View* view, mw->area()->views()) {
            if (view != activeView)
                mw->area()->closeView(view);
        }
        activeView->widget()->setFocus();
    }
}

IDocument* DocumentController::activeDocument() const
{
    UiController *uiController = Core::self()->uiControllerInternal();
    if( !uiController->activeSublimeWindow() || !uiController->activeSublimeWindow()->activeView() ) return 0;
    return dynamic_cast<IDocument*>(uiController->activeSublimeWindow()->activeView()->document());
}

QString DocumentController::activeDocumentPath( QString target ) const
{
    if(target.size()) {
        foreach(IProject* project, Core::self()->projectController()->projects()) {
            if(project->name().startsWith(target, Qt::CaseInsensitive)) {
                return project->folder().pathOrUrl() + "/.";
            }
        }
    }
    IDocument* doc = activeDocument();
    if(!doc || target == "[selection]")
    {
        Context* selection = ICore::self()->selectionController()->currentSelection();
        if(selection && selection->type() == Context::ProjectItemContext && static_cast<ProjectItemContext*>(selection)->items().size())
        {
            QString ret = static_cast<ProjectItemContext*>(selection)->items()[0]->path().pathOrUrl();
            if(static_cast<ProjectItemContext*>(selection)->items()[0]->folder())
                ret += "/.";
            return  ret;
        }
        return QString();
    }
    return doc->url().pathOrUrl();
}

QStringList DocumentController::activeDocumentPaths() const
{
    UiController *uiController = Core::self()->uiControllerInternal();
    if( !uiController->activeSublimeWindow() ) return QStringList();
    
    QSet<QString> documents;
    foreach(Sublime::View* view, uiController->activeSublimeWindow()->area()->views())
        documents.insert(KUrl(view->document()->documentSpecifier()).pathOrUrl());
    
    return documents.toList();
}

void DocumentController::registerDocumentForMimetype( const QString& mimetype,
                                        KDevelop::IDocumentFactory* factory )
{
    if( !d->factories.contains( mimetype ) )
        d->factories[mimetype] = factory;
}

QStringList DocumentController::documentTypes() const
{
    return QStringList() << "Text";
}

bool DocumentController::isEmptyDocumentUrl(const KUrl &url)
{
    QRegExp r(QString("^%1(\\s\\(\\d+\\))?$").arg(EMPTY_DOCUMENT_URL));
    return r.indexIn(url.prettyUrl()) != -1;
}

KUrl DocumentController::nextEmptyDocumentUrl()
{
    int nextEmptyDocNumber = 0;
    foreach (IDocument *doc, Core::self()->documentControllerInternal()->openDocuments())
    {
        if (DocumentController::isEmptyDocumentUrl(doc->url()))
        {
            QRegExp r(QString("^%1\\s\\((\\d+)\\)$").arg(EMPTY_DOCUMENT_URL));
            if (r.indexIn(doc->url().prettyUrl()) != -1)
                nextEmptyDocNumber = qMax(nextEmptyDocNumber, r.cap(1).toInt()+1);
            else
                nextEmptyDocNumber = qMax(nextEmptyDocNumber, 1);
        }
    }
    
    KUrl url;
    if (nextEmptyDocNumber > 0)
        url = KUrl(QString("%1 (%2)").arg(EMPTY_DOCUMENT_URL).arg(nextEmptyDocNumber));
    else
        url = KUrl(EMPTY_DOCUMENT_URL);
    return url;
}

IDocumentFactory* DocumentController::factory(const QString& mime) const
{
    return d->factories.value(mime);
}

KTextEditor::Document* DocumentController::globalTextEditorInstance()
{
    if(!d->globalTextEditorInstance)
        d->globalTextEditorInstance = Core::self()->partControllerInternal()->createTextPart();
    return d->globalTextEditorInstance;
}

bool DocumentController::openDocumentsSimple( QStringList urls )
{
    Sublime::Area* area = Core::self()->uiControllerInternal()->activeArea();
    Sublime::AreaIndex* areaIndex = area->rootIndex();

    QList<Sublime::View*> topViews = static_cast<Sublime::MainWindow*>(Core::self()->uiControllerInternal()->activeMainWindow())->getTopViews();
    
    if(Sublime::View* activeView = Core::self()->uiControllerInternal()->activeSublimeWindow()->activeView())
        areaIndex = area->indexOf(activeView);

    kDebug() << "opening " << urls << " to area " << area << " index " << areaIndex << " with children " << areaIndex->first() << " " << areaIndex->second();
    
    bool isFirstView = true;
    
    bool ret = openDocumentsWithSplitSeparators( areaIndex, urls, isFirstView );
    
    kDebug() << "area arch. after opening: " << areaIndex->print();
    
    // Required because sublime sometimes doesn't update correctly when the area-index contents has been changed
    // (especially when views have been moved to other indices, through unsplit, split, etc.)
    static_cast<Sublime::MainWindow*>(Core::self()->uiControllerInternal()->activeMainWindow())->reconstructViews(topViews);
    
    return ret;
}

bool DocumentController::openDocumentsWithSplitSeparators( Sublime::AreaIndex* index, QStringList urlsWithSeparators, bool& isFirstView )
{
    kDebug() << "opening " << urlsWithSeparators << " index " << index << " with children " << index->first() << " " << index->second() << " view-count " << index->viewCount();
    if(urlsWithSeparators.isEmpty())
        return true;
    
    Sublime::Area* area = Core::self()->uiControllerInternal()->activeArea();
    
    QList<int> topLevelSeparators; // Indices of the top-level separators (with groups skipped)
    QStringList separators = QStringList() << "/" << "-";
    QList<QStringList> groups;
    
    bool ret = true;
    
    {
        int parenDepth = 0;
        int groupStart = 0;
        for(int pos = 0; pos < urlsWithSeparators.size(); ++pos)
        {
            QString item = urlsWithSeparators[pos];
            if(separators.contains(item))
            {
                if(parenDepth == 0)
                    topLevelSeparators << pos;
            }else if(item == "[")
            {
                if(parenDepth == 0)
                    groupStart = pos+1;
                ++parenDepth;
            }
            else if(item == "]")
            {
                if(parenDepth > 0)
                {
                    --parenDepth;
                    
                    if(parenDepth == 0)
                        groups << urlsWithSeparators.mid(groupStart, pos-groupStart);
                }
                else{
                    kDebug() << "syntax error in " << urlsWithSeparators << ": parens do not match";
                    ret = false;
                }
            }else if(parenDepth == 0)
            {
                groups << (QStringList() << item);
            }
        }
    }
    
    if(topLevelSeparators.isEmpty())
    {
        if(urlsWithSeparators.size() > 1)
        {
            foreach(QStringList group, groups)
                ret &= openDocumentsWithSplitSeparators( index, group, isFirstView );
        }else{
            while(index->isSplit())
                index = index->first();
            // Simply open the document into the area index
            IDocument* doc = Core::self()->documentControllerInternal()->openDocument(KUrl(urlsWithSeparators.front()),
                        KTextEditor::Cursor::invalid(),
                        (IDocumentController::DocumentActivation) ( IDocumentController::DoNotActivate | IDocumentController::DoNotCreateView) );
            Sublime::Document *sublimeDoc = dynamic_cast<Sublime::Document*>(doc);
            if (sublimeDoc) {
                Sublime::View* view = sublimeDoc->createView();
                area->addView(view, index);
                if(isFirstView)
                {
                    static_cast<Sublime::MainWindow*>(Core::self()->uiControllerInternal()->activeMainWindow())->activateView(view);
                    isFirstView = false;
                }
            }else{
                ret = false;
            }
        }
        return ret;
    }
    
    // Pick a separator in the middle
    
    int pickSeparator = topLevelSeparators[topLevelSeparators.size()/2];
    
    bool activeViewToSecondChild = false;
    if(pickSeparator == urlsWithSeparators.size()-1)
    {
        // There is no right child group, so the right side should be filled with the currently active views
        activeViewToSecondChild = true;
    }else{
        QStringList separatorsAndParens = separators;
        separatorsAndParens << "[" << "]";
        // Check if the second child-set contains an unterminated separator, which means that the active views should end up there
        for(int pos = pickSeparator+1; pos < urlsWithSeparators.size(); ++pos)
            if( separators.contains(urlsWithSeparators[pos]) && (pos == urlsWithSeparators.size()-1 ||
                separatorsAndParens.contains(urlsWithSeparators[pos-1]) ||
                separatorsAndParens.contains(urlsWithSeparators[pos-1])) )
                    activeViewToSecondChild = true;
    }
    
    Qt::Orientation orientation = urlsWithSeparators[pickSeparator] == "/" ? Qt::Horizontal : Qt::Vertical;
    
    if(!index->isSplit())
    {
        kDebug() << "splitting " << index << "orientation" << orientation << "to second" << activeViewToSecondChild;
        index->split(orientation, activeViewToSecondChild);
    }else{
        index->setOrientation(orientation);
        kDebug() << "WARNING: Area is already split (shouldn't be)" << urlsWithSeparators;
    }
    
    openDocumentsWithSplitSeparators( index->first(), urlsWithSeparators.mid(0, pickSeparator) , isFirstView );
    if(pickSeparator != urlsWithSeparators.size() - 1)
        openDocumentsWithSplitSeparators( index->second(), urlsWithSeparators.mid(pickSeparator+1, urlsWithSeparators.size() - (pickSeparator+1) ), isFirstView );
    
    // Clean up the child-indices, because document-loading may fail
    
    if(!index->first()->viewCount() && !index->first()->isSplit())
    {
        kDebug() << "unsplitting first";
        index->unsplit(index->first());
    }
    else if(!index->second()->viewCount() && !index->second()->isSplit())
    {
        kDebug() << "unsplitting second";
        index->unsplit(index->second());
    }

    return ret;
}

void DocumentController::vcsAnnotateCurrentDocument()
{
    IDocument* doc = activeDocument();
    KUrl url = doc->url();
    IProject* project = KDevelop::ICore::self()->projectController()->findProjectForUrl(url);
    if(project && project->versionControlPlugin()) {
        IBasicVersionControl* iface = 0;
        iface = project->versionControlPlugin()->extension<IBasicVersionControl>();
        auto helper = new VcsPluginHelper(project->versionControlPlugin(), iface);
        connect(doc->textDocument(), SIGNAL(aboutToClose(KTextEditor::Document*)),
                helper, SLOT(disposeEventually(KTextEditor::Document*)));
        connect(doc->textDocument()->activeView(), SIGNAL(annotationBorderVisibilityChanged(View*,bool)),
                helper, SLOT(disposeEventually(View*, bool)));
        helper->addContextDocument(url);
        helper->annotation();
    }
    else {
        KMessageBox::error(0, i18n("Could not annotate the document because it is not "
                                   "part of a version-controlled project."));
    }
}

}

#include "documentcontroller.moc"
