/****************************************************************************
**
** 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$
**
****************************************************************************/
#ifndef QMESSAGEWINHELPERPRIVATE_H
#define QMESSAGEWINHELPERPRIVATE_H

#include <QHash>
#include <QMap>
#include <QPair>
#include <QSharedPointer>
#include <QString>
#include <QByteArray>
#include <MAPIUtil.h>
#include <qmessagemanager.h>
#include <QVector>
#include <QQueue>
#include <QEvent>

QTM_BEGIN_NAMESPACE

QTM_END_NAMESPACE

#ifndef _WIN32_WCE

#define USES_IID_IMAPIForm
#define USES_IID_IMAPIMessageSite
#define USES_IID_IPersistMessage

#include <initguid.h>
#include <mapiguid.h>
#include <mapiform.h>

#endif


// As per http://support.microsoft.com/kb/816477
#ifndef PR_MSG_EDITOR_FORMAT
#define PR_MSG_EDITOR_FORMAT PROP_TAG( PT_LONG, 0x5909 )
#define EDITOR_FORMAT_DONTKNOW ((ULONG)0)
#define EDITOR_FORMAT_PLAINTEXT ((ULONG)1)
#define EDITOR_FORMAT_HTML ((ULONG)2)
#ifndef _WIN32_WCE
#define EDITOR_FORMAT_RTF ((ULONG)3)
#else
// Not a real definition, but useful for us:
#define EDITOR_FORMAT_MIME ((ULONG)3)
#endif
#endif

#ifndef PR_ATTACH_CONTENT_ID
// This is not available in my SDK version...
#define PR_ATTACH_CONTENT_ID PROP_TAG( PT_UNICODE, 0x3712 )
#endif

#ifndef PR_BODY_HTML
#define PR_BODY_HTML PROP_TAG( PT_TSTRING, 0x1013 )
#endif

#define mapiRelease(a) if(a){a->Release();a=0;}


QTM_BEGIN_NAMESPACE

class MapiEntryId : public QByteArray
{
public:
    MapiEntryId():QByteArray(){}
    MapiEntryId(LPENTRYID mapiEntryId, ULONG entryIdLength)
    :
    QByteArray(reinterpret_cast<const char*>(mapiEntryId),entryIdLength){}

    MapiEntryId(LPBYTE mapiEntryId, ULONG entryIdLength)
    :
    QByteArray(reinterpret_cast<const char*>(mapiEntryId),entryIdLength){}
};

class MapiRecordKey : public QByteArray
{
public:
    MapiRecordKey():QByteArray(){}
    MapiRecordKey(LPENTRYID mapiRecordKey, ULONG entryIdLength)
    :
    QByteArray(reinterpret_cast<const char*>(mapiRecordKey),entryIdLength){}

    MapiRecordKey(LPBYTE mapiRecordKey, ULONG entryIdLength)
    :
    QByteArray(reinterpret_cast<const char*>(mapiRecordKey),entryIdLength){}
};

class MapiFolder;
class MapiStore;
class MapiSession;
typedef QSharedPointer<MapiFolder> MapiFolderPtr;
typedef QSharedPointer<MapiStore> MapiStorePtr;
typedef QSharedPointer<MapiSession> MapiSessionPtr;

namespace WinHelpers {


enum SavePropertyOption { DontSavePropertyChanges = 0, SavePropertyChanges };

QString QStringFromLpctstr(LPCTSTR lpszValue);
void LptstrFromQString(const QString &value, LPTSTR *lpsz);

typedef QPair<QMessageId, ULONG> AttachmentLocator;
QMessageContentContainer fromLocator(const WinHelpers::AttachmentLocator &l);

ULONG createNamedProperty(IMAPIProp *object, const QString &name);
ULONG getNamedPropertyTag(IMAPIProp *object, const QString &name);

bool setNamedProperty(IMAPIProp *object, ULONG tag, const QString &value);
QString getNamedProperty(IMAPIProp *object, ULONG tag);

QByteArray contentTypeFromExtension(const QString &extension);

class Lptstr : public QVector<TCHAR>
{
public:
    Lptstr():QVector<TCHAR>(){}
    Lptstr(int length) : QVector<TCHAR>(length){}
    operator TCHAR* (){ return QVector<TCHAR>::data(); }
};

Lptstr LptstrFromQString(const QString &src);

class MapiInitializer;

typedef QSharedPointer<MapiInitializer> MapiInitializationToken;

MapiInitializationToken initializeMapi();

class MapiInitializer
{
    friend MapiInitializationToken WinHelpers::initializeMapi();

private:
    MapiInitializer();
    MapiInitializer &operator=(const MapiInitializer &);

public:
    ~MapiInitializer();

