/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Mobility Components.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "modestengine_maemo_p.h"
#include "maemohelpers_p.h"
#include "qmessage_p.h"
#include "qmessageaccount.h"
#include "qmessageaccount_p.h"
#include "qmessageaccountfilter.h"
#include "qmessageaccountfilter_p.h"
#include "qmessagefolder_p.h"
#include "qmessagestore_p_p.h"
#include "qmessageservice_maemo_p.h"
#include "qmessagecontentcontainer_maemo_p.h"
#include <QUrl>
#include <QtDBus>
#include <QFileSystemWatcher>
#include <QTimer>

#include <dbus/dbus.h>
#include <QDebug>

#include </usr/include/libmodest-dbus-client-1.0/libmodest-dbus-client/libmodest-dbus-api.h>
#include </usr/include/sys/inotify.h>
#include </usr/include/sys/ioctl.h>
#include </usr/include/sys/fcntl.h>

// Marshall the ModestStringMap data into a D-Bus argument
QDBusArgument &operator<<(QDBusArgument &argument,
                          const QtMobility::ModestStringMap &map)
{
    QtMobility::ModestStringMap::const_iterator iter;

    argument.beginMap (QVariant::String, QVariant::String);
    for (iter = map.constBegin(); iter != map.constEnd(); iter++) {
        argument.beginMapEntry();
        argument << iter.key() << iter.value();
        argument.endMapEntry();
    }
    argument.endMap();

    return argument;
}

// Retrieve the ModestStringMap data from the D-Bus argument
const QDBusArgument &operator>>(const QDBusArgument &argument,
                                QtMobility::ModestStringMap &map)
{
    map.clear();

    argument.beginMap();
    while (!argument.atEnd()) {
        QString key, value;
        argument.beginMapEntry();
        argument >> key >> value;
        argument.endMapEntry();
        map[key] = value;
    }
    argument.endMap();

    return argument;
}

QDBusArgument &operator<<(QDBusArgument &argument,
                          const QtMobility::ModestUnreadMessageDBusStruct &unreadMessage)
{
    argument.beginStructure();
    argument << unreadMessage.timeStamp;
    argument << unreadMessage.subject;
    argument.endStructure();
    return argument;
}

const QDBusArgument &operator>>(const QDBusArgument &argument,
                                QtMobility::ModestUnreadMessageDBusStruct &unreadMessage)
{
    argument.beginStructure();
    argument >> unreadMessage.timeStamp;
    argument >> unreadMessage.subject;
    argument.endStructure();
    return argument;
}

QDBusArgument &operator<<(QDBusArgument &argument,
                          const QtMobility::ModestAccountsUnreadMessagesDBusStruct &unreadMessages)
{
    argument.beginStructure();
    argument << unreadMessages.accountId;
    argument << unreadMessages.accountName;
    argument << unreadMessages.accountProtocol;
    argument << unreadMessages.unreadCount;
    argument << unreadMessages.unreadMessages;
    argument.endStructure();
    return argument;
}

const QDBusArgument &operator>>(const QDBusArgument &argument,
                                QtMobility::ModestAccountsUnreadMessagesDBusStruct &unreadMessages)
{
    argument.beginStructure();
    argument >> unreadMessages.accountId;
    argument >> unreadMessages.accountName;
    argument >> unreadMessages.accountProtocol;
    argument >> unreadMessages.unreadCount;
    argument >> unreadMessages.unreadMessages;
    argument.endStructure();
    return argument;
}

QDBusArgument &operator<<(QDBusArgument &argument, const QtMobility::ModestMessage &message)
{
    argument.beginStructure();
    argument << message.id;
    argument << message.subject;
    argument << message.folder;
    argument << message.sender;
    argument << message.size;
    argument << message.hasAttachment;
    argument << message.isUnread;
    argument << message.timeStamp;
    argument.endStructure();
    return argument;
}

const QDBusArgument &operator>>(const QDBusArgument &argument, QtMobility::ModestMessage &message)
{
    argument.beginStructure();
    argument >> message.id;
    argument >> message.subject;
    argument >> message.folder;
    argument >> message.sender;
    argument >> message.size;
    argument >> message.hasAttachment;
    argument >> message.isUnread;
    argument >> message.timeStamp;
    argument.endStructure();
    return argument;
}

QDBusArgument &operator<<(QDBusArgument &argument, const QtMobility::MessagingModestMimePart &mimePart)
{
    argument.beginStructure();
    argument << mimePart.mimeType;
    argument << mimePart.isAttachment;
    argument << mimePart.fileName;
    argument << mimePart.contentId;
    argument.endStructure();
    return argument;
}

const QDBusArgument &operator>>(const QDBusArgument &argument, QtMobility::MessagingModestMimePart &mimePart)
{
    argument.beginStructure();
    argument >> mimePart.mimeType;
    argument >> mimePart.isAttachment;
    argument >> mimePart.fileName;
    argument >> mimePart.contentId;
    argument.endStructure();
    return argument;
}

QDBusArgument &operator<<(QDBusArgument &argument, const QtMobility::MessagingModestFolder &folder)
{
    argument.beginStructure();
    argument << folder.parentAccountId;
    argument << folder.parentFolderId;
    argument << folder.modestId;
    argument << static_cast<uint>(folder.type);
    argument << folder.localizedName;
    argument.endStructure();
    return argument;
}

const QDBusArgument &operator>>(const QDBusArgument &argument, QtMobility::MessagingModestFolder &folder)
{
    argument.beginStructure();

    argument >> folder.parentAccountId;
    QByteArray str = folder.parentAccountId.toUtf8();
    gchar* escaped_string = gconf_escape_key(str.data(), str.length());
    folder.parentAccountId = QString::fromUtf8(escaped_string);
    g_free(escaped_string);

    argument >> folder.parentFolderId;
    argument >> folder.modestId;

    uint type;
    argument >> type;
    folder.type = static_cast<QtMobility::MessagingModestFolderType>(type);

    argument >> folder.localizedName;
    argument.endStructure();
    return argument;
}

QTM_BEGIN_NAMESPACE

/* configuration key definitions for modest */
#define MODESTENGINE_ACCOUNT_NAMESPACE         "/apps/modest/accounts"
#define MODESTENGINE_SERVER_ACCOUNT_NAMESPACE  "/apps/modest/server_accounts"
#define MODESTENGINE_DEFAULT_ACCOUNT           "/apps/modest/default_account"
#define MODESTENGINE_ACCOUNT_ENABLED           "enabled"
#define MODESTENGINE_ACCOUNT_DISPLAY_NAME      "display_name"
#define MODESTENGINE_ACCOUNT_FULLNAME          "fullname"
#define MODESTENGINE_ACCOUNT_EMAIL             "email"
#define MODESTENGINE_ACCOUNT_STORE_ACCOUNT     "store_account"
#define MODESTENGINE_ACCOUNT_TRANSPORT_ACCOUNT "transport_account"
#define MODESTENGINE_ACCOUNT_PROTOCOL          "proto"
#define MODESTENGINE_ACCOUNT_USERNAME          "username"
#define MODESTENGINE_ACCOUNT_HOSTNAME          "hostname"
#define MODESTENGINE_ACCOUNT_PORT              "port"

// The modest engine has a new plugin, we need service names for it
#define MODESTENGINE_QTM_PLUGIN_PATH           "/com/nokia/Qtm/Modest/Plugin"
#define MODESTENGINE_QTM_PLUGIN_NAME           "com.nokia.Qtm.Modest.Plugin"

typedef enum {
    MODEST_DBUS_SEARCH_SUBJECT   = (1 << 0),
    MODEST_DBUS_SEARCH_SENDER    = (1 << 1),
    MODEST_DBUS_SEARCH_RECIPIENT = (1 << 2),
    MODEST_DBUS_SEARCH_SIZE      = (1 << 3),
    MODEST_DBUS_SEARCH_BODY      = (1 << 6)
} ModestDBusSearchFlags;

// Specific priority settings to translate to modest priorities
#define MODESTENGINE_HIGH_PRIORITY             2
#define MODESTENGINE_NORMAL_PRIORITY           0
#define MODESTENGINE_LOW_PRIORITY              1

Q_GLOBAL_STATIC(ModestEngine,modestEngine);

ModestEngine::ModestEngine()
 : m_filterId(0), m_allEmailMessagesInCache(false)
{
    qWarning() << "ModestEngine::ModestEngine Starting to initialize";
    g_type_init();
    m_gconfclient = gconf_client_get_default();
    if (!m_gconfclient) {
        qWarning("qtmessaging: could not get gconf client");
    } else {
        updateEmailAccounts();
    }

    qWarning() << "ModestEngine::ModestEngine Connecting to Modest DBus Interface";
    // Setup DBus Interface for Modest
    m_ModestDBusInterface = new QDBusInterface(MODEST_DBUS_SERVICE,
                                               MODEST_DBUS_OBJECT,
                                               MODEST_DBUS_IFACE,
                                               QDBusConnection::sessionBus(),
                                               this);
    if (m_ModestDBusInterface->isValid()) {
        qWarning() << "ModestEngine::ModestEngine Connected to Modest DBus Interface";
    } else {
        qWarning() << "ModestEngine::ModestEngine Unable to connect to Modest DBus Interface";
    }

    // Get notifications of Incoming Messages
    m_ModestDBusInterface->connection().connect(MODEST_DBUS_SERVICE,
                                                MODEST_DBUS_OBJECT,
                                                MODEST_DBUS_IFACE,
                                                MODEST_DBUS_SIGNAL_FOLDER_UPDATED,
                                                this, SLOT(folderUpdatedSlot(QDBusMessage)));

    // Get notifications of message Read/Unread state changes
    m_ModestDBusInterface->connection().connect(MODEST_DBUS_SERVICE,
                                                MODEST_DBUS_OBJECT,
                                                MODEST_DBUS_IFACE,
                                                MODEST_DBUS_SIGNAL_MSG_READ_CHANGED,
                                               this, SLOT(messageReadChangedSlot(QDBusMessage)));

    qWarning() << "ModestEngine::ModestEngine Connecting to Qt Mobility Modest Plugin DBus Interface";
    // Setup Qt Mobility Modest Plugin based DBus Interface for Modest
    m_QtmPluginDBusInterface = new QDBusInterface(MODESTENGINE_QTM_PLUGIN_NAME,
                                                  MODESTENGINE_QTM_PLUGIN_PATH,
                                                  MODESTENGINE_QTM_PLUGIN_NAME,
                                                  QDBusConnection::sessionBus(),
                                                  this);

    if (m_QtmPluginDBusInterface->isValid()) {
        qWarning() << "ModestEngine::ModestEngine Connected to Qt Mobility Modest Plugin DBus Interface";
    } else {
        qWarning() << "ModestEngine::ModestEngine Unable to connect to Qt Mobility Modest Plugin DBus Interface";
    }

    qDBusRegisterMetaType< ModestStringMap >();
    qDBusRegisterMetaType< ModestStringMapList >();

    qRegisterMetaType<INotifyWatcher::FileNotification>();

    qRegisterMetaType<ModestUnreadMessageDBusStruct>();
    qRegisterMetaType<ModestAccountsUnreadMessagesDBusStruct>();
    qDBusRegisterMetaType<ModestMessage>();

    qRegisterMetaType<MessagingModestMimePart>();

    qRegisterMetaType<MessagingModestFolder>();

    qRegisterMetaType<QList<QtMobility::QMessageId> >("QList<QtMobility::QMessageId>");

    connect(&m_MailFoldersWatcher, SIGNAL(fileChanged(int, QString, uint)),
            this, SLOT(fileChangedSlot(int, QString, uint)));

    watchAllKnownEmailFolders();

    // Get latest messages from each account
    // => This ensures that notifications of all incoming messages will be sent
    int messagesPerAccount = 1;
    QDBusPendingCall pendingCall = m_ModestDBusInterface->asyncCall(MODEST_DBUS_METHOD_GET_UNREAD_MESSAGES,
                                                                    messagesPerAccount);
    QDBusPendingCallWatcher* pendingCallWatcher = new QDBusPendingCallWatcher(pendingCall);
    pendingCallWatcher->setProperty("setOnlyDates", true);
    connect(pendingCallWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
            this, SLOT(pendingGetUnreadMessagesFinishedSlot(QDBusPendingCallWatcher*)));

    QList<MessagingModestFolder> modestFolders;
    foldersFromModest(modestFolders);
    if (modestFolders.count() > 0) {
        for (int i=0; i < modestFolders.count(); i++) {
            if (modestFolders[i].type == MessagingModestFolderTypeInbox && modestFolders[i].parentAccountId != "local_folders") {
                QMessageAccountId accountId = accountIdFromModestAccountId(modestFolders[i].parentAccountId);
                if (accountExists(accountId)) {
                    QString id = accountId.toString()+"&"+accountEmailProtocolAsString(accountId)+"&"+modestFolders[i].modestId;
                    startObservingModestFolder(id);
                }
            }
        }
    }

    qWarning() << "ModestEngine::ModestEngine Initialized successfully";
}

ModestEngine::~ModestEngine()
{
    g_object_unref(m_gconfclient);
    m_gconfclient=NULL;

    if (m_observed_folders.count()) {
        for (int i = m_observed_folders.count()-1; i >= 0; i--) {
            stopObservingModestFolder(m_observed_folders[i]);
        }
    }
}

ModestEngine* ModestEngine::instance()
{
    return modestEngine();
}

MessagingModestMessage ModestEngine::messageFromModest(const QString& accountId, const QString &folderId, const QString& messageId) const
{
    MessagingModestMessage modestMessage;

    if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) {
        return modestMessage;
    }

    QDBusPendingCall pendingCall = m_QtmPluginDBusInterface->asyncCall("GetMessage",
                                                                       accountId,
                                                                       folderId,
                                                                       messageId);
    QDBusPendingCallWatcher pendingCallWatcher(pendingCall);
    pendingCallWatcher.waitForFinished();

    QDBusMessage msg = pendingCallWatcher.reply();

    if (msg.type() == QDBusMessage::ReplyMessage) {
        modestMessage.id = messageId;
        modestMessage.accountId = accountId;
        modestMessage.folderId = folderId;
        modestMessage.url = msg.arguments()[0].toString();
        modestMessage.mimeType = msg.arguments()[1].toString();
        modestMessage.from = msg.arguments()[2].toString();
        modestMessage.to = msg.arguments()[3].toString();
        modestMessage.cc = msg.arguments()[4].toString();
        modestMessage.bcc = msg.arguments()[5].toString();
        modestMessage.replyTo = msg.arguments()[6].toString();
        modestMessage.subject = msg.arguments()[7].toString();
        modestMessage.dateReceived = msg.arguments()[8].toLongLong();
        modestMessage.dateSent = msg.arguments()[9].toLongLong();
        modestMessage.size = msg.arguments()[10].toLongLong();
        modestMessage.flags = static_cast<MessagingModestMessageFlags>(msg.arguments()[11].toUInt());
        modestMessage.priority = static_cast<MessagingModestMessagePriority>(msg.arguments()[12].toUInt());

        QVariant variant = msg.arguments()[13];
        QDBusArgument argument = variant.value<QDBusArgument>();
        argument >> modestMessage.mimeParts;
    } else {
        modestMessage.dateReceived = 0;
        modestMessage.dateSent = 0;
        modestMessage.size = 0;
        modestMessage.flags = MessagingModestMessageNotDefined;
        modestMessage.priority = MessagingModestMessagePriorityNotDefined;
    }

    return modestMessage;
}

