/*
 *  Copyright (c) 2015 Daniel Vrátil <dvratil@redhat.com>
 *  Copyright (c) 2016 Daniel Vrátil <dvratil@kde.org>
 *
 *  This library is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU Library General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or (at your
 *  option) any later version.
 *
 *  This library is distributed in the hope that it will be useful, but WITHOUT
 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
 *  License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this library; see the file COPYING.LIB.  If not, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 *  02110-1301, USA.
 */

#include "protocol_p.h"
#include "scope_p.h"
#include "imapset_p.h"
#include "datastream_p_p.h"

#include <type_traits>
#include <typeinfo>

#include <QGlobalStatic>
#include <QHash>
#include <QMap>
#include <QDateTime>
#include <QStack>

#include <cassert>

#undef AKONADI_DECLARE_PRIVATE
#define AKONADI_DECLARE_PRIVATE(Class) \
inline Class##Private* Class::d_func() {\
    return reinterpret_cast<Class##Private*>(d_ptr.data()); \
} \
inline const Class##Private* Class::d_func() const {\
    return reinterpret_cast<const Class##Private*>(d_ptr.constData()); \
}

#define COMPARE(prop) \
    (prop == ((decltype(this)) other)->prop)

namespace Akonadi {
namespace Protocol {

QDebug operator<<(QDebug _dbg, Command::Type type)
{
    QDebug dbg(_dbg.noquote());

    switch (type)
    {
    case Command::Invalid:
        return dbg << "Invalid";

    case Command::Hello:
        return dbg << "Hello";
    case Command::Login:
        return dbg << "Login";
    case Command::Logout:
        return dbg << "Logout";

    case Command::Transaction:
        return dbg << "Transaction";

    case Command::CreateItem:
        return dbg << "CreateItem";
    case Command::CopyItems:
        return dbg << "CopyItems";
    case Command::DeleteItems:
        return dbg << "DeleteItems";
    case Command::FetchItems:
        return dbg << "FetchItems";
    case Command::LinkItems:
        return dbg << "LinkItems";
    case Command::ModifyItems:
        return dbg << "ModifyItems";
    case Command::MoveItems:
        return dbg << "MoveItems";

    case Command::CreateCollection:
        return dbg << "CreateCollection";
    case Command::CopyCollection:
        return dbg << "CopyCollection";
    case Command::DeleteCollection:
        return dbg << "DeleteCollection";
    case Command::FetchCollections:
        return dbg << "FetchCollections";
    case Command::FetchCollectionStats:
        return dbg << "FetchCollectionStats";
    case Command::ModifyCollection:
        return dbg << "ModifyCollection";
    case Command::MoveCollection:
        return dbg << "MoveCollection";

    case Command::Search:
        return dbg << "Search";
    case Command::SearchResult:
        return dbg << "SearchResult";
    case Command::StoreSearch:
        return dbg << "StoreSearch";

    case Command::CreateTag:
        return dbg << "CreateTag";
    case Command::DeleteTag:
        return dbg << "DeleteTag";
    case Command::FetchTags:
        return dbg << "FetchTags";
    case Command::ModifyTag:
        return dbg << "ModifyTag";

    case Command::FetchRelations:
        return dbg << "FetchRelations";
    case Command::ModifyRelation:
        return dbg << "ModifyRelation";
    case Command::RemoveRelations:
        return dbg << "RemoveRelations";

    case Command::SelectResource:
        return dbg << "SelectResource";

    case Command::StreamPayload:
        return dbg << "StreamPayload";
    case Command::ItemChangeNotification:
        return dbg << "ItemChangeNotification";
    case Command::CollectionChangeNotification:
        return dbg << "CollectionChangeNotification";
    case Command::TagChangeNotification:
        return dbg << "TagChangeNotification";
    case Command::RelationChangeNotification:
        return dbg << "RelationChangeNotification";
    case Command::SubscriptionChangeNotification:
        return dbg << "SubscriptionChangeNotification";
    case Command::DebugChangeNotification:
        return dbg << "DebugChangeNotification";
    case Command::CreateSubscription:
        return dbg << "CreateSubscription";
    case Command::ModifySubscription:
        return dbg << "ModifySubscription";

    case Command::_ResponseBit:
        Q_ASSERT(false);
        return dbg << (int)type;
    }

    Q_ASSERT(false);
    return dbg << (int)type;
}

template<typename T>
DataStream &operator<<(DataStream &stream, const QSharedPointer<T> &ptr)
{
    Protocol::serialize(stream.device(), ptr);
    return stream;
}

template<typename T>
DataStream &operator>>(DataStream &stream, QSharedPointer<T> &ptr)
{
    ptr = Protocol::deserialize(stream.device()).staticCast<T>();
    return stream;
}

/******************************************************************************/

Command::Command()
    : mType(Invalid)
{
}

Command::Command(const Command &other)
    : mType(other.mType)
{
}

Command::Command(quint8 type)
    : mType(type)
{
}

Command::~Command()
{
}

Command& Command::operator=(const Command &other)
{
    mType = other.mType;
    return *this;
}

bool Command::operator==(const Command &other) const
{
    return mType == other.mType;
}

DataStream &operator<<(DataStream &stream, const Command &cmd)
{
    return stream << cmd.mType;
}

DataStream &operator>>(DataStream &stream, Command &cmd)
{
    return stream >> cmd.mType;
}

QDebug operator<<(QDebug dbg, const Command &cmd)
{
    return dbg.noquote() << ((cmd.mType & Command::_ResponseBit) ? "Response:" : "Command:")
                         << static_cast<Command::Type>(cmd.mType & ~Command::_ResponseBit) << "\n";
}

/******************************************************************************/

Response::Response()
    : Response(Command::Invalid)
{
}

Response::Response(const Response &other)
    : Command(other)
    , mErrorCode(other.mErrorCode)
    , mErrorMsg(other.mErrorMsg)
{
}

Response::Response(Command::Type type)
    : Command(type | Command::_ResponseBit)
    , mErrorCode(0)
{
}

Response &Response::operator=(const Response &other)
{
    Command::operator=(other);
    mErrorMsg = other.mErrorMsg;
    mErrorCode = other.mErrorCode;
    return *this;
}

bool Response::operator==(const Response &other) const
{
    return *static_cast<const Command*>(this) == static_cast<const Command&>(other)
        && mErrorCode == other.mErrorCode
        && mErrorMsg == other.mErrorMsg;
}

DataStream &operator<<(DataStream &stream, const Response &cmd)
{
    return stream << static_cast<const Command &>(cmd)
                  << cmd.mErrorCode
                  << cmd.mErrorMsg;
}

DataStream &operator>>(DataStream &stream, Response &cmd)
{
    return stream >> static_cast<Command&>(cmd)
                  >> cmd.mErrorCode
                  >> cmd.mErrorMsg;
}

QDebug operator<<(QDebug dbg, const Response &resp)
{
    return dbg.noquote() << static_cast<const Command &>(resp)
                         << "Error code:" << resp.mErrorCode << "\n"
                         << "Error msg:" << resp.mErrorMsg << "\n";
}

/******************************************************************************/


class FactoryPrivate
{
public:
    typedef CommandPtr (*CommandFactoryFunc)();
    typedef ResponsePtr (*ResponseFactoryFunc)();