    bool _initialized;
};

bool getMapiProperty(IMAPIProp *object, ULONG tag, ULONG *value);
bool getMapiProperty(IMAPIProp *object, ULONG tag, LONG *value);
bool getMapiProperty(IMAPIProp *object, ULONG tag, QByteArray *value);
bool getMapiProperty(IMAPIProp *object, ULONG tag, QString value);

bool setMapiProperty(IMAPIProp *object, ULONG tag, const QString &value);
bool setMapiProperty(IMAPIProp *object, ULONG tag, LONG value);
bool setMapiProperty(IMAPIProp *object, ULONG tag, ULONG value);
bool setMapiProperty(IMAPIProp *object, ULONG tag, bool value);
bool setMapiProperty(IMAPIProp *object, ULONG tag, FILETIME value);
bool setMapiProperty(IMAPIProp *object, ULONG tag, MapiEntryId value);

}

/* Note on links:
    - Session must close at exit for correct cleanup
        Session must be a singleton
        Stores must have weak ref to session
    - Stores must remain open after opening to enable notifications
        Session holds strong ref to stores
    - Folders should not remain open
        Store holds weak ref to folders
        Folder can have strong ref to store

    * Session keeps stores open
    * Folders keep store open
*/

class MapiFolder {

public:
    static MapiFolderPtr createFolder(QMessageManager::Error *error, const MapiStorePtr &store, IMAPIFolder *folder, const MapiRecordKey &recordKey, const QString &name, const MapiEntryId &entryId, bool hasSubFolders, uint messageCount);

    ~MapiFolder();

    MapiFolderPtr nextSubFolder(QMessageManager::Error *error);

    LPMAPITABLE queryBegin(QMessageManager::Error *error, const QMessageFilter &filter, const QMessageSortOrder &sortOrder);
    QMessageIdList queryNext(QMessageManager::Error *error, LPMAPITABLE messagesTable, const QMessageFilter &filter);
    void queryEnd(LPMAPITABLE messagesTable);

    uint countMessages(QMessageManager::Error *error, const QMessageFilter &filter = QMessageFilter()) const;

    void removeMessages(QMessageManager::Error *error, const QMessageIdList &ids);

    MapiEntryId messageEntryId(QMessageManager::Error *error, const MapiRecordKey &messagekey);

    QMessageFolderId id() const;

    QMessageAccountId accountId() const;
    QMessageFolderId parentId() const;
    QList<QMessageFolderId> ancestorIds() const;

    bool isValid() const { return _valid; }
    IMAPIFolder* folder() const { return _folder; }
    MapiRecordKey recordKey() const { return _key; }
    MapiRecordKey storeKey() const;
#ifdef _WIN32_WCE
    MapiEntryId storeEntryId() const;
#endif
    QString name() const { return _name; }
    MapiEntryId entryId() const { return _entryId; }
    bool hasSubFolders() const { return _hasSubFolders; }
    uint messageCount() const { return _messageCount; }

    IMessage *createMessage(QMessageManager::Error* error);
    IMessage *createMessage(QMessageManager::Error* error, const QMessage& source, const MapiSessionPtr &session, WinHelpers::SavePropertyOption saveOption = WinHelpers::SavePropertyChanges );

    IMessage *openMessage(QMessageManager::Error *error, const MapiEntryId &entryId);

    QMessageFolder folder(QMessageManager::Error *error, const QMessageFolderId& id) const;
    QMessage message(QMessageManager::Error *error, const QMessageId& id) const;

    QMessage::StandardFolder standardFolder() const;

private:
    MapiFolder();
    MapiFolder(const MapiStorePtr &store, IMAPIFolder *folder, const MapiRecordKey &recordKey, const QString &name, const MapiEntryId &entryId, bool hasSubFolders, uint messageCount);

    void findSubFolders(QMessageManager::Error *error);

