/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Mobility Components.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** 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, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qmessageservice.h"
#include "qmfhelpers_p.h"

#include <qmailserviceaction.h>

#include <QTimer>
#include <qmessage_p.h>


QTM_BEGIN_NAMESPACE

namespace {

struct TextPartSearcher
{
    QString _search;

    TextPartSearcher(const QString &searchText) : _search(searchText) {}

    bool operator()(const QMailMessagePart &part)
    {
        if (part.contentType().type().toLower() == "text") {
            if (part.body().data().contains(_search, Qt::CaseInsensitive)) {
                // We have located some matching text - stop the traversal
                return false;
            }
        }

        return true;
    }
};

}

using namespace QmfHelpers;

class QMessageServicePrivate : public QObject
{
    Q_OBJECT

public:
    QMessageServicePrivate();

    QMailTransmitAction _transmit;
    QMailRetrievalAction _retrieval;
    QMailStorageAction _storage;
    QMailServiceAction *_active;
    QMessageManager::Error _error;
    bool _activeStoreAction;

    QList<QMessageId> _matchingIds;
    QList<QMailMessageId> _candidateIds;
    QMessageFilter _lastFilter;
    QMessageSortOrder _lastOrdering;
    QString _match;
    int _limit;
    int _offset;

    QMailMessageIdList _transmitIds;
    
    bool isBusy() const;

signals:
    void stateChanged(QMessageService::State);
    void progressChanged(uint, uint);
    void messagesFound(const QMessageIdList&);
    void messagesCounted(int);

protected slots:
    void transmitActivityChanged(QMailServiceAction::Activity a);
    void retrievalActivityChanged(QMailServiceAction::Activity a);
    void storageActivityChanged(QMailServiceAction::Activity a);
    void statusChanged(const QMailServiceAction::Status &s);
    void completed();
    void reportMatchingIds();
    void reportMatchingCount();
    void findMatchingIds();
    void testNextMessage();

private:
    bool messageMatch(const QMailMessageId &messageid);
};

QMessageServicePrivate::QMessageServicePrivate()
    : QObject(),
      _active(0),
      _error(QMessageManager::NoError),
      _activeStoreAction(false),
      _limit(0),
      _offset(0)
{
    connect(&_transmit, SIGNAL(activityChanged(QMailServiceAction::Activity)), this, SLOT(transmitActivityChanged(QMailServiceAction::Activity)));
    connect(&_transmit, SIGNAL(statusChanged(QMailServiceAction::Status)), this, SLOT(statusChanged(QMailServiceAction::Status)));

    connect(&_retrieval, SIGNAL(activityChanged(QMailServiceAction::Activity)), this, SLOT(retrievalActivityChanged(QMailServiceAction::Activity)));
    connect(&_retrieval, SIGNAL(statusChanged(QMailServiceAction::Status)), this, SLOT(statusChanged(QMailServiceAction::Status)));

    connect(&_storage, SIGNAL(activityChanged(QMailServiceAction::Activity)), this, SLOT(storageActivityChanged(QMailServiceAction::Activity)));
    connect(&_storage, SIGNAL(statusChanged(QMailServiceAction::Status)), this, SLOT(statusChanged(QMailServiceAction::Status)));
}

bool QMessageServicePrivate::isBusy() const
{
    if ((_active && ((_active->activity() == QMailServiceAction::Pending) || (_active->activity() == QMailServiceAction::InProgress))) ||
        _activeStoreAction) {
        qWarning() << "Action is currently busy";
        return true;
    }

    return false;
}

