/*
 * This file is part of KMail.
 * Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
 *
 * Parts based on KMail code by:
 * Various authors.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU 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 "attachmentcontrollerbase.h"

#include "messagecomposer/attachment/attachmentmodel.h"
#include "messagecomposer/job/attachmentjob.h"
#include "messagecomposer/job/attachmentfrompublickeyjob.h"
#include "messagecomposer/job/attachmentvcardfromaddressbookjob.h"
#include "messagecomposer/composer/composer.h"

#include "messagecomposer/part/globalpart.h"
#include "messageviewer/viewer/editorwatcher.h"
#include "messageviewer/viewer/nodehelper.h"
#include "messageviewer/utils/util.h"

#include <akonadi/itemfetchjob.h>
#include <kio/jobuidelegate.h>

#include <QMenu>
#include <QPointer>
#include <QTreeView>

#include <KAction>
#include <KActionCollection>
#include <KDebug>
#include <KEncodingFileDialog>
#include <KFileDialog>
#include <KLocalizedString>
#include <KMessageBox>
#include <KMimeTypeTrader>
#include <KPushButton>
#include <KRun>
#include <KTemporaryFile>
#include <KFileItemActions>
#include <KActionMenu>

#include <kpimutils/kfileio.h>

#include <libkleo/kleo/cryptobackendfactory.h>
#include <libkleo/ui/keyselectiondialog.h>

#include <messagecore/attachment/attachmentcompressjob.h>
#include <messagecore/attachment/attachmentfromfolderjob.h>
#include <messagecore/attachment/attachmentfrommimecontentjob.h>
#include <messagecore/attachment/attachmentfromurljob.h>
#include <messagecore/attachment/attachmentpropertiesdialog.h>
#include <messagecore/attachment/attachmentupdatejob.h>
#include <messagecore/attachment/attachmentfromurlutils.h>
#include <settings/messagecomposersettings.h>
#include <KIO/Job>
#include <Akonadi/Contact/EmailAddressSelectionDialog>
#include <Akonadi/Contact/EmailAddressSelectionWidget>

#include <KMime/Content>

using namespace MessageComposer;
using namespace MessageCore;

class MessageComposer::AttachmentControllerBase::Private
{
public:
    Private( AttachmentControllerBase *qq );
    ~Private();

    void attachmentRemoved( AttachmentPart::Ptr part ); // slot
    void compressJobResult( KJob *job ); // slot
    void loadJobResult( KJob *job ); // slot
    void openSelectedAttachments(); // slot
    void viewSelectedAttachments(); // slot
    void editSelectedAttachment(); // slot
    void editSelectedAttachmentWith(); // slot
    void removeSelectedAttachments(); // slot
    void saveSelectedAttachmentAs(); // slot
    void selectedAttachmentProperties(); // slot
    void editDone( MessageViewer::EditorWatcher *watcher ); // slot
    void attachPublicKeyJobResult( KJob *job ); // slot
    void slotAttachmentContentCreated( KJob *job ); // slot
    void attachVcardFromAddressBook( KJob *job ); //slot
    void addAttachmentPart( AttachmentPart::Ptr part );
    void selectedAllAttachment();
    void createOpenWithMenu( QMenu *topMenu, AttachmentPart::Ptr part );
    void reloadAttachment();
    void updateJobResult(KJob*);

    AttachmentControllerBase *const q;
    bool encryptEnabled;
    bool signEnabled;
    MessageComposer::AttachmentModel *model;
    QWidget *wParent;
    QHash<MessageViewer::EditorWatcher*,AttachmentPart::Ptr> editorPart;
    QHash<MessageViewer::EditorWatcher*,KTemporaryFile*> editorTempFile;

    AttachmentPart::List selectedParts;
    KActionCollection *mActionCollection;
    QAction *attachPublicKeyAction;
    QAction *attachMyPublicKeyAction;
    QAction *openContextAction;
    QAction *viewContextAction;
    QAction *editContextAction;
    QAction *editWithContextAction;
    QAction *removeAction;
    QAction *removeContextAction;
    QAction *saveAsAction;
    QAction *saveAsContextAction;
    QAction *propertiesAction;
    QAction *propertiesContextAction;
    QAction *addAttachmentFileAction;
    QAction *addAttachmentDirectoryAction;
    QAction *addContextAction;
    QAction *selectAllAction;
    KActionMenu *attachmentMenu;
    QAction *addOwnVcardAction;
    QAction *reloadAttachmentAction;
    QAction *attachVCardsAction;

    // If part p is compressed, uncompressedParts[p] is the uncompressed part.
    QHash<AttachmentPart::Ptr, AttachmentPart::Ptr> uncompressedParts;
};

AttachmentControllerBase::Private::Private( AttachmentControllerBase *qq )
    : q( qq )
    , encryptEnabled( false )
    , signEnabled( false )
    , model( 0 )
    , wParent( 0 )
    , attachPublicKeyAction( 0 )
    , attachMyPublicKeyAction( 0 )
    , openContextAction( 0 )
    , viewContextAction( 0 )
    , editContextAction( 0 )
    , editWithContextAction( 0 )
    , removeAction( 0 )
    , removeContextAction( 0 )
    , saveAsAction( 0 )
    , saveAsContextAction( 0 )
    , propertiesAction( 0 )
    , propertiesContextAction( 0 )
    , addAttachmentFileAction( 0 )
    , addAttachmentDirectoryAction( 0 )
    , addContextAction( 0 )
    , selectAllAction( 0 )
    , attachmentMenu( 0 )
    , addOwnVcardAction( 0 )
    , attachVCardsAction( 0 )
{
}

AttachmentControllerBase::Private::~Private()
{
}

void AttachmentControllerBase::setSelectedParts( const AttachmentPart::List &selectedParts)
{
    d->selectedParts = selectedParts;
    const int selectedCount = selectedParts.count();
    const bool enableEditAction = (selectedCount == 1) &&
            ( !selectedParts.first()->isMessageOrMessageCollection() );

    d->openContextAction->setEnabled( selectedCount > 0 );
    d->viewContextAction->setEnabled( selectedCount > 0 );
    d->editContextAction->setEnabled( enableEditAction );
    d->editWithContextAction->setEnabled( enableEditAction );
    d->removeAction->setEnabled( selectedCount > 0 );
    d->removeContextAction->setEnabled( selectedCount > 0 );
    d->saveAsAction->setEnabled( selectedCount == 1 );
    d->saveAsContextAction->setEnabled( selectedCount == 1 );
    d->propertiesAction->setEnabled( selectedCount == 1 );
    d->propertiesContextAction->setEnabled( selectedCount == 1 );
}

void AttachmentControllerBase::Private::attachmentRemoved( AttachmentPart::Ptr part )
{
    if( uncompressedParts.contains( part ) ) {
        uncompressedParts.remove( part );
    }
}

void AttachmentControllerBase::Private::compressJobResult( KJob *job )
{
    if( job->error() ) {
        KMessageBox::sorry( wParent, job->errorString(), i18n( "Failed to compress attachment" ) );
        return;
    }

    Q_ASSERT( dynamic_cast<AttachmentCompressJob*>( job ) );
    AttachmentCompressJob *ajob = static_cast<AttachmentCompressJob*>( job );
    //AttachmentPart *originalPart = const_cast<AttachmentPart*>( ajob->originalPart() );
    AttachmentPart::Ptr originalPart = ajob->originalPart();
    AttachmentPart::Ptr compressedPart = ajob->compressedPart();

    if( ajob->isCompressedPartLarger() ) {
        const int result = KMessageBox::questionYesNo( wParent,
                                                       i18n( "The compressed attachment is larger than the original. "
                                                             "Do you want to keep the original one?" ),
                                                       QString( /*caption*/ ),
                                                       KGuiItem( i18nc( "Do not compress", "Keep" ) ),
                                                       KGuiItem( i18n( "Compress" ) ) );
        if( result == KMessageBox::Yes ) {
            // The user has chosen to keep the uncompressed file.
            return;
        }
    }

    kDebug() << "Replacing uncompressed part in model.";
    uncompressedParts[ compressedPart ] = originalPart;
    bool ok = model->replaceAttachment( originalPart, compressedPart );
    if( !ok ) {
        // The attachment was removed from the model while we were compressing.
        kDebug() << "Compressed a zombie.";
    }
}