    friend class MapiStore;

    QWeakPointer<MapiFolder> _self;
    MapiStorePtr _store;
    bool _valid;
    IMAPIFolder* _folder;
    MapiRecordKey _key;
    QString _name;
    MapiEntryId _entryId;
    bool _hasSubFolders;
    uint _messageCount;
    bool _init;
    QList<MapiEntryId> _subFolders;
};

class MapiStore {
public:
    static MapiStorePtr createStore(QMessageManager::Error *error, const MapiSessionPtr &session, IMsgStore *store, const MapiRecordKey &key, const MapiEntryId &entryId, const QString &name, bool cachedMode);

    ~MapiStore();

    MapiFolderPtr findFolder(QMessageManager::Error *error, QMessage::StandardFolder sf);

    QMessageFolderIdList folderIds(QMessageManager::Error *error) const;
    QMessageFolder folderFromId(QMessageManager::Error *error, const QMessageFolderId &folderId);

    QList<MapiFolderPtr> filterFolders(QMessageManager::Error *error, const QMessageFolderFilter &filter) const;

    MapiEntryId messageEntryId(QMessageManager::Error *error, const MapiRecordKey &folderKey, const MapiRecordKey &messageKey);

    MapiFolderPtr openFolder(QMessageManager::Error *error, const MapiEntryId& id) const;
    MapiFolderPtr openFolderWithKey(QMessageManager::Error *error, const MapiRecordKey& key) const;

    bool supports(ULONG featureFlag) const;

    bool isValid() const { return _valid; }
    QMessageAccountId id() const;
    MapiEntryId entryId() const { return _entryId; }
    QString name() const { return _name; }
    IMsgStore* store() const { return _store; }
    MapiRecordKey recordKey() const { return _key; }
    QMessage::TypeFlags types() const;
    QMessageAddress address() const;

    MapiSessionPtr session() const;

    MapiFolderPtr rootFolder(QMessageManager::Error *error) const;
    MapiFolderPtr receiveFolder(QMessageManager::Error *error) const;

    IMessage *openMessage(QMessageManager::Error *error, const MapiEntryId &entryId);

    QMessageFolder folder(QMessageManager::Error *error, const QMessageFolderId& id) const;
    QMessage message(QMessageManager::Error *error, const QMessageId& id) const;

    QMessage::StandardFolder standardFolder(const MapiEntryId &entryId) const;

    void notifyEvents(ULONG mask);

#ifdef _WIN32_WCE
    QString transportName() const;
#endif

private:
    MapiStore();
    MapiStore(const MapiSessionPtr &session, IMsgStore *store, const MapiRecordKey &key, const MapiEntryId &entryId, const QString &name, bool cachedMode);

    MapiEntryId standardFolderId(QMessageManager::Error *error, QMessage::StandardFolder sf) const;
    MapiEntryId rootFolderId(QMessageManager::Error *error) const;
    MapiEntryId receiveFolderId(QMessageManager::Error *error) const;

    IMAPIFolder *openMapiFolder(QMessageManager::Error *error, const MapiEntryId &entryId) const;

    bool setAdviseSink(ULONG mask, IMAPIAdviseSink *sink);

    class AdviseSink : public IMAPIAdviseSink
    {
        MapiStore *_store;
        LONG _refCount;

    public:
        AdviseSink(MapiStore *store) : _store(store), _refCount(0) {}

        STDMETHOD(QueryInterface)(REFIID riid, LPVOID FAR* ppvObj);
        STDMETHOD_(ULONG, AddRef)();
        STDMETHOD_(ULONG, Release)();

        STDMETHOD_(ULONG, OnNotify)(ULONG cNotification, LPNOTIFICATION lpNotifications);
    };

    QWeakPointer<MapiStore> _self;
    QWeakPointer<MapiSession> _session;
    bool _valid;
    IMsgStore* _store;
    MapiRecordKey _key;
    MapiEntryId _entryId;
    QString _name;
    bool _cachedMode;
    ULONG _adviseConnection;
    QMap<QMessage::StandardFolder, MapiEntryId> _standardFolderId;

    mutable QHash<MapiEntryId, QWeakPointer<MapiFolder> > _folderMap;
};

class MapiSession : public QObject
{
    Q_OBJECT

public:
    enum NotifyType { Added = 1, Removed, Updated };