void ModestEngine::updateEmailAccounts() const
{
    iDefaultEmailAccountId = QMessageAccountId();
    iAccounts.clear();

    GSList *accounts;
    GError *error = NULL;

    accounts = gconf_client_all_dirs(m_gconfclient, MODESTENGINE_ACCOUNT_NAMESPACE, &error);
    if (error) {
#ifdef MESSAGING_DEBUG
        qDebug("qtmessaging: failed to list '%s': '%s'", MODESTENGINE_ACCOUNT_NAMESPACE, error->message);
#endif
        g_error_free(error);
    } else {
        gchar *default_account_id = gconf_client_get_string(m_gconfclient, MODESTENGINE_DEFAULT_ACCOUNT, &error);
        if (error) {
            qWarning("qtmessaging: failed to get '%s': %s", MODESTENGINE_DEFAULT_ACCOUNT, error->message);
            g_error_free(error);
        }

        const size_t prefix_len = strlen(MODESTENGINE_ACCOUNT_NAMESPACE) + 1;

        GSList *iter = accounts;
        while (iter) {
            if (!(iter->data)) {
                iter = iter->next;
                continue;
            }

            const gchar* account_key = (const gchar*)iter->data;
            // account_key = /apps/modest/server_accounts/<account id>
            // => take account id from account_key & unescape account id
            gchar* unescaped_account_id = gconf_unescape_key(account_key+prefix_len, strlen(account_key)-prefix_len);

            gboolean account_ok = FALSE;
            // Check if account is enabled
            if (account_key) {
                gchar* key = g_strconcat(account_key, "/", MODESTENGINE_ACCOUNT_ENABLED, NULL);
                account_ok = gconf_client_get_bool(m_gconfclient, key, NULL);
                g_free(key);
            }

            // Check if account store is defined
            if (account_ok) {
                gchar* key = g_strconcat(account_key, "/", MODESTENGINE_ACCOUNT_STORE_ACCOUNT, NULL);
                gchar* server_account_name = gconf_client_get_string(m_gconfclient, key, NULL);
                if (server_account_name) {
                    gchar* escaped_server_account_name = gconf_escape_key(server_account_name, strlen(server_account_name));
                    g_free(server_account_name);
                    gchar* store_account_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_server_account_name, NULL);
                    if (!gconf_client_dir_exists(m_gconfclient, store_account_key, NULL)) {
                        account_ok = FALSE;
                    }
                    g_free(store_account_key);
                    g_free(escaped_server_account_name);
                }
                g_free(key);
            }

            // Check if account transport is defined
            if (account_ok) {
                gchar* key = g_strconcat(account_key, "/", MODESTENGINE_ACCOUNT_TRANSPORT_ACCOUNT, NULL);
                gchar* server_account_name = gconf_client_get_string(m_gconfclient, key, NULL);
                if (server_account_name) {
                    gchar* escaped_server_account_name = gconf_escape_key(server_account_name, strlen(server_account_name));
                    g_free(server_account_name);
                    gchar* transport_account_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_server_account_name, NULL);
                    if (!gconf_client_dir_exists(m_gconfclient, transport_account_key, NULL)) {
                        account_ok = FALSE;
                    }
                    g_free(transport_account_key);
                    g_free(escaped_server_account_name);
                }
                g_free(key);
            }

            if (account_ok) {
                gchar* escaped_account_id = gconf_escape_key(unescaped_account_id, strlen(unescaped_account_id));
                QString accountId = "MO_"+QString::fromUtf8(escaped_account_id);
                g_free(escaped_account_id);

                gchar* name_key = g_strconcat(account_key, "/", MODESTENGINE_ACCOUNT_DISPLAY_NAME, NULL);
                gchar* account_name = gconf_client_get_string(m_gconfclient, name_key, NULL);
                QString accountName = QString::fromUtf8(account_name);
                g_free(account_name);
                g_free(name_key);

                gchar* email_key = g_strconcat(account_key, "/", MODESTENGINE_ACCOUNT_EMAIL, NULL);
                gchar* email = gconf_client_get_string(m_gconfclient, email_key, NULL);
                QString accountAddress = QString::fromUtf8(email);
                g_free(email);
                g_free(email_key);

                QMessageAccount account = QMessageAccountPrivate::from(QMessageAccountId(accountId),
                                                                       accountName,
                                                                       QMessageAddress(QMessageAddress::Email, accountAddress),
                                                                       QMessage::Email);
                iAccounts.insert(accountId, account);

                // Check if newly added account is default account
                if (default_account_id) {
                    if (!strncmp(default_account_id, unescaped_account_id, strlen(default_account_id))) {
                        iDefaultEmailAccountId = accountId;
                    }
                }
            }

            g_free(unescaped_account_id);
            g_free(iter->data);
            iter->data = NULL;
            iter = g_slist_next(iter);
        }
        // strings were freed in while loop
        // => it's enough to just free accounts list
        g_slist_free(accounts);
        g_free(default_account_id);
    }
}

QMessageAccountIdList ModestEngine::queryAccounts(const QMessageAccountFilter &filter, const QMessageAccountSortOrder &sortOrder,
                                                  uint limit, uint offset, bool &isFiltered, bool &isSorted) const
{
    Q_UNUSED(sortOrder)
    Q_UNUSED(limit)
    Q_UNUSED(offset)

    QMessageAccountIdList accountIds;

    if (!m_QtmPluginDBusInterface->isValid()) {
        return accountIds;
    }

    updateEmailAccounts();
    foreach (QMessageAccount value, iAccounts) {
        accountIds.append(value.id());
    }

    MessagingHelper::filterAccounts(accountIds, filter);
    isFiltered = true;

    isSorted = false;

    return accountIds;
}

int ModestEngine::countAccounts(const QMessageAccountFilter &filter) const
{
    bool isFiltered, isSorted;
    return queryAccounts(filter, QMessageAccountSortOrder(), 0, 0, isFiltered, isSorted).count();
}

QMessageAccount ModestEngine::account(const QMessageAccountId &id) const
{
    updateEmailAccounts();
    return iAccounts[id.toString()];
}

QMessageAccountId ModestEngine::defaultAccount() const
{
    updateEmailAccounts();
    return iDefaultEmailAccountId;
}

QFileInfoList ModestEngine::localFolders() const
{
    QDir dir(localRootFolder());
    dir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
    QFileInfoList fileInfoList = dir.entryInfoList();
    appendLocalSubFolders(fileInfoList, 0);
    return fileInfoList;
}

void ModestEngine::appendLocalSubFolders(QFileInfoList& fileInfoList, int startIndex) const
{
    int endIndex = fileInfoList.count();
    for (int i=startIndex; i < endIndex; i++) {
        QDir dir(fileInfoList[i].absoluteFilePath());
        if (dir.exists()) {
            dir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
            QFileInfoList dirs = dir.entryInfoList();
            for (int j = 0; j < dirs.count(); j++) {
                QString fileName = dirs[j].fileName();
                if (fileName != "cur" && fileName != "new" && fileName != "tmp") {
                    fileInfoList.append(dirs[j]);
                }
            }
        }
    }
    if (fileInfoList.count() > endIndex) {
        appendLocalSubFolders(fileInfoList, endIndex);
    }
}

void ModestEngine::appendIMAPSubFolders(QFileInfoList& fileInfoList, int startIndex) const
{
    int endIndex = fileInfoList.count();
    for (int i=startIndex; i < endIndex; i++) {
        QDir dir(fileInfoList[i].absoluteFilePath()+QString("/subfolders"));
        if (dir.exists()) {
            dir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
            fileInfoList.append(dir.entryInfoList());
        }
    }
    if (fileInfoList.count() > endIndex) {
        appendIMAPSubFolders(fileInfoList, endIndex);
    }
}

QString ModestEngine::localRootFolder() const
{
    return QDir::home().absolutePath()+QString("/.modest/local_folders");
}

QString ModestEngine::accountRootFolder(QMessageAccountId& accountId) const
{
    QString modestAccountId = escapeString(modestAccountIdFromAccountId(accountId));

    QString userName;
    QString hostName;
    QString port;
    QString protocol;

    gchar* store_account_key = g_strconcat(MODESTENGINE_ACCOUNT_NAMESPACE, "/", modestAccountId.toUtf8().data(), "/", MODESTENGINE_ACCOUNT_STORE_ACCOUNT, NULL);
    gchar* store_account_name = gconf_client_get_string(m_gconfclient, store_account_key, NULL);
    g_free(store_account_key);
    if (store_account_name) {
        gchar* escaped_store_account_name = gconf_escape_key(store_account_name, strlen(store_account_name));
        g_free(store_account_name);

        gchar* username_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_store_account_name, "/", MODESTENGINE_ACCOUNT_USERNAME, NULL);
        gchar* account_username = gconf_client_get_string(m_gconfclient, username_key, NULL);
        userName = QString::fromUtf8(account_username);
        g_free(account_username);
        g_free(username_key);

        gchar* hostname_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_store_account_name, "/", MODESTENGINE_ACCOUNT_HOSTNAME, NULL);
        gchar* account_hostname = gconf_client_get_string(m_gconfclient, hostname_key, NULL);
        hostName = QString::fromUtf8(account_hostname);
        g_free(account_hostname);
        g_free(hostname_key);

        gchar* port_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_store_account_name, "/", MODESTENGINE_ACCOUNT_PORT, NULL);
        gint account_port = gconf_client_get_int(m_gconfclient, port_key, NULL);
        port = QString::number(account_port);
        g_free(port_key);

        gchar* protocol_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_store_account_name, "/", MODESTENGINE_ACCOUNT_PROTOCOL, NULL);
        gchar* account_protocol = gconf_client_get_string(m_gconfclient, protocol_key, NULL);
        protocol = QString::fromUtf8(account_protocol);
        g_free(account_protocol);
        g_free(protocol_key);

        g_free(escaped_store_account_name);

        if (protocol == "pop") {
            return QDir::home().absolutePath()+"/.modest/cache/mail/"+protocol+"/"+userName+"__"+hostName+"_"+port;
        } else if (protocol == "imap") {
            return QDir::home().absolutePath()+"/.modest/cache/mail/"+protocol+"/"+userName+"__"+hostName+"_"+port+"/folders";
        }
    }

    return QString();
}

QFileInfoList ModestEngine::accountFolders(QMessageAccountId& accountId) const
{
    QFileInfoList fileInfoList;

    EmailProtocol protocol = accountEmailProtocol(accountId);

    if (protocol == ModestEngine::EmailProtocolPop3) {
        QFileInfo fileInfo = QFileInfo(accountRootFolder(accountId)+"/cache");
        fileInfoList.append(fileInfo);
    } else if (protocol == ModestEngine::EmailProtocolIMAP) {
        QDir dir(accountRootFolder(accountId));
        dir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
        fileInfoList = dir.entryInfoList();
        appendIMAPSubFolders(fileInfoList, 0);
    }

    return fileInfoList;
}

ModestEngine::EmailProtocol ModestEngine::accountEmailProtocol(QMessageAccountId& accountId) const
{
    EmailProtocol protocol = EmailProtocolUnknown;

    QString modestAccountId = escapeString(modestAccountIdFromAccountId(accountId));

    gchar* store_account_key = g_strconcat(MODESTENGINE_ACCOUNT_NAMESPACE, "/", modestAccountId.toUtf8().data(), "/", MODESTENGINE_ACCOUNT_STORE_ACCOUNT, NULL);
    gchar* store_account_name = gconf_client_get_string(m_gconfclient, store_account_key, NULL);
    g_free(store_account_key);
    if (store_account_name) {
        gchar* escaped_store_account_name = gconf_escape_key(store_account_name, strlen(store_account_name));
        g_free(store_account_name);

        gchar* protocol_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_store_account_name, "/", MODESTENGINE_ACCOUNT_PROTOCOL, NULL);
        gchar* account_protocol = gconf_client_get_string(m_gconfclient, protocol_key, NULL);
        if (account_protocol) {
            if (QString("pop") == account_protocol) {
                protocol = EmailProtocolPop3;
            } else if (QString("imap") == account_protocol) {
                protocol = EmailProtocolIMAP;
            }
            g_free(account_protocol);
        }
        g_free(protocol_key);

        g_free(escaped_store_account_name);
    }

    return protocol;
}

bool ModestEngine::accountExists(const QMessageAccountId& accountId) const
{
    bool retVal = false;

    QString modestAccountId = escapeString(modestAccountIdFromAccountId(accountId));
    gchar* account_key = g_strconcat(MODESTENGINE_ACCOUNT_NAMESPACE, "/", modestAccountId.toUtf8().data(), NULL);
    retVal = gconf_client_dir_exists(m_gconfclient, account_key, NULL);
    g_free(account_key);

    return retVal;
}

QString ModestEngine::accountEmailProtocolAsString(const QMessageAccountId& accountId) const
{
    QString protocol;

    QString modestAccountId = escapeString(modestAccountIdFromAccountId(accountId));

    gchar* store_account_key = g_strconcat(MODESTENGINE_ACCOUNT_NAMESPACE, "/", modestAccountId.toUtf8().data(), "/", MODESTENGINE_ACCOUNT_STORE_ACCOUNT, NULL);
    gchar* store_account_name = gconf_client_get_string(m_gconfclient, store_account_key, NULL);
    g_free(store_account_key);
    if (store_account_name) {
        gchar* escaped_store_account_name = gconf_escape_key(store_account_name, strlen(store_account_name));
        g_free(store_account_name);

        gchar* protocol_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_store_account_name, "/", MODESTENGINE_ACCOUNT_PROTOCOL, NULL);
        gchar* account_protocol = gconf_client_get_string(m_gconfclient, protocol_key, NULL);
        if (account_protocol) {
            protocol = QString::fromUtf8(account_protocol);
            g_free(account_protocol);
        }
        g_free(protocol_key);

        g_free(escaped_store_account_name);
    }

    return protocol;
}

QString ModestEngine::accountUsername(QMessageAccountId& accountId) const
{
    QString username;

    QString modestAccountId = escapeString(modestAccountIdFromAccountId(accountId));

    gchar* store_account_key = g_strconcat(MODESTENGINE_ACCOUNT_NAMESPACE, "/", modestAccountId.toUtf8().data(), "/", MODESTENGINE_ACCOUNT_STORE_ACCOUNT, NULL);
    gchar* store_account_name = gconf_client_get_string(m_gconfclient, store_account_key, NULL);
    g_free(store_account_key);
    if (store_account_name) {
        gchar* escaped_store_account_name = gconf_escape_key(store_account_name, strlen(store_account_name));
        g_free(store_account_name);

        gchar* username_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_store_account_name, "/", MODESTENGINE_ACCOUNT_USERNAME, NULL);
        gchar* account_username = gconf_client_get_string(m_gconfclient, username_key, NULL);
        username = QString::fromUtf8(account_username);
        g_free(account_username);
        g_free(username_key);

        g_free(escaped_store_account_name);
    }

    return username;
}

QString ModestEngine::accountHostname(QMessageAccountId& accountId) const
{
    QString host;

    QString modestAccountId = escapeString(modestAccountIdFromAccountId(accountId));

    gchar* store_account_key = g_strconcat(MODESTENGINE_ACCOUNT_NAMESPACE, "/", modestAccountId.toUtf8().data(), "/", MODESTENGINE_ACCOUNT_STORE_ACCOUNT, NULL);
    gchar* store_account_name = gconf_client_get_string(m_gconfclient, store_account_key, NULL);
    g_free(store_account_key);
    if (store_account_name) {
        gchar* escaped_store_account_name = gconf_escape_key(store_account_name, strlen(store_account_name));
        g_free(store_account_name);

        gchar* host_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_store_account_name, "/", MODESTENGINE_ACCOUNT_HOSTNAME, NULL);
        gchar* account_host = gconf_client_get_string(m_gconfclient, host_key, NULL);
        host = QString::fromUtf8(account_host);
        g_free(account_host);
        g_free(host_key);

        g_free(escaped_store_account_name);
    }

    return host;
}

void ModestEngine::foldersFromModest(QList<MessagingModestFolder>& folders) const
{
    if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) {
        return;
    }

    QDBusPendingCall pendingCall = m_QtmPluginDBusInterface->asyncCall("GetFolders");
    QDBusPendingCallWatcher pendingCallWatcher(pendingCall);
    pendingCallWatcher.waitForFinished();

    QDBusMessage msg = pendingCallWatcher.reply();

    if (msg.type() == QDBusMessage::ReplyMessage) {
        QVariant variant = msg.arguments()[0];
        QDBusArgument argument = variant.value<QDBusArgument>();
        argument >> folders;
    }
}

QMessageFolderIdList ModestEngine::queryFolders(const QMessageFolderFilter &filter, const QMessageFolderSortOrder &sortOrder,
                                                uint limit, uint offset, bool &isFiltered, bool &isSorted) const
{
    Q_UNUSED(sortOrder)
    Q_UNUSED(limit)
    Q_UNUSED(offset)

    QMessageFolderIdList folderIds;

    if (!m_QtmPluginDBusInterface->isValid()) {
        return folderIds;
    }

    updateEmailAccounts();

    if (iAccounts.isEmpty()) {
        return folderIds;
    }

    m_folderCache.clear();

    QList<MessagingModestFolder> modestFolders;
    foldersFromModest(modestFolders);

    if (modestFolders.count() > 0) {
        foreach (QMessageAccount value, iAccounts) {
            QMessageAccountId accountId = value.id();
            // Add folder to cache
            QString modestAccountId = modestAccountIdFromAccountId(accountId);
            for (int j=0; j < modestFolders.count(); j++) {
                if (modestFolders[j].parentAccountId == modestAccountId) {
                    MessagingModestFolder folder = modestFolders[j];
                    QString id = accountId.toString()+"&"+accountEmailProtocolAsString(accountId)+"&"+folder.modestId;
                    folder.id = id;
                    m_folderCache.insert(id, folder);

                    folderIds.append(QMessageFolderId(id));
                }
            }
            for (int j=0; j < modestFolders.count(); j++) {
                if (modestFolders[j].parentAccountId == "local_folders") {
                    MessagingModestFolder folder = modestFolders[j];
                    QString id = accountId.toString()+"&"+"maildir"+"&"+folder.modestId;
                    folder.id = id;
                    m_folderCache.insert(id, folder);

                    folderIds.append(QMessageFolderId(id));
                }
            }
        }
    } else {
        QFileInfoList localFolders = this->localFolders();
        QString localRootFolder = this->localRootFolder();

        foreach (QMessageAccount value, iAccounts) {
            QMessageAccountId accountId = value.id();
            QString rootFolder = accountRootFolder(accountId);
            QFileInfoList folders = this->accountFolders(accountId);

            for (int i=0; i < folders.count(); i++) {
                QString filePath = folders[i].absoluteFilePath();
                QString modestId = filePath.right(filePath.size()-rootFolder.size()-1);
                modestId = modestId.remove("/subfolders");
                QString id = accountId.toString()+"&"+accountEmailProtocolAsString(accountId)+"&"+modestId;
                folderIds.append(QMessageFolderId(id));

                // Add folder to cache
                MessagingModestFolder folder;
                folder.id = id;
                folder.modestId = modestId;
                if (modestId.lastIndexOf('/') == -1) {
                    // Folder does not have subfolders
                    folder.name = modestId;
                    if ((accountEmailProtocolAsString(accountId) == "pop") && (folder.name == "cache")) {
                        folder.name = "Inbox";
                    }
                } else {
                    // Folder has subfolders
                    folder.name = modestId.right(modestId.length()-modestId.lastIndexOf('/')-1);
                    folder.parentFolderId = modestId.left(modestId.lastIndexOf('/'));
                }
                folder.parentAccountId = modestAccountIdFromAccountId(accountId);
                folder.type = MessagingModestFolderTypeUnknown;
                m_folderCache.insert(id, folder);
            }

            // Each account sees local folders as account folders
            for (int i=0; i < localFolders.count(); i++) {
                QString filePath = localFolders[i].absoluteFilePath();
                QString modestId = filePath.right(filePath.size()-localRootFolder.size()-1);
                QString id = accountId.toString()+"&"+"maildir"+"&"+modestId;
                folderIds.append(QMessageFolderId(id));

                // Add folder to cache
                MessagingModestFolder folder;
                folder.id = id;
                folder.modestId = modestId;
                if (modestId.lastIndexOf('/') == -1) {
                    // Folder does not have subfolders
                    folder.name = modestId;
                    if ((accountEmailProtocolAsString(accountId) == "pop") && (folder.name == "cache")) {
                        folder.name = "Inbox";
                    }
                } else {
                    // Folder has subfolders
                    folder.name = modestId.right(modestId.length()-modestId.lastIndexOf('/')-1);
                    folder.parentFolderId = modestId.left(modestId.lastIndexOf('/'));
                }
                folder.parentAccountId = "local_folders";
                folder.type = MessagingModestFolderTypeUnknown;
                m_folderCache.insert(id, folder);
            }
        }
    }

    MessagingHelper::filterFolders(folderIds, filter);
    isFiltered = true;
    isSorted = false;
    return folderIds;
}