void AttachmentControllerBase::Private::loadJobResult( KJob *job )
{
    if( job->error() ) {
        KMessageBox::sorry( wParent, job->errorString(), i18n( "Failed to attach file" ) );
        return;
    }

    Q_ASSERT( dynamic_cast<AttachmentLoadJob*>( job ) );
    AttachmentLoadJob *ajob = static_cast<AttachmentLoadJob*>( job );
    AttachmentPart::Ptr part = ajob->attachmentPart();
    q->addAttachment( part );
}

void AttachmentControllerBase::Private::openSelectedAttachments()
{
    Q_ASSERT( selectedParts.count() >= 1 );
    foreach( AttachmentPart::Ptr part, selectedParts ) {
        q->openAttachment( part );
    }
}

void AttachmentControllerBase::Private::viewSelectedAttachments()
{
    Q_ASSERT( selectedParts.count() >= 1 );
    foreach( AttachmentPart::Ptr part, selectedParts ) {
        q->viewAttachment( part );
    }
}

void AttachmentControllerBase::Private::editSelectedAttachment()
{
    Q_ASSERT( selectedParts.count() == 1 );
    q->editAttachment( selectedParts.first(), MessageViewer::EditorWatcher::NoOpenWithDialog );
}

void AttachmentControllerBase::Private::editSelectedAttachmentWith()
{
    Q_ASSERT( selectedParts.count() == 1 );
    q->editAttachment( selectedParts.first(), MessageViewer::EditorWatcher::OpenWithDialog );
}

void AttachmentControllerBase::Private::removeSelectedAttachments()
{
    Q_ASSERT( selectedParts.count() >= 1 );
    foreach( AttachmentPart::Ptr part, selectedParts ) {
        model->removeAttachment( part );
    }
}

void AttachmentControllerBase::Private::saveSelectedAttachmentAs()
{
    Q_ASSERT( selectedParts.count() == 1 );
    q->saveAttachmentAs( selectedParts.first() );
}

void AttachmentControllerBase::Private::selectedAttachmentProperties()
{
    Q_ASSERT( selectedParts.count() == 1 );
    q->attachmentProperties( selectedParts.first() );
}

void AttachmentControllerBase::Private::reloadAttachment()
{
    Q_ASSERT( selectedParts.count() == 1 );
    AttachmentUpdateJob *ajob = new AttachmentUpdateJob(selectedParts.first(), q);
    connect( ajob, SIGNAL(result(KJob*)), q, SLOT(updateJobResult(KJob*)) );
    ajob->start();
}