void QMessageServicePrivate::transmitActivityChanged(QMailServiceAction::Activity a)
{
    if (a == QMailServiceAction::Successful) {
        if (!_transmitIds.isEmpty()) {
            // If these messages were transmitted, we need to move them to Sent folder
            QMailMessageKey filter(QMailMessageKey::id(_transmitIds));
            foreach (const QMailMessageId &id, mailStoreInstance()->queryMessages(filter & QMailMessageKey::status(QMailMessage::Sent))) {
                QMessage message(convert(id));

                QMessagePrivate::setStandardFolder(message,QMessage::SentFolder);
                if (!QMessageManager().updateMessage(&message)) {
                    qWarning() << "Unable to mark message as sent!";
                }
            }

            _transmitIds.clear();
        }
    } else if (a == QMailServiceAction::Failed) {
        _transmitIds.clear();

        if (_error == QMessageManager::NoError) {
            // We may have failed due to some part of the request remaining incomplete
            _error = QMessageManager::RequestIncomplete;
        }
    }

    emit stateChanged(convert(a));
}

void QMessageServicePrivate::statusChanged(const QMailServiceAction::Status &s)
{
    if (s.errorCode != QMailServiceAction::Status::ErrNoError) {
        qWarning() << QString("Service error %1: \"%2\"").arg(s.errorCode).arg(s.text);

        if (s.errorCode == QMailServiceAction::Status::ErrNotImplemented) {
            _error = QMessageManager::NotYetImplemented;
        } else if ((s.errorCode == QMailServiceAction::Status::ErrNonexistentMessage) ||
                   (s.errorCode == QMailServiceAction::Status::ErrEnqueueFailed) ||
                   (s.errorCode == QMailServiceAction::Status::ErrInvalidAddress) ||
                   (s.errorCode == QMailServiceAction::Status::ErrInvalidData)) {
            _error = QMessageManager::ConstraintFailure;
        } else {
            _error = QMessageManager::FrameworkFault;
        }
    }
}

void QMessageServicePrivate::retrievalActivityChanged(QMailServiceAction::Activity a)
{
    if ((a == QMailServiceAction::Failed) && (_error == QMessageManager::NoError)) {
        // We may have failed due to some part of the request remaining incomplete
        _error = QMessageManager::RequestIncomplete;
    }

    emit stateChanged(convert(a));
}


void QMessageServicePrivate::completed()
{
    _activeStoreAction = false;
    emit stateChanged(QMessageService::FinishedState);
}

bool QMessageServicePrivate::messageMatch(const QMailMessageId &messageId)
{
    if (_match.isEmpty()) {
        return true;
    }

    const QMailMessage candidate(messageId);
    if (candidate.id().isValid()) {
        // Search only messages or message parts that are of type 'text/*'
        if (candidate.hasBody()) {
            if (candidate.contentType().type().toLower() == "text") {
                if (candidate.body().data().contains(_match, Qt::CaseInsensitive))
                    return true;
            }
        } else if (candidate.multipartType() != QMailMessage::MultipartNone) {
            // Search all 'text/*' parts within this message
            TextPartSearcher searcher(_match);
            if (candidate.foreachPart<TextPartSearcher&>(searcher) == false) {
                // We found a matching part in the message
                return true;
            }
        }
    }

    return false;
}

void QMessageServicePrivate::reportMatchingIds()
{
    emit messagesFound(convert(_candidateIds));
    completed();
}

void QMessageServicePrivate::reportMatchingCount()
{
    emit messagesCounted(_candidateIds.count());
    completed();
}

void QMessageServicePrivate::findMatchingIds()
{
    int required = ((_offset + _limit) - _matchingIds.count());

    // Are any of the existing IDs part of the result set?
    if (required < _limit) {
        emit messagesFound(_matchingIds.mid(_offset, _limit));
    }

    if ((required > 0 || _limit == 0) && !_candidateIds.isEmpty()) {
        QTimer::singleShot(0, this, SLOT(testNextMessage()));
    } else {
        completed();
    }
}

void QMessageServicePrivate::testNextMessage()
{
    int required = ((_offset + _limit) - _matchingIds.count());
    if (required > 0 || _limit==0) {
        QMailMessageId messageId(_candidateIds.takeFirst());
        if (messageMatch(messageId)) {
            _matchingIds.append(convert(messageId));
            --required;

            if (required < _limit) {
                // This is within the result set
                emit messagesFound(QMessageIdList() << _matchingIds.last());
            }
        }

        if ((required > 0 || _limit == 0) && !_candidateIds.isEmpty()) {
            QTimer::singleShot(0, this, SLOT(testNextMessage()));
            return;
        }
    }

    completed();
}