int ModestEngine::countFolders(const QMessageFolderFilter &filter) const
{
    bool isFiltered, isSorted;
    return queryFolders(filter, QMessageFolderSortOrder(), 0, 0, isFiltered, isSorted).count();
}

QMessageFolder ModestEngine::folder(const QMessageFolderId &id) const
{
    QMessageFolder folder;

    QString idString = id.toString();
    if (m_folderCache.contains(idString)) {
        MessagingModestFolder modestFolder = m_folderCache.value(idString);
        int endOfAccountId = idString.indexOf('&');
        QString accountId = idString.left(endOfAccountId);
        QString folderPath = idString.right(idString.length()-idString.lastIndexOf('&')-1);
        QMessageFolderId parentId;
        QString name;
        if (folderPath.lastIndexOf('/') != -1) {
            // Folder has subfolders
            parentId = idString.left(idString.lastIndexOf('/'));
        }

        if (modestFolder.localizedName.isEmpty()) {
            folder = QMessageFolderPrivate::from(id,
                                                 QMessageAccountId(accountId),
                                                 parentId,
                                                 modestFolder.name,
                                                 folderPath);
        } else {
            folder = QMessageFolderPrivate::from(id,
                                                 QMessageAccountId(accountId),
                                                 parentId,
                                                 modestFolder.localizedName,
                                                 folderPath);
        }
    }

    return folder;
}

bool ModestEngine::startObservingModestFolder(const QMessageFolderId& folderId) const
{
    if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) {
        return false;
    }

    if (!m_observed_folders.contains(folderId)) {
        QDBusPendingCall pendingCall = m_QtmPluginDBusInterface->asyncCall("AddFolderObserver",
                                                                           QVariant::fromValue(modestAccountIdFromFolderId(folderId)),
                                                                           QVariant::fromValue(modestFolderIdFromFolderId(folderId)));
        QDBusPendingCallWatcher pendingCallWatcher(pendingCall);
        pendingCallWatcher.waitForFinished();

        QDBusMessage msg = pendingCallWatcher.reply();

        if (msg.type() == QDBusMessage::ReplyMessage) {
            if (m_observed_folders.count() == 0) {
                m_QtmPluginDBusInterface->connection().connect(MODESTENGINE_QTM_PLUGIN_NAME,
                                                               MODESTENGINE_QTM_PLUGIN_PATH,
                                                               MODESTENGINE_QTM_PLUGIN_NAME,
                                                               "FolderContentsChanged",
                                                               (ModestEngine*)this,
                                                               SLOT(modestFolderContentsChangedSlot(QDBusMessage)));
            }

            m_observed_folders.append(folderId);

            return true;
        }
    } else {
        return true;
    }

    return false;
}

bool ModestEngine::stopObservingModestFolder(const QMessageFolderId& folderId) const
{
    if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) {
        return false;
    }

    if (m_observed_folders.contains(folderId)) {
        QDBusPendingCall pendingCall = m_QtmPluginDBusInterface->asyncCall("RemoveFolderObserver",
                                                                           QVariant::fromValue(modestAccountIdFromFolderId(folderId)),
                                                                           QVariant::fromValue(modestFolderIdFromFolderId(folderId)));
        QDBusPendingCallWatcher pendingCallWatcher(pendingCall);
        pendingCallWatcher.waitForFinished();

        QDBusMessage msg = pendingCallWatcher.reply();

        if (msg.type() == QDBusMessage::ReplyMessage) {
            m_observed_folders.removeOne(folderId);

            if (m_observed_folders.count() == 0) {
                m_QtmPluginDBusInterface->connection().disconnect(MODESTENGINE_QTM_PLUGIN_NAME,
                                                                  MODESTENGINE_QTM_PLUGIN_PATH,
                                                                  MODESTENGINE_QTM_PLUGIN_NAME,
                                                                  "FolderContentsChanged",
                                                                  (ModestEngine*)this,
                                                                  SLOT(modestFolderContentsChangedSlot(QDBusMessage)));
            }

            return true;
        }
    }

    return false;
}

void ModestEngine::modestFolderContentsChangedSlot(QDBusMessage msg)
{
    QList<QVariant> arguments = msg.arguments();

    QString modestAccountId = arguments.takeFirst().toString();
    QString modestFolderId = arguments.takeFirst().toString();
    QMessageAccountId accountId = accountIdFromModestAccountId(modestAccountId);
    QString protocol = accountEmailProtocolAsString(accountId);
    if ((protocol == "pop") && (modestFolderId == "INBOX")) {
        modestFolderId = "cache";
    }
    QString folderId = accountId.toString()+"&"+accountEmailProtocolAsString(accountId)+"&"+modestFolderId;

    qlonglong change = arguments.takeFirst().toLongLong();
    QVariant variant = arguments.takeFirst();
    QList<QString> messageIds = variant.toStringList();
    if (change == 4) { // Headers added
        for (int i = 0; i < messageIds.count(); i++) {
            QMessageId messageId = folderId+"/"+messageIds[i];
            notification(messageId, ModestEngine::Added);
        }
    }/* else if (change == 8) { // Headers removed
        for (int i = 0; i < messageIds.count(); i++) {
            QMessageId messageId = folderId+"/"+messageIds[i];
            notification(messageId, ModestEngine::Removed);
        }
    }*/
}

void ModestEngine::watchAllKnownEmailFolders()
{
    QFileInfoList localFolders = this->localFolders();

    // Changes in local Email folders can be monitored using directory
    // monitoring. <=> All messages are stored as individual files.
    for (int i=0; i < localFolders.count(); i++) {
        m_MailFoldersWatcher.addDirectory(localFolders[i].absoluteFilePath()+"/cur", IN_MOVED_TO | IN_DELETE);
    }

    // Monitor changes also in root folder to see if new folders are added
    m_MailFoldersWatcher.addDirectory(localRootFolder(), IN_CREATE);

    // Changes in remote Email folders must be monitored using file monitoring.
    // That's because message headers are stored into summary.mmap file (and summary.mmap
    // file maybe the only place that contains message information).
    // => summary.mmap files will be monitored
    foreach (QMessageAccount value, iAccounts) {
        QMessageAccountId accountId = value.id();
        QString rootFolder = accountRootFolder(accountId);
        EmailProtocol protocol = accountEmailProtocol(accountId);
        QFileInfoList folders = this->accountFolders(accountId);

        for (int i=0; i < folders.count(); i++) {
            if (protocol == ModestEngine::EmailProtocolPop3) {
                QString folder = folders[i].absoluteFilePath();
                m_MailFoldersWatcher.addDirectory(folder, IN_CREATE);
            } else if (protocol == ModestEngine::EmailProtocolIMAP) {
                m_MailFoldersWatcher.addDirectory(folders[i].absoluteFilePath(), IN_MOVED_TO | IN_DELETE);
            }
        }

    }
}

void ModestEngine::fileChangedSlot(int watchDescriptor, QString filePath, uint events)
{
    Q_UNUSED(watchDescriptor)

    if (events & IN_CREATE) {
        if (QFileInfo(filePath).isDir()) {
            // New directory was added
            // => Start watching new folder
            QString newDirPath = QString(filePath.toUtf8());
            m_MailFoldersWatcher.addDirectory(newDirPath + "/cur");
            return;
        }
    }

    int filenameBegin = filePath.lastIndexOf('/')+1;
    QString fileName = filePath.mid(filenameBegin,filePath.lastIndexOf('.')-filenameBegin);
    if (!fileName.endsWith("summary")) {
        if (events & (IN_MOVED_TO | IN_CREATE)) {
            if (events != (IN_MOVED_TO | IN_MOVED_FROM)) {
                // Wait a moment to make sure that Modest has finished adding message
                QEventLoop eventLoop;
                QTimer::singleShot(100, &eventLoop, SLOT(quit()));
                eventLoop.exec();
                notification(messageIdFromModestMessageFilePath(filePath), ModestEngine::Added);
            }
        } else if (events & IN_DELETE) {
            notification(messageIdFromModestMessageFilePath(filePath), ModestEngine::Removed);
        }
    }
}

bool ModestEngine::sendEmail(QMessage &message)
{
    if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) {
        return false;
    }

    ModestStringMap senderInfo;
    ModestStringMap recipients;
    ModestStringMap messageData;
    ModestStringMapList attachments;
    ModestStringMapList images;
    uint priority = 0;
    ModestStringMap headers;

    senderInfo = getModestSenderInfo(message);

    if (senderInfo.isEmpty()) {
        return false;
    }

    recipients = getModestRecipients(message);

    if (recipients.isEmpty()) {
        return false;
    }

    messageData = getModestMessageData(message);
    attachments = getModestAttachments(message);
    images = getModestImages(message);
    priority = getModestPriority(message);
    headers = getModestHeaders(message);

    qDebug() << "Sending D-BUS message";

    QDBusPendingCall call = m_QtmPluginDBusInterface->asyncCall (
            "SendEmail",
            QVariant::fromValue (senderInfo),
            QVariant::fromValue (recipients),
            QVariant::fromValue (messageData),
            QVariant::fromValue (attachments),
            QVariant::fromValue (images),
            priority,
            QVariant::fromValue (headers));

    qDebug() << "Message sent";

    if (call.isError()) {
        qWarning() << "Call failed! " << call.error();
        return false;
    }

    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher (call, this);

    connect (watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
             this, SLOT(sendEmailCallEnded(QDBusPendingCallWatcher*)));

    return true;
}

bool ModestEngine::composeEmail(const QMessage &message)
{
    QString mailString("mailto:");
    QList<QMessageAddress> list(message.to());
    if (!list.empty()){
        for (int i = 0; i < list.size(); ++i) {
            if (i == 0) {
                mailString += list.at(i).addressee();
            } else {
                mailString += QString("%2C%20");
                mailString += list.at(i).addressee();
            }
        }
    }

    bool firstParameter = true;

    QString subject = message.subject();
    if (!subject.isEmpty()) {
        if (firstParameter) {
            mailString += QString("?");
            firstParameter = false;
        } else {
            mailString += QString("&");
        }
        mailString += QString("subject=");
        mailString += subject;
    }

    QString body = message.textContent();
    if (!body.isEmpty()) {
        if (firstParameter) {
            mailString += QString("?");
            firstParameter = false;
        } else {
            mailString += QString("&");
        }
        mailString += QString("body=");
        mailString += QString(body);
    }

    QUrl url(mailString);

    QDBusInterface dbusif(MODEST_DBUS_SERVICE, MODEST_DBUS_OBJECT, MODEST_DBUS_IFACE);
    QDBusMessage msg = dbusif.call(MODEST_DBUS_METHOD_MAIL_TO, url.toString());

    return true;
}

bool ModestEngine::showMessage(const QMessageId &id)
{
    QMessage msg = message(id, false);
    QMessagePrivate* privateMessage = QMessagePrivate::implementation(msg);
    if (privateMessage->_url.isEmpty()) {
        return false;
    } else {
        m_ModestDBusInterface->call(MODEST_DBUS_METHOD_OPEN_MESSAGE,
                                    privateMessage->_url);
    }
    return true;
}

bool ModestEngine::exportUpdates(const QMessageAccountId &id)
{
    Q_UNUSED(id) // Modest does not offer Account specific updates

    m_ModestDBusInterface->call(MODEST_DBUS_METHOD_SEND_RECEIVE);
    return true;
}

bool ModestEngine::retrieveMessageMimeInformation(QMessage& message)
{
    QMessagePrivate* privateMessage = QMessagePrivate::implementation(message);
    if (!privateMessage->_mimeInformationRetrieved) {
        QString modestAccountId = modestAccountIdFromMessageId(message.id());
        QString modestFolderId = modestFolderIdFromMessageId(message.id());
        QString modestMessageId = modestMessageIdFromMessageId(message.id());

        MessagingModestMessage modestMessage = messageFromModest(modestAccountId,
                                                                 modestFolderId,
                                                                 modestMessageId);

        if (modestMessage.flags & MessagingModestMessageDeleted) {
            return false;
        }

        if (modestMessage.accountId.isEmpty()) {
            return false;
        }

        privateMessage->_mimeInformationRetrieved = true;

        mimeInformationFromModestMessageToMessage(modestMessage, message);
        MessageCache::instance()->insert(message);
    }

    return true;
}

QMessage ModestEngine::message(const QMessageId &id, bool useCache) const
{
    QMessage message;

    if (useCache) {
        message = MessageCache::instance()->message(id.toString());
    }

    if (message.type() == QMessage::NoType) {
        QString modestAccountId = modestAccountIdFromMessageId(id);
        QString modestFolderId = modestFolderIdFromMessageId(id);
        QString modestMessageId = modestMessageIdFromMessageId(id);

        MessagingModestMessage modestMessage = messageFromModest(modestAccountId,
                                                                 modestFolderId,
                                                                 modestMessageId);

        if (modestMessage.flags & MessagingModestMessageDeleted) {
            return QMessage();
        }

        if (modestMessage.accountId.isEmpty()) {
            return QMessage();
        }

        message = messageFromModestMessage(modestMessage, accountIdFromMessageId(id));
        MessageCache::instance()->insert(message);
    }

    return message;
}

QMessage::StandardFolder ModestEngine::standardFolderFromModestFolderId(const QString& modestFolderId) const
{
    if (!QString::compare(modestFolderId, "INBOX", Qt::CaseInsensitive)) {
        return QMessage::InboxFolder;
    } else if (!QString::compare(modestFolderId, "drafts", Qt::CaseInsensitive)) {
        return QMessage::DraftsFolder;
    } else if (!QString::compare(modestFolderId, "sent", Qt::CaseInsensitive)) {
        return QMessage::SentFolder;
    }

    return QMessage::DraftsFolder;
}

QString ModestEngine::modestFolderIdFromStandardFolder(QMessage::StandardFolder standardFolder) const
{
    switch (standardFolder) {
    case QMessage::InboxFolder:  return "INBOX";
    case QMessage::DraftsFolder: return "drafts";
    case QMessage::SentFolder:   return "sent";
    case QMessage::OutboxFolder: return "outbox";
    case QMessage::TrashFolder:  return "thrash";
    }
    return "drafts";
}