void AttachmentControllerBase::Private::updateJobResult(KJob *job)
{
    if( job->error() ) {
        KMessageBox::sorry( wParent, job->errorString(), i18n( "Failed to reload attachment" ) );
        return;
    }
    Q_ASSERT( dynamic_cast<AttachmentUpdateJob*>( job ) );
    AttachmentUpdateJob *ajob = static_cast<AttachmentUpdateJob*>( job );
    AttachmentPart::Ptr originalPart = ajob->originalPart();
    AttachmentPart::Ptr updatedPart = ajob->updatedPart();

    attachmentRemoved( originalPart );
    bool ok = model->replaceAttachment( originalPart, updatedPart );
    if( !ok ) {
        // The attachment was removed from the model while we were compressing.
        kDebug() << "Updated a zombie.";
    }
}

void AttachmentControllerBase::Private::editDone( MessageViewer::EditorWatcher *watcher )
{
    AttachmentPart::Ptr part = editorPart.take( watcher );
    Q_ASSERT( part );
    KTemporaryFile *tempFile = editorTempFile.take( watcher );
    Q_ASSERT( tempFile );
    if( watcher->fileChanged() ) {
        kDebug() << "File has changed.";
        const QString name = watcher->url().path();
        QFile file( name );
        if ( file.open( QIODevice::ReadOnly ) ) {
            const QByteArray data = file.readAll();
            part->setData( data );
            model->updateAttachment( part );
        }
    }

    delete tempFile;
    // The watcher deletes itself.
}


void AttachmentControllerBase::Private::createOpenWithMenu( QMenu *topMenu, AttachmentPart::Ptr part )
{
    const QString contentTypeStr = QString::fromLatin1(part->mimeType());
    const KService::List offers = KFileItemActions::associatedApplications(QStringList()<<contentTypeStr, QString() );
    if (!offers.isEmpty()) {
        QMenu* menu = topMenu;
        QActionGroup *actionGroup = new QActionGroup( menu );
        connect( actionGroup, SIGNAL(triggered(QAction*)), q, SLOT(slotOpenWithAction(QAction*)) );

        if (offers.count() > 1) { // submenu 'open with'
            menu = new QMenu(i18nc("@title:menu", "&Open With"), topMenu);
            menu->menuAction()->setObjectName(QLatin1String("openWith_submenu")); // for the unittest
            topMenu->addMenu(menu);
        }
        //kDebug() << offers.count() << "offers" << topMenu << menu;

        KService::List::ConstIterator it = offers.constBegin();
        KService::List::ConstIterator end = offers.constEnd();
        for(; it != end; ++it) {
            KAction* act = MessageViewer::Util::createAppAction(*it,
                                                                // no submenu -> prefix single offer
                                                                menu == topMenu, actionGroup,menu);
            menu->addAction(act);
        }

        QString openWithActionName;
        if (menu != topMenu) { // submenu
            menu->addSeparator();
            openWithActionName = i18nc("@action:inmenu Open With", "&Other...");
        } else {
            openWithActionName = i18nc("@title:menu", "&Open With...");
        }
        KAction *openWithAct = new KAction(menu);
        openWithAct->setText(openWithActionName);
        QObject::connect(openWithAct, SIGNAL(triggered()), q, SLOT(slotOpenWithDialog()));
        menu->addAction(openWithAct);
    }
    else { // no app offers -> Open With...
        KAction *act = new KAction(topMenu);
        act->setText(i18nc("@title:menu", "&Open With..."));
        QObject::connect(act, SIGNAL(triggered()), q, SLOT(slotOpenWithDialog()));
        topMenu->addAction(act);
    }
}



void AttachmentControllerBase::exportPublicKey( const QString &fingerprint )
{
    if( fingerprint.isEmpty() || !Kleo::CryptoBackendFactory::instance()->openpgp() ) {
        kWarning() << "Tried to export key with empty fingerprint, or no OpenPGP.";
        Q_ASSERT( false ); // Can this happen?
        return;
    }

    MessageComposer::AttachmentFromPublicKeyJob *ajob = new MessageComposer::AttachmentFromPublicKeyJob( fingerprint, this );
    connect( ajob, SIGNAL(result(KJob*)), this, SLOT(attachPublicKeyJobResult(KJob*)) );
    ajob->start();
}

void AttachmentControllerBase::Private::attachPublicKeyJobResult( KJob *job )
{
    // The only reason we can't use loadJobResult() and need a separate method
    // is that we want to show the proper caption ("public key" instead of "file")...

    if( job->error() ) {
        KMessageBox::sorry( wParent, job->errorString(), i18n( "Failed to attach public key" ) );
        return;
    }

    Q_ASSERT( dynamic_cast<MessageComposer::AttachmentFromPublicKeyJob*>( job ) );
    MessageComposer::AttachmentFromPublicKeyJob *ajob = static_cast<MessageComposer::AttachmentFromPublicKeyJob*>( job );
    AttachmentPart::Ptr part = ajob->attachmentPart();
    q->addAttachment( part );
}