    FactoryPrivate()
    {
        // Session management
        registerType<Command::Hello, Command /* invalid */, HelloResponse>();
        registerType<Command::Login, LoginCommand, LoginResponse>();
        registerType<Command::Logout, LogoutCommand, LogoutResponse>();

        // Transactions
        registerType<Command::Transaction, TransactionCommand, TransactionResponse>();

        // Items
        registerType<Command::CreateItem, CreateItemCommand, CreateItemResponse>();
        registerType<Command::CopyItems, CopyItemsCommand, CopyItemsResponse>();
        registerType<Command::DeleteItems, DeleteItemsCommand, DeleteItemsResponse>();
        registerType<Command::FetchItems, FetchItemsCommand, FetchItemsResponse>();
        registerType<Command::LinkItems, LinkItemsCommand, LinkItemsResponse>();
        registerType<Command::ModifyItems, ModifyItemsCommand, ModifyItemsResponse>();
        registerType<Command::MoveItems, MoveItemsCommand, MoveItemsResponse>();

        // Collections
        registerType<Command::CreateCollection, CreateCollectionCommand, CreateCollectionResponse>();
        registerType<Command::CopyCollection, CopyCollectionCommand, CopyCollectionResponse>();
        registerType<Command::DeleteCollection, DeleteCollectionCommand, DeleteCollectionResponse>();
        registerType<Command::FetchCollections, FetchCollectionsCommand, FetchCollectionsResponse>();
        registerType<Command::FetchCollectionStats, FetchCollectionStatsCommand, FetchCollectionStatsResponse>();
        registerType<Command::ModifyCollection, ModifyCollectionCommand, ModifyCollectionResponse>();
        registerType<Command::MoveCollection, MoveCollectionCommand, MoveCollectionResponse>();

        // Search
        registerType<Command::Search, SearchCommand, SearchResponse>();
        registerType<Command::SearchResult, SearchResultCommand, SearchResultResponse>();
        registerType<Command::StoreSearch, StoreSearchCommand, StoreSearchResponse>();

        // Tag
        registerType<Command::CreateTag, CreateTagCommand, CreateTagResponse>();
        registerType<Command::DeleteTag, DeleteTagCommand, DeleteTagResponse>();
        registerType<Command::FetchTags, FetchTagsCommand, FetchTagsResponse>();
        registerType<Command::ModifyTag, ModifyTagCommand, ModifyTagResponse>();

        // Relation
        registerType<Command::FetchRelations, FetchRelationsCommand, FetchRelationsResponse>();
        registerType<Command::ModifyRelation, ModifyRelationCommand, ModifyRelationResponse>();
        registerType<Command::RemoveRelations, RemoveRelationsCommand, RemoveRelationsResponse>();

        // Resources
        registerType<Command::SelectResource, SelectResourceCommand, SelectResourceResponse>();

        // Other...?
        registerType<Command::StreamPayload, StreamPayloadCommand, StreamPayloadResponse>();
        registerType<Command::ItemChangeNotification, ItemChangeNotification, Response /* invalid */>();
        registerType<Command::CollectionChangeNotification, CollectionChangeNotification, Response /* invalid */>();
        registerType<Command::TagChangeNotification, TagChangeNotification, Response /* invalid */>();
        registerType<Command::RelationChangeNotification, RelationChangeNotification, Response /* invalid */>();
        registerType<Command::SubscriptionChangeNotification, SubscriptionChangeNotification, Response /* invalid */>();
        registerType<Command::DebugChangeNotification, DebugChangeNotification, Response /* invalid */>();
        registerType<Command::CreateSubscription, CreateSubscriptionCommand, CreateSubscriptionResponse>();
        registerType<Command::ModifySubscription, ModifySubscriptionCommand, ModifySubscriptionResponse>();
    }