QMessage ModestEngine::messageFromModestMessage(const MessagingModestMessage& modestMessage,
                                                QMessageAccountId accountId) const
{
    QMessage message;
    QMessagePrivate* privateMessage = QMessagePrivate::implementation(message);

    // Type
    message.setType(QMessage::Email);

    // Parent Account Id
    QMessageAccountId parentAccountId;
    if (accountId.isValid()) {
        parentAccountId = accountId;
    } else {
        parentAccountId = realAccountId(modestMessage);
    }
    message.setParentAccountId(parentAccountId);

    // Parent Folder Id
    QMessageFolderId folderId;
    if (modestMessage.accountId == "local_folders") {
        folderId = folderIdFromModestFolderId(parentAccountId, true, modestMessage.folderId);
    } else {
        // Since Message is not in local folder, message status can be set to Incoming
        privateMessage->_status = privateMessage->_status | QMessage::Incoming;
        folderId = folderIdFromModestFolderId(parentAccountId, false, modestMessage.folderId);
    }
    privateMessage->_parentFolderId = folderId;

    // Message Id
    QMessageId messageId = QMessageId(folderId.toString()+"/"+modestMessage.id);
    privateMessage->_id = messageId;

    // Dates
    message.setDate(QDateTime::fromTime_t(modestMessage.dateSent));
    message.setReceivedDate(QDateTime::fromTime_t(modestMessage.dateReceived));

    // Priority
    switch (modestMessage.priority) {
    case QtMobility::MessagingModestMessageHighPriority:
        message.setPriority(QMessage::HighPriority);
        break;
    case QtMobility::MessagingModestMessageNormalPriority:
        message.setPriority(QMessage::NormalPriority);
        break;
    case QtMobility::MessagingModestMessageLowPriority:
        message.setPriority(QMessage::LowPriority);
        break;
    }

    // Standard Folder
    QMessagePrivate::setStandardFolder(message,
                                       standardFolderFromModestFolderId(modestMessage.folderId));

    // MIME information (for actual message & attachments)
    mimeInformationFromModestMessageToMessage(modestMessage, message);

    // From
    if (modestMessage.from.size() > 0) {
        message.setFrom(QMessageAddress(QMessageAddress::Email, modestMessage.from));
        QMessagePrivate::setSenderName(message, modestMessage.from);
    }

    // To
    if (modestMessage.to.size() > 0) {
        QMessageAddressList toAddresses;
        foreach (const QString &element, modestMessage.to.split(",", QString::SkipEmptyParts)) {
            QMessageAddress addr;
            addr.setType(QMessageAddress::Email);
            addr.setAddressee(element.trimmed());
            toAddresses.append(addr);
        }
        message.setTo(toAddresses);
    }

    // Cc
    if (modestMessage.cc.size() > 0) {
        QMessageAddressList ccAddresses;
        foreach (const QString &element, modestMessage.cc.split(",", QString::SkipEmptyParts)) {
            QMessageAddress addr;
            addr.setType(QMessageAddress::Email);
            addr.setAddressee(element.trimmed());
            ccAddresses.append(addr);
        }
        message.setCc(ccAddresses);
    }

    // Bcc
    if (modestMessage.bcc.size() > 0) {
        QMessageAddressList bccAddresses;
        foreach (const QString &element, modestMessage.bcc.split(",", QString::SkipEmptyParts)) {
            QMessageAddress addr;
            addr.setType(QMessageAddress::Email);
            addr.setAddressee(element.trimmed());
            bccAddresses.append(addr);
        }
        message.setBcc(bccAddresses);
    }

    // Subject
    message.setSubject(modestMessage.subject);

    // Size
    privateMessage->_size = modestMessage.size;

    // Read Status
    if (modestMessage.flags & MessagingModestMessageSeen) {
        privateMessage->_status = privateMessage->_status | QMessage::Read;
    }

    // Attachments Status
    if (modestMessage.flags & MessagingModestMessageAttachments) {
        privateMessage->_status = privateMessage->_status | QMessage::HasAttachments;
    }

    // Modest specific url
    privateMessage->_url = modestMessage.url;

    // Modified flag
    privateMessage->_modified = false;

    return message;
}

void ModestEngine::mimeInformationFromModestMessageToMessage(const MessagingModestMessage& modestMessage,
                                                             QMessage& message) const
{
    QMessagePrivate* privateMessage = QMessagePrivate::implementation(message);
    QMessageContentContainerPrivate* container = QMessagePrivate::containerImplementation(message);

    QMessageId messageId = message.id();

    // Message MIME type
    QString fullMimeType = modestMessage.mimeType;
    int slashIndex = fullMimeType.indexOf('/');
    QByteArray mimeType = fullMimeType.left(slashIndex).toAscii();
    QByteArray mimeSubType = fullMimeType.mid(slashIndex+1).toAscii();
    container->_type = mimeType.data();
    container->_subType = mimeSubType.data();

    // Body & Attachments handling
    for (int i=0; i < modestMessage.mimeParts.count(); i++) {
        if (!modestMessage.mimeParts[i].isAttachment) {
            // Body
            QByteArray fullMimeType = modestMessage.mimeParts[i].mimeType.toUtf8();
            QString contentId = modestMessage.mimeParts[i].contentId;
            QByteArray fileName = modestMessage.mimeParts[i].fileName.toAscii();

            QByteArray mainType("text");
            QByteArray subType("plain");
            QByteArray charset;

            int index = fullMimeType.indexOf("/");
            if (index != -1) {
                mainType = fullMimeType.left(index).trimmed();

                subType = fullMimeType.mid(index + 1).trimmed();
                index = subType.indexOf(";");
                if (index != -1) {
                    QString remainder = subType.mid(index + 1);
                    subType = subType.left(index).trimmed();

                    QRegExp charsetPattern("charset=(\\S+)");
                    index = charsetPattern.indexIn(remainder);
                    if (index != -1) {
                        charset = charsetPattern.cap(1).toLatin1().toUpper();
                    }
                }
            }

            if (charset.isEmpty()) {
                charset = "UTF-8";
            }

            // Accept only text type Body
            if (mainType.toLower() == "text") {
                QMessageContentContainerId existingBodyId(message.bodyId());
                if (existingBodyId.isValid()) {
                    if (existingBodyId == QMessageContentContainerPrivate::bodyContentId()) {
                        // The body content is in the message itself
                        container->_containingMessageId = messageId.toString();
                        container->_attachmentId = contentId;
                        container->_name = fileName;
                        container->_type = mainType;
                        container->_subType = subType;
                        container->_charset = charset;
                        container->_size = 0;
                        container->_available = true;
                    } else {
                        // The body content is in the first attachment
                        QMessageContentContainerPrivate *attachmentContainer(QMessageContentContainerPrivate::implementation(*container->attachment(existingBodyId)));
                        attachmentContainer->_containingMessageId = messageId.toString();
                        attachmentContainer->_attachmentId = contentId;
                        attachmentContainer->_name = fileName;
                        attachmentContainer->_type = mainType;
                        attachmentContainer->_subType = subType;
                        attachmentContainer->_charset = charset;
                        attachmentContainer->_size = 0;
                        attachmentContainer->_available = true;
                    }
                } else {
                    if (container->_attachments.isEmpty()) {
                        // Put the content directly into the message
                        container->_containingMessageId = messageId.toString();
                        container->_attachmentId = contentId;
                        container->_name = fileName;
                        container->_type = mainType;
                        container->_subType = subType;
                        container->_charset = charset;
                        container->_size = 0;
                        container->_available = true;
                        privateMessage->_bodyId = QMessageContentContainerPrivate::bodyContentId();
                    } else {
                        // Add the body as the first attachment
                        QMessageContentContainer newBody;
                        QMessageContentContainerPrivate *attachmentContainer = QMessageContentContainerPrivate::implementation(newBody);
                        attachmentContainer->_containingMessageId = messageId.toString();
                        attachmentContainer->_attachmentId = contentId;
                        attachmentContainer->_name = fileName;
                        attachmentContainer->_type = mainType;
                        attachmentContainer->_subType = subType;
                        attachmentContainer->_charset = charset;
                        attachmentContainer->_size = 0;
                        attachmentContainer->_available = true;
                        privateMessage->_bodyId = container->prependContent(newBody);
                    }
                }
            }
        } else {
            // Attachment
            QString fullMimeType = modestMessage.mimeParts[i].mimeType;
            QString contentId = modestMessage.mimeParts[i].contentId;
            int slashIndex = fullMimeType.indexOf('/');
            QByteArray mimeType = fullMimeType.left(slashIndex).toAscii();
            QByteArray mimeSubType = fullMimeType.mid(slashIndex+1).toAscii();
            // TODO: Attachment size
            QByteArray fileName = modestMessage.mimeParts[i].fileName.toAscii();
            fileName = fileName.mid(fileName.lastIndexOf('/')+1);
            QString msgId = messageId.toString();
            QMessageContentContainer attachment =
                    QMessageContentContainerPrivate::from(msgId,
                                                          contentId,
                                                          fileName,
                                                          mimeType,
                                                          mimeSubType,
                                                          0);
            appendAttachmentToMessage(message, attachment);
        }
    }
}

void ModestEngine::appendAttachmentToMessage(QMessage& message, QMessageContentContainer& attachment) const
{
    QMessagePrivate* privateMessage = QMessagePrivate::implementation(message);
    QMessageContentContainerPrivate* container = QMessagePrivate::containerImplementation(message);

    if (container->_attachments.isEmpty()) {
        QMessageContentContainerId existingBodyId(message.bodyId());
        if (existingBodyId == QMessageContentContainerPrivate::bodyContentId()) {
            // The body content is in the message itself - move it to become the first attachment
            QMessageContentContainer newBody(message);
            QMessageContentContainerPrivate::implementation(newBody)->setDerivedMessage(0);

            container->setContentType("multipart", "mixed", "");
            privateMessage->_bodyId = container->prependContent(newBody);
        } else {
            // This message is now multipart
            container->setContentType("multipart", "mixed", "");
        }

        container->_available = true;
    }

    container->appendContent(attachment);

    bool haveAttachments = !container->_attachments.isEmpty();
    message.setStatus(QMessage::HasAttachments,haveAttachments);

    privateMessage->_modified = true;
}

bool ModestEngine::addMessage(QMessage &message)
{
    if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) {
        return false;
    }

    QString modestFolder;
    ModestStringMap senderInfo;
    ModestStringMap recipients;
    ModestStringMap messageData;
    ModestStringMapList attachments;
    ModestStringMapList images;
    uint priority = 0;
    ModestStringMap headers;

    qDebug() << __PRETTY_FUNCTION__;

    senderInfo = getModestSenderInfo (message);
    recipients = getModestRecipients (message);
    messageData = getModestMessageData (message);
    attachments = getModestAttachments (message);
    images = getModestImages (message);
    priority = getModestPriority (message);
    headers = getModestHeaders (message);

    QString accountName;
    if (message.parentFolderId().isValid()) {
        modestFolder = modestFolderIdFromFolderId (message.parentFolderId());
        accountName = modestAccountIdFromFolderId(message.parentFolderId());
    } else {
        modestFolder = modestFolderIdFromStandardFolder(message.standardFolder());
        if (message.standardFolder() == QMessage::DraftsFolder) {
            accountName = "local_folders";
        } else {
            accountName = modestAccountIdFromAccountId(message.parentAccountId());
        }
    }
    senderInfo["account-name"] = accountName;

    QList<QVariant> arguments;
    arguments << modestFolder;
    arguments << QVariant::fromValue(senderInfo);
    arguments << QVariant::fromValue(recipients);
    arguments << QVariant::fromValue(messageData);
    arguments << QVariant::fromValue(attachments);
    arguments << QVariant::fromValue(images);
    arguments << priority;
    arguments << QVariant::fromValue(headers);

    QDBusPendingCall pendingCall = m_QtmPluginDBusInterface->asyncCallWithArgumentList("AddMessage",
                                                                                       arguments);

    if (pendingCall.isError()) {
        qWarning() << "DBus call failed! " << pendingCall.error();
        return false;
    }

    QDBusPendingCallWatcher pendingCallWatcher(pendingCall);
    pendingCallWatcher.waitForFinished();
    QDBusMessage msg = pendingCallWatcher.reply();
    if (msg.type() == QDBusMessage::ErrorMessage) {
        qWarning() << "Failed to add message via modest: " << msg.errorMessage();
        return false;
    }

    QMessagePrivate* privateMessage = QMessagePrivate::implementation(message);
    QString messageId;
    if (message.parentFolderId().isValid()) {
        messageId = message.parentFolderId().toString()+"/"+msg.arguments()[0].toString();
    } else if (accountName == "local_folders") {
        messageId = message.parentAccountId().toString()+
                    "&maildir&"+modestFolder+"/"+msg.arguments()[0].toString();
    } else {
        messageId = message.parentAccountId().toString()+"&"+
                    accountEmailProtocolAsString(message.parentAccountId())+
                    "&"+modestFolder+"/"+msg.arguments()[0].toString();
    }
    privateMessage->_id = QMessageId(messageId);

    // Use update functionality to set Priority & Read status
    // <=> Modest Plugin can not set Priority & Read status
    //     immediately after message creation in add call
    updateMessage(message);

    // Make sure that there will be instant notification about added message
    notification(message.id(), ModestEngine::Added);

    return true;
}

bool ModestEngine::updateMessage(QMessage &message)
{
    if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) {
        return false;
    }

    if (!message.id().isValid()) {
        return false;
    }

    QString modestAccountId = modestAccountIdFromMessageId(message.id());
    QString modestFolderId  = modestFolderIdFromMessageId(message.id());
    QString modestMessageId = modestMessageIdFromMessageId(message.id());

    QString modestFolder;
    ModestStringMap senderInfo;
    ModestStringMap recipients;
    ModestStringMap messageData;
    ModestStringMapList attachments;
    ModestStringMapList images;
    uint priority = 0;
    ModestStringMap headers;

    qDebug() << __PRETTY_FUNCTION__;

    // Only read status & priority can be updated using Modest
    if (message.status() & QMessage::Read) {
        messageData["read"] = "";
    }
    priority = getModestPriority (message);

    QList<QVariant> arguments;
    arguments << QVariant::fromValue(modestAccountId);
    arguments << QVariant::fromValue(modestFolderId);
    arguments << QVariant::fromValue(modestMessageId);
    arguments << QVariant::fromValue(senderInfo);
    arguments << QVariant::fromValue(recipients);
    arguments << QVariant::fromValue(messageData);
    arguments << QVariant::fromValue(attachments);
    arguments << QVariant::fromValue(images);
    arguments << priority;
    arguments << QVariant::fromValue(headers);

    QDBusPendingCall pendingCall = m_QtmPluginDBusInterface->asyncCallWithArgumentList("UpdateMessage",
                                                                                       arguments);

    if (pendingCall.isError()) {
        qWarning() << "DBus call failed! " << pendingCall.error();
        return false;
    }

    QDBusPendingCallWatcher pendingCallWatcher(pendingCall);
    pendingCallWatcher.waitForFinished();
    QDBusMessage msg = pendingCallWatcher.reply();
    if (msg.type() == QDBusMessage::ErrorMessage) {
        qWarning() << "Failed to update message via modest: " << msg.errorMessage();
        return false;
    }

    return true;
}

bool ModestEngine::removeMessage(const QMessageId &id, QMessageManager::RemovalOption option)
{
    Q_UNUSED(option) // TODO:

    QMessage msg = message(id, false);
    QMessagePrivate* privateMessage = QMessagePrivate::implementation(msg);
    if (privateMessage->_url.isEmpty()) {
        return false;
    } else {
        m_ModestDBusInterface->call(MODEST_DBUS_METHOD_DELETE_MESSAGE,
                                    privateMessage->_url);
        // Make sure that there will be instant notification about removed message
        notification(id, ModestEngine::Removed);
    }
    return true;
}

bool ModestEngine::filterMessage(const QMessage& message, QMessageFilterPrivate::SortedMessageFilterList filterList, int start) const
{
    if (filterList.count() > start) {
        for (int j=start; j < filterList.count(); j++) {
            QMessageFilterPrivate* pf = QMessageFilterPrivate::implementation(filterList[j]);
            if (!pf->filter(message)) {
                return false;
            }
        }
    }
    return true;
}

QMessageIdList ModestEngine::queryMessagesSync(const QMessageFilter &filter, const QMessageSortOrder &sortOrder,
                                               uint limit, uint offset, bool &isFiltered, bool &isSorted) const
{
    QMessageIdList ids;

    if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) {
        return ids;
    }

    QMessageServicePrivate* privateService = QMessageServicePrivate::implementation(m_service);
    if (privateService->queryMessages(m_service, filter, sortOrder, limit, offset,
                                      QMessageServicePrivate::EnginesToCallModest)) {
        QObject::connect(&m_service, SIGNAL(stateChanged(QMessageService::State)),
                         this, SLOT(stateChanged(QMessageService::State)));

        m_eventLoop.exec();

        isSorted = m_isSorted;
        isFiltered = m_isFiltered;
        ids = m_ids;
        m_ids.clear();
    }

    return ids;
}

QMessageIdList ModestEngine::queryMessagesSync(const QMessageFilter &filter, const QString &body,
                                               QMessageDataComparator::MatchFlags matchFlags,
                                               const QMessageSortOrder &sortOrder, uint limit, uint offset,
                                               bool &isFiltered, bool &isSorted) const
{
    QMessageIdList ids;

    if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) {
        return ids;
    }

    QMessageServicePrivate* privateService = QMessageServicePrivate::implementation(m_service);
    if (privateService->queryMessages(m_service, filter, body, matchFlags,
                                      sortOrder, limit, offset,
                                      QMessageServicePrivate::EnginesToCallModest)) {
        QObject::connect(&m_service, SIGNAL(stateChanged(QMessageService::State)),
                         this, SLOT(stateChanged(QMessageService::State)));

        m_eventLoop.exec();

        isSorted = m_isSorted;
        isFiltered = m_isFiltered;
        ids = m_ids;
        m_ids.clear();
    }

    return ids;
}

int ModestEngine::countMessagesSync(const QMessageFilter &filter) const
{
    int count;

    if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) {
        return 0;
    }

    QMessageServicePrivate* privateService = QMessageServicePrivate::implementation(m_service);
    if (privateService->countMessages(m_service, filter, QMessageServicePrivate::EnginesToCallModest)) {
        QObject::connect(&m_service, SIGNAL(stateChanged(QMessageService::State)),
                         this, SLOT(stateChanged(QMessageService::State)));

        m_eventLoop.exec();

        count = m_count;
    }

    return count;
}