void AttachmentControllerBase::Private::attachVcardFromAddressBook( KJob *job )
{
    if( job->error() ) {
        qDebug()<<" Error during when get vcard";
        return;
    }

    MessageComposer::AttachmentVcardFromAddressBookJob *ajob = static_cast<MessageComposer::AttachmentVcardFromAddressBookJob*>( job );
    AttachmentPart::Ptr part = ajob->attachmentPart();
    q->addAttachment( part );
}


static KTemporaryFile *dumpAttachmentToTempFile( const AttachmentPart::Ptr part ) // local
{
    KTemporaryFile *file = new KTemporaryFile;
    if( !file->open() ) {
        kError() << "Could not open tempfile" << file->fileName();
        delete file;
        return 0;
    }
    if( file->write( part->data() ) == -1 ) {
        kError() << "Could not dump attachment to tempfile.";
        delete file;
        return 0;
    }
    file->flush();
    return file;
}



AttachmentControllerBase::AttachmentControllerBase( MessageComposer::AttachmentModel *model, QWidget *wParent, KActionCollection *actionCollection )
    : QObject( wParent )
    , d( new Private( this ) )
{
    d->model = model;
    connect( model, SIGNAL(attachUrlsRequested(KUrl::List)), this, SLOT(addAttachments(KUrl::List)) );
    connect( model, SIGNAL(attachmentRemoved(MessageCore::AttachmentPart::Ptr)),
             this, SLOT(attachmentRemoved(MessageCore::AttachmentPart::Ptr)) );
    connect( model, SIGNAL(attachmentCompressRequested(MessageCore::AttachmentPart::Ptr,bool)),
             this, SLOT(compressAttachment(MessageCore::AttachmentPart::Ptr,bool)) );
    connect( model, SIGNAL(encryptEnabled(bool)), this, SLOT(setEncryptEnabled(bool)) );
    connect( model, SIGNAL(signEnabled(bool)), this, SLOT(setSignEnabled(bool)) );

    d->wParent = wParent;
    d->mActionCollection = actionCollection;
}

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