    // clang has problem resolving the right qHash() overload for Command::Type,
    // so use its underlying integer type instead
    QHash<std::underlying_type<Command::Type>::type, QPair<CommandFactoryFunc, ResponseFactoryFunc>> registrar;

private:
    template<typename T>
    static CommandPtr commandFactoryFunc()
    {
        return QSharedPointer<T>::create();
    }
    template<typename T>
    static ResponsePtr responseFactoryFunc()
    {
        return QSharedPointer<T>::create();
    }

    template<Command::Type T,typename CmdType, typename RespType>
    void registerType() {
        CommandFactoryFunc cmdFunc = &commandFactoryFunc<CmdType>;
        ResponseFactoryFunc respFunc = &responseFactoryFunc<RespType>;
        registrar.insert(T, qMakePair<CommandFactoryFunc, ResponseFactoryFunc>(cmdFunc, respFunc));
    }
};

Q_GLOBAL_STATIC(FactoryPrivate, sFactoryPrivate)

CommandPtr Factory::command(Command::Type type)
{
    auto iter = sFactoryPrivate->registrar.constFind(type);
    if (iter == sFactoryPrivate->registrar.constEnd()) {
        return QSharedPointer<Command>::create();
    }
    return iter->first();
}

ResponsePtr Factory::response(Command::Type type)
{
    auto iter = sFactoryPrivate->registrar.constFind(type);
    if (iter == sFactoryPrivate->registrar.constEnd()) {
        return QSharedPointer<Response>::create();
    }
    return iter->second();
}

/******************************************************************************/





/******************************************************************************/

FetchScope::FetchScope()
    : mAncestorDepth(NoAncestor)
    , mFlags(None)
{
}

FetchScope::FetchScope(const FetchScope &other)
    : mAncestorDepth(other.mAncestorDepth)
    , mFlags(other.mFlags)
    , mRequestedParts(other.mRequestedParts)
    , mChangedSince(other.mChangedSince)
    , mTagFetchScope(other.mTagFetchScope)
{
}

FetchScope::~FetchScope()
{
}

FetchScope &FetchScope::operator=(const FetchScope &other)
{
    mAncestorDepth = other.mAncestorDepth;
    mFlags = other.mFlags;
    mRequestedParts = other.mRequestedParts;
    mChangedSince = other.mChangedSince;
    mTagFetchScope = other.mTagFetchScope;
    return *this;
}

bool FetchScope::operator==(const FetchScope &other) const
{
    return mRequestedParts == other.mRequestedParts
            && mChangedSince == other.mChangedSince
            && mTagFetchScope == other.mTagFetchScope
            && mAncestorDepth == other.mAncestorDepth
            && mFlags == other.mFlags;
}

QVector<QByteArray> FetchScope::requestedPayloads() const
{
    QVector<QByteArray> rv;
    std::copy_if(mRequestedParts.begin(), mRequestedParts.end(),
                 std::back_inserter(rv),
                 [](const QByteArray &ba) { return ba.startsWith("PLD:"); });
    return rv;
}

void FetchScope::setFetch(FetchFlags attributes, bool fetch)
{
    if (fetch) {
        mFlags |= attributes;
        if (attributes & FullPayload) {
            if (!mRequestedParts.contains(AKONADI_PARAM_PLD_RFC822)) {
                mRequestedParts << AKONADI_PARAM_PLD_RFC822;
            }
        }
    } else {
        mFlags &= ~attributes;
    }
}

bool FetchScope::fetch(FetchFlags flags) const
{
    if (flags == None) {
        return mFlags == None;
    } else {
        return mFlags & flags;
    }
}

QDebug operator<<(QDebug dbg, FetchScope::AncestorDepth depth)
{
    switch (depth) {
    case FetchScope::NoAncestor:
        return dbg << "No ancestor";
    case FetchScope::ParentAncestor:
        return dbg << "Parent ancestor";
    case FetchScope::AllAncestors:
        return dbg << "All ancestors";
    }
    Q_UNREACHABLE();
}

DataStream &operator<<(DataStream &stream, const FetchScope &scope)
{
    return stream << scope.mRequestedParts
                  << scope.mChangedSince
                  << scope.mTagFetchScope
                  << scope.mAncestorDepth
                  << scope.mFlags;
}

DataStream &operator>>(DataStream &stream, FetchScope &scope)
{
    return stream >> scope.mRequestedParts
                  >> scope.mChangedSince
                  >> scope.mTagFetchScope
                  >> scope.mAncestorDepth
                  >> scope.mFlags;
}

QDebug operator<<(QDebug dbg, const FetchScope &scope)
{
    return dbg.noquote() << "FetchScope(\n"
            << "Fetch Flags:" << scope.mFlags << "\n"
            << "Tag Fetch Scope:" << scope.mTagFetchScope << "\n"
            << "Changed Since:" << scope.mChangedSince << "\n"
            << "Ancestor Depth:" << scope.mAncestorDepth << "\n"
            << "Requested Parts:" << scope.mRequestedParts << ")\n";
}


/******************************************************************************/


ScopeContext::ScopeContext()
{
}

ScopeContext::ScopeContext(Type type, qint64 id)
{
    if (type == ScopeContext::Tag) {
        mTagCtx = id;
    } else if (type == ScopeContext::Collection) {
        mColCtx = id;
    }
}

ScopeContext::ScopeContext(Type type, const QString &ctx)
{
    if (type == ScopeContext::Tag) {
        mTagCtx = ctx;
    } else if (type == ScopeContext::Collection) {
        mColCtx = ctx;
    }
}

ScopeContext::ScopeContext(const ScopeContext &other)
    : mColCtx(other.mColCtx)
    , mTagCtx(other.mTagCtx)
{
}

ScopeContext::~ScopeContext()
{
}

ScopeContext &ScopeContext::operator=(const ScopeContext &other)
{
    mColCtx = other.mColCtx;
    mTagCtx = other.mTagCtx;
    return *this;
}

bool ScopeContext::operator==(const ScopeContext &other) const
{
    return mColCtx == other.mColCtx && mTagCtx == other.mTagCtx;
}

DataStream &operator<<(DataStream &stream, const ScopeContext &context)
{
    // We don't have a custom generic DataStream streaming operator for QVariant
    // because it's very hard, esp. without access to QVariant private
    // stuff, so we have have to decompose it manually here.
    QVariant::Type vType = context.mColCtx.type();
    stream << vType;
    if (vType == QVariant::LongLong) {
        stream << context.mColCtx.toLongLong();
    } else if (vType == QVariant::String) {
        stream << context.mColCtx.toString();
    }

    vType = context.mTagCtx.type();
    stream << vType;
    if (vType == QVariant::LongLong) {
        stream << context.mTagCtx.toLongLong();
    } else if (vType == QVariant::String) {
        stream << context.mTagCtx.toString();
    }

    return stream;
}

DataStream &operator>>(DataStream &stream, ScopeContext &context)
{
    QVariant::Type vType;
    qint64 id;
    QString rid;

    for (ScopeContext::Type type : { ScopeContext::Collection, ScopeContext::Tag }) {
        stream >> vType;
        if (vType == QVariant::LongLong) {
            stream >> id;
            context.setContext(type, id);
        } else if (vType == QVariant::String) {
            stream >> rid;
            context.setContext(type, rid);
        }
    }

    return stream;
}

QDebug operator<<(QDebug _dbg, const ScopeContext &ctx)
{
    QDebug dbg(_dbg.noquote());
    dbg << "ScopeContext(";
    if (ctx.isEmpty()) {
        dbg << "empty";
    } else if (ctx.hasContextId(ScopeContext::Tag)) {
        dbg << "Tag ID:" << ctx.contextId(ScopeContext::Tag);
    } else if (ctx.hasContextId(ScopeContext::Collection)) {
        dbg << "Col ID:" << ctx.contextId(ScopeContext::Collection);
    } else if (ctx.hasContextRID(ScopeContext::Tag)) {
        dbg << "Tag RID:" << ctx.contextRID(ScopeContext::Tag);
    } else if (ctx.hasContextRID(ScopeContext::Collection)) {
        dbg << "Col RID:" << ctx.contextRID(ScopeContext::Collection);
    }
    return dbg << ")\n";
}


/******************************************************************************/

ChangeNotification::ChangeNotification(Command::Type type)
    : Command(type)
{
}

ChangeNotification::ChangeNotification(const ChangeNotification &other)
    : Command(other)
    , mSessionId(other.mSessionId)
    , mMetaData(other.mMetaData)
{
}

ChangeNotification &ChangeNotification::operator=(const ChangeNotification &other)
{
    *static_cast<Command*>(this) = static_cast<const Command&>(other);
    mSessionId = other.mSessionId;
    mMetaData = other.mMetaData;
    return *this;
}

bool ChangeNotification::operator==(const ChangeNotification &other) const
{
    return static_cast<const Command &>(*this) == other
        && mSessionId == other.mSessionId;
        // metadata are not compared
}

bool ChangeNotification::isRemove() const
{
    switch (type()) {
    case Command::Invalid:
        return false;
    case Command::ItemChangeNotification:
        return static_cast<const class ItemChangeNotification *>(this)->operation() == ItemChangeNotification::Remove;
    case Command::CollectionChangeNotification:
        return static_cast<const class CollectionChangeNotification *>(this)->operation() == CollectionChangeNotification::Remove;
    case Command::TagChangeNotification:
        return static_cast<const class TagChangeNotification *>(this)->operation() == TagChangeNotification::Remove;
    case Command::RelationChangeNotification:
        return static_cast<const class RelationChangeNotification *>(this)->operation() == RelationChangeNotification::Remove;
    case Command::SubscriptionChangeNotification:
        return static_cast<const class SubscriptionChangeNotification *>(this)->operation() == SubscriptionChangeNotification::Remove;
    case Command::DebugChangeNotification:
        return false;
    default:
        Q_ASSERT_X(false, __FUNCTION__, "Unknown ChangeNotification type");
    }

    return false;
}

bool ChangeNotification::isMove() const
{
    switch (type()) {
    case Command::Invalid:
        return false;
    case Command::ItemChangeNotification:
        return static_cast<const class ItemChangeNotification *>(this)->operation() == ItemChangeNotification::Move;
    case Command::CollectionChangeNotification:
        return static_cast<const class CollectionChangeNotification *>(this)->operation() == CollectionChangeNotification::Move;
    case Command::TagChangeNotification:
    case Command::RelationChangeNotification:
    case Command::SubscriptionChangeNotification:
    case Command::DebugChangeNotification:
        return false;
    default:
        Q_ASSERT_X(false, __FUNCTION__, "Unknown ChangeNotification type");
    }

    return false;
}

bool ChangeNotification::appendAndCompress(ChangeNotificationList &list, const ChangeNotificationPtr &msg)
{
    //It is likely that compressable notifications are within the last few notifications, so avoid searching a list that is potentially huge
    static const int maxCompressionSearchLength = 10;
    int searchCounter = 0;
    // There are often multiple Collection Modify notifications in the queue,
    // so we optimize for this case.

    if (msg->type() == Command::CollectionChangeNotification) {
        const auto &cmsg = Protocol::cmdCast<class CollectionChangeNotification>(msg);
        if (cmsg.operation() == CollectionChangeNotification::Modify) {
            // We are iterating from end, since there's higher probability of finding
            // matching notification
            for (auto iter = list.end(), begin = list.begin(); iter != begin;) {
                --iter;
                auto &it = Protocol::cmdCast<class CollectionChangeNotification>(*iter);
                if (cmsg.id() == it.id()
                    && cmsg.remoteId() == it.remoteId()
                    && cmsg.remoteRevision() == it.remoteRevision()
                    && cmsg.resource() == it.resource()
                    && cmsg.destinationResource() == it.destinationResource()
                    && cmsg.parentCollection() == it.parentCollection()
                    && cmsg.parentDestCollection() == it.parentDestCollection())
                {
                    // both are modifications, merge them together and drop the new one
                    if (cmsg.operation() == CollectionChangeNotification::Modify
                            && it.operation() == CollectionChangeNotification::Modify) {
                        const auto parts = it.changedParts();
                        it.setChangedParts(parts + cmsg.changedParts());
                        return false;
                    }

                    // we found Add notification, which means we can drop this modification
                    if (it.operation() == CollectionChangeNotification::Add) {
                        return false;
                    }
                }
                searchCounter++;
                if (searchCounter >= maxCompressionSearchLength) {
                    break;
                }
            }
        }
    }

    // All other cases are just append, as the compression becomes too expensive in large batches
    list.append(msg);
    return true;
}

DataStream &operator<<(DataStream &stream, const ChangeNotification &ntf)
{
    return stream << static_cast<const Command &>(ntf)
                  << ntf.mSessionId;
}

DataStream &operator>>(DataStream &stream, ChangeNotification &ntf)
{
    return stream >> static_cast<Command &>(ntf)
                  >> ntf.mSessionId;
}

QDebug operator<<(QDebug dbg, const ChangeNotification &ntf)
{
    return dbg.noquote() << static_cast<const Command &>(ntf)
                  << "Session:" << ntf.mSessionId << "\n"
                  << "MetaData:" << ntf.mMetaData << "\n";
}


DataStream &operator>>(DataStream &stream, ChangeNotification::Item &item)
{
    return stream >> item.id
                  >> item.mimeType
                  >> item.remoteId
                  >> item.remoteRevision;
}

DataStream &operator<<(DataStream &stream, const ChangeNotification::Item &item)
{
    return stream << item.id
                  << item.mimeType
                  << item.remoteId
                  << item.remoteRevision;
}

QDebug operator<<(QDebug _dbg, const ChangeNotification::Item &item)
{
    QDebug dbg(_dbg.noquote());
    return dbg << "Item:" << item.id << "(RID:" << item.remoteId
               << ", RREV:" << item.remoteRevision << ", mimetype: " << item.mimeType;
}


DataStream &operator>>(DataStream &stream, ChangeNotification::Relation &relation)
{
    return stream >> relation.type
                  >> relation.leftId
                  >> relation.rightId;
}

DataStream &operator<<(DataStream &stream, const ChangeNotification::Relation &relation)
{
    return stream << relation.type
                  << relation.leftId
                  << relation.rightId;
}

QDebug operator<<(QDebug _dbg, const ChangeNotification::Relation &rel)
{
    QDebug dbg(_dbg.noquote());
    return dbg << "Left: " << rel.leftId << ", Right:" << rel.rightId << ", Type: " << rel.type;
}


} // namespace Protocol
} // namespace Akonadi

/******************************************************************************/

// Here comes the generated protocol implementation
#include "protocol_gen.cpp"

/******************************************************************************/