QMessageService::QMessageService(QObject *parent)
    : QObject(parent),
      d_ptr(new QMessageServicePrivate)
{
    // Ensure the message store is initialized
    QMessageManager();

    connect(d_ptr, SIGNAL(stateChanged(QMessageService::State)), 
            this, SIGNAL(stateChanged(QMessageService::State)));
    connect(d_ptr, SIGNAL(messagesFound(QMessageIdList)), 
            this, SIGNAL(messagesFound(QMessageIdList)));
    connect(d_ptr, SIGNAL(messagesCounted(int)), 
            this, SIGNAL(messagesCounted(int)));
    connect(d_ptr, SIGNAL(progressChanged(uint, uint)), 
            this, SIGNAL(progressChanged(uint, uint)));
}

QMessageService::~QMessageService()
{
    delete d_ptr;
}

bool QMessageService::queryMessages(const QMessageFilter &filter, const QMessageSortOrder &sortOrder, uint limit, uint offset)
{
    if (d_ptr->isBusy()) {
        return false;
    }
    d_ptr->_active = 0;
    d_ptr->_activeStoreAction = true;
    emit stateChanged(QMessageService::ActiveState);

    d_ptr->_candidateIds = mailStoreInstance()->queryMessages(convert(filter), convert(sortOrder), limit, offset);
    d_ptr->_error = convert(mailStoreInstance()->lastError());

    if (d_ptr->_error == QMessageManager::NoError) {
        d_ptr->_lastFilter = QMessageFilter();
        d_ptr->_lastOrdering = QMessageSortOrder();
        d_ptr->_match = QString();
        d_ptr->_limit = static_cast<int>(limit);
        d_ptr->_offset = 0;
        d_ptr->_matchingIds.clear();
        QTimer::singleShot(0, d_ptr, SLOT(reportMatchingIds()));
        return true;
    }

    return false;
}

bool QMessageService::queryMessages(const QMessageFilter &filter, const QString &body, QMessageDataComparator::MatchFlags matchFlags, const QMessageSortOrder &sortOrder, uint limit, uint offset)
{
    if (matchFlags) {
        //TODO: Support matchFlags
        return false;
    }

    if (body.isEmpty()) {
        return queryMessages(filter, sortOrder, limit, offset);
    }

    if (d_ptr->isBusy()) {
        return false;
    }

    d_ptr->_active = 0;
    d_ptr->_activeStoreAction = true;
    emit stateChanged(QMessageService::ActiveState);

    if ((filter == d_ptr->_lastFilter) && (sortOrder == d_ptr->_lastOrdering) && (body == d_ptr->_match)) {
        // This is a continuation of the last search
        d_ptr->_limit = static_cast<int>(limit);
        d_ptr->_offset = static_cast<int>(offset);
        QTimer::singleShot(0, d_ptr, SLOT(findMatchingIds()));
        return true;
    }

    // Find all messages to perform the body search on
    d_ptr->_candidateIds = mailStoreInstance()->queryMessages(convert(filter), convert(sortOrder));
    d_ptr->_error = convert(mailStoreInstance()->lastError());

    if (d_ptr->_error == QMessageManager::NoError) {
        d_ptr->_lastFilter = filter;
        d_ptr->_lastOrdering = sortOrder;
        d_ptr->_match = body;
        d_ptr->_limit = static_cast<int>(limit);
        d_ptr->_offset = static_cast<int>(offset);
        d_ptr->_matchingIds.clear();
        QTimer::singleShot(0, d_ptr, SLOT(findMatchingIds()));
        return true;
    }

    return false;
}