void AttachmentControllerBase::createActions()
{
    // Create the actions.
    d->attachPublicKeyAction = new KAction( i18n( "Attach &Public Key..." ), this );
    connect( d->attachPublicKeyAction, SIGNAL(triggered(bool)),
             this, SLOT(showAttachPublicKeyDialog()) );

    d->attachMyPublicKeyAction = new KAction( i18n( "Attach &My Public Key" ), this );
    connect( d->attachMyPublicKeyAction, SIGNAL(triggered(bool)), this, SLOT(attachMyPublicKey()) );

    d->attachmentMenu = new KActionMenu( KIcon( QLatin1String( "mail-attachment" ) ), i18n( "Attach" ), this );
    connect( d->attachmentMenu, SIGNAL(triggered(bool)), this, SLOT(showAddAttachmentFileDialog()) );

    d->attachmentMenu->setDelayed(true);

    d->addAttachmentFileAction = new KAction( KIcon( QLatin1String( "mail-attachment" ) ), i18n( "&Attach File..." ), this );
    d->addAttachmentFileAction->setIconText( i18n( "Attach" ) );
    d->addContextAction = new KAction( KIcon( QLatin1String( "mail-attachment" ) ),
                                       i18n( "Add Attachment..." ), this );
    connect( d->addAttachmentFileAction, SIGNAL(triggered(bool)), this, SLOT(showAddAttachmentFileDialog()) );
    connect( d->addContextAction, SIGNAL(triggered(bool)), this, SLOT(showAddAttachmentFileDialog()) );

    d->addAttachmentDirectoryAction = new KAction( KIcon( QLatin1String( "mail-attachment" ) ), i18n( "&Attach Directory..." ), this );
    d->addAttachmentDirectoryAction->setIconText( i18n( "Attach" ) );
    connect( d->addAttachmentDirectoryAction, SIGNAL(triggered(bool)), this, SLOT(showAddAttachmentCompressedDirectoryDialog()) );


    d->addOwnVcardAction = new KAction( i18n("Attach Own vCard"),this );
    d->addOwnVcardAction->setIconText( i18n( "Own vCard" ) );
    d->addOwnVcardAction->setCheckable(true);
    connect(d->addOwnVcardAction, SIGNAL(triggered(bool)), this, SIGNAL(addOwnVcard(bool)));

    d->attachVCardsAction = new KAction( KIcon( QLatin1String( "mail-attachment" ) ), i18n( "&Attach vCards..." ), this );
    d->attachVCardsAction->setIconText( i18n( "Attach" ) );
    connect( d->attachVCardsAction, SIGNAL(triggered(bool)), this, SLOT(showAttachVcard()) );


    d->attachmentMenu->addAction(d->addAttachmentFileAction);
    d->attachmentMenu->addAction(d->addAttachmentDirectoryAction);
    d->attachmentMenu->addSeparator();
    d->attachmentMenu->addAction(d->addOwnVcardAction);
    d->attachmentMenu->addSeparator();
    d->attachmentMenu->addAction(d->attachVCardsAction);

    d->removeAction = new KAction( KIcon(QLatin1String("edit-delete")), i18n( "&Remove Attachment" ), this );
    d->removeContextAction = new KAction( KIcon(QLatin1String("edit-delete")), i18n( "Remove" ), this ); // FIXME need two texts. is there a better way?
    connect( d->removeAction, SIGNAL(triggered(bool)), this, SLOT(removeSelectedAttachments()) );
    connect( d->removeContextAction, SIGNAL(triggered(bool)), this, SLOT(removeSelectedAttachments()) );

    d->openContextAction = new KAction( i18nc( "to open", "Open" ), this );
    connect( d->openContextAction, SIGNAL(triggered(bool)), this, SLOT(openSelectedAttachments()) );

    d->viewContextAction = new KAction( i18nc( "to view", "View" ), this );
    connect( d->viewContextAction, SIGNAL(triggered(bool)), this, SLOT(viewSelectedAttachments()) );

    d->editContextAction = new KAction( i18nc( "to edit", "Edit" ), this );
    connect( d->editContextAction, SIGNAL(triggered(bool)), this, SLOT(editSelectedAttachment()) );

    d->editWithContextAction = new KAction( i18n( "Edit With..." ), this );
    connect( d->editWithContextAction, SIGNAL(triggered(bool)), this, SLOT(editSelectedAttachmentWith()) );

    d->saveAsAction = new KAction( KIcon( QLatin1String( "document-save-as" ) ),
                                   i18n( "&Save Attachment As..." ), this );
    d->saveAsContextAction = new KAction( KIcon( QLatin1String( "document-save-as" ) ),
                                          i18n( "Save As..." ), this );
    connect( d->saveAsAction, SIGNAL(triggered(bool)),
             this, SLOT(saveSelectedAttachmentAs()) );
    connect( d->saveAsContextAction, SIGNAL(triggered(bool)),
             this, SLOT(saveSelectedAttachmentAs()) );

    d->propertiesAction = new KAction( i18n( "Attachment Pr&operties..." ), this );
    d->propertiesContextAction = new KAction( i18n( "Properties" ), this );
    connect( d->propertiesAction, SIGNAL(triggered(bool)),
             this, SLOT(selectedAttachmentProperties()) );
    connect( d->propertiesContextAction, SIGNAL(triggered(bool)),
             this, SLOT(selectedAttachmentProperties()) );

    d->selectAllAction = new KAction( i18n("Select All"), this);
    connect( d->selectAllAction, SIGNAL(triggered(bool)),
             this, SIGNAL(selectedAllAttachment()) );

    d->reloadAttachmentAction = new KAction( KIcon(QLatin1String("view-refresh")), i18n("Reload"), this);
    connect( d->reloadAttachmentAction, SIGNAL(triggered(bool)),
             this, SLOT(reloadAttachment()) );


    // Insert the actions into the composer window's menu.
    KActionCollection *collection = d->mActionCollection;
    collection->addAction( QLatin1String( "attach_public_key" ), d->attachPublicKeyAction );
    collection->addAction( QLatin1String( "attach_my_public_key" ), d->attachMyPublicKeyAction );
    collection->addAction( QLatin1String( "attach" ), d->addAttachmentFileAction );
    collection->addAction( QLatin1String( "attach_directory" ), d->addAttachmentDirectoryAction );
    collection->addAction( QLatin1String( "remove" ), d->removeAction );
    collection->addAction( QLatin1String( "attach_save" ), d->saveAsAction );
    collection->addAction( QLatin1String( "attach_properties" ), d->propertiesAction );
    collection->addAction( QLatin1String( "select_all_attachment"), d->selectAllAction);
    collection->addAction( QLatin1String( "attach_menu"), d->attachmentMenu );
    collection->addAction( QLatin1String( "attach_own_vcard"), d->addOwnVcardAction );
    collection->addAction( QLatin1String( "attach_vcards"), d->attachVCardsAction );

    setSelectedParts( AttachmentPart::List());
    emit actionsCreated();
}

void AttachmentControllerBase::setEncryptEnabled( bool enabled )
{
    d->encryptEnabled = enabled;
}

void AttachmentControllerBase::setSignEnabled( bool enabled )
{
    d->signEnabled = enabled;
}

void AttachmentControllerBase::compressAttachment( AttachmentPart::Ptr part, bool compress )
{
    if( compress ) {
        kDebug() << "Compressing part.";

        AttachmentCompressJob *ajob = new AttachmentCompressJob( part, this );
        connect( ajob, SIGNAL(result(KJob*)), this, SLOT(compressJobResult(KJob*)) );
        ajob->start();
    } else {
        kDebug() << "Uncompressing part.";

        // Replace the compressed part with the original uncompressed part, and delete
        // the compressed part.
        AttachmentPart::Ptr originalPart = d->uncompressedParts.take( part );
        Q_ASSERT( originalPart ); // Found in uncompressedParts.
        bool ok = d->model->replaceAttachment( part, originalPart );
        Q_ASSERT( ok );
        Q_UNUSED( ok );
    }
}