    class NotifyEvent : public QEvent
    {
    public:
        static QEvent::Type eventType();

        NotifyEvent(MapiStore *store, const QMessageId &id, MapiSession::NotifyType type);

        virtual Type type();

        MapiStore *_store;
        QMessageId _id;
        NotifyType _notifyType;
    };

    static MapiSessionPtr createSession(QMessageManager::Error *error);

    ~MapiSession();

    bool isValid() const { return (_mapiSession != 0); }

    MapiStorePtr findStore(QMessageManager::Error *error, const QMessageAccountId &id = QMessageAccountId(), bool cachedMode = true) const;
    MapiStorePtr defaultStore(QMessageManager::Error *error, bool cachedMode = true) const { return findStore(error,QMessageAccountId(),cachedMode); }

    QList<MapiStorePtr> filterStores(QMessageManager::Error *error, const QMessageAccountFilter &filter, const QMessageAccountSortOrder &sortOrder = QMessageAccountSortOrder(), uint limit = 0, uint offset = 0, bool cachedMode = true) const;

    QList<MapiStorePtr> allStores(QMessageManager::Error *error, bool cachedMode = true) const;

    QList<MapiFolderPtr> filterFolders(QMessageManager::Error *error, const QMessageFolderFilter &filter, const QMessageFolderSortOrder &sortOrder = QMessageFolderSortOrder(), uint limit = 0, uint offset = 0, bool cachedMode = true) const;

    MapiStorePtr openStore(QMessageManager::Error *error, const MapiEntryId& id, bool cachedMode = true) const;
    MapiStorePtr openStoreWithKey(QMessageManager::Error *error, const MapiRecordKey& key, bool cachedMode = true) const;

    QMessageAccountId defaultAccountId(QMessageManager::Error *error, QMessage::Type type) const;

    MapiEntryId messageEntryId(QMessageManager::Error *error, const MapiRecordKey &storeKey, const MapiRecordKey &folderKey, const MapiRecordKey &messageKey);

    MapiRecordKey messageRecordKey(QMessageManager::Error *error, const QMessageId &id);
    MapiRecordKey folderRecordKey(QMessageManager::Error *error, const QMessageId &id);

#ifdef _WIN32_WCE
    MapiEntryId folderEntryId(QMessageManager::Error *error, const QMessageId &id);
#endif

    bool equal(const MapiEntryId &lhs, const MapiEntryId &rhs) const;

    QMessageFolder folder(QMessageManager::Error *error, const QMessageFolderId& id) const;
    QMessage message(QMessageManager::Error *error, const QMessageId& id) const;

    bool updateMessageProperties(QMessageManager::Error *error, QMessage *msg) const;
    bool updateMessageRecipients(QMessageManager::Error *error, QMessage *msg) const;
    bool updateMessageBody(QMessageManager::Error *error, QMessage *msg) const;
    bool updateMessageAttachments(QMessageManager::Error *error, QMessage *msg) const;

    bool haveAttachmentData(QMessageManager::Error* error, const QMessageId& id, ULONG number) const;
    QByteArray attachmentData(QMessageManager::Error *error, const QMessageId& id, ULONG number) const;

    QMessageIdList queryMessages(QMessageManager::Error *error, const QMessageFilter &filter, const QMessageSortOrder &sortOrder = QMessageSortOrder(), uint limit = 0, uint offset = 0, const QString &body = QString(), QMessageDataComparator::MatchFlags matchFlags = 0) const;

    void updateMessage(QMessageManager::Error* error, const QMessage& source);

    void removeMessages(QMessageManager::Error *error, const QMessageIdList &ids);

    IMAPISession* session() const { return _mapiSession; }

    QMessageManager::NotificationFilterId registerNotificationFilter(QMessageManager::Error *error, const QMessageFilter &filter);
    void unregisterNotificationFilter(QMessageManager::Error *error, QMessageManager::NotificationFilterId filterId);

    static QMessagePrivate *messageImpl(const QMessage &message);
    static QMessageContentContainerPrivate *containerImpl(const QMessageContentContainer &);