void ModestEngine::stateChanged(QMessageService::State newState)
{
    if (newState == QMessageService::FinishedState) {
        QMessageServicePrivate* privateService = QMessageServicePrivate::implementation(m_service);

        m_ids = privateService->_ids;
        m_isSorted = privateService->_sorted;
        m_isFiltered = privateService->_filtered;
        m_count = privateService->_count;

        m_eventLoop.quit();
    }
}

bool ModestEngine::queryMessages(QMessageService& messageService, const QMessageFilter &filter, const QMessageSortOrder &sortOrder, uint limit, uint offset) const
{
    return queryMessages(messageService, filter, QString(), 0, sortOrder, limit, offset);
}

bool ModestEngine::countMessages(QMessageService& messageService, const QMessageFilter &filter)
{
    if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) {
        return false;
    }

    m_pendingMessageQueries.append(MessageQueryInfo());

    MessageQueryInfo &queryInfo = m_pendingMessageQueries[m_pendingMessageQueries.count()-1];

    queryInfo.queryId = -1;
    queryInfo.filter = filter;
    queryInfo.limit = 0;
    queryInfo.offset = 0;
    queryInfo.privateService = QMessageServicePrivate::implementation(messageService);
    queryInfo.currentFilterListIndex = 0;
    queryInfo.handledFiltersCount = 0;
    queryInfo.isQuery = false;
    queryInfo.returnWithSingleShot = false;
    queryInfo.allMessagesQuery = false;
    queryInfo.queryThread = 0;

    if (m_allEmailMessagesInCache) {
        queryInfo.queryThread = new MessageQueryThread(filter, QMessageSortOrder());
        connect(queryInfo.queryThread, SIGNAL(queryFinished(void*, QList<QtMobility::QMessageId>)),
                this, SLOT(messageQueryFinishedSlot(void*, QList<QtMobility::QMessageId>)));

        queryInfo.queryThread->start();
    } else {
        if (!startQueryingAndFilteringMessages(m_pendingMessageQueries[m_pendingMessageQueries.count()-1])) {
            m_pendingMessageQueries.removeAt(m_pendingMessageQueries.count()-1);
            if (m_pendingMessageQueries.count() == 0) {
                // This was last query in pending queries queue
                // => Disconnect from "GetHeaders" request related DBus signals
                // Note: Disconnecting signals is done to optimize signal handling
                //       <=> Disconnecting prevents unnecessary handling of signals
                //           which have been sent from other applications using
                //           Qt Mobility Messaging API
                m_QtmPluginDBusInterface->connection().disconnect(MODESTENGINE_QTM_PLUGIN_NAME,
                                                                  MODESTENGINE_QTM_PLUGIN_PATH,
                                                                  MODESTENGINE_QTM_PLUGIN_NAME,
                                                                  "HeadersReceived",
                                                                  (ModestEngine*)this,
                                                                  SLOT(searchMessagesHeadersReceivedSlot(QDBusMessage)));

                m_QtmPluginDBusInterface->connection().disconnect(MODESTENGINE_QTM_PLUGIN_NAME,
                                                                  MODESTENGINE_QTM_PLUGIN_PATH,
                                                                  MODESTENGINE_QTM_PLUGIN_NAME,
                                                                  "HeadersFetched",
                                                                  (ModestEngine*)this,
                                                                  SLOT(searchMessagesHeadersFetchedSlot(QDBusMessage)));
            }
            return false;
        }
    }

    return true;
}

bool ModestEngine::queryMessages(QMessageService& messageService, const QMessageFilter &filter, const QString &body,
                                 QMessageDataComparator::MatchFlags matchFlags, const QMessageSortOrder &sortOrder,
                                 uint limit, uint offset) const
{
    if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) {
        return false;
    }

    m_pendingMessageQueries.append(MessageQueryInfo());

    MessageQueryInfo &queryInfo = m_pendingMessageQueries[m_pendingMessageQueries.count()-1];

    queryInfo.queryId = -1;
    queryInfo.body = body;
    queryInfo.matchFlags = matchFlags;
    queryInfo.filter = filter;
    queryInfo.sortOrder = sortOrder;
    queryInfo.limit = limit;
    queryInfo.offset = offset;
    queryInfo.privateService = QMessageServicePrivate::implementation(messageService);
    queryInfo.currentFilterListIndex = 0;
    queryInfo.handledFiltersCount = 0;
    queryInfo.isQuery = true;
    queryInfo.returnWithSingleShot = false;
    queryInfo.allMessagesQuery = false;
    queryInfo.queryThread = 0;

    if (m_allEmailMessagesInCache) {
        queryInfo.queryThread = new MessageQueryThread(filter, sortOrder);
        connect(queryInfo.queryThread, SIGNAL(queryFinished(void*, QList<QtMobility::QMessageId>)),
                this, SLOT(messageQueryFinishedSlot(void*, QList<QtMobility::QMessageId>)));
        queryInfo.queryThread->start();
    } else {
        if (!startQueryingAndFilteringMessages(m_pendingMessageQueries[m_pendingMessageQueries.count()-1])) {
            m_pendingMessageQueries.removeAt(m_pendingMessageQueries.count()-1);
            if (m_pendingMessageQueries.count() == 0) {
                // This was last query in pending queries queue
                // => Disconnect from "GetHeaders" request related DBus signals
                // Note: Disconnecting signals is done to optimize signal handling
                //       <=> Disconnecting prevents unnecessary handling of signals
                //           which have been sent from other applications using
                //           Qt Mobility Messaging API
                m_QtmPluginDBusInterface->connection().disconnect(MODESTENGINE_QTM_PLUGIN_NAME,
                                                                  MODESTENGINE_QTM_PLUGIN_PATH,
                                                                  MODESTENGINE_QTM_PLUGIN_NAME,
                                                                  "HeadersReceived",
                                                                  (ModestEngine*)this,
                                                                  SLOT(searchMessagesHeadersReceivedSlot(QDBusMessage)));

                m_QtmPluginDBusInterface->connection().disconnect(MODESTENGINE_QTM_PLUGIN_NAME,
                                                                  MODESTENGINE_QTM_PLUGIN_PATH,
                                                                  MODESTENGINE_QTM_PLUGIN_NAME,
                                                                  "HeadersFetched",
                                                                  (ModestEngine*)this,
                                                                  SLOT(searchMessagesHeadersFetchedSlot(QDBusMessage)));
            }
            return false;
        }
    }

    return true;
}

bool ModestEngine::startQueryingAndFilteringMessages(MessageQueryInfo &msgQueryInfo) const
{
    QMessageFilterPrivate* pf = QMessageFilterPrivate::implementation(msgQueryInfo.filter);
    if (pf->_filterList.count() == 0) {
        if ((pf->_field == QMessageFilterPrivate::None) &&
            (pf->_filterList.count() == 0) &&
            (pf->_notFilter)) {
            // There is only one filter: empty ~QMessageFilter()
            // => return empty QMessageIdList
            msgQueryInfo.ids.clear();
            msgQueryInfo.returnWithSingleShot = true;
            QTimer::singleShot(0, (ModestEngine*)this, SLOT(returnQueryResultsSlot()));
            return true;
        } else {
            // One single filter to be handled
            QMessageFilter newFilter;
            QMessageFilterPrivate* pfNew = QMessageFilterPrivate::implementation(newFilter);
            pfNew->_filterList.append(QMessageFilterPrivate::SortedMessageFilterList());
            pfNew->_filterList[0] << msgQueryInfo.filter;
            msgQueryInfo.filter = newFilter;
        }
    }

    return queryAndFilterMessages(msgQueryInfo);
}

void ModestEngine::returnQueryResultsSlot()
{
    for (int i=m_pendingMessageQueries.count()-1; i >= 0; i--) {
        if (m_pendingMessageQueries[i].returnWithSingleShot) {
            if (m_pendingMessageQueries[i].isQuery) {
                m_pendingMessageQueries[i].privateService->messagesFound(m_pendingMessageQueries[i].ids, true, true);
            } else {
                m_pendingMessageQueries[i].privateService->messagesCounted(m_pendingMessageQueries[i].ids.count());
            }
            m_pendingMessageQueries.removeAt(i);
        }
    }
}

bool ModestEngine::queryAndFilterMessages(MessageQueryInfo &msgQueryInfo) const
{
    QStringList accountIds;
    QStringList folderUris;
    QDateTime startTimeStamp;
    QDateTime endTimeStamp;
    QDateTime startReceptionTimeStamp;
    QDateTime endReceptionTimeStamp;

    QMessageFilterPrivate* pf = QMessageFilterPrivate::implementation(msgQueryInfo.filter);

    // Filters have been sorted into matrix of filters:
    // - there is AND operation between every filter in one row
    // - there is OR operation between every row
    // => Every row can be handled individually
    // => Final result set can be created by combining result sets
    //    from individual row based queries
    QMessageFilterPrivate::SortedMessageFilterList filters = pf->_filterList[msgQueryInfo.currentFilterListIndex];

    msgQueryInfo.realAccountId = QString();

    int handledFiltersCount = 0;
    pf = QMessageFilterPrivate::implementation(filters[handledFiltersCount]);
    if ((filters.count() == 1) &&
        (pf->_field == QMessageFilterPrivate::None) &&
        (pf->_filterList.count() == 0)) {
        if (pf->_notFilter) {
            // There is only one filter: empty ~QMessageFilter()
            // => this query results empty QMessageIdList
            // => Skip to next query round
            int index = -1;
            for (int i=0; i < m_pendingMessageQueries.count(); i++) {
                if (m_pendingMessageQueries[i].queryId == msgQueryInfo.queryId) {
                    index = i;
                    break;
                }
            }
            if (index >= 0) {
                handleQueryFinished(index);
            }
            return true;
        } else {
            // There is only one filter: empty QMessageFilter()
            // => return all messages
            handledFiltersCount++;
        }
    }

    bool handled = true;
    while (handledFiltersCount < filters.count() && handled) {
        handled = false;
        pf = QMessageFilterPrivate::implementation(filters[handledFiltersCount]);
        switch (pf->_field) {
        case QMessageFilterPrivate::Id:
            break;
        case QMessageFilterPrivate::ParentAccountId:
            {
            if (pf->_comparatorType == QMessageFilterPrivate::Equality) {
                if (accountIds.count()) {
                    // Only one account/one query can be used
                    break;
                }
                QMessageDataComparator::EqualityComparator cmp(static_cast<QMessageDataComparator::EqualityComparator>(pf->_comparatorValue));
                if (cmp == QMessageDataComparator::Equal) {
                    if (pf->_value.toString().length() > 0) {
                        accountIds.append(modestAccountIdFromAccountId(pf->_value.toString()));
                        // Local folders are not account specific
                        // => Make sure that account specific messages are searched from local_folders account
                        accountIds.append("local_folders");
                        handled = true;
                    }
                }
            }
            break;
        }
        case QMessageFilterPrivate::ParentFolderId:
            {
            if (pf->_comparatorType == QMessageFilterPrivate::Equality) {
                QMessageDataComparator::EqualityComparator cmp(static_cast<QMessageDataComparator::EqualityComparator>(pf->_comparatorValue));
                if (cmp == QMessageDataComparator::Equal) {
                    if (pf->_value.toString().length() > 0) {
                        folderUris.append(modestFolderIdFromFolderId(pf->_value.toString()));
                        if (accountIds.count() == 0) {
                            accountIds.append(modestAccountIdFromFolderId(pf->_value.toString()));

                            // Note: Even though local folders belong to "local_folders" account
                            //       inside Modest, local folders can belong to any "real"
                            //       POP3 or IMAP account in Qt Mobility Messaging side
                            //       <=> Qt Mobility Messaging does not have "Local Folders"
                            //           account
                            // If folder is local folder, "local_folders" accountId will be
                            // added to accountIds list to enable correct filtering inside
                            // Modest Plugin
                            accountIds.append("local_folders");

                            // realAccountId will contain id of Qt Mobility Messaging account
                            // (AccountId can be found from the beginning of folderId string)
                            // <=> realAccountId will not ever be "local_folders"
                            msgQueryInfo.realAccountId = accountIdFromFolderId(pf->_value.toString()).toString();
                        }
                        handled = true;
                    }
                }
            }
            break;
            }
        case QMessageFilterPrivate::AncestorFolderIds:
            break;
        case QMessageFilterPrivate::Type:
            break;
        case QMessageFilterPrivate::StandardFolder:
            if (pf->_comparatorType == QMessageFilterPrivate::Equality) {
                QMessageDataComparator::EqualityComparator cmp(static_cast<QMessageDataComparator::EqualityComparator>(pf->_comparatorValue));
                if (cmp == QMessageDataComparator::Equal) {
                    QMessage::StandardFolder standardFolder = static_cast<QMessage::StandardFolder>(pf->_value.toInt());
                    if (standardFolder == QMessage::SentFolder) {
                        // Possible "real" accountIds can be ignored in this phase
                        // because messages are searched from "local_folders" account
                        // <=> Actual account filtering is done when search from Modest
                        //     has finished
                        accountIds.clear();

                        accountIds.append("local_folders");
                        folderUris.append("sent");
                    } else if (standardFolder == QMessage::DraftsFolder) {
                        // Possible "real" accountIds can be ignored in this phase
                        // because messages are searched from "local_folders" account
                        // <=> Actual account filtering is done when search from Modest
                        //     has finished
                        accountIds.clear();

                        accountIds.append("local_folders");
                        folderUris.append("drafts");
                    }
                    handled = true;
                }
            }
            break;
        case QMessageFilterPrivate::ParentAccountIdFilter:
            break;
        case QMessageFilterPrivate::ParentFolderIdFilter:
            break;
        case QMessageFilterPrivate::TimeStamp:
            {
            if (pf->_comparatorType == QMessageFilterPrivate::Equality) {
                QMessageDataComparator::EqualityComparator cmp(static_cast<QMessageDataComparator::EqualityComparator>(pf->_comparatorValue));
                if (cmp == QMessageDataComparator::Equal) {
                    endTimeStamp = pf->_value.toDateTime();
                    startTimeStamp = pf->_value.toDateTime();
                    handled = true;
                }
            }
            if (pf->_comparatorType == QMessageFilterPrivate::Relation) {
                QMessageDataComparator::RelationComparator cmp(static_cast<QMessageDataComparator::RelationComparator>(pf->_comparatorValue));
                if (cmp == QMessageDataComparator::LessThan) {
                    endTimeStamp = pf->_value.toDateTime();
                    handled = true;
                } else if (cmp == QMessageDataComparator::LessThanEqual) {
                    endTimeStamp = pf->_value.toDateTime();
                    handled = true;
                } else if (cmp == QMessageDataComparator::GreaterThan) {
                    startTimeStamp = pf->_value.toDateTime();
                    handled = true;
                } if (cmp == QMessageDataComparator::GreaterThanEqual) {
                    startTimeStamp = pf->_value.toDateTime();
                    handled = true;
                }
            }
            break;
            }
        case QMessageFilterPrivate::ReceptionTimeStamp:
            {
            if (pf->_comparatorType == QMessageFilterPrivate::Equality) {
                QMessageDataComparator::EqualityComparator cmp(static_cast<QMessageDataComparator::EqualityComparator>(pf->_comparatorValue));
                if (cmp == QMessageDataComparator::Equal) {
                    endReceptionTimeStamp = pf->_value.toDateTime();
                    startReceptionTimeStamp = pf->_value.toDateTime();
                    handled = true;
                }
            }
            if (pf->_comparatorType == QMessageFilterPrivate::Relation) {
                QMessageDataComparator::RelationComparator cmp(static_cast<QMessageDataComparator::RelationComparator>(pf->_comparatorValue));
                if (cmp == QMessageDataComparator::LessThan) {
                    endReceptionTimeStamp = pf->_value.toDateTime();
                    handled = true;
                } else if (cmp == QMessageDataComparator::LessThanEqual) {
                    endReceptionTimeStamp = pf->_value.toDateTime();
                    handled = true;
                } else if (cmp == QMessageDataComparator::GreaterThan) {
                    startReceptionTimeStamp = pf->_value.toDateTime();
                    handled = true;
                } if (cmp == QMessageDataComparator::GreaterThanEqual) {
                    startReceptionTimeStamp = pf->_value.toDateTime();
                    handled = true;
                }
            }
            break;
            }
        case QMessageFilterPrivate::Sender:
            break;
        case QMessageFilterPrivate::Recipients:
            break;
        case QMessageFilterPrivate::Subject:
            break;
        case QMessageFilterPrivate::Status:
            break;
        case QMessageFilterPrivate::Priority:
            break;
        case QMessageFilterPrivate::Size:
            break;
        case QMessageFilterPrivate::None:
            break;
        }
        handledFiltersCount++;
    }

    msgQueryInfo.handledFiltersCount = 0; // Do filtering also for filters which has been handled above

    return searchMessages(msgQueryInfo, accountIds, folderUris, msgQueryInfo.body, startTimeStamp,
                          endTimeStamp, startReceptionTimeStamp, endReceptionTimeStamp);
}