void AttachmentControllerBase::showContextMenu()
{
    emit refreshSelection();


    const int numberOfParts(d->selectedParts.count());
    QMenu *menu = new QMenu;

    const bool enableEditAction = (numberOfParts == 1) &&
            ( !d->selectedParts.first()->isMessageOrMessageCollection() );

    if(numberOfParts>0) {
        if (numberOfParts == 1) {
            const QString mimetype = QString::fromLatin1(d->selectedParts.first()->mimeType());
            const QStringList parentMimeType = KMimeType::mimeType( mimetype )->allParentMimeTypes();
            if ((mimetype == QLatin1String("text/plain")) ||
                    (mimetype == QLatin1String("image/png")) ||
                    (mimetype == QLatin1String("image/jpeg")) ||
                    parentMimeType.contains(QLatin1String("text/plain")) ||
                    parentMimeType.contains(QLatin1String("image/png")) ||
                    parentMimeType.contains(QLatin1String("image/jpeg"))
                    ) {
                menu->addAction(d->viewContextAction);
            }
            d->createOpenWithMenu(menu, d->selectedParts.first());
        }
        menu->addAction(d->openContextAction);
    }
    if(enableEditAction) {
        menu->addAction(d->editWithContextAction);
        menu->addAction(d->editContextAction);
    }
    if(numberOfParts>0) {
        menu->addAction(d->removeContextAction);
    }
    if(numberOfParts == 1) {
        if (!d->selectedParts.first()->url().isEmpty()) {
            menu->addAction(d->reloadAttachmentAction);
        }
        menu->addAction(d->saveAsContextAction);
        menu->addSeparator();
        menu->addAction(d->propertiesContextAction);
    }

    const int nbAttachment = d->model->rowCount();
    if (nbAttachment != numberOfParts) {
        menu->addSeparator();
        menu->addAction(d->selectAllAction);
    }
    if (numberOfParts == 0) {
        menu->addSeparator();
        menu->addAction(d->addContextAction);
    }

    menu->exec( QCursor::pos() );
    delete menu;
}

void AttachmentControllerBase::slotOpenWithDialog()
{
    openWith();
}

void AttachmentControllerBase::slotOpenWithAction(QAction*act)
{
    KService::Ptr app = act->data().value<KService::Ptr>();
    Q_ASSERT( d->selectedParts.count() == 1 );

    openWith(app);
}

void AttachmentControllerBase::openWith(KService::Ptr offer)
{
    KTemporaryFile *tempFile = dumpAttachmentToTempFile( d->selectedParts.first() );
    if( !tempFile ) {
        KMessageBox::sorry( d->wParent,
                            i18n( "KMail was unable to write the attachment to a temporary file." ),
                            i18n( "Unable to open attachment" ) );
        return;
    }
    tempFile->setPermissions( QFile::ReadUser );
    KUrl::List lst;
    KUrl url = KUrl::fromPath(tempFile->fileName());
    lst.append( url );
    bool result = false;
    if(offer) {
        result = KRun::run( *offer, lst, d->wParent, false );
    } else {
        result = KRun::displayOpenWithDialog( lst, d->wParent, false );
    }
    if ( !result ) {
        delete tempFile;
        tempFile = 0;
    } else {
        // The file was opened.  Delete it only when the composer is closed
        // (and this object is destroyed).
        tempFile->setParent( this ); // Manages lifetime.
    }
}

void AttachmentControllerBase::openAttachment( AttachmentPart::Ptr part )
{
    KTemporaryFile *tempFile = dumpAttachmentToTempFile( part );
    if( !tempFile ) {
        KMessageBox::sorry( d->wParent,
                            i18n( "KMail was unable to write the attachment to a temporary file." ),
                            i18n( "Unable to open attachment" ) );
        return;
    }
    tempFile->setPermissions( QFile::ReadUser );
    bool success = KRun::runUrl( KUrl::fromPath( tempFile->fileName() ),
                                 QString::fromLatin1( part->mimeType() ),
                                 d->wParent,
                                 true /*tempFile*/,
                                 false /*runExecutables*/ );
    if( !success ) {
        if( KMimeTypeTrader::self()->preferredService( QString::fromLatin1( part->mimeType() ) ).isNull() ) {
            // KRun showed an Open-With dialog, and it was canceled.
        } else {
            // KRun failed.
            KMessageBox::sorry( d->wParent,
                                i18n( "KMail was unable to open the attachment." ),
                                i18n( "Unable to open attachment" ) );
        }
        delete tempFile;
        tempFile = 0;
    } else {
        // The file was opened.  Delete it only when the composer is closed
        // (and this object is destroyed).
        tempFile->setParent( this ); // Manages lifetime.
    }
}

void AttachmentControllerBase::viewAttachment( AttachmentPart::Ptr part )
{
    MessageComposer::Composer *composer = new MessageComposer::Composer;
    composer->globalPart()->setFallbackCharsetEnabled( true );
    MessageComposer::AttachmentJob *attachmentJob = new MessageComposer::AttachmentJob( part, composer );
    connect( attachmentJob, SIGNAL(result(KJob*)),
             this, SLOT(slotAttachmentContentCreated(KJob*)) );
    attachmentJob->start();
}

void AttachmentControllerBase::Private::slotAttachmentContentCreated( KJob *job )
{
    if ( !job->error() ) {
        const MessageComposer::AttachmentJob * const attachmentJob = dynamic_cast<MessageComposer::AttachmentJob*>( job );
        Q_ASSERT( attachmentJob );
        emit q->showAttachment( attachmentJob->content(), QByteArray() );
    } else {
        // TODO: show warning to the user
        kWarning() << "Error creating KMime::Content for attachment:" << job->errorText();
    }
}

