/*
 * 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 <QMimeData>
#include <QUrl>

#include "messagecomposer_debug.h"
#include <KLocalizedString>
#include <QTemporaryDir>
#include <QFileDevice>

#include <kmime/kmime_headers.h>
#include <kmime/kmime_util.h>
#include <AkonadiCore/item.h>

#include <KFormat>

using namespace MessageComposer;
using namespace MessageCore;

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

class Q_DECL_HIDDEN 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<QTemporaryDir *> 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);

    qCDebug(MESSAGECOMPOSER_LOG) << "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.
    QList<QUrl> urls = data->urls();
    if (!urls.isEmpty()) {
        Akonadi::Item::List items;
        foreach (const QUrl &url, urls) {
            Akonadi::Item item = Akonadi::Item::fromUrl(url);
            if (item.isValid()) {
                items << item;
            }
        }
        if (items.isEmpty()) {
            Q_EMIT attachUrlsRequested(urls);
        } else {
            Q_EMIT attachItemsRequester(items);
        }
        return true;
    } else {
        return false;
    }
}

QMimeData *AttachmentModel::mimeData(const QModelIndexList &indexes) const
{
    qCDebug(MESSAGECOMPOSER_LOG);
    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.
            qCWarning(MESSAGECOMPOSER_LOG) << "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");
        }

        QTemporaryDir *tempDir = new QTemporaryDir; // Will remove the directory on destruction.
        d->tempDirs.append(tempDir);
        const QString fileName = tempDir->path() + QLatin1Char('/') + attachmentName;
        QFile f(fileName);
        if (!f.open(QIODevice::WriteOnly)) {
            qCWarning(MESSAGECOMPOSER_LOG) << "Cannot write attachment:" << f.errorString();
            continue;
        }
        const QByteArray data = part->data();
        if (f.write(data) != data.length()) {
            qCWarning(MESSAGECOMPOSER_LOG) << "Failed to write all data to file!";
            continue;
        }
        f.setPermissions(f.permissions() | QFileDevice::ReadUser | QFileDevice::WriteUser);
        f.close();

        QUrl url;
        url.setScheme(QStringLiteral("file"));
        url.setPath(fileName);
        qCDebug(MESSAGECOMPOSER_LOG) <<" temporary file " << url;
        urls.append(url);
    }

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

QStringList AttachmentModel::mimeTypes() const
{
    QStringList types;
    types << QStringLiteral("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;
    Q_EMIT encryptEnabled(enabled);
}

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

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

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

void AttachmentModel::setSignEnabled(bool enabled)
{
    d->signEnabled = enabled;
    Q_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);
    }
    Q_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);
    }
    Q_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(KFormat().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 {
            qCWarning(MESSAGECOMPOSER_LOG) << "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(KFormat().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()) {
                Q_EMIT attachmentCompressRequested(part, toZip);
                emitDataChanged = false; // Will Q_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) {
        Q_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) {
        qCWarning(MESSAGECOMPOSER_LOG) << "Tried to update non-existent part.";
        return false;
    }
    // Emit dataChanged() for the whole row.
    Q_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) {
        qCWarning(MESSAGECOMPOSER_LOG) << "Tried to replace non-existent part.";
        return false;
    }
    d->parts[ idx ] = newPart;
    // Emit dataChanged() for the whole row.
    Q_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) {
        qCWarning(MESSAGECOMPOSER_LOG) << "Attachment not found.";
        return false;
    }

    beginRemoveRows(QModelIndex(), idx, idx);
    d->parts.removeAt(idx);
    endRemoveRows();
    Q_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:
        qCWarning(MESSAGECOMPOSER_LOG) << "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()) {
        qCWarning(MESSAGECOMPOSER_LOG) << "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;
}