bool ModestEngine::searchMessages(MessageQueryInfo &msgQueryInfo, const QStringList& accountIds,
                                  const QStringList& folderUris, const QString& body,
                                  const QDateTime& startTimeStamp, const QDateTime& endTimeStamp,
                                  const QDateTime& startReceptionTimeStamp, const QDateTime& endReceptionTimeStamp) const
{
    Q_UNUSED(body) //TODO: Body search

    qulonglong sDate = 0;
    if (startTimeStamp.isValid() && startReceptionTimeStamp.isValid()) {
        if (startTimeStamp < startReceptionTimeStamp) {
            sDate = startTimeStamp.toTime_t();
        } else {
            sDate = startReceptionTimeStamp.toTime_t();
        }
    } else {
        if (startTimeStamp.isValid()) {
            sDate = startTimeStamp.toTime_t();
        } else if (startReceptionTimeStamp.isValid()) {
            sDate = startReceptionTimeStamp.toTime_t();
        }
    }


    qulonglong eDate = 0;
    if (endTimeStamp.isValid() && endReceptionTimeStamp.isValid()) {
        if (endTimeStamp > endReceptionTimeStamp) {
            eDate = endTimeStamp.toTime_t();
        } else {
            eDate = endReceptionTimeStamp.toTime_t();
        }
    } else {
        if (endTimeStamp.isValid()) {
            eDate = endTimeStamp.toTime_t();
        } else if (endReceptionTimeStamp.isValid()) {
            eDate = endReceptionTimeStamp.toTime_t();
        }
    }

    if (sDate == 0 && eDate == 0 && accountIds.isEmpty() && folderUris.isEmpty()) {
        msgQueryInfo.allMessagesQuery = true;
    } else {
        msgQueryInfo.allMessagesQuery = false;
    }

    if (m_pendingMessageQueries.count() == 1) {
        // This is the first query in pending queries queue
        // => connect to signals
        m_QtmPluginDBusInterface->connection().connect(MODESTENGINE_QTM_PLUGIN_NAME,
                                                       MODESTENGINE_QTM_PLUGIN_PATH,
                                                       MODESTENGINE_QTM_PLUGIN_NAME,
                                                       "HeadersReceived",
                                                       (ModestEngine*)this,
                                                       SLOT(searchMessagesHeadersReceivedSlot(QDBusMessage)));

        m_QtmPluginDBusInterface->connection().connect(MODESTENGINE_QTM_PLUGIN_NAME,
                                                       MODESTENGINE_QTM_PLUGIN_PATH,
                                                       MODESTENGINE_QTM_PLUGIN_NAME,
                                                       "HeadersFetched",
                                                       (ModestEngine*)this,
                                                       SLOT(searchMessagesHeadersFetchedSlot(QDBusMessage)));
    }

    QDBusMessage reply = m_QtmPluginDBusInterface->call("GetHeaders",
                                                        accountIds,
                                                        folderUris,
                                                        sDate,
                                                        eDate,
                                                        false);
    if (reply.type() != QDBusMessage::ErrorMessage) {
        msgQueryInfo.queryId = reply.arguments().takeFirst().toInt();
    } else {
        return false;
    }

    return true;
}

void ModestEngine::searchMessagesHeadersReceivedSlot(QDBusMessage msg)
{
    QList<QVariant> arguments = msg.arguments();
    int queryId = arguments.takeFirst().toInt();

    int index = -1;
    for (int i=0; i < m_pendingMessageQueries.count(); i++) {
        if (m_pendingMessageQueries[i].queryId == queryId) {
            index = i;
            break;
        }
    }
    if (index == -1) {
        // Received DBus Message is not reply for the DBus query
        // that was sent from this process/instance of modest engine
        // => Continue waiting
        return;
    }

    MessageQueryInfo &queryInfo = m_pendingMessageQueries[index];

    QString reportedAccountId = arguments.takeFirst().toString();
    QString accountId;
    if (!queryInfo.realAccountId.isEmpty()) {
        // Search was done to Modest "local_folders" account
        // => Correct Qt Mobility Messaging AccountId
        //    can be taken realAccountId field
        // => Transform Messaging AccountId to Modest accountId
        //    by removing "MO_" from the beginning of accountId string
        accountId = queryInfo.realAccountId;
        accountId.remove(0,3);
    } else {
        accountId = reportedAccountId.remove("_store");
    }

    QString folderId = arguments.takeFirst().toString();
    QVariant variant = arguments.takeFirst();
    QDBusArgument argument = variant.value<QDBusArgument>();
    QList<QMap<QString, QVariant> > messages;
    argument >> messages;

    QMessageFilterPrivate::SortedMessageFilterList filters;
    int firstUnhandledFilterIndex = 0;

    QMessageFilterPrivate* pf = QMessageFilterPrivate::implementation(queryInfo.filter);
    if (pf->_filterList.count() == 0) {
        filters.append(queryInfo.filter);
    } else {
        if (queryInfo.handledFiltersCount < pf->_filterList[queryInfo.currentFilterListIndex].count()) {
            filters = pf->_filterList[queryInfo.currentFilterListIndex];
        }
    }
    firstUnhandledFilterIndex = queryInfo.handledFiltersCount;

    for (int i=0; i < messages.count(); i++) {
        MessagingModestMessage modestMessage;
        modestMessage.accountId = accountId;
        modestMessage.folderId = folderId;
        modestMessage.dateReceived = 0;
        modestMessage.dateSent = 0;
        modestMessage.size = 0;
        modestMessage.flags = MessagingModestMessageNotDefined;
        modestMessage.priority = MessagingModestMessagePriorityNotDefined;

        QMapIterator<QString, QVariant> j(messages[i]);
        while (j.hasNext()) {
            j.next();
            if (j.key() == "url") {
                modestMessage.url = j.value().toString();
            } else if (j.key() == "message-uid") {
                modestMessage.id = j.value().toString();
            } else if (j.key() == "from") {
                modestMessage.from = j.value().toString();
            } else if (j.key() == "to") {
                modestMessage.to = j.value().toString();
            } else if (j.key() == "cc") {
                modestMessage.cc = j.value().toString();
            } else if (j.key() == "bcc") {
                modestMessage.bcc = j.value().toString();
            } else if (j.key() == "replyto") {
                modestMessage.replyTo = j.value().toString();
            } else if (j.key() == "subject") {
                modestMessage.subject = j.value().toString();
            } else if (j.key() == "date-received") {
                modestMessage.dateReceived = j.value().toLongLong();
            } else if (j.key() == "date-sent") {
                modestMessage.dateSent = j.value().toLongLong();
            } else if (j.key() == "size") {
                modestMessage.size = j.value().toLongLong();
            } else if (j.key() == "flags") {
                qlonglong flags = j.value().toUInt();
                qlonglong priority = flags & MessagingModestMessageHasPriority;
                modestMessage.priority = static_cast<MessagingModestMessagePriority>(priority);
                modestMessage.flags = static_cast<MessagingModestMessageFlags>(flags);
            }
        }

        QMessage *message = new QMessage(messageFromModestMessage(modestMessage));
        QMessagePrivate* privateMessage = QMessagePrivate::implementation(*message);
        privateMessage->_mimeInformationRetrieved = false;

        if (reportedAccountId == "local_folders") {
            QString id = privateMessage->_id.toString();
            QString newProtocol = "maildir";
            replaceProtocol(id, newProtocol);
            privateMessage->_id = QMessageId(id);
            id = privateMessage->_parentFolderId.toString();
            replaceProtocol(id, newProtocol);
            privateMessage->_parentFolderId = QMessageFolderId(id);
        }

        if (filterMessage(*message, filters, 0)) {
            if (!queryInfo.ids.contains(message->id())) {
                queryInfo.ids.append(message->id());
            }
        }
        MessageCache::instance()->insertObject(message);
    }
}

void ModestEngine::searchMessagesHeadersFetchedSlot(QDBusMessage msg)
{
    QList<QVariant> arguments = msg.arguments();
    int queryId = arguments.takeFirst().toInt();

    int index = -1;
    for (int i=0; i < m_pendingMessageQueries.count(); i++) {
        if (m_pendingMessageQueries[i].queryId == queryId) {
            index = i;
            break;
        }
    }
    if (index == -1) {
        // Received DBus Message is not reply for the DBus query
        // that was sent from this process/instance of modest engine
        // => Continue waiting
        return;
    }

    handleQueryFinished(index);
}

void ModestEngine::handleQueryFinished(int index) const
{
    MessageQueryInfo &queryInfo = m_pendingMessageQueries[index];

    queryInfo.currentFilterListIndex++;
    QMessageFilterPrivate* pf = QMessageFilterPrivate::implementation(queryInfo.filter);
    if (queryInfo.currentFilterListIndex < pf->_filterList.count()) {
        if (queryAndFilterMessages(queryInfo)) {
            // Continue searching
            return;
        }
    }

    MessagingHelper::orderMessages(queryInfo.ids, queryInfo.sortOrder);
    MessagingHelper::applyOffsetAndLimitToMessageIdList(queryInfo.ids, queryInfo.limit, queryInfo.offset);

    // Search finished
    if (queryInfo.isQuery) {
        queryInfo.privateService->messagesFound(queryInfo.ids, true, true);
    } else {
        queryInfo.privateService->messagesCounted(queryInfo.ids.count());
    }

    if (queryInfo.allMessagesQuery && !MessageCache::instance()->isFull()) {
        m_allEmailMessagesInCache = true;
    }

    m_pendingMessageQueries.removeAt(index);

    if (m_pendingMessageQueries.count() == 0) {
        // This was last query in pending queries queue
        // => Disconnect from "GetHeaders" request related DBus signals
        // Note: Disconnecting signals is done to optimize signal handling
        //       <=> Disconnecting prevents unnecessary handling of signals
        //           which have been sent from other applications using
        //           Qt Mobility Messaging API
        m_QtmPluginDBusInterface->connection().disconnect(MODESTENGINE_QTM_PLUGIN_NAME,
                                                          MODESTENGINE_QTM_PLUGIN_PATH,
                                                          MODESTENGINE_QTM_PLUGIN_NAME,
                                                          "HeadersReceived",
                                                          (ModestEngine*)this,
                                                          SLOT(searchMessagesHeadersReceivedSlot(QDBusMessage)));

        m_QtmPluginDBusInterface->connection().disconnect(MODESTENGINE_QTM_PLUGIN_NAME,
                                                          MODESTENGINE_QTM_PLUGIN_PATH,
                                                          MODESTENGINE_QTM_PLUGIN_NAME,
                                                          "HeadersFetched",
                                                          (ModestEngine*)this,
                                                          SLOT(searchMessagesHeadersFetchedSlot(QDBusMessage)));
    }
}

void ModestEngine::messageQueryFinishedSlot(void* queryThread, QList<QtMobility::QMessageId> ids)
{
    int index = -1;
    for (int i=0; i < m_pendingMessageQueries.count(); i++) {
        if (m_pendingMessageQueries[i].queryThread == queryThread) {
            index = i;
            break;
        }
    }

    if (index >= 0) {
        MessageQueryInfo &queryInfo = m_pendingMessageQueries[index];

        if (queryInfo.isQuery) {
            queryInfo.privateService->messagesFound(ids, true, true);
        } else {
            queryInfo.privateService->messagesCounted(ids.count());
        }

        m_pendingMessageQueries.removeAt(index);
    }
}

void
ModestEngine::sendEmailCallEnded(QDBusPendingCallWatcher *watcher)
{
    if (watcher->isError ()) {
        // TODO: Emit a failure
        qWarning() << "Failed to send email via modest: " << watcher->error();
    } else {
        // TODO: Emit a success (or put to outbox)
        qDebug() << "Message should be outboxed now...";
    }
}

void
ModestEngine::addMessageCallEnded(QDBusPendingCallWatcher *watcher)
{
    QDBusPendingReply<QString> reply = *watcher;
    if (reply.isError ()) {
        // TODO: Emit a failure
        qWarning() << "Failed to add message via modest: " << reply.error();
    } else {
        QString id = reply.argumentAt<0>();
        // TODO: Emit a success ... with message's id?
        qDebug() << "Message with id" << id << "should be added now";
    }
}

void ModestEngine::folderUpdatedSlot(QDBusMessage msg)
{
    QList<QVariant> arguments = msg.arguments();
    QString modestAccountId = arguments.takeFirst().toString();
    QString modestFolderId = arguments.takeFirst().toString();
    QMessageFolderId updatedFolderId;

    if (modestAccountId == "local_folders") {
        updatedFolderId = QMessageFolderId("MO_LOCAL&maildir&"+modestFolderId);
    } else {
        if (modestAccountId.endsWith("_store")) {
            modestAccountId = modestAccountId.mid(0,modestAccountId.length()-6);
        }
        if (modestFolderId == "inbox") {
            modestFolderId = "INBOX";
        }
        QMessageAccountId accountId = QMessageAccountId("MO_"+escapeString(modestAccountId));
        QString protocol = accountEmailProtocolAsString(accountId);
        if ((protocol == "pop") && (modestFolderId == "INBOX")) {
            modestFolderId = "cache";
        }
        updatedFolderId = QMessageFolderId(accountId.toString()+"&"+protocol+"&"+modestFolderId);
    }

    int messagesPerAccount = 5;
    QDBusPendingCall pendingCall = m_ModestDBusInterface->asyncCall(MODEST_DBUS_METHOD_GET_UNREAD_MESSAGES,
                                                                    messagesPerAccount);
    QDBusPendingCallWatcher* pendingCallWatcher = new QDBusPendingCallWatcher(pendingCall);
    pendingCallWatcher->setProperty("folderId", updatedFolderId.toString());
    connect(pendingCallWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
            this, SLOT(pendingGetUnreadMessagesFinishedSlot(QDBusPendingCallWatcher*)));
}

void ModestEngine::pendingGetUnreadMessagesFinishedSlot(QDBusPendingCallWatcher* pendingCallWatcher)
{
    if (pendingCallWatcher->isError()) {
        return;
    }

    QDBusMessage msg = pendingCallWatcher->reply();
    if (msg.arguments().count() == 0) {
        return;
    }

    QVariant variant = msg.arguments().takeFirst();
    QDBusArgument argument = variant.value<QDBusArgument>();
    QList<ModestAccountsUnreadMessagesDBusStruct> accountsWithUnreadMessages;
    argument >> accountsWithUnreadMessages;

    bool setOnlyDates = false;
    if (pendingCallWatcher->property("setOnlyDates").isValid()) {
        setOnlyDates = true;
    }
    QMessageFolderId folderId;
    if (pendingCallWatcher->property("folderId").isValid()) {
        folderId = QMessageFolderId(pendingCallWatcher->property("folderId").toString());
    }

    for (int i=0; i < accountsWithUnreadMessages.count(); i++) {
        QDateTime newLatestTimeStamp;
        QDateTime latestTimeStamp = accountsLatestTimestamp.take(accountsWithUnreadMessages[i].accountId);
        for (int j=0; j < accountsWithUnreadMessages[i].unreadMessages.count(); j++) {
            ModestUnreadMessageDBusStruct unreadMessage = accountsWithUnreadMessages[i].unreadMessages[j];
            QDateTime time = QDateTime::fromTime_t(unreadMessage.timeStamp);
            if (time > newLatestTimeStamp) {
                newLatestTimeStamp = time;
            }
            if (!setOnlyDates) {
                if (time > latestTimeStamp) {
                    searchNewMessages(unreadMessage.subject, QString(), time, time, MODEST_DBUS_SEARCH_SUBJECT, 0);
                }
            }
        }
        if (newLatestTimeStamp.isValid()) {
            accountsLatestTimestamp.remove(accountsWithUnreadMessages[i].accountId);
            accountsLatestTimestamp.insert(accountsWithUnreadMessages[i].accountId, newLatestTimeStamp);
        } else {
            accountsLatestTimestamp.insert(accountsWithUnreadMessages[i].accountId, QDateTime::currentDateTime());
        }
    }
}

void ModestEngine::searchNewMessages(const QString& searchString, const QString& folderToSearch,
                                  const QDateTime& startDate, const QDateTime& endDate,
                                  int searchflags, uint minimumMessageSize) const
{
    qlonglong sDate = 0;
    if (startDate.isValid()) {
        sDate = startDate.toTime_t();
    }
    qlonglong eDate = 0;
    if (endDate.isValid()) {
        eDate = endDate.toTime_t();
    }

    QDBusInterface interface(MODEST_DBUS_SERVICE, MODEST_DBUS_OBJECT, MODEST_DBUS_IFACE);
    QDBusPendingCall pendingCall = interface.asyncCall(MODEST_DBUS_METHOD_SEARCH,
                                                       searchString,
                                                       folderToSearch,
                                                       sDate,
                                                       eDate,
                                                       searchflags,
                                                       minimumMessageSize);
    QDBusPendingCallWatcher* pendingCallWatcher = new QDBusPendingCallWatcher(pendingCall);
    connect(pendingCallWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
            this, SLOT(pendingSearchFinishedSlot(QDBusPendingCallWatcher*)));
}

void ModestEngine::pendingSearchFinishedSlot(QDBusPendingCallWatcher* pendingCallWatcher)
{
    if (pendingCallWatcher->isError()) {
        return;
    }

    QDBusMessage msg = pendingCallWatcher->reply();
    if (msg.arguments().count() == 0) {
        return;
    }

    QVariant variant = msg.arguments().takeFirst();
    QDBusArgument argument = variant.value<QDBusArgument>();
    QList<ModestMessage> messages;
    argument >> messages;

    for (int i=0; i < messages.count(); i++) {
        notification(messageIdFromModestMessageId(messages[i].id), ModestEngine::Added);
    }
}