void AttachmentControllerBase::editAttachment( AttachmentPart::Ptr part, MessageViewer::EditorWatcher::OpenWithOption openWithOption )
{
    KTemporaryFile *tempFile = dumpAttachmentToTempFile( part );
    if( !tempFile ) {
        KMessageBox::sorry( d->wParent,
                            i18n( "KMail was unable to write the attachment to a temporary file." ),
                            i18n( "Unable to edit attachment" ) );
        return;
    }

    MessageViewer::EditorWatcher *watcher = new MessageViewer::EditorWatcher(
                KUrl::fromPath( tempFile->fileName() ),
                QString::fromLatin1( part->mimeType() ), openWithOption,
                this, d->wParent );
    connect( watcher, SIGNAL(editDone(MessageViewer::EditorWatcher*)),
             this, SLOT(editDone(MessageViewer::EditorWatcher*)) );

    switch (watcher->start()) {
    case MessageViewer::EditorWatcher::NoError: {
        // The attachment is being edited.
        // We will clean things up in editDone().
        d->editorPart[ watcher ] = part;
        d->editorTempFile[ watcher ] = tempFile;

        // Delete the temp file if the composer is closed (and this object is destroyed).
        tempFile->setParent( this ); // Manages lifetime.
        break;
    }
    case MessageViewer::EditorWatcher::CannotStart:
        kWarning() << "Could not start EditorWatcher.";
    case MessageViewer::EditorWatcher::Unknown:
    case MessageViewer::EditorWatcher::NoServiceFound: {
        delete watcher;
        delete tempFile;
        break;
    }
    }
}

void AttachmentControllerBase::editAttachmentWith( AttachmentPart::Ptr part )
{
    editAttachment( part, MessageViewer::EditorWatcher::OpenWithDialog );
}

void AttachmentControllerBase::saveAttachmentAs( AttachmentPart::Ptr part )
{
    QString pname = part->name();
    if( pname.isEmpty() ) {
        pname = i18n( "unnamed" );
    }

    KUrl url = KFileDialog::getSaveUrl( pname,
                                        QString( /*filter*/ ), d->wParent,
                                        i18n( "Save Attachment As" ) );

    if( url.isEmpty() ) {
        kDebug() << "Save Attachment As dialog canceled.";
        return;
    }

    byteArrayToRemoteFile(part->data(), url);
}

void AttachmentControllerBase::byteArrayToRemoteFile(const QByteArray &aData, const KUrl &aURL, bool overwrite)
{
    KIO::StoredTransferJob *job = KIO::storedPut( aData, aURL, -1, overwrite ? KIO::Overwrite : KIO::DefaultFlags );
    connect( job, SIGNAL(result(KJob*)), SLOT(slotPutResult(KJob*)) );
}

void AttachmentControllerBase::slotPutResult(KJob *job)
{
    KIO::StoredTransferJob *_job = qobject_cast<KIO::StoredTransferJob *>( job );

    if (job->error())
    {
        if (job->error() == KIO::ERR_FILE_ALREADY_EXIST)
        {
            if (KMessageBox::warningContinueCancel(0,
                                                   i18n("File %1 exists.\nDo you want to replace it?", _job->url().toLocalFile()), i18n("Save to File"), KGuiItem(i18n("&Replace")))
                    == KMessageBox::Continue)
                byteArrayToRemoteFile(_job->data(), _job->url(), true);
        }
        else {
            KIO::JobUiDelegate *ui = static_cast<KIO::Job*>( job )->ui();
            ui->showErrorMessage();
        }
    }
}

void AttachmentControllerBase::attachmentProperties( AttachmentPart::Ptr part )
{
    QPointer<AttachmentPropertiesDialog> dialog = new AttachmentPropertiesDialog(
                part, false, d->wParent );

    dialog->setEncryptEnabled( d->encryptEnabled );
    dialog->setSignEnabled( d->signEnabled );

    if( dialog->exec() && dialog ) {
        d->model->updateAttachment( part );
    }
    delete dialog;
}

void AttachmentControllerBase::attachFileDirectory(const KUrl::List &urls, const QString &encoding)
{
    const int numberOfFiles(urls.count());
    for (int i=0; i<numberOfFiles; ++i) {
        const KUrl url = urls.at( i );
        KUrl urlWithEncoding = url;
        urlWithEncoding.setFileEncoding( encoding );
        if ( KMimeType::findByUrl( urlWithEncoding )->name() == QLatin1String( "inode/directory" ) ) {
            const int rc = KMessageBox::warningYesNo( d->wParent,i18n("Do you really want to attach this directory \"%1\" ?", url.toLocalFile() ),i18n( "Attach directory" ) );
            if ( rc == KMessageBox::Yes ) {
                addAttachment( urlWithEncoding );
            }
        } else {
            addAttachment( urlWithEncoding );
        }
    }
}

void AttachmentControllerBase::showAttachVcard()
{
    QPointer<Akonadi::EmailAddressSelectionDialog> dlg = new Akonadi::EmailAddressSelectionDialog(d->wParent);
    dlg->view()->view()->setSelectionMode( QAbstractItemView::MultiSelection );
    if (dlg->exec()) {
        const Akonadi::EmailAddressSelection::List selectedEmail = dlg->selectedAddresses();
        Q_FOREACH (const Akonadi::EmailAddressSelection& selected, selectedEmail) {
            MessageComposer::AttachmentVcardFromAddressBookJob *ajob = new MessageComposer::AttachmentVcardFromAddressBookJob( selected.item(), this);
            connect( ajob, SIGNAL(result(KJob*)), this, SLOT(attachVcardFromAddressBook(KJob*)) );
            ajob->start();
        }
    }
    delete dlg;
}