    void addToNotifyQueue(const NotifyEvent& e);
    void flushNotifyQueue();

signals:
    void messageAdded(const QMessageId &id, const QMessageManager::NotificationFilterIdSet &matchingFilterIds);
    void messageRemoved(const QMessageId &id, const QMessageManager::NotificationFilterIdSet &matchingFilterIds);
    void messageUpdated(const QMessageId &id, const QMessageManager::NotificationFilterIdSet &matchingFilterIds);

public slots:
    void dispatchNotifications();
    void processNotifyQueue();

private:
    MapiSession();
    MapiSession(QMessageManager::Error *error);

    IMsgStore *openMapiStore(QMessageManager::Error *error, const MapiEntryId &entryId, bool cachedMode = true) const;
    IMessage *openMapiMessage(QMessageManager::Error *error, const QMessageId &id, MapiStorePtr *storePtr = 0) const;

    void addRecipients(LPMESSAGE message, const QMessageAddressList& addressList, unsigned long mapiAddressType);
    void addAttachment(LPMESSAGE message, const QMessageContentContainer& attachmentContainer);

    bool event(QEvent *e);

    void notify(MapiStore *store, const QMessageId &id, NotifyType notifyType);

    template<typename Predicate, typename Ordering>
    QList<MapiStorePtr> filterStores(QMessageManager::Error *error, Predicate predicate, Ordering sortOrder, uint limit, uint offset, bool cachedMode) const;

private:
    friend class SessionManager;

    QWeakPointer<MapiSession> _self;
    WinHelpers::MapiInitializationToken _token;
    IMAPISession* _mapiSession;
    QMessageManager::NotificationFilterId _filterId;
    QMap<QMessageManager::NotificationFilterId, QMessageFilter> _filters;
    bool _registered;
    QQueue<NotifyEvent> _notifyEventQueue;

    mutable QHash<MapiEntryId, MapiStorePtr> _storeMap;
};

#ifndef _WIN32_WCE

class MapiForm : public IMAPIMessageSite
{
public:
    MapiForm(IMsgStore* mapiStore,
             IMAPISession* mapiSession,
             IMAPIFolder* mapiFolder,
             IMessage* mapiMessage);

    virtual ~MapiForm();

    // IUnknown interface
    STDMETHODIMP QueryInterface (REFIID riid, void** ppvObj);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    STDMETHODIMP GetLastError(HRESULT hResult, ulong flags, LPMAPIERROR FAR * lppMAPIError);

    // IMAPIMessageSite interface
    STDMETHODIMP GetSession(IMAPISession* FAR * mapiSession);
    STDMETHODIMP GetStore(IMsgStore* FAR * mapiStore);
    STDMETHODIMP GetFolder(IMAPIFolder* FAR * mapiFolder);
    STDMETHODIMP GetMessage(IMessage* FAR * mapiMessage);
    STDMETHODIMP GetFormManager(IMAPIFormMgr* FAR * ppFormMgr);
    STDMETHODIMP NewMessage(ULONG fComposeInFolder,
                            IMAPIFolder* mapiFolder,
                            IPersistMessage* pPersistMessage,
                            IMessage* FAR * mapiMessage,
                            IMAPIMessageSite* FAR * ppMessageSite,
                            LPMAPIVIEWCONTEXT FAR * ppViewContext);
    STDMETHODIMP CopyMessage(IMAPIFolder* pFolderDestination);
    STDMETHODIMP MoveMessage(IMAPIFolder* pFolderDestination,
                             LPMAPIVIEWCONTEXT pViewContext,
                             LPCRECT prcPosRect);
    STDMETHODIMP DeleteMessage(LPMAPIVIEWCONTEXT pViewContext, LPCRECT prcPosRect);
    STDMETHODIMP SaveMessage();
    STDMETHODIMP SubmitMessage(ulong flags);
    STDMETHODIMP GetSiteStatus(ulong* status);

    bool show();

private:
    HRESULT setPersistMessage(LPMAPIFORM lpForm, IPersistMessage* mapiPersistMessage);
    void releasePersistMessage();
    void releaseAll();

private:
    long m_referenceCount;
    IMAPIFolder* m_mapiFolder;
    IMessage* m_mapiMessage;
    IMsgStore* m_mapiStore;
    IMAPISession* m_mapiSession;
    IPersistMessage* m_mapiPersistMessage;
};

#endif

QTM_END_NAMESPACE

#endif