void ModestEngine::messageReadChangedSlot(QDBusMessage msg)
{
    QString changedMessageId = msg.arguments().takeFirst().toString();
    notification(messageIdFromModestMessageId(changedMessageId), ModestEngine::Updated);
}

QMessageManager::NotificationFilterId ModestEngine::registerNotificationFilter(QMessageStorePrivate& messageStore,
                                                                               const QMessageFilter &filter,
                                                                               QMessageManager::NotificationFilterId id)
{
    m_messageStore = &messageStore;

    int filterId = id;
    if (filterId == 0) {
        filterId = ++m_filterId;
    }
    m_filters.insert(filterId, filter);
    return filterId;
}

void ModestEngine::unregisterNotificationFilter(QMessageManager::NotificationFilterId notificationFilterId)
{
    m_filters.remove(notificationFilterId);
}

QByteArray ModestEngine::getMimePart (const QMessageId &id, const QString &attachmentId)
{
    QByteArray result;

    QString modestAccountId = modestAccountIdFromMessageId(id);
    QString modestFolderId  = modestFolderIdFromMessageId(id);
    QString modestMessageId = modestMessageIdFromMessageId(id);

    QString filePath, mimeType;
    int mimeSize = -1;
    bool expunge = false, isAttachment = false;

    QDBusPendingReply<QString, QString, int, bool, bool> reply =
            m_QtmPluginDBusInterface->asyncCall(
                    "GetMimePart",
                    QVariant::fromValue(modestAccountId),
                    QVariant::fromValue(modestFolderId),
                    QVariant::fromValue(modestMessageId),
                    QVariant::fromValue(attachmentId));

    reply.waitForFinished();

    if (reply.isError()) {
        return result;
    }

    filePath = reply.argumentAt<0>();
    mimeType = reply.argumentAt<1>();
    mimeSize = reply.argumentAt<2>();
    isAttachment = reply.argumentAt<3>();
    expunge = reply.argumentAt<4>();

    if (filePath.isEmpty()) {
        qWarning() << "Received empty file path!";
        return result;
    }

    QFile file(filePath);

    QIODevice::OpenMode openMode = QIODevice::ReadOnly;
    if (expunge) {
        openMode = QIODevice::ReadWrite;
    }
    
    if (file.open(openMode) == false) {
        qWarning() << "Failed to open file" << filePath << ": "
                << file.errorString();
        return result;
    }

    result = file.readAll();

    if (expunge) {
        file.remove();
    } else {
        file.close();
    }

    return result;
}

bool ModestEngine::retrieveBody(QMessageService& messageService, const QMessageId &id)
{
    if (!id.isValid()) return false;

    QMessage msg = message(id, true);
    QMessageContentContainerId attachmentId = msg.bodyId();

    //if (!attachmentId.isValid()) return false;

    return retrieve(messageService, id, attachmentId, &msg);
}

bool ModestEngine::retrieve(QMessageService& messageService, const QMessageId &id,
                            const QMessageContentContainerId &attachmentId, QMessage *msg)
{
    QByteArray result;

    if (!id.isValid()) return false;
    //if (!attachmentId.isValid()) return false;

    QString modestAccountId = modestAccountIdFromMessageId(id);
    QString modestFolderId  = modestFolderIdFromMessageId(id);
    QString modestMessageId = modestMessageIdFromMessageId(id);
    QString modestAttachmentId;
    int opId = -1;

    QMessage message;

    if (msg != 0) {
        message = *msg;
    } else {
        message = this->message(id, true);
    }

    QMessageContentContainer cont = message.find(attachmentId);
    QMessageContentContainerPrivate *contPrivate = QMessageContentContainerPrivate::implementation(cont);

    if (contPrivate != 0) {
        modestAttachmentId = contPrivate->_attachmentId;
    }

    if (m_pending_downloads.count() == 0) {
        m_QtmPluginDBusInterface->connection().connect(MODESTENGINE_QTM_PLUGIN_NAME,
                                                       MODESTENGINE_QTM_PLUGIN_PATH,
                                                       MODESTENGINE_QTM_PLUGIN_NAME,
                                                       "PartDownloadFinished",
                                                       (ModestEngine*)this,
                                                       SLOT(mimePartDownloadFinishedSlot(QDBusMessage)));
    }

    QDBusPendingReply<int> reply = m_QtmPluginDBusInterface->asyncCall("DownloadMimePart",
                                                                        QVariant::fromValue(modestAccountId),
                                                                        QVariant::fromValue(modestFolderId),
                                                                        QVariant::fromValue(modestMessageId),
                                                                        QVariant::fromValue(modestAttachmentId));

    reply.waitForFinished();

    if (reply.isError()) {
        if (m_pending_downloads.count() == 0) {
            m_QtmPluginDBusInterface->connection().disconnect(MODESTENGINE_QTM_PLUGIN_NAME,
                                                              MODESTENGINE_QTM_PLUGIN_PATH,
                                                              MODESTENGINE_QTM_PLUGIN_NAME,
                                                              "PartDownloadFinished",
                                                              (ModestEngine*)this,
                                                              SLOT(mimePartDownloadFinishedSlot(QDBusMessage)));
        }
        return false;
    }

    opId = reply.argumentAt<0>();

    QMessageServicePrivate* privateService = QMessageServicePrivate::implementation(messageService);
    m_pending_downloads.insert(opId, privateService);

    return true;
}

void ModestEngine::mimePartDownloadFinishedSlot(QDBusMessage msg)
{
    QList<QVariant> arguments = msg.arguments();
    int operationId = arguments.takeFirst().toInt();

    if (m_pending_downloads.contains(operationId)) {
        QMessageServicePrivate* privateService = m_pending_downloads.take(operationId);

        privateService->_pendingRequestCount--;

        bool success = arguments.takeFirst().toBool();
        if (success) {
            privateService->setFinished(true);
        } else {
            privateService->setFinished(false);
        }

        if (m_pending_downloads.count() == 0) {
            m_QtmPluginDBusInterface->connection().disconnect(MODESTENGINE_QTM_PLUGIN_NAME,
                                                              MODESTENGINE_QTM_PLUGIN_PATH,
                                                              MODESTENGINE_QTM_PLUGIN_NAME,
                                                              "PartDownloadFinished",
                                                              (ModestEngine*)this,
                                                              SLOT(mimePartDownloadFinishedSlot(QDBusMessage)));
        }
    }
}

void ModestEngine::notification(const QMessageId& messageId, NotificationType notificationType) const
{
    QMessageId realMessageId = messageId;
    QString modestAccountId = modestAccountIdFromMessageId(messageId);
    QString modestFolderId = modestFolderIdFromMessageId(messageId);
    QString modestMessageId = modestMessageIdFromMessageId(messageId);

    // Make sure that there will not be many Added or Removed notifications
    // in a row for the same message
    // Make also sure that there will not be updated notification for a
    // message that has already been notified to be removed.
    QString searchId;
    if (notificationType == ModestEngine::Added) {
        searchId = "A"+modestAccountId+modestFolderId+modestMessageId;
    } else {
        searchId = "D"+modestAccountId+modestFolderId+modestMessageId;
    }
    if (!m_latestAddOrRemoveNotifications.contains(searchId)) {
        if ((notificationType == ModestEngine::Added) || (notificationType == ModestEngine::Removed)) {
            // Only Added & Removed notification will be checked
            if (m_latestAddOrRemoveNotifications.count() > 10) {
                // Remove the oldest notification from the beginning of the list
                m_latestAddOrRemoveNotifications.removeFirst();
            }
            // Append new notification
            m_latestAddOrRemoveNotifications.append(searchId);
        }
    } else {
        // Add or Remove notification for the message was already handled!
        // => Skip unwanted notification
        return;
    }

    QMessageManager::NotificationFilterIdSet matchingFilters;
    QMessage message;

    MessagingModestMessage modestMessage = messageFromModest(modestAccountId,
                                                             modestFolderId,
                                                             modestMessageId);

    if (modestMessage.accountId.isEmpty()) {
        return;
    }

    if (modestMessage.flags & MessagingModestMessageDeleted) {
        notificationType = ModestEngine::Removed;
    }
    if (modestMessage.size == 0) {
        notificationType = ModestEngine::Removed;
    }

    message = messageFromModestMessage(modestMessage);

    realMessageId = message.id();

    if (notificationType == ModestEngine::Added) {
        MessageCache::instance()->insert(message);
    } else if (notificationType == ModestEngine::Updated) {
        MessageCache::instance()->update(message);
    } else if (notificationType == ModestEngine::Removed) {
        MessageCache::instance()->remove(message.id());
    }

    // Copy the filter map to protect against modification during traversal
    QMap<int, QMessageFilter> filters(m_filters);
    QMap<int, QMessageFilter>::const_iterator it = filters.begin(), end = filters.end();
    int counter = 1;
    for ( ; it != end; ++it) {
        const QMessageFilter &filter(it.value());
        counter++;

        if (filter.isEmpty()) {
            // Empty filter matches to all messages
            matchingFilters.insert(it.key());
        } else {
            if (message.type() == QMessage::NoType) {
                continue;
            }
            QMessageFilterPrivate* privateMessageFilter = QMessageFilterPrivate::implementation(filter);
            if (privateMessageFilter->filter(message)) {
                matchingFilters.insert(it.key());
            }
        }
    }

    if (matchingFilters.count() > 0) {
        if (notificationType == ModestEngine::Added) {
            m_messageStore->messageNotification(QMessageStorePrivate::Added, realMessageId, matchingFilters);
        } else if (notificationType == ModestEngine::Updated) {
            m_messageStore->messageNotification(QMessageStorePrivate::Updated, realMessageId, matchingFilters);
        } else if (notificationType == ModestEngine::Removed) {
            m_messageStore->messageNotification(QMessageStorePrivate::Removed, realMessageId, matchingFilters);
        }
    }
}

QMessageAccountId ModestEngine::accountIdFromModestMessageId(const QString& modestMessageId) const
{
    // Modest messageId format is following:
    //   <email protocol>://<username>@<hostname>:<port>...
    QMessageAccountId accountId;

    int usernameBegin = modestMessageId.indexOf("//")+2;
    int usernameEnd = modestMessageId.indexOf("@");
    QString username = QUrl::fromPercentEncoding(modestMessageId.mid(usernameBegin, usernameEnd-usernameBegin).toUtf8());
    int hostnameBegin = usernameEnd+1;
    int hostnameEnd = modestMessageId.indexOf(':',hostnameBegin);
    QString hostname = QUrl::fromPercentEncoding(modestMessageId.mid(hostnameBegin, hostnameEnd-hostnameBegin).toUtf8());

    foreach (QMessageAccount value, iAccounts) {
        QMessageAccountId accId = value.id();
        if ((accountUsername(accId) == username) &&
            (accountHostname(accId) == hostname)) {
            accountId = accId;
        }
    }

    if (!accountId.isValid()) {
        if (modestMessageId.left(modestMessageId.indexOf(':')) == "maildir") {
            accountId = QMessageAccountId("MO_LOCAL");
        }
    }

    return accountId;
}

QMessageFolderId ModestEngine::folderIdFromModestMessageId(const QString& modestMessageId,
                                                           const QMessageAccountId accountId) const
{
    // Modest messageId format is following:
    //   <email protocol>://<username>@<hostname>:<port>...
    QMessageFolderId folderId;
    QString folderIdAsString;

    if (!accountId.isValid()) {
        folderIdAsString = accountIdFromModestMessageId(modestMessageId).toString();
    } else {
        folderIdAsString = accountId.toString();
    }

    int protocolEnd = modestMessageId.indexOf(':');
    QString protocol = modestMessageId.left(protocolEnd);
    folderIdAsString += "&" + protocol;
    if (protocol == "pop") {
        folderIdAsString += "&cache";
    } else if (protocol == "imap") {
        int pathBegin = modestMessageId.indexOf('/',modestMessageId.lastIndexOf(':'))+1;
        int pathEnd = modestMessageId.lastIndexOf('/');
        folderIdAsString += "&" + modestMessageId.mid(pathBegin, pathEnd-pathBegin);
    } else if (protocol == "maildir") {
        int pathBegin = modestMessageId.indexOf('#')+1;
        int pathEnd = modestMessageId.lastIndexOf('/');
        folderIdAsString += "&" + modestMessageId.mid(pathBegin, pathEnd-pathBegin);
    }
    folderId = QMessageFolderId(QUrl::fromPercentEncoding(folderIdAsString.toUtf8()));

    return folderId;
}

QString ModestEngine::modestAccountIdFromAccountId(const QMessageAccountId& accountId) const
{
    // Just remove "MO_" prefix from the beginning of id string
    return accountId.toString().remove(0,3);
}

QString ModestEngine::modestFolderIdFromFolderId(const QMessageFolderId& folderId) const
{
    QString modestFolderId;

    QString folderIdString = folderId.toString();
    int protocolBegin = folderIdString.indexOf('&');
    int protocolEnd = folderIdString.lastIndexOf('&');

    modestFolderId = folderIdString.mid(protocolEnd+1);
    QString protocol = folderIdString.mid(protocolBegin+1,protocolEnd-protocolBegin-1);
    if ((protocol == "pop") && (modestFolderId == "cache")) {
        modestFolderId = "INBOX";
    }

    return modestFolderId;
}

QString ModestEngine::modestFolderUriFromFolderId(const QMessageFolderId& folderId) const
{
    Q_UNUSED(folderId) //TODO:
    return QString();
}

QString ModestEngine::modestAccountIdFromMessageId(const QMessageId& messageId,
                                                   bool checkProtocol) const
{
    QString id = messageId.toString();
    int protocolBegin = id.indexOf('&');
    int protocolEnd = id.lastIndexOf('&');
    QString protocol = id.mid(protocolBegin+1,protocolEnd-protocolBegin-1);
    if (checkProtocol && protocol == "maildir") {
        return "local_folders";
    }

    return unescapeString(id.left(protocolBegin).remove(0,3));
}

QMessageAccountId ModestEngine::accountIdFromFolderId(const QMessageFolderId& folderId) const
{
    QString id = folderId.toString();
    int protocolBegin = id.indexOf('&');
    return QMessageAccountId(id.left(protocolBegin));
}

QMessageAccountId ModestEngine::accountIdFromMessageId(const QMessageId& messageId) const
{
    QString id = messageId.toString();
    int protocolBegin = id.indexOf('&');
    return QMessageAccountId(id.left(protocolBegin));
}

QString ModestEngine::modestAccountIdFromFolderId(const QMessageFolderId& folderId,
                                                  bool checkProtocol) const
{
    QString id = folderId.toString();
    int protocolBegin = id.indexOf('&');
    int protocolEnd = id.lastIndexOf('&');
    QString protocol = id.mid(protocolBegin+1,protocolEnd-protocolBegin-1);
    if (checkProtocol && protocol == "maildir") {
        return "local_folders";
    }

    return unescapeString(id.left(protocolBegin).remove(0,3));
}

QString ModestEngine::modestFolderIdFromMessageId(const QMessageId& messageId) const
{
    QString id = messageId.toString();

    int protocolBegin = id.indexOf('&');
    int protocolEnd = id.lastIndexOf('&');
    int folderEnd = id.lastIndexOf('/');

    QString protocol = id.mid(protocolBegin+1,protocolEnd-protocolBegin-1);
    id = id.mid(protocolEnd+1,folderEnd-protocolEnd-1);
    if ((protocol == "pop") && (id == "cache")) {
        id = "INBOX";
    }

    return id;
}

void ModestEngine::replaceProtocol(QString& id, const QString& newProtocol) const
{
    int protocolBegin = id.indexOf('&');
    int protocolEnd = id.lastIndexOf('&');
    id.remove(protocolBegin+1,protocolEnd-protocolBegin-1);
    id.insert(protocolBegin+1, newProtocol);
}

QMessageAccountId ModestEngine::realAccountId(const MessagingModestMessage& modestMessage) const
{
    QMessageAccountId accountId;

    if (modestMessage.accountId == "local_folders") {
        QString accountIdString;
        // Message is in local foldar, but message can be linked
        // to actual account using 'From', 'To', 'Cc' or 'Bcc' fields
        foreach (QMessageAccount value, iAccounts) {
            QMessageAccountPrivate* privAccount = QMessageAccountPrivate::implementation(value);
            if (modestMessage.from.contains(privAccount->_address.addressee())) {
                accountIdString = value.id().toString();
                break;
            } else if (modestMessage.to.contains(privAccount->_address.addressee())) {
                accountIdString = value.id().toString();
                break;
            } else if (modestMessage.cc.contains(privAccount->_address.addressee())) {
                accountIdString = value.id().toString();
                break;
            } else if (modestMessage.bcc.contains(privAccount->_address.addressee())) {
                accountIdString = value.id().toString();
                break;
            }
        }
        if (!accountIdString.isEmpty()) {
            accountId = QMessageAccountId(accountIdString);
        }
    } else {
        accountId = accountIdFromModestAccountId(modestMessage.accountId);
    }

    return accountId;
}

QString ModestEngine::modestMessageIdFromMessageId(const QMessageId& messageId) const
{
    QString id = messageId.toString();
    return id.mid(id.lastIndexOf('/')+1);
}