bool QMessageService::countMessages(const QMessageFilter &filter)
{
    if (d_ptr->isBusy()) {
        return false;
    }
    d_ptr->_active = 0;
    d_ptr->_activeStoreAction = true;
    
    d_ptr->_candidateIds = mailStoreInstance()->queryMessages(convert(filter));
    d_ptr->_error = convert(mailStoreInstance()->lastError());

    if (d_ptr->_error == QMessageManager::NoError) {
        d_ptr->_lastFilter = QMessageFilter();
        d_ptr->_lastOrdering = QMessageSortOrder();
        d_ptr->_match = QString();
        d_ptr->_limit = 0;
        d_ptr->_offset = 0;
        d_ptr->_matchingIds.clear();
        QTimer::singleShot(0, d_ptr, SLOT(reportMatchingCount()));
        return true;
    }

    return false;
}

bool QMessageService::send(QMessage &message)
{
    if (d_ptr->isBusy()) {
        return false;
    }
    d_ptr->_active = 0;

    if (!message.parentAccountId().isValid()) {
        // Attach to the default account
        message.setParentAccountId(QMessageAccount::defaultAccount(message.type()));
        if (!message.parentAccountId().isValid()) {
            d_ptr->_error = QMessageManager::InvalidId;
            qWarning() << "Invalid message account ID";
            return false;
        }
    }

    // Ensure the message contains a timestamp
    if (!message.date().isValid()) {
        message.setDate(QDateTime::currentDateTime());
    }

    QMessageFolderId existingFolderId(message.parentFolderId());

    // Move the message to the Outbox folder
    QMessagePrivate::setStandardFolder(message,QMessage::OutboxFolder);

    QMailMessage *msg(convert(&message));

    // Ensure that the from address is added
    if (msg->from().isNull()) {
        QMailAccount account(msg->parentAccountId());
        msg->setFrom(account.fromAddress());
    }

    // Mark this message as outgoing
    msg->setStatus(QMailMessage::Outbox, true);

#ifdef Q_WS_MEEGO
    QMessageAccount account(message.parentAccountId());
    if (account.messageTypes() & QMessage::Sms) {
        d_ptr->_error = QMessageManager::FrameworkFault;
        qWarning() << "Sending SMS is not supported on this platform.";
        return false;
    }
#endif

    if (msg->id().isValid()) {
        // Update the message
        if (!mailStoreInstance()->updateMessage(msg)) {
            d_ptr->_error = QMessageManager::FrameworkFault;
            qWarning() << "Unable to mark message as outgoing";
            return false;
        }
    } else {
        // Add this message to the store
        if (!mailStoreInstance()->addMessage(msg)) {
            d_ptr->_error = QMessageManager::FrameworkFault;
            qWarning() << "Unable to store message for transmission";
            return false;
        }
    }

    d_ptr->_transmitIds = mailStoreInstance()->queryMessages(QMailMessageKey::status(QMailMessage::Outgoing) & QMailMessageKey::parentAccountId(msg->parentAccountId()));

    d_ptr->_error = QMessageManager::NoError;
    d_ptr->_active = &d_ptr->_transmit;
    d_ptr->_transmit.transmitMessages(msg->parentAccountId());
    return true;
}

bool QMessageService::compose(const QMessage &message)
{
    if (d_ptr->isBusy()) {
        return false;
    }
    d_ptr->_active = 0;

    // TODO: To be implemented by integrator
    d_ptr->_error = QMessageManager::NotYetImplemented;
    qWarning() << "QMessageService::compose not yet implemented";
    return false;

    Q_UNUSED(message)
}

bool QMessageService::retrieveHeader(const QMessageId& id)
{
    if (d_ptr->isBusy()) {
        return false;
    }
    d_ptr->_active = 0;

    QMailMessageId messageId(convert(id));
    if (!messageId.isValid()) {
        d_ptr->_error = QMessageManager::InvalidId;
        qWarning() << "Invalid message ID";
        return false;
    }

    // Operation is not relevant to QMF - meta data retrieval always includes header information
    d_ptr->_error = QMessageManager::NoError;
    d_ptr->_active = 0;
    QTimer::singleShot(0, d_ptr, SLOT(completed()));
    return true;
}