void AttachmentControllerBase::showAddAttachmentCompressedDirectoryDialog()
{
#ifndef KDEPIM_MOBILE_UI
    QPointer<KEncodingFileDialog> dialog = new KEncodingFileDialog(
                QString( /*startDir*/ ), QString( /*encoding*/ ), QString( /*filter*/ ),
                i18n( "Attach Directory" ), KFileDialog::Other, d->wParent );

    dialog->okButton()->setGuiItem( KGuiItem( i18n("&Attach"), QLatin1String( "document-open" ) ) );
    dialog->setMode( KFile::Directory );
    if( dialog->exec() == KDialog::Accepted && dialog ) {
        const QString encoding = MessageViewer::NodeHelper::fixEncoding( dialog->selectedEncoding() );
        attachFileDirectory(dialog->selectedUrls(), encoding);
    }
    delete dialog;
#else
    // use native dialog, while being much simpler, it actually fits on the screen much better than our own monster dialog
    const QString fileName = KFileDialog::getExistingDirectory(KUrl(), QString(), d->wParent, i18n("Attach Directory" ) );
    if ( !fileName.isEmpty() ) {
        addAttachment( KUrl::fromLocalFile( fileName ) );
    }
#endif
}


void AttachmentControllerBase::showAddAttachmentFileDialog()
{
#ifndef KDEPIM_MOBILE_UI
    QPointer<KEncodingFileDialog> dialog = new KEncodingFileDialog(
                QString( /*startDir*/ ), QString( /*encoding*/ ), QString( /*filter*/ ),
                i18n( "Attach File" ), KFileDialog::Other, d->wParent );

    dialog->okButton()->setGuiItem( KGuiItem( i18n("&Attach"), QLatin1String( "document-open" ) ) );
    dialog->setMode( KFile::Files );
    if( dialog->exec() == KDialog::Accepted && dialog ) {
        const QString encoding = MessageViewer::NodeHelper::fixEncoding( dialog->selectedEncoding() );
        attachFileDirectory(dialog->selectedUrls(), encoding);
    }
    delete dialog;
#else
    // use native dialog, while being much simpler, it actually fits on the screen much better than our own monster dialog
    const QString fileName = KFileDialog::getOpenFileName( KUrl(), QString(), d->wParent, i18n("Attach File" ) );
    if ( !fileName.isEmpty() ) {
        addAttachment( KUrl::fromLocalFile( fileName ) );
    }
#endif
}

void AttachmentControllerBase::addAttachment( AttachmentPart::Ptr part )
{
    part->setEncrypted( d->model->isEncryptSelected() );
    part->setSigned( d->model->isSignSelected() );
    d->model->addAttachment( part );

    if( MessageComposer::MessageComposerSettings::self()->showMessagePartDialogOnAttach() ) {
        attachmentProperties( part );
    }
    emit fileAttached();
}

void AttachmentControllerBase::addAttachmentUrlSync(const KUrl &url)
{
    MessageCore::AttachmentFromUrlBaseJob *ajob = MessageCore::AttachmentFromUrlUtils::createAttachmentJob(url, this);
    if(ajob->exec()) {
        AttachmentPart::Ptr part = ajob->attachmentPart();
        addAttachment( part );
    } else {
        if( ajob->error() ) {
            KMessageBox::sorry( d->wParent, ajob->errorString(), i18n( "Failed to attach file" ) );
        }
    }
}

void AttachmentControllerBase::addAttachment( const KUrl &url )
{
    MessageCore::AttachmentFromUrlBaseJob *ajob = MessageCore::AttachmentFromUrlUtils::createAttachmentJob(url, this);
    connect( ajob, SIGNAL(result(KJob*)), this, SLOT(loadJobResult(KJob*)) );
    ajob->start();
}

void AttachmentControllerBase::addAttachments( const KUrl::List &urls )
{
    foreach( const KUrl &url, urls ) {
        addAttachment( url );
    }
}

void AttachmentControllerBase::showAttachPublicKeyDialog()
{
    using Kleo::KeySelectionDialog;
    QPointer<KeySelectionDialog> dialog = new KeySelectionDialog(
                i18n( "Attach Public OpenPGP Key" ),
                i18n( "Select the public key which should be attached." ),
                std::vector<GpgME::Key>(),
                KeySelectionDialog::PublicKeys|KeySelectionDialog::OpenPGPKeys,
                false /* no multi selection */,
                false /* no remember choice box */,
                d->wParent, "attach public key selection dialog" );

    if( dialog->exec() == KDialog::Accepted && dialog ) {
        exportPublicKey( dialog->fingerprint() );
    }
    delete dialog;
}

void AttachmentControllerBase::attachMyPublicKey()
{

}

void AttachmentControllerBase::enableAttachPublicKey( bool enable )
{
    d->attachPublicKeyAction->setEnabled( enable );
}

void AttachmentControllerBase::enableAttachMyPublicKey( bool enable )
{
    d->attachMyPublicKeyAction->setEnabled( enable );
}

void AttachmentControllerBase::setAttachOwnVcard(bool attachVcard)
{
    d->addOwnVcardAction->setChecked(attachVcard);
}

bool AttachmentControllerBase::attachOwnVcard() const
{
    return  d->addOwnVcardAction->isChecked();
}

void AttachmentControllerBase::setIdentityHasOwnVcard(bool state)
{
    d->addOwnVcardAction->setEnabled(state);
}

#include "moc_attachmentcontrollerbase.cpp"