QMessageAccountId ModestEngine::accountIdFromModestAccountId(const QString& accountId) const
{
    // Just add "MO_" prefix to the beginning of id string & escape created Id
    return QMessageAccountId(escapeString("MO_"+accountId));
}

QMessageFolderId ModestEngine::folderIdFromModestFolderId(const QMessageAccountId& accountId,
                                                          bool isLocalFolder,
                                                          const QString& modestFolderId) const
{
    // Format: <accountId>&<email protocol>&<Modest folderId>
    QMessageFolderId folderId;

    if (isLocalFolder) {
         folderId = QMessageFolderId(accountId.toString()+"&maildir&"+modestFolderId);
    } else {
        QString protocol = accountEmailProtocolAsString(accountId);
        if ((protocol == "pop") && (modestFolderId == "INBOX")) {
            folderId = QMessageFolderId(accountId.toString()+"&"+protocol+"&cache");
        } else {
            folderId = QMessageFolderId(accountId.toString()+"&"+protocol+"&"+modestFolderId);
        }
    }

    return folderId;
}

QMessageId ModestEngine::messageIdFromModestMessageId(const QString& messageId) const
{
    QString messageIdString = folderIdFromModestMessageId(messageId).toString();
    int idPartBegin = messageId.lastIndexOf('/');
    messageIdString += messageId.mid(idPartBegin, idPartBegin-messageId.length());
    return QMessageId(messageIdString);
}

QMessageId ModestEngine::messageIdFromModestMessageFilePath(const QString& messageFilePath) const
{
    QString messageIdString;

    QString filePath = messageFilePath;
    QString localRootFolder = this->localRootFolder();
    if (filePath.startsWith(localRootFolder)) {
        messageIdString = "MO_LOCAL&maildir&";
        filePath.remove(0,localRootFolder.length()+1);
        filePath.remove("/cur");
        messageIdString += filePath.left(filePath.lastIndexOf('!'));
    } else {
        foreach (QMessageAccount value, iAccounts) {
            QMessageAccountId accountId = value.id();
            QString rootFolder = accountRootFolder(accountId);
            if (filePath.startsWith(rootFolder)) {
                QString protocol = accountEmailProtocolAsString(accountId);
                messageIdString = accountId.toString()+"&"+protocol+"&";
                filePath.remove(0,rootFolder.length()+1);
                filePath.remove("/subfolders");
                messageIdString += filePath.left(filePath.lastIndexOf('.'));
                if (protocol == "pop") {
                    QDir dir(messageFilePath);
                    dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
                    QFileInfoList dirs = dir.entryInfoList();
                    if (dirs.count() > 0) {
                        QString fileName = dirs[0].fileName();
                        // Remove folder that contains actual message
                        messageIdString = messageIdString.left(messageIdString.lastIndexOf('/')+1);
                        // Add message name
                        messageIdString = messageIdString+fileName;
                    }
                }
            }
        }
    }

    return QMessageId(messageIdString);
}

QString ModestEngine::unescapeString(const QString& string)
{
    QString unescapedString;

    QByteArray str = string.toUtf8();
    gchar* unescaped_string = gconf_unescape_key(str.data(), str.length());
    unescapedString = QString::fromUtf8(unescaped_string);
    g_free(unescaped_string);

    return unescapedString;
}

QString ModestEngine::escapeString(const QString& string)
{
    QString escapedString;

    QByteArray str = unescapeString(string).toUtf8();
    gchar* escaped_string = gconf_escape_key(str.data(), str.length());
    escapedString = QString::fromUtf8(escaped_string);
    g_free(escaped_string);

    return escapedString;
}

INotifyWatcher::INotifyWatcher()
{
    // Initialize inotify instance
    // => returned file descriptor is associated with
    //    a new inotify event queue
    // O_CLOEXEC flag makes sure that file descriptor
    //           is closed if execution is transferred
    //           from this process to a new program
    //           (Check more info from 'execve' documentation)
#ifdef IN_CLOEXEC
    m_inotifyFileDescriptor = inotify_init1(IN_CLOEXEC);
#else
    m_inotifyFileDescriptor = inotify_init();
    if (m_inotifyFileDescriptor >= 0) {
        ::fcntl(m_inotifyFileDescriptor, F_SETFD, FD_CLOEXEC);
    }
#endif
    if (m_inotifyFileDescriptor >= 0) {
        // Change thread affinity for this object to this
        // thread.
        // => Event processing (for this objects events) will
        //    be done in this thread
        moveToThread(this);
    }
}

INotifyWatcher::~INotifyWatcher()
{
    // Tell the thread's event loop to exit
    // => thread returns from the call to exec()
    exit();

    // Wait until this thread has finished execution
    // <=> waits until thread returns from run()
    wait();

    clear();

    // Close file descriptor that's referring to inotify instance
    // => The underlying inotify object and its resources are freed
    ::close(m_inotifyFileDescriptor);
}

void INotifyWatcher::clear()
{
    // Remove all watches from inotify instance watch list
    QMapIterator<int, QString> i(m_dirs);
    while (i.hasNext()) {
        inotify_rm_watch(m_inotifyFileDescriptor, i.next().key());
    }
    m_dirs.clear();

    QMapIterator<int, QString> j(m_files);
    while (j.hasNext()) {
        inotify_rm_watch(m_inotifyFileDescriptor, j.next().key());
    }
    m_files.clear();
}

void INotifyWatcher::run()
{
    // Start listening inotify
    QSocketNotifier socketNotifier(m_inotifyFileDescriptor, QSocketNotifier::Read, this);
    connect(&socketNotifier, SIGNAL(activated(int)), SLOT(notifySlot()));

    // Enter the thread event loop
    (void) exec();
}

int INotifyWatcher::addFile(const QString& path, uint eventsToWatch)
{
    int watchDescriptor = 0;
    QMutexLocker locker(&m_mutex);

    if (m_inotifyFileDescriptor >= 0) {
        int watchDescriptor = 0;
        if (eventsToWatch == 0) {
            watchDescriptor = inotify_add_watch(m_inotifyFileDescriptor,
                                                QFile::encodeName(path),
                                                0 | IN_ATTRIB
                                                  | IN_MODIFY
                                                  | IN_MOVE
                                                  | IN_MOVE_SELF
                                                  | IN_DELETE_SELF);
        } else {
            watchDescriptor = inotify_add_watch(m_inotifyFileDescriptor,
                                                QFile::encodeName(path),
                                                eventsToWatch);
        }
        if (watchDescriptor > 0) {
            m_files.insert(watchDescriptor, path);
        } else {
            watchDescriptor = 0;
        }
    }

    // Start thread (if thread is not already running)
    start();

    return watchDescriptor;
}

int INotifyWatcher::addDirectory(const QString& path, uint eventsToWatch)
{
    int watchDescriptor = 0;
    QMutexLocker locker(&m_mutex);

    if (m_inotifyFileDescriptor >= 0) {
        int watchDescriptor = 0;
        if (eventsToWatch == 0) {
            watchDescriptor = inotify_add_watch(m_inotifyFileDescriptor,
                                                QFile::encodeName(path),
                                                0 | IN_ATTRIB
                                                  | IN_MOVE
                                                  | IN_CREATE
                                                  | IN_DELETE
                                                  | IN_DELETE_SELF);
        } else {
            watchDescriptor = inotify_add_watch(m_inotifyFileDescriptor,
                                                QFile::encodeName(path),
                                                eventsToWatch);
        }
        if (watchDescriptor > 0) {
            m_dirs.insert(watchDescriptor, path);
        } else {
            watchDescriptor = 0;
        }
    }

    // Start thread (if thread is not already running)
    start();

    return watchDescriptor;
}

QStringList INotifyWatcher::directories() const
{
    return m_dirs.values();
}

QStringList INotifyWatcher::files() const
{
    return m_dirs.values();
}

void INotifyWatcher::notifySlot()
{
    QMutexLocker locker(&m_mutex);

    int bufferSize = 0;
    ioctl(m_inotifyFileDescriptor, FIONREAD, (char*) &bufferSize);
    QVarLengthArray<char, 4096> buffer(bufferSize);
    bufferSize = read(m_inotifyFileDescriptor, buffer.data(), bufferSize);
    const char* at = buffer.data();
    const char* const end = at + bufferSize;

    QMap<int, INotifyEvent> eventsForWatchedFile;
    QMap<QString, INotifyEvent> eventsForFileInWatchedDirectory;
    while (at < end) {
        const inotify_event *event = reinterpret_cast<const inotify_event *>(at);
        if (m_files.contains(event->wd)) {
            // File event handling
            if (eventsForWatchedFile.contains(event->wd)) {
                // There is already unhandled event for this file in queue
                // => Mask is ORed to existing event
                eventsForWatchedFile[event->wd].mask |= event->mask;
            } else {
                // There is no event for this file in queue
                // => New event is created
                INotifyEvent inotifyEvent;
                inotifyEvent.watchDescriptor = event->wd;
                inotifyEvent.mask = event->mask;
                inotifyEvent.fileName = QString::fromAscii(event->name, event->len);
                eventsForWatchedFile.insert(event->wd, inotifyEvent);
            }
        } else {
            // Directory event handling
            QString changeForFileInDirectory = QString::fromAscii(event->name, event->len);
            // Remove unnecessary postfix (starting with '!') from the file name
            changeForFileInDirectory = changeForFileInDirectory.left(changeForFileInDirectory.lastIndexOf('!'));
            if (!changeForFileInDirectory.isEmpty()) {
                QString eventId = QString::number(event->wd)+changeForFileInDirectory;
                if (eventsForFileInWatchedDirectory.contains(eventId)) {
                    // There is already unhandled event for this file in queue
                    // => Mask is ORed to existing event
                    eventsForFileInWatchedDirectory[eventId].mask |= event->mask;
                } else {
                    // There is no event for this file in queue
                    // => New event is created
                    INotifyEvent inotifyEvent;
                    inotifyEvent.watchDescriptor = event->wd;
                    inotifyEvent.mask = event->mask;
                    inotifyEvent.fileName = QString::fromAscii(event->name, event->len);
                    eventsForFileInWatchedDirectory.insert(eventId, inotifyEvent);
                }
            }
        }
        at += sizeof(inotify_event) + event->len;
    }

    QMap<int, INotifyEvent>::const_iterator it = eventsForWatchedFile.constBegin();
    while (it != eventsForWatchedFile.constEnd()) {
        INotifyEvent event = *it;
        QString file = m_files.value(event.watchDescriptor);
        if (!file.isEmpty()) {
            emit fileChanged(event.watchDescriptor, file, event.mask);
        }
        ++it;
    }

    QMap<QString, INotifyEvent>::const_iterator jt = eventsForFileInWatchedDirectory.constBegin();
    while (jt != eventsForFileInWatchedDirectory.constEnd()) {
        INotifyEvent event = *jt;
        QString file = m_dirs.value(event.watchDescriptor)+"/"+event.fileName;
        emit fileChanged(event.watchDescriptor, file, event.mask);
        ++jt;
    }
}

ModestStringMap ModestEngine::getModestSenderInfo(QMessage &message)
{
    QMessageAddress address;
    ModestStringMap senderInfo;
    QMessageAccountId accountId;
    QString value;

    accountId = message.parentAccountId();
    if (accountId.isValid() == false) {
        qWarning () << "Account ID is invalid";
        return ModestStringMap();
    }

    senderInfo["account-name"] = unescapeString(modestAccountIdFromAccountId(accountId));

    QMessageAccount messageAccount = account(accountId);
    QMessageAccountPrivate* privAccount = QMessageAccountPrivate::implementation(messageAccount);
    address = privAccount->_address;
    value = address.addressee();

    if (value.isEmpty() == false && value.isNull() == false) {
        senderInfo["from"] = value;
    }

    return senderInfo;
}

ModestStringMap ModestEngine::getModestRecipients(QMessage &message)
{
    QMessageAddressList addresses;
    QMessageAddress address;
    ModestStringMap recipients;
    QString value;

    addresses = message.to();
    value.clear();
    for (int i = 0; i < addresses.length(); i++) {
        address = addresses[i];

        if (value.isEmpty()) {
            value = address.addressee();
        } else {
            value.append (",");
            value.append (address.addressee());
        }
    }

    if (value.isEmpty() == false && value.isNull() == false) {
        recipients["to"] = value;
    }

    addresses = message.cc();
    value.clear();
    for (int i = 0; i < addresses.length(); i++) {
        address = addresses[i];

        if (value.isEmpty()) {
            value = address.addressee();
        } else {
            value.append (",");
            value.append (address.addressee());
        }
    }

    if (value.isEmpty() == false && value.isNull() == false) {
        recipients["cc"] = value;
    }

    addresses = message.bcc();
    value.clear();
    for (int i = 0; i < addresses.length(); i++) {
        address = addresses[i];

        if (value.isEmpty()) {
            value = address.addressee();
        } else {
            value.append (",");
            value.append (address.addressee());
        }
    }

    if (value.isEmpty() == false && value.isNull() == false) {
        recipients["bcc"] = value;
    }

    return recipients;
}

ModestStringMap ModestEngine::getModestMessageData(QMessage &message)
{
    QMessageContentContainerId bodyId;
    QMessageContentContainer body;
    ModestStringMap messageData;
    QString value;

    value = message.subject();

    if (value.isEmpty() == false && value.isNull() == false) {
        messageData["subject"] = value;
    }

    bodyId = message.bodyId();
    if (bodyId.isValid()) {
        body = message.find (bodyId);
    } else {
        body = message;
    }

    value = body.contentType();

    if (value == "text") {
        QString key, data;
        bool hasContent = false;

        value = body.contentSubType();

        if ((hasContent = body.isContentAvailable()) == true) {
            data = body.textContent();
        }

        if (value == "plain") {
            key = "plain-body";
        } else if (value == "html") {
            key = "html-body";
        }

        if (key.isEmpty() == false && key.isNull() == false && hasContent) {
            messageData[key] = data;
        }
    }

    if (message.status() & QMessage::Read) {
        messageData["read"] = "";
    }

    return messageData;
}

ModestStringMapList ModestEngine::getModestAttachments(QMessage &message)
{
    QMessageContentContainerIdList attachmentIds;
    ModestStringMapList attachments;
    QMessage::StatusFlags messageStatus;
    QString value;

    messageStatus = message.status();

    if (messageStatus & QMessage::HasAttachments) {
        attachmentIds = message.attachmentIds();

        foreach (QMessageContentContainerId identifier, attachmentIds) {
            ModestStringMap attachmentData;
            QMessageContentContainer attachmentCont;

            if (identifier.isValid() == false) continue;

            attachmentCont = message.find (identifier);

            if (attachmentCont.isContentAvailable () == false) continue;

            attachmentData.clear();

            value = attachmentCont.contentType();

            if (value.isEmpty() == false) {
                value.append("/");
                value.append (attachmentCont.contentSubType());
                attachmentData["mime-type"] = value;

                qDebug() << "mime-type: " << value;
            }

            value = QMessageContentContainerPrivate::attachmentFilename (
                attachmentCont);

            if (value.isEmpty() == false) {
                attachmentData["filename"] = value;
                qDebug() << "filename: " << value;
            }

            qDebug() << "Charset: " << attachmentCont.contentCharset();
            qDebug() << "Headers: " << attachmentCont.headerFields();

            if (attachmentData.isEmpty() == false) {
                attachmentData["content-id"] = identifier.toString();
                attachments.append (attachmentData);
            }
        }
    }

    return attachments;
}

ModestStringMapList ModestEngine::getModestImages(QMessage &message)
{
    Q_UNUSED(message);
    // Don't know if this even makes sense. Modest expects inlined images
    // to be in a separate list, but that doesn't make much sense?
    return ModestStringMapList();
}

uint ModestEngine::getModestPriority(QMessage &message)
{
    uint priority = 0;

    switch (message.priority()) {
    case QMessage::HighPriority:
        priority = MODESTENGINE_HIGH_PRIORITY;
        break;

    default:
    case QMessage::NormalPriority:
        priority = MODESTENGINE_NORMAL_PRIORITY;
        break;

    case QMessage::LowPriority:
        priority = MODESTENGINE_LOW_PRIORITY;
        break;
    }

    return priority;
}

ModestStringMap ModestEngine::getModestHeaders(QMessage &message)
{
    Q_UNUSED(message);
    return ModestStringMap(); // stub
}

MessageQueryThread::MessageQueryThread(const QMessageFilter &filter,
                                       const QMessageSortOrder &sortOrder)
 : m_filter(filter), m_sortOrder(sortOrder)
{
}

MessageQueryThread::~MessageQueryThread()
{
    // Tell the thread's event loop to exit
    // => thread returns from the call to exec()
    exit();

    // Wait until this thread has finished execution
    // <=> waits until thread returns from run()
    wait();
}

void MessageQueryThread::run()
{
    QMessageIdList ids = MessageCache::instance()->messageIds();

    MessagingHelper::filterMessages(ids, m_filter & QMessageFilter::byType(QMessage::Email));
    MessagingHelper::orderMessages(ids, m_sortOrder);

    emit queryFinished(this, ids);

    QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection);

    // Enter the thread event loop
    (void) exec();
}

#include "moc_modestengine_maemo_p.cpp"

QTM_END_NAMESPACE