bool QMessageService::retrieveBody(const QMessageId& id)
{
    if (d_ptr->isBusy()) {
        return false;
    }
    d_ptr->_active = 0;

    QMailMessageId messageId(convert(id));
    if (!messageId.isValid()) {
        d_ptr->_error = QMessageManager::InvalidId;
        qWarning() << "Invalid message ID";
        return false;
    }

    d_ptr->_error = QMessageManager::NoError;
    d_ptr->_active = &d_ptr->_retrieval;
    d_ptr->_retrieval.retrieveMessages(QMailMessageIdList() << messageId, QMailRetrievalAction::Content);
    return true;
}

bool QMessageService::retrieve(const QMessageId &messageId, const QMessageContentContainerId& id)
{
    if (d_ptr->isBusy()) {
        return false;
    }
    d_ptr->_active = 0;

    QMailMessagePart::Location location(convert(id));
    if (!location.isValid()) {
        d_ptr->_error = QMessageManager::InvalidId;
        qWarning() << "Invalid message part location";
        return false;
    }
    
    d_ptr->_error = QMessageManager::NoError;
    d_ptr->_active = &d_ptr->_retrieval;
    d_ptr->_retrieval.retrieveMessagePart(location);
    return true;

    Q_UNUSED(messageId)
}

bool QMessageService::show(const QMessageId& id)
{
    if (d_ptr->isBusy()) {
        return false;
    }
    d_ptr->_active = 0;

    // TODO: To be implemented by integrator
    d_ptr->_error = QMessageManager::NotYetImplemented;
    qWarning() << "QMessageService::show not yet implemented";
    return false;

    Q_UNUSED(id)
}

bool QMessageService::exportUpdates(const QMessageAccountId &id)
{
    if (d_ptr->isBusy()) {
        return false;
    }
    d_ptr->_active = 0;

    QMailAccountId accountId(convert(id));
    if (!accountId.isValid()) {
        d_ptr->_error = QMessageManager::InvalidId;
        qWarning() << "Account ID is not valid";
        return false;
    }

    d_ptr->_error = QMessageManager::NoError;
    d_ptr->_active = &d_ptr->_retrieval;
    d_ptr->_retrieval.exportUpdates(accountId);
    return true;
}

void QMessageServicePrivate::storageActivityChanged(QMailServiceAction::Activity a)
{
    if ((a == QMailServiceAction::Failed) && (_error == QMessageManager::NoError)) {
        _error = QMessageManager::RequestIncomplete;
    }

    emit stateChanged(convert(a));
}

bool QMessageService::moveMessages(const QMessageIdList &messageIds, const QMessageFolderId &toFolderId)
{
    if (d_ptr->isBusy()) {
        return false;
    }
    d_ptr->_active = 0;
    d_ptr->_error = QMessageManager::NoError;
    d_ptr->_active = &d_ptr->_storage;
    d_ptr->_storage.onlineMoveMessages(convert(messageIds), convert(toFolderId));
    return true;
}

bool QMessageService::synchronize(const QMessageAccountId &id)
{
    if (d_ptr->isBusy()) {
        return false;
    }

    d_ptr->_active = 0;
    d_ptr->_error = QMessageManager::NoError;
    d_ptr->_active = &d_ptr->_retrieval;
    d_ptr->_retrieval.synchronize(convert(id), 1);
    return true;
}

QMessageService::State QMessageService::state() const
{
    if (d_ptr->_active) {
        return convert(d_ptr->_active->activity());
    }
    else if(d_ptr->_activeStoreAction)
        return QMessageService::ActiveState;

    return FinishedState;
}

void QMessageService::cancel()
{
    if (d_ptr->_active) {
        d_ptr->_active->cancelOperation();
    }
    else if(d_ptr->_activeStoreAction)
        d_ptr->_activeStoreAction = false;
}

QMessageManager::Error QMessageService::error() const
{
    return d_ptr->_error;
}

#include "qmessageservice_qmf.moc"


QTM_END_NAMESPACE
