/*
 * This file is part of KMail.
 * Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
 *
 * 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 "attachmentmodel.h"

#include <boost/shared_ptr.hpp>

#include <QMimeData>
#include <QUrl>

#include <KDebug>
#include <KGlobal>
#include <KLocalizedString>
#include <KLocale>
#include <KTempDir>

#include <kmime/kmime_util.h>
#include <akonadi/item.h>

#include <kpimutils/kfileio.h>

using namespace MessageComposer;
using namespace MessageCore;

static Qt::CheckState boolToCheckState( bool checked ) // local
{
    if( checked ) {
        return Qt::Checked;
    } else {
        return Qt::Unchecked;
    }
}

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

    AttachmentModel *const q;
    AttachmentPart::List parts;
    bool modified;
    bool encryptEnabled;
    bool signEnabled;
    bool encryptSelected;
    bool signSelected;
    bool autoDisplayEnabled;
    QList<KTempDir*> tempDirs;
};

AttachmentModel::Private::Private( AttachmentModel *qq )
    : q( qq )
    , modified( false )
    , encryptEnabled( false )
    , signEnabled( false )
    , encryptSelected( false )
    , signSelected( false )
    , autoDisplayEnabled( false )
{
}

AttachmentModel::Private::~Private()
{
    // There should be an automatic way to manage the lifetime of these...
    qDeleteAll( tempDirs );
}



AttachmentModel::AttachmentModel( QObject *parent )
    : QAbstractItemModel( parent )
    , d( new Private( this ) )
{
    QHash<int, QByteArray> names = roleNames();
    names.insert( NameRole, "attachmentName" );
    names.insert( SizeRole, "attachmentSize" );
    names.insert( EncodingRole, "attachmentEncoding" );
    names.insert( MimeTypeRole, "attachmentMimeType" );
    names.insert( CompressRole, "attachmentIsCompressed" );
    names.insert( EncryptRole, "attachmentIsEncrypted" );
    names.insert( SignRole, "attachmentIsSigned" );
    names.insert( AutoDisplayRole, "attachmentIsAutoDiplayed" );

    setRoleNames( names );
}

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

bool AttachmentModel::dropMimeData( const QMimeData *data, Qt::DropAction action,
                                    int row, int column, const QModelIndex &parent )
{
    Q_UNUSED( row );
    Q_UNUSED( column );
    Q_UNUSED( parent );

    kDebug() << "data has formats" << data->formats()
             << "urls" << data->urls()
             << "action" << int( action );

    if( action == Qt::IgnoreAction ) {
        return true;
        //} else if( action != Qt::CopyAction ) {
        //  return false;
    }
    // The dropped data is a list of URLs.
    KUrl::List urls = KUrl::List::fromMimeData( data );
    if( !urls.isEmpty() ) {
        Akonadi::Item::List items;
        foreach ( const KUrl &url, urls ) {
            Akonadi::Item item = Akonadi::Item::fromUrl( url );
            if ( item.isValid() ) {
                items << item;
            }
        }
        if ( items.isEmpty() )
            emit attachUrlsRequested( urls );
        else {
            emit attachItemsRequester( items );
        }
        return true;
    } else {
        return false;
    }
}

QMimeData *AttachmentModel::mimeData( const QModelIndexList &indexes ) const
{
    kDebug();
    QList<QUrl> urls;
    foreach( const QModelIndex &index, indexes ) {
        if( index.column() != 0 ) {
            // Avoid processing the same attachment more than once, since the entire
            // row is selected.
            kWarning() << "column != 0. Possibly duplicate rows passed to mimeData().";
            continue;
        }

        const AttachmentPart::Ptr part = d->parts[ index.row() ];
        QString attachmentName = part->fileName();
        if( attachmentName.isEmpty() ) {
            attachmentName = part->name();
        }
        if( attachmentName.isEmpty() ) {
            attachmentName = i18n( "unnamed attachment" );
        }

        KTempDir *tempDir = new KTempDir; // Will remove the directory on destruction.
        d->tempDirs.append( tempDir );
        const QString fileName = tempDir->name() + attachmentName;
        KPIMUtils::kByteArrayToFile( part->data(),
                                     fileName,
                                     false, false, false );
        KPIMUtils::checkAndCorrectPermissionsIfPossible( fileName, false, true, true );
        QUrl url;
        url.setScheme( QLatin1String( "file" ) );
        url.setPath( fileName );
        kDebug() << url;
        urls.append( url );
    }

    QMimeData *mimeData = new QMimeData;
    mimeData->setUrls( urls );
    return mimeData;
}

QStringList AttachmentModel::mimeTypes() const
{
    QStringList types;
    types << QString::fromLatin1( "text/uri-list" );
    return types;
}

Qt::DropActions AttachmentModel::supportedDropActions() const
{
    return Qt::CopyAction | Qt::MoveAction;
}

bool AttachmentModel::isModified() const
{
    return d->modified; // TODO actually set modified=true sometime...
}

void AttachmentModel::setModified( bool modified )
{
    d->modified = modified;
}

bool AttachmentModel::isEncryptEnabled() const
{
    return d->encryptEnabled;
}

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

bool AttachmentModel::isAutoDisplayEnabled() const
{
    return d->autoDisplayEnabled;
}

void AttachmentModel::setAutoDisplayEnabled( bool enabled )
{
    d->autoDisplayEnabled = enabled;
    emit autoDisplayEnabled( enabled );
}


bool AttachmentModel::isSignEnabled() const
{
    return d->signEnabled;
}

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

bool AttachmentModel::isEncryptSelected() const
{
    return d->encryptSelected;
}

void AttachmentModel::setEncryptSelected( bool selected )
{
    d->encryptSelected = selected;
    foreach( AttachmentPart::Ptr part, d->parts ) {
        part->setEncrypted( selected );
    }
    emit dataChanged( index( 0, EncryptColumn ), index( rowCount() - 1, EncryptColumn ) );
}

bool AttachmentModel::isSignSelected() const
{
    return d->signSelected;
}

void AttachmentModel::setSignSelected( bool selected )
{
    d->signSelected = selected;
    foreach( AttachmentPart::Ptr part, d->parts ) {
        part->setSigned( selected );
    }
    emit dataChanged( index( 0, SignColumn ), index( rowCount() - 1, SignColumn ) );
}

QVariant AttachmentModel::data( const QModelIndex &index, int role ) const
{
    if( !index.isValid() ) {
        return QVariant();
    }

    const AttachmentPart::Ptr part = d->parts.at( index.row() );

    if( role == Qt::DisplayRole ) {
        switch( index.column() ) {
        case NameColumn:
            return QVariant::fromValue( part->name().isEmpty() ? part->fileName() : part->name() );
        case SizeColumn:
            return QVariant::fromValue( KGlobal::locale()->formatByteSize( part->size() ) );
        case EncodingColumn:
            return QVariant::fromValue( KMime::nameForEncoding( part->encoding() ) );
        case MimeTypeColumn:
            return QVariant::fromValue( part->mimeType() );
        default:
            return QVariant();
        }
    } else if( role == Qt::CheckStateRole ) {
        switch( index.column() ) {
        case CompressColumn:
            return QVariant::fromValue( int( boolToCheckState( part->isCompressed() ) ) );
        case EncryptColumn:
            return QVariant::fromValue( int( boolToCheckState( part->isEncrypted() ) ) );
        case SignColumn:
            return QVariant::fromValue( int( boolToCheckState( part->isSigned() ) ) );
        case AutoDisplayColumn:
            return QVariant::fromValue( int( boolToCheckState( part->isInline() ) ) );
        default:
            return QVariant();
        }
    } else if( role == AttachmentPartRole ) {
        if( index.column() == 0 ) {
            return QVariant::fromValue( part );
        } else {
            kWarning() << "AttachmentPartRole and column != 0.";
            return QVariant();
        }
    } else if ( role == NameRole ) {
        return QVariant::fromValue( part->fileName().isEmpty() ? part->name() : part->fileName() );
    } else if ( role == SizeRole ) {
        return QVariant::fromValue( KGlobal::locale()->formatByteSize( part->size() ) );
    } else if ( role == EncodingRole ) {
        return QVariant::fromValue( KMime::nameForEncoding( part->encoding() ) );
    } else if ( role == MimeTypeRole ) {
        return QVariant::fromValue( part->mimeType() );
    } else if ( role == CompressRole ) {
        return QVariant::fromValue( part->isCompressed() );
    } else if ( role == EncryptRole ) {
        return QVariant::fromValue( part->isEncrypted() );
    } else if ( role == SignRole ) {
        return QVariant::fromValue( part->isSigned() );
    } else if ( role == AutoDisplayRole ) {
        return QVariant::fromValue( part->isInline() );
    } else {
        return QVariant();
    }
}

bool AttachmentModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
    bool emitDataChanged = true;
    AttachmentPart::Ptr part = d->parts[ index.row() ];

    if( role == Qt::CheckStateRole ) {
        switch( index.column() ) {
        case CompressColumn:
        {
            bool toZip = value.toBool();
            if( toZip != part->isCompressed() ) {
                emit attachmentCompressRequested( part, toZip );
                emitDataChanged = false; // Will emit when the part is updated.
            }
            break;
        }
        case EncryptColumn:
            part->setEncrypted( value.toBool() );
            break;
        case SignColumn:
            part->setSigned( value.toBool() );
            break;
        case AutoDisplayColumn:
            part->setInline( value.toBool() );
            break;
        default:
            break; // Do nothing.
        };
    } else {
        return false;
    }

    if( emitDataChanged ) {
        emit dataChanged( index, index );
    }
    return true;
}

bool AttachmentModel::addAttachment( AttachmentPart::Ptr part )
{
    Q_ASSERT( !d->parts.contains( part ) );

    beginInsertRows( QModelIndex(), rowCount(), rowCount() );
    d->parts.append( part );
    endInsertRows();
    return true;
}

bool AttachmentModel::updateAttachment( AttachmentPart::Ptr part )
{
    int idx = d->parts.indexOf( part );
    if( idx == -1 ) {
        kWarning() << "Tried to update non-existent part.";
        return false;
    }
    // Emit dataChanged() for the whole row.
    emit dataChanged( index( idx, 0 ), index( idx, LastColumn - 1 ) );
    return true;
}

bool AttachmentModel::replaceAttachment( AttachmentPart::Ptr oldPart, AttachmentPart::Ptr newPart )
{
    Q_ASSERT( oldPart != newPart );

    int idx = d->parts.indexOf( oldPart );
    if( idx == -1 ) {
        kWarning() << "Tried to replace non-existent part.";
        return false;
    }
    d->parts[ idx ] = newPart;
    // Emit dataChanged() for the whole row.
    emit dataChanged( index( idx, 0 ), index( idx, LastColumn - 1 ) );
    return true;
}

bool AttachmentModel::removeAttachment( AttachmentPart::Ptr part )
{
    int idx = d->parts.indexOf( part );
    if( idx < 0 ) {
        kWarning() << "Attachment not found.";
        return false;
    }

    beginRemoveRows( QModelIndex(), idx, idx );
    d->parts.removeAt( idx );
    endRemoveRows();
    emit attachmentRemoved( part );
    return true;
}

AttachmentPart::List AttachmentModel::attachments() const
{
    return d->parts;
}

Qt::ItemFlags AttachmentModel::flags( const QModelIndex &index ) const
{
    Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);

    if( !index.isValid() ) {
        return Qt::ItemIsDropEnabled | defaultFlags;
    }

    if( index.column() == CompressColumn ||
            index.column() == EncryptColumn ||
            index.column() == SignColumn ||
            index.column() == AutoDisplayColumn ) {
        return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable | defaultFlags;
    } else {
        return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
    }
}

QVariant AttachmentModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
    if( orientation != Qt::Horizontal || role != Qt::DisplayRole ) {
        return QVariant();
    }

    switch( section ) {
    case NameColumn:
        return i18nc( "@title column attachment name.", "Name" );
    case SizeColumn:
        return i18nc( "@title column attachment size.", "Size" );
    case EncodingColumn:
        return i18nc( "@title column attachment encoding.", "Encoding" );
    case MimeTypeColumn:
        return i18nc( "@title column attachment type.", "Type" );
    case CompressColumn:
        return i18nc( "@title column attachment compression checkbox.", "Compress" );
    case EncryptColumn:
        return i18nc( "@title column attachment encryption checkbox.", "Encrypt" );
    case SignColumn:
        return i18nc( "@title column attachment signed checkbox.", "Sign" );
    case AutoDisplayColumn:
        return i18nc( "@title column attachment inlined checkbox.", "Suggest Automatic Display" );
    default:
        kWarning() << "Bad column" << section;
        return QVariant();
    };
}

QModelIndex AttachmentModel::index( int row, int column, const QModelIndex &parent ) const
{
    if( !hasIndex( row, column, parent ) ) {
        return QModelIndex();
    }
    Q_ASSERT( row >= 0 && row < rowCount() );

    if( parent.isValid() ) {
        kWarning() << "Called with weird parent.";
        return QModelIndex();
    }

    return createIndex( row, column );
}

QModelIndex AttachmentModel::parent( const QModelIndex &index ) const
{
    Q_UNUSED( index );
    return QModelIndex(); // No parent.
}

int AttachmentModel::rowCount( const QModelIndex &parent ) const
{
    if( parent.isValid() ) {
        return 0; // Items have no children.
    }
    Q_UNUSED( parent );
    return d->parts.count();
}

int AttachmentModel::columnCount( const QModelIndex &parent ) const
{
    Q_UNUSED( parent );
    return LastColumn;
}

