/****************************************************************************
**
** Copyright (C) 2012 Róbert Márki
**
** This file is part of Web Feeds.
**
** Web Feeds is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** Web Feeds is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with Web Feeds.  If not, see <http://www.gnu.org/licenses/>.
****************************************************************************/

#include "googlereaderclient.h"
#include "googlereaderreply.h"
#include "googlereaderenum.h"
#include "json.h"
#include <QNetworkAccessManager>
#include <QNetworkCookie>
#include <QNetworkCookieJar>
#include <QTextDocumentFragment>
#include <QSet>

namespace GoogleReader
{
const char* GoogleReaderClient::s_baseUrl                   = "http://www.google.com/reader/api/0/";
const char* GoogleReaderClient::s_secureBaseUrl             = "https://www.google.com/";
const char* GoogleReaderClient::s_faviconUrl                = "http://s2.googleusercontent.com/s2/favicons";
const char* GoogleReaderClient::s_loginPath                 = "accounts/ClientLogin";
const char* GoogleReaderClient::s_logoutPath                = "accounts/Logout";
const char* GoogleReaderClient::s_subscriptionsPath         = "subscription/list";
const char* GoogleReaderClient::s_tagsPath                  = "tag/list";
const char* GoogleReaderClient::s_unreadCountsPath          = "unread-count";
const char* GoogleReaderClient::s_listPreferencePath        = "preference/stream/list";
const char* GoogleReaderClient::s_feedStoriesPath           = "stream/contents/";
const char* GoogleReaderClient::s_editTokenPath             = "token";
const char* GoogleReaderClient::s_editTagPath               = "edit-tag";
const char* GoogleReaderClient::s_markAllAsReadPath         = "mark-all-as-read";
const char* GoogleReaderClient::s_editSubscriptionPath      = "subscription/edit";
const char* GoogleReaderClient::s_renameTagPath             = "rename-tag";
const char* GoogleReaderClient::s_disableTagPath            = "disable-tag";
const char* GoogleReaderClient::s_findFeedsUrl              = "https://ajax.googleapis.com/ajax/services/feed/find";
const char* GoogleReaderClient::s_addFeedPath               = "subscription/quickadd";
const char* GoogleReaderClient::s_sessionIdName             = "SID";
const char* GoogleReaderClient::s_authIdName                = "Auth";
const char* GoogleReaderClient::s_sessionCookiePath         = "/";
const char* GoogleReaderClient::s_sessionCookieDomain       = ".google.com";
const char* GoogleReaderClient::s_authHeaderName            = "Authorization";
const char* GoogleReaderClient::s_authHeaderValuePrefix     = "GoogleLogin auth=";

const char* GoogleReaderClient::s_everythingTagId           = "user/-/state/com.google/reading-list";
const char* GoogleReaderClient::s_starredTagId              = "user/-/state/com.google/starred";
const char* GoogleReaderClient::s_rootItemId                = "user/-/state/com.google/root";

const char* GoogleReaderClient::s_readStateId               = "user/-/state/com.google/read";
const char* GoogleReaderClient::s_keptUnreadStateId         = "user/-/state/com.google/kept-unread";
const char* GoogleReaderClient::s_starredStateId            = "user/-/state/com.google/starred";
const char* GoogleReaderClient::s_readingListStateId        = "user/-/state/com.google/reading-list";

const char* GoogleReaderClient::s_sortByNewestValue         = "n";
const char* GoogleReaderClient::s_sortByOldestValue         = "o";
const char* GoogleReaderClient::s_sortByMagicValue          = "a";

const char* GoogleReaderClient::s_serviceParameter                      = "service";
const char* GoogleReaderClient::s_serviceValue                          = "reader";
const char* GoogleReaderClient::s_emailParameter                        = "Email";
const char* GoogleReaderClient::s_passwordParameter                     = "Passwd";
const char* GoogleReaderClient::s_outputParameter                       = "output";
const char* GoogleReaderClient::s_outputParameterValue                  = "json";
const char* GoogleReaderClient::s_timestampParemeter                    = "ck";
const char* GoogleReaderClient::s_clientParameter                       = "client";
const char* GoogleReaderClient::s_clientParameterValue                  = "scroll";
const char* GoogleReaderClient::s_maxNumberOfResultsParameter           = "n";
const char* GoogleReaderClient::s_excludeTargetParameter                = "xt";
const char* GoogleReaderClient::s_sortOrderParameter                    = "r";
const char* GoogleReaderClient::s_continuationParameter                 = "c";
const char* GoogleReaderClient::s_startTimeParameter                    = "ot";
const char* GoogleReaderClient::s_editTokenParameter                    = "T";
const char* GoogleReaderClient::s_streamIdParameter                     = "s";
const char* GoogleReaderClient::s_itemIdParameter                       = "i";
const char* GoogleReaderClient::s_addTagParameter                       = "a";
const char* GoogleReaderClient::s_removeTagParameter                    = "r";
const char* GoogleReaderClient::s_streamTitleParameter                  = "t";
const char* GoogleReaderClient::s_actionParameter                       = "ac";
const char* GoogleReaderClient::s_actionParameterEditValue              = "edit";
const char* GoogleReaderClient::s_actionParameterSubscribeValue         = "subscribe";
const char* GoogleReaderClient::s_actionParameterUnsubscribeValue       = "unsubscribe";
const char* GoogleReaderClient::s_destParameter                         = "dest";
const char* GoogleReaderClient::s_feedSearchApiVersionParameter         = "v";
const char* GoogleReaderClient::s_feedSearchApiVersionParameterValue    = "1.0";
const char* GoogleReaderClient::s_feedSearchApiQueryParameter           = "q";
const char* GoogleReaderClient::s_quickAddParameter                     = "quickadd";

const char* GoogleReaderClient::s_idValue                   = "id";
const char* GoogleReaderClient::s_countValue                = "count";
const char* GoogleReaderClient::s_titleValue                = "title";
const char* GoogleReaderClient::s_categoriesValue           = "categories";
const char* GoogleReaderClient::s_subscriptionsValue        = "subscriptions";
const char* GoogleReaderClient::s_tagsValue                 = "tags";
const char* GoogleReaderClient::s_htmlUrlValue              = "htmlUrl";
const char* GoogleReaderClient::s_unreadCountsValue         = "unreadcounts";
const char* GoogleReaderClient::s_sortIdValue               = "sortid";
const char* GoogleReaderClient::s_streamPreferencesValue    = "streamprefs";
const char* GoogleReaderClient::s_subscriptionOrderingValue = "subscription-ordering";
const char* GoogleReaderClient::s_valueValue                = "value";
const char* GoogleReaderClient::s_continuationValue         = "continuation";
const char* GoogleReaderClient::s_itemsValue                = "items";
const char* GoogleReaderClient::s_summaryValue              = "summary";
const char* GoogleReaderClient::s_contentsValue             = "content";
const char* GoogleReaderClient::s_alternateValue            = "alternate";
const char* GoogleReaderClient::s_typeValue                 = "type";
const char* GoogleReaderClient::s_alternateTypeValue        = "text/html";
const char* GoogleReaderClient::s_hrefValue                 = "href";
const char* GoogleReaderClient::s_publishedValue            = "published";
const char* GoogleReaderClient::s_timestampValue            = "timestampUsec";
const char* GoogleReaderClient::s_authorValue               = "author";
const char* GoogleReaderClient::s_originValue               = "origin";
const char* GoogleReaderClient::s_streamIdValue             = "streamId";
const char* GoogleReaderClient::s_startTimeValue            = "firstitemmsec";
const char* GoogleReaderClient::s_readStateLockedValue      = "isReadStateLocked";
const char* GoogleReaderClient::s_trueValue                 = "true";
const char* GoogleReaderClient::s_responseDataValue         = "responseData";
const char* GoogleReaderClient::s_entriesValue              = "entries";
const char* GoogleReaderClient::s_urlValue                  = "url";
const char* GoogleReaderClient::s_numberOfResultsValue      = "numResults";
const char* GoogleReaderClient::s_maxValue                  = "max";
const char* GoogleReaderClient::s_okReplyValue              = "OK";
const char* GoogleReaderClient::s_tagIdPrefix               = "user/-/label/";
const char* GoogleReaderClient::s_tagIdPattern              = "/label/";
const char* GoogleReaderClient::s_stateIdPattern            = "/state/";
const char* GoogleReaderClient::s_feedIdPrefix              = "feed/";
const int GoogleReaderClient::s_itemIdLength                = 8;
const int GoogleReaderClient::s_timeStampLength             = 10;

GoogleReaderClient::GoogleReaderClient(QObject *parent) :
    WebServiceClient(parent)
{
    addNetworkRequestType(GoogleReaderClient::NrtLogin,
                          QString(GoogleReaderClient::s_secureBaseUrl) + GoogleReaderClient::s_loginPath,
                          QNetworkAccessManager::PostOperation);
    addNetworkRequestType(GoogleReaderClient::NrtLogout,
                          QString(GoogleReaderClient::s_secureBaseUrl) + GoogleReaderClient::s_logoutPath,
                          QNetworkAccessManager::PostOperation);
    addNetworkRequestType(GoogleReaderClient::NrtSubscriptions,
                          QString(GoogleReaderClient::s_baseUrl) + GoogleReaderClient::s_subscriptionsPath,
                          QNetworkAccessManager::GetOperation);
    addNetworkRequestType(GoogleReaderClient::NrtTags,
                          QString(GoogleReaderClient::s_baseUrl) + GoogleReaderClient::s_tagsPath,
                          QNetworkAccessManager::GetOperation);
    addNetworkRequestType(GoogleReaderClient::NrtUnreadCounts,
                          QString(GoogleReaderClient::s_baseUrl) + GoogleReaderClient::s_unreadCountsPath,
                          QNetworkAccessManager::GetOperation);
    addNetworkRequestType(GoogleReaderClient::NrtFavicon,
                          GoogleReaderClient::s_faviconUrl,
                          QNetworkAccessManager::GetOperation);
    addNetworkRequestType(GoogleReaderClient::NrtListPreferences,
                          QString(GoogleReaderClient::s_baseUrl) + GoogleReaderClient::s_listPreferencePath,
                          QNetworkAccessManager::GetOperation);
    addNetworkRequestType(GoogleReaderClient::NrtLoadFeedStories,
                          QString(GoogleReaderClient::s_baseUrl) + GoogleReaderClient::s_feedStoriesPath,
                          QNetworkAccessManager::GetOperation);
    addNetworkRequestType(GoogleReaderClient::NrtEditToken,
                          QString(GoogleReaderClient::s_baseUrl) + GoogleReaderClient::s_editTokenPath,
                          QNetworkAccessManager::GetOperation);
    addNetworkRequestType(GoogleReaderClient::NrtEditTag,
                          QString(GoogleReaderClient::s_baseUrl) + GoogleReaderClient::s_editTagPath,
                          QNetworkAccessManager::PostOperation);
    addNetworkRequestType(GoogleReaderClient::NrtMarkAllAsRead,
                          QString(GoogleReaderClient::s_baseUrl) + GoogleReaderClient::s_markAllAsReadPath,
                          QNetworkAccessManager::PostOperation);
    addNetworkRequestType(GoogleReaderClient::NrtEditSubscription,
                          QString(GoogleReaderClient::s_baseUrl) + GoogleReaderClient::s_editSubscriptionPath,
                          QNetworkAccessManager::PostOperation);
    addNetworkRequestType(GoogleReaderClient::NrtRenameTag,
                          QString(GoogleReaderClient::s_baseUrl) + GoogleReaderClient::s_renameTagPath,
                          QNetworkAccessManager::PostOperation);
    addNetworkRequestType(GoogleReaderClient::NrtDisableTag,
                          QString(GoogleReaderClient::s_baseUrl) + GoogleReaderClient::s_disableTagPath,
                          QNetworkAccessManager::PostOperation);
    addNetworkRequestType(GoogleReaderClient::NrtFindFeeds,
                          GoogleReaderClient::s_findFeedsUrl,
                          QNetworkAccessManager::GetOperation);
    addNetworkRequestType(GoogleReaderClient::NrtAddFeed,
                          QString(GoogleReaderClient::s_baseUrl) + GoogleReaderClient::s_addFeedPath,
                          QNetworkAccessManager::PostOperation);
}

bool GoogleReaderClient::isLoggedIn() const
{
    return networkAccessManager()->cookieJar()->cookiesForUrl(QUrl(GoogleReaderClient::s_secureBaseUrl)).count();
}

WebServiceReplyPtr GoogleReaderClient::verifyAccount(const QString &userName, const QString &password)
{
    GoogleReaderReplyPtr reply(new GoogleReaderReply);
    reply->setRequestType(GoogleReaderClient::TpVerifyAccount);
    QList<QPair<QString, QString> > parameters;
    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_serviceParameter,
                                              GoogleReaderClient::s_serviceValue);
    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_emailParameter,
                                              userName);
    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_passwordParameter,
                                              password);
    QNetworkReply* networkReply = sendRequest(GoogleReaderClient::NrtLogin, QString(), parameters);
    addReply(networkReply, reply);
    return reply;
}

void GoogleReaderClient::logout()
{
    networkAccessManager()->setCookieJar(new QNetworkCookieJar());
}

UpdateTreeReplyPtr GoogleReaderClient::updateTree()
{
    UpdateTreeReplyPtr reply(new UpdateTreeReply);
    reply->setRequestType(GoogleReaderClient::TpUpdateTree);
    QNetworkReply* networkReply;
    if(!isLoggedIn())
    {
        QList<QPair<QString, QString> > parameters;
        QAuthenticator authenticator;
        emit authenticationRequired(&authenticator);
        parameters << qMakePair<QString, QString>(GoogleReaderClient::s_serviceParameter,
                                                  GoogleReaderClient::s_serviceValue);
        parameters << qMakePair<QString, QString>(GoogleReaderClient::s_emailParameter,
                                                  authenticator.user());
        parameters << qMakePair<QString, QString>(GoogleReaderClient::s_passwordParameter,
                                                  authenticator.password());
        networkReply = sendRequest(GoogleReaderClient::NrtLogin, QString(), parameters);
    }
    else
    {
        QList<QPair<QString, QString> > parameters;
        parameters << qMakePair<QString, QString>(GoogleReaderClient::s_outputParameter,
                                                  GoogleReaderClient::s_outputParameterValue);
        parameters << qMakePair<QString, QString>(GoogleReaderClient::s_clientParameter,
                                                  GoogleReaderClient::s_clientParameterValue);
        parameters << qMakePair<QString, QString>(GoogleReaderClient::s_timestampParemeter,
                                                  currentTimestamp());
        networkReply = sendRequest(GoogleReaderClient::NrtTags, QString(), parameters);
    }
    addReply(networkReply, reply);
    return reply;
}

UnreadCountsReplyPtr GoogleReaderClient::updateUnreadCounts()
{
    UnreadCountsReplyPtr reply(new UnreadCountsReply);
    reply->setRequestType(GoogleReaderClient::TpUpdateUnreadCounts);
    QNetworkReply* networkReply;
    QList<QPair<QString, QString> > parameters;
    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_outputParameter,
                                              GoogleReaderClient::s_outputParameterValue);
    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_clientParameter,
                                              GoogleReaderClient::s_clientParameterValue);
    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_timestampParemeter,
                                              currentTimestamp());
    networkReply = sendRequest(GoogleReaderClient::NrtUnreadCounts, QString(), parameters);
    addReply(networkReply, reply);
    return reply;
}

StoryListReplyPtr GoogleReaderClient::loadFeedStories(const QString &feedId, GoogleReaderEnum::ItemFilterType itemFilter,
                                                      GoogleReaderEnum::ItemSortType itemSort, int preferredStoryCount,
                                                      const QString &continuation, quint64 startTime)
{
    StoryListReplyPtr reply(new StoryListReply);
    reply->setRequestType(GoogleReaderClient::TpLoadFeedStories);
    reply->m_filterType = itemFilter;
    QNetworkReply* networkReply;
    QList<QPair<QString, QString> > parameters;
    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_outputParameter,
                                              GoogleReaderClient::s_outputParameterValue);
    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_clientParameter,
                                              GoogleReaderClient::s_clientParameterValue);
    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_timestampParemeter,
                                              currentTimestamp());
    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_maxNumberOfResultsParameter,
                                              QString::number(preferredStoryCount));
    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_sortOrderParameter,
                                              sortTypeString(itemSort));
    QString filterString = filterTypeString(itemFilter);
    if(!filterString.isEmpty())
        parameters << qMakePair<QString, QString>(GoogleReaderClient::s_excludeTargetParameter,
                                                  filterString);
    if(!continuation.isEmpty())
        parameters << qMakePair<QString, QString>(GoogleReaderClient::s_continuationParameter,
                                                  continuation);
    if((startTime > 0) && (itemFilter != GoogleReaderEnum::IftAllItems))
        parameters << qMakePair<QString, QString>(GoogleReaderClient::s_startTimeParameter,
                                                  QString::number(startTime).left(GoogleReaderClient::s_timeStampLength));


    QString prefix = feedId.section("/", 0, 0);
    QString encodedPathSegment = QUrl::toPercentEncoding("/" + feedId.section("/", 1));
    QString path = prefix + encodedPathSegment;
    networkReply = sendRequest(GoogleReaderClient::NrtLoadFeedStories, path, parameters);
    addReply(networkReply, reply);
    return reply;
}

EditReplyPtr GoogleReaderClient::starStory(const QString &feedId, const QString &storyId)
{
    EditReplyPtr reply(new EditReply);
    reply->setRequestType(GoogleReaderClient::TpStarStory);
    reply->m_itemId = storyId;
    reply->m_streamId = feedId;
    reply->m_tagsToAdd << GoogleReaderClient::s_starredStateId;
    QNetworkReply* networkReply = sendRequest(GoogleReaderClient::NrtEditToken);
    addReply(networkReply, reply);
    return reply;
}

EditReplyPtr GoogleReaderClient::unstarStory(const QString &feedId, const QString &storyId)
{
    EditReplyPtr reply(new EditReply);
    reply->setRequestType(GoogleReaderClient::TpUnstarStory);
    reply->m_itemId = storyId;
    reply->m_streamId = feedId;
    reply->m_tagsToRemove << GoogleReaderClient::s_starredStateId;
    QNetworkReply* networkReply = sendRequest(GoogleReaderClient::NrtEditToken);
    addReply(networkReply, reply);
    return reply;
}

EditReplyPtr GoogleReaderClient::markStoryRead(const QString &feedId, const QString &storyId, bool keptUnread)
{
    EditReplyPtr reply(new EditReply);
    reply->setRequestType(GoogleReaderClient::TpMarkStoryRead);
    reply->m_itemId = storyId;
    reply->m_streamId = feedId;
    reply->m_tagsToAdd << GoogleReaderClient::s_readStateId;
    if(keptUnread)
        reply->m_tagsToRemove << GoogleReaderClient::s_keptUnreadStateId;
    QNetworkReply* networkReply = sendRequest(GoogleReaderClient::NrtEditToken);
    addReply(networkReply, reply);
    return reply;
}

EditReplyPtr GoogleReaderClient::markStoryUnread(const QString &feedId, const QString &storyId)
{
    EditReplyPtr reply(new EditReply);
    reply->setRequestType(GoogleReaderClient::TpMarkStoryUnread);
    reply->m_itemId = storyId;
    reply->m_streamId = feedId;
    reply->m_tagsToAdd << GoogleReaderClient::s_keptUnreadStateId;
    reply->m_tagsToRemove << GoogleReaderClient::s_readStateId;
    QNetworkReply* networkReply = sendRequest(GoogleReaderClient::NrtEditToken);
    addReply(networkReply, reply);
    return reply;
}

EditReplyPtr GoogleReaderClient::markFeedAsRead(const QString &feedId)
{
    EditReplyPtr reply(new EditReply);
    reply->setRequestType(GoogleReaderClient::TpMarkFeedAsRead);
    reply->m_streamId = feedId;
    QNetworkReply* networkReply = sendRequest(GoogleReaderClient::NrtEditToken);
    addReply(networkReply, reply);
    return reply;
}

EditReplyPtr GoogleReaderClient::markEverythingAsRead()
{
    return markFeedAsRead(GoogleReaderClient::s_readingListStateId);
}

EditReplyPtr GoogleReaderClient::setStoryTags(const QString &feedId, const QString &storyId,
                                                   const QStringList &newTags, const QStringList &currentTags)
{
    EditReplyPtr reply(new EditReply);
    reply->setRequestType(GoogleReaderClient::TpEditStoryTags);
    reply->m_streamId = feedId;
    reply->m_itemId = storyId;
    QSet<QString> newTagSet = newTags.toSet();
    QSet<QString> currentTagSet = currentTags.toSet();
    reply->m_tagsToAdd = (newTagSet - currentTagSet).toList();
    reply->m_tagsToRemove = (currentTagSet - newTagSet).toList();
    QNetworkReply* networkReply = sendRequest(GoogleReaderClient::NrtEditToken);
    addReply(networkReply, reply);
    return reply;
}

EditReplyPtr GoogleReaderClient::setFeedTags(const QString &feedId, const QString &feedTitle,
                                                  const QStringList &newTags, const QStringList &currentTags)
{
    EditReplyPtr reply(new EditReply);
    reply->setRequestType(GoogleReaderClient::TpEditFeedTags);
    reply->m_streamId = feedId;
    reply->m_feedTitle = feedTitle;
    reply->m_action = GoogleReaderClient::s_actionParameterEditValue;
    QSet<QString> newTagSet = newTags.toSet();
    QSet<QString> currentTagSet = currentTags.toSet();
    reply->m_tagsToAdd = (newTagSet - currentTagSet).toList();
    reply->m_tagsToRemove = (currentTagSet - newTagSet).toList();
    QNetworkReply* networkReply = sendRequest(GoogleReaderClient::NrtEditToken);
    addReply(networkReply, reply);
    return reply;
}

EditReplyPtr GoogleReaderClient::renameTag(const QString &tagId, const QString &newName)
{
    EditReplyPtr reply(new EditReply);
    reply->setRequestType(GoogleReaderClient::TpRenameTag);
    reply->m_streamId = tagId;
    reply->m_feedTitle = tagTitleFromId(tagId);
    reply->m_newStreamId = tagIdFromTitle(newName);
    QNetworkReply* networkReply = sendRequest(GoogleReaderClient::NrtEditToken);
    addReply(networkReply, reply);
    return reply;
}

EditReplyPtr GoogleReaderClient::renameFeed(const QString &feedId, const QString &newName)
{
    EditReplyPtr reply(new EditReply);
    reply->setRequestType(GoogleReaderClient::TpRenameFeed);
    reply->m_streamId = feedId;
    reply->m_feedTitle = newName;
    reply->m_action = GoogleReaderClient::s_actionParameterEditValue;
    QNetworkReply* networkReply = sendRequest(GoogleReaderClient::NrtEditToken);
    addReply(networkReply, reply);
    return reply;
}

EditReplyPtr GoogleReaderClient::deleteTag(const QString &tagId)
{
    EditReplyPtr reply(new EditReply);
    reply->setRequestType(GoogleReaderClient::TpDeleteTag);
    reply->m_streamId = tagId;
    reply->m_feedTitle = tagTitleFromId(tagId);
    QNetworkReply* networkReply = sendRequest(GoogleReaderClient::NrtEditToken);
    addReply(networkReply, reply);
    return reply;
}

EditReplyPtr GoogleReaderClient::deleteFeed(const QString &feedId, const QString &feedTitle)
{
    EditReplyPtr reply(new EditReply);
    reply->setRequestType(GoogleReaderClient::TpDeleteFeed);
    reply->m_streamId = feedId;
    reply->m_feedTitle = feedTitle;
    reply->m_action = GoogleReaderClient::s_actionParameterUnsubscribeValue;
    QNetworkReply* networkReply = sendRequest(GoogleReaderClient::NrtEditToken);
    addReply(networkReply, reply);
    return reply;
}

EditReplyPtr GoogleReaderClient::addFeed(const QString &feedUrl)
{
    EditReplyPtr reply(new EditReply);
    reply->setRequestType(GoogleReaderClient::TpAddFeed);
    reply->m_streamId = feedUrl;
    QNetworkReply* networkReply = sendRequest(GoogleReaderClient::NrtEditToken);
    addReply(networkReply, reply);
    return reply;
}

FeedSearchResultsReplyPtr GoogleReaderClient::findFeeds(const QString &searchTerm)
{
    FeedSearchResultsReplyPtr reply(new FeedSearchResultsReply);
    reply->setRequestType(GoogleReaderClient::TpFindFeeds);
    QNetworkReply* networkReply;
    QList<QPair<QString, QString> > parameters;
    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_feedSearchApiVersionParameter,
                                              GoogleReaderClient::s_feedSearchApiVersionParameterValue);
    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_feedSearchApiQueryParameter,
                                              searchTerm);
    networkReply = sendRequest(GoogleReaderClient::NrtFindFeeds, QString(), parameters);
    addReply(networkReply, reply);
    return reply;
}

QNetworkReply* GoogleReaderClient::networkRequestFinished(QNetworkReply *networkReply,
                                                          WebServiceReplyPtr webServiceReply,
                                                          int networkRequestType)
{
    //read the contents of the network reply
    QByteArray replyContents = networkReply->readAll();
    //initialize the pointer to a new reply to 0, since it's not known yet
    //if a new request will be sent or not
    QNetworkReply* newReply = 0;
    if(networkReply->error() != QNetworkReply::NoError)
    {
        if((networkReply->error() == QNetworkReply::ContentOperationNotPermittedError)
            && (networkRequestType == GoogleReaderClient::NrtLogin))
            onRequestFailed(webServiceReply, GoogleReaderEnum::ErrInvalidAccount);
        else
            onRequestFailed(webServiceReply, GoogleReaderEnum::ErrNetworkError);
    }
    //if no error has occurred
    else
    {
        bool parsingOk = true;
        //parse the contents of the reply
        QVariant parsedData;
        if((networkRequestType != GoogleReaderClient::NrtLogin)
        && (networkRequestType != GoogleReaderClient::NrtEditToken)
        && (networkRequestType != GoogleReaderClient::NrtEditTag)
        && (networkRequestType != GoogleReaderClient::NrtMarkAllAsRead)
        && (networkRequestType != GoogleReaderClient::NrtEditSubscription)
        && (networkRequestType != GoogleReaderClient::NrtRenameTag)
        && (networkRequestType != GoogleReaderClient::NrtDisableTag))
        {
            parsedData = Json::parse(replyContents, parsingOk);
        }
        //if the parsing failed abort the operation
        if(!parsingOk)
        {
            onRequestFailed(webServiceReply, GoogleReaderEnum::ErrInvalidReplyContents);
        }
        else if(webServiceReply->requestType() == GoogleReaderClient::TpVerifyAccount)
        {
            QString sessionId = QString(replyContents).section("=", 1);
            if(!sessionId.isEmpty())
                onRequestFinished(webServiceReply);
            else
                onRequestFailed(webServiceReply, GoogleReaderEnum::ErrInvalidAccount);
        }
        else if(webServiceReply->requestType() == GoogleReaderClient::TpUpdateTree)
        {
            UpdateTreeReplyPtr updateTreeReply = webServiceReply.staticCast<UpdateTreeReply>();
            if(networkRequestType == GoogleReaderClient::NrtLogin)
            {
                QString sessionId;
                QStringList values = QString(replyContents).split("\n");
                QString sessionIdPrefix = QString(GoogleReaderClient::s_sessionIdName) + "=";
                QString authIdPrefix = QString(GoogleReaderClient::s_authIdName) + "=";
                foreach(const QString& value, values)
                {
                    if(value.contains(sessionIdPrefix))
                        sessionId = value.mid(value.indexOf(sessionIdPrefix) + sessionIdPrefix.length());
                    else if(value.contains(authIdPrefix))
                        m_authId = value.mid(value.indexOf(authIdPrefix) + authIdPrefix.length());
                }
                if(!sessionId.isEmpty())
                {
                    QList<QNetworkCookie> cookies;
                    QNetworkCookie sessionCookie(QString(GoogleReaderClient::s_sessionIdName).toAscii(), sessionId.toAscii());
                    sessionCookie.setPath(GoogleReaderClient::s_sessionCookiePath);
                    sessionCookie.setExpirationDate(QDateTime());
                    sessionCookie.setDomain(GoogleReaderClient::s_sessionCookieDomain);
                    cookies << sessionCookie;
                    networkAccessManager()->cookieJar()->setCookiesFromUrl(cookies, QUrl(GoogleReaderClient::s_secureBaseUrl));

                    QList<QPair<QString, QString> > parameters;
                    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_outputParameter,
                                                              GoogleReaderClient::s_outputParameterValue);
                    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_clientParameter,
                                                              GoogleReaderClient::s_clientParameterValue);
                    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_timestampParemeter,
                                                              currentTimestamp());
                    newReply = sendRequest(GoogleReaderClient::NrtTags, QString(), parameters);
                }
                else
                {
                    onRequestFailed(webServiceReply, GoogleReaderEnum::ErrInvalidAccount);
                }
            }
            else if(networkRequestType == GoogleReaderClient::NrtTags)
            {
                updateTreeReply->m_tagItems = createTagItems(parsedData.toMap().value(GoogleReaderClient::s_tagsValue).toList());
                updateTreeReply->m_starredTagItem = StarredTagItemPtr(new StarredTagItem);
                updateTreeReply->m_starredTagItem->setItemId(GoogleReaderClient::s_starredTagId);
                updateTreeReply->m_starredTagItem->setCanFetchMore(true);
                updateTreeReply->m_everythingTagItem = EverythingTagItemPtr(new EverythingTagItem);
                updateTreeReply->m_everythingTagItem->setItemId(GoogleReaderClient::s_everythingTagId);
                updateTreeReply->m_everythingTagItem->setCanFetchMore(true);

                QList<QPair<QString, QString> > parameters;
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_outputParameter,
                                                          GoogleReaderClient::s_outputParameterValue);
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_clientParameter,
                                                          GoogleReaderClient::s_clientParameterValue);
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_timestampParemeter,
                                                          currentTimestamp());
                newReply = sendRequest(GoogleReaderClient::NrtSubscriptions, QString(), parameters);
            }
            else if(networkRequestType == GoogleReaderClient::NrtSubscriptions)
            {
                updateTreeReply->m_feedItems = createFeedItems(parsedData.toMap().value(GoogleReaderClient::s_subscriptionsValue).toList(),
                                                               &updateTreeReply->m_feedsPerTags);

                QList<QPair<QString, QString> > parameters;
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_outputParameter,
                                                          GoogleReaderClient::s_outputParameterValue);
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_clientParameter,
                                                          GoogleReaderClient::s_clientParameterValue);
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_timestampParemeter,
                                                          currentTimestamp());
                newReply = sendRequest(GoogleReaderClient::NrtListPreferences, QString(), parameters);
            }
            else if(networkRequestType == GoogleReaderClient::NrtListPreferences)
            {
                QHash<QString, QStringList> itemOrdering = createItemOrderingHash(parsedData.toMap().value(GoogleReaderClient::s_streamPreferencesValue).toMap());

                QMultiHash<QString, FeedItemPtr> feedItemsHash = updateTreeReply->m_feedItems;
                QMultiHash<QString, TagItemPtr> tagItemsHash = updateTreeReply->m_tagItems;

                updateTreeReply->m_rootItem->appendChild(updateTreeReply->m_everythingTagItem);
                updateTreeReply->m_rootItem->appendChild(updateTreeReply->m_starredTagItem);

                QList<GoogleReaderItemPtr> topLevelItems;
                QHash<QString, GoogleReaderItemPtr> topLevelItemsPerSortId;

                QStringList folderIds = updateTreeReply->m_feedsPerTags.uniqueKeys();                
                foreach(const QString& folderId, folderIds)
                {
                    FolderItemPtr folderItem(new FolderItem);
                    TagItemPtr childTag = tagItemsHash.take(folderId);
                    folderItem->setTitle(childTag->title());
                    folderItem->setItemId(childTag->itemId());
                    folderItem->setCanFetchMore(false);
                    folderItem->appendChild(childTag);
                    folderItem->setSortId(childTag->sortId());
                    updateTreeReply->m_folderItems.insertMulti(folderId, folderItem);
                    topLevelItems << folderItem;
                    topLevelItemsPerSortId.insert(folderItem->sortId(), folderItem);

                    QStringList feedIdsInTag = updateTreeReply->m_feedsPerTags.values(folderId);
                    QList<GoogleReaderItemPtr> feedItems;
                    QHash<QString, GoogleReaderItemPtr> feedItemsPerSortId;
                    foreach(const QString& feedId, feedIdsInTag)
                    {
                        GoogleReaderItemPtr feedItem = feedItemsHash.take(feedId);
                        feedItems << feedItem;
                        feedItemsPerSortId.insert(feedItem->sortId(), feedItem);
                    }
                    qSort(feedItems.begin(), feedItems.end(), GoogleReaderClient::itemSortLessThan);
                    foreach(const QString& sortId, itemOrdering.value(folderId))
                    {
                        if(feedItemsPerSortId.contains(sortId))
                        {
                            GoogleReaderItemPtr item = feedItemsPerSortId.value(sortId);
                            folderItem->appendChild(item);
                            feedItems.removeAll(item);
                        }
                    }
                    foreach(GoogleReaderItemPtr feedItem, feedItems)
                        folderItem->appendChild(feedItem);
                }
                foreach(FeedItemPtr feedItem, feedItemsHash.values())
                {
                    topLevelItems << feedItem;
                    topLevelItemsPerSortId.insert(feedItem->sortId(), feedItem);
                }
                foreach(TagItemPtr tagItem, tagItemsHash.values())
                {
                    topLevelItems << tagItem;
                    topLevelItemsPerSortId.insert(tagItem->sortId(), tagItem);
                }
                qSort(topLevelItems.begin(), topLevelItems.end(), GoogleReaderClient::itemSortLessThan);
                foreach(const QString& sortId, itemOrdering.value(GoogleReaderClient::s_rootItemId))
                {
                    if(topLevelItemsPerSortId.contains(sortId))
                    {
                        GoogleReaderItemPtr item = topLevelItemsPerSortId.value(sortId);
                        updateTreeReply->m_rootItem->appendChild(item);
                        topLevelItems.removeAll(item);
                    }
                }
                foreach(GoogleReaderItemPtr item, topLevelItems)
                    updateTreeReply->m_rootItem->appendChild(item);

                QList<QPair<QString, QString> > parameters;
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_outputParameter,
                                                          GoogleReaderClient::s_outputParameterValue);
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_clientParameter,
                                                          GoogleReaderClient::s_clientParameterValue);
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_timestampParemeter,
                                                          currentTimestamp());
                newReply = sendRequest(GoogleReaderClient::NrtUnreadCounts, QString(), parameters);
            }
            else if(networkRequestType == GoogleReaderClient::NrtUnreadCounts)
            {
                QVariantMap unreadCountMap = parsedData.toMap();
                QVariantList unreadCountList = unreadCountMap.value(GoogleReaderClient::s_unreadCountsValue).toList();
                updateTreeReply->m_unreadCounts = createIdUnreadCounts(unreadCountList);
                updateTreeReply->m_unreadCountLimit = unreadCountMap.value(GoogleReaderClient::s_maxValue).toInt();
                onRequestFinished(webServiceReply);
            }
        }
        else if(webServiceReply->requestType() == GoogleReaderClient::TpUpdateUnreadCounts)
        {
            UnreadCountsReplyPtr unreadCountsReply = webServiceReply.staticCast<UnreadCountsReply>();
            QVariantMap unreadCountMap = parsedData.toMap();
            QVariantList unreadCountList = unreadCountMap.value(GoogleReaderClient::s_unreadCountsValue).toList();
            unreadCountsReply->m_unreadCounts = createIdUnreadCounts(unreadCountList);
            unreadCountsReply->m_unreadCountLimit = unreadCountMap.value(GoogleReaderClient::s_maxValue).toInt();
            onRequestFinished(webServiceReply);
        }
        else if(webServiceReply->requestType() == GoogleReaderClient::TpLoadFeedStories)
        {
            StoryListReplyPtr storyListReply = webServiceReply.staticCast<StoryListReply>();
            storyListReply->m_continuation = parsedData.toMap().value(GoogleReaderClient::s_continuationValue).toString();
            QVariantList itemList = parsedData.toMap().value(GoogleReaderClient::s_itemsValue).toList();
            foreach(const QVariant& item, itemList)
            {
                QVariantMap itemMap = item.toMap();
                QStringList categories;
                QStringList tags;
                QVariantList categoryList = itemMap.value(GoogleReaderClient::s_categoriesValue).toList();
                foreach(QVariant category, categoryList)
                {
                    QString categoryId = generalItemId(category.toByteArray());
                    if(categoryId.contains(GoogleReaderClient::s_tagIdPattern))
                        tags << categoryId;
                    categories << categoryId;
                }
                bool isRead = categories.contains(GoogleReaderClient::s_readStateId);
                if((storyListReply->m_filterType == GoogleReaderEnum::IftNewItems) && isRead)
                    continue;
                StoryItemPtr storyItem(new StoryItem);
                storyItem->setRead(isRead);
                storyItem->setKeptUnread(categories.contains(GoogleReaderClient::s_keptUnreadStateId));
                storyItem->setStarred(categories.contains(GoogleReaderClient::s_starredStateId));
                storyItem->setInReadingList(categories.contains(GoogleReaderClient::s_readingListStateId));
                storyItem->setTags(tags);
                storyItem->setItemId(itemMap.value(GoogleReaderClient::s_idValue).toString());
                storyItem->setTitle(toPlainText(QString::fromUtf8(itemMap.value(GoogleReaderClient::s_titleValue).toByteArray())));                
                QVariantMap summaryMap;
                if(itemMap.contains(GoogleReaderClient::s_summaryValue))
                    summaryMap = itemMap.value(GoogleReaderClient::s_summaryValue).toMap();
                else
                    summaryMap = itemMap.value(GoogleReaderClient::s_contentsValue).toMap();
                QString contents = QString::fromUtf8(summaryMap.value(GoogleReaderClient::s_contentsValue).toByteArray());
                storyItem->setContents(contents);
                QVariantList alternateList = itemMap.value(GoogleReaderClient::s_alternateValue).toList();
                foreach(const QVariant& alternateItem, alternateList)
                {
                    QVariantMap alternateItemMap = alternateItem.toMap();
                    if(alternateItemMap.value(GoogleReaderClient::s_typeValue).toString() == GoogleReaderClient::s_alternateTypeValue)
                    {
                        storyItem->setUrl(alternateItemMap.value(GoogleReaderClient::s_hrefValue).toString());
                        break;
                    }
                }
                storyItem->setDate(QDateTime::fromTime_t(itemMap.value(GoogleReaderClient::s_timestampValue).toString()
                                                         .left(GoogleReaderClient::s_timeStampLength).toInt()));
                storyItem->setAuthor(itemMap.value(GoogleReaderClient::s_authorValue).toString());
                QVariantMap originMap = itemMap.value(GoogleReaderClient::s_originValue).toMap();
                storyItem->setFeedId(originMap.value(GoogleReaderClient::s_streamIdValue).toString());
                storyItem->setReadStateLocked(itemMap.value(GoogleReaderClient::s_readStateLockedValue).toString() == GoogleReaderClient::s_trueValue);
                storyListReply->m_stories.append(storyItem);
            }
            onRequestFinished(webServiceReply);
        }
        else if(webServiceReply->requestType() == GoogleReaderClient::TpFindFeeds)
        {
            FeedSearchResultsReplyPtr searchResultsReply = webServiceReply.staticCast<FeedSearchResultsReply>();
            QVariantList entriesList = parsedData.toMap().value(GoogleReaderClient::s_responseDataValue)
                    .toMap().value(GoogleReaderClient::s_entriesValue).toList();
            foreach(const QVariant& entry, entriesList)
            {
                QVariantMap entryMap = entry.toMap();
                searchResultsReply->m_urls << QString::fromUtf8(entryMap.value(GoogleReaderClient::s_urlValue).toByteArray());
                searchResultsReply->m_feedTitles << toPlainText(QString::fromUtf8(entryMap.value(GoogleReaderClient::s_titleValue).toByteArray()));
            }
            onRequestFinished(webServiceReply);
        }
        else if((webServiceReply->requestType() == GoogleReaderClient::TpAddFeed)
                && (networkRequestType == GoogleReaderClient::NrtAddFeed))
        {
            QVariantMap resultsMap = parsedData.toMap();
            if(resultsMap.value(GoogleReaderClient::s_numberOfResultsValue).toInt() > 0)
                onRequestFinished(webServiceReply);
            else
                onRequestFailed(webServiceReply, GoogleReaderEnum::ErrResponseResultFalse);
        }
        else if(networkRequestType == GoogleReaderClient::NrtEditToken)
        {
            EditReplyPtr editStoryReply = webServiceReply.staticCast<EditReply>();
            QList<QPair<QString, QString> > parameters;
            parameters << qMakePair<QString, QString>(GoogleReaderClient::s_editTokenParameter,
                                                      replyContents);
            if(webServiceReply->requestType() == GoogleReaderClient::TpMarkFeedAsRead)
            {
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_streamIdParameter,
                                                          editStoryReply->m_streamId);
                newReply = sendRequest(GoogleReaderClient::NrtMarkAllAsRead, QString(), parameters);
            }
            else if((webServiceReply->requestType() == GoogleReaderClient::TpEditFeedTags)
                    || (webServiceReply->requestType() == GoogleReaderClient::TpRenameFeed)
                    || (webServiceReply->requestType() == GoogleReaderClient::TpDeleteFeed))
            {
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_streamIdParameter,
                                                          editStoryReply->m_streamId);
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_streamTitleParameter,
                                                          editStoryReply->m_feedTitle);
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_actionParameter,
                                                          editStoryReply->m_action);
                foreach(const QString& tagToAdd, editStoryReply->m_tagsToAdd)
                    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_addTagParameter,
                                                              tagToAdd);
                foreach(const QString& tagToRemove, editStoryReply->m_tagsToRemove)
                    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_removeTagParameter,
                                                              tagToRemove);
                newReply = sendRequest(GoogleReaderClient::NrtEditSubscription, QString(), parameters);
            }
            else if(webServiceReply->requestType() == GoogleReaderClient::TpAddFeed)
            {
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_quickAddParameter,
                                                          editStoryReply->m_streamId);
                newReply = sendRequest(GoogleReaderClient::NrtAddFeed, QString(), parameters);
            }
            else if(webServiceReply->requestType() == GoogleReaderClient::TpRenameTag)
            {
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_streamIdParameter,
                                                          editStoryReply->m_streamId);
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_streamTitleParameter,
                                                          editStoryReply->m_feedTitle);
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_destParameter,
                                                          editStoryReply->m_newStreamId);
                newReply = sendRequest(GoogleReaderClient::NrtRenameTag, QString(), parameters);
            }
            else if(webServiceReply->requestType() == GoogleReaderClient::TpDeleteTag)
            {
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_streamIdParameter,
                                                          editStoryReply->m_streamId);
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_streamTitleParameter,
                                                          editStoryReply->m_feedTitle);
                newReply = sendRequest(GoogleReaderClient::NrtDisableTag, QString(), parameters);
            }
            else
            {
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_streamIdParameter,
                                                          editStoryReply->m_streamId);
                parameters << qMakePair<QString, QString>(GoogleReaderClient::s_itemIdParameter,
                                                          editStoryReply->m_itemId);
                foreach(const QString& tagToAdd, editStoryReply->m_tagsToAdd)
                    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_addTagParameter,
                                                              tagToAdd);
                foreach(const QString& tagToRemove, editStoryReply->m_tagsToRemove)
                    parameters << qMakePair<QString, QString>(GoogleReaderClient::s_removeTagParameter,
                                                              tagToRemove);
                newReply = sendRequest(GoogleReaderClient::NrtEditTag, QString(), parameters);
            }
        }
        else if((networkRequestType == GoogleReaderClient::NrtEditTag)
                || (networkRequestType == GoogleReaderClient::NrtMarkAllAsRead)
                || (networkRequestType == GoogleReaderClient::NrtEditSubscription)
                || (networkRequestType == GoogleReaderClient::NrtRenameTag)
                || (networkRequestType == GoogleReaderClient::NrtDisableTag))
        {
            if(replyContents == GoogleReaderClient::s_okReplyValue)
                onRequestFinished(webServiceReply);
            else
                onRequestFailed(webServiceReply, GoogleReaderEnum::ErrResponseResultFalse);
        }        
    }
    return newReply;
}

QNetworkReply *GoogleReaderClient::sendRequest(int requestTypeId, const QString &path,
                                               const StringPairList &parameters,
                                               const StringPairList &additionalHeaders)
{
    QList<QPair<QString, QString> > headers = additionalHeaders;
    if(isLoggedIn())
        headers << qMakePair<QString, QString>(GoogleReaderClient::s_authHeaderName, GoogleReaderClient::s_authHeaderValuePrefix + m_authId);
    return WebServiceClient::sendRequest(requestTypeId, path, parameters, headers);
}

QString GoogleReaderClient::currentTimestamp() const
{
    return QString::number(QDateTime::currentMSecsSinceEpoch());
}

QMultiHash<QString, TagItemPtr> GoogleReaderClient::createTagItems(const QVariantList &tagList) const
{
    QMultiHash<QString, TagItemPtr> ret;
    foreach(const QVariant& tag, tagList)
    {
        QVariantMap tagMap = tag.toMap();
        QString tagId = generalItemId(tagMap.value(GoogleReaderClient::s_idValue).toByteArray());
        if(!tagId.contains(GoogleReaderClient::s_stateIdPattern))
        {
            TagItemPtr tagItem(new TagItem);
            tagItem->setItemId(tagId);
            tagItem->setTitle(tagTitleFromId(tagItem->itemId()));
            tagItem->setSortId(tagMap.value(GoogleReaderClient::s_sortIdValue).toString());
            tagItem->setCanFetchMore(true);
            ret.insert(tagItem->itemId(), tagItem);
        }        
    }
    return ret;
}

QMultiHash<QString, FeedItemPtr> GoogleReaderClient::createFeedItems(const QVariantList &feedList,
                                                                     QMultiHash<QString, QString> *feedCategories) const
{
    QMultiHash<QString, FeedItemPtr> ret;
    foreach(const QVariant& feed, feedList)
    {
        QVariantMap feedMap = feed.toMap();
        QStringList categories = categoryIdList(feedMap.value(GoogleReaderClient::s_categoriesValue).toList());
        QStringList folders;
        foreach(const QString& category, categories)
        {
            if(category.contains(GoogleReaderClient::s_tagIdPattern))
                folders << category;
        }
        QString feedId;
        int feedInstanceCount = 1;
        if(!categories.isEmpty())
            feedInstanceCount = categories.count();
        for(int i = 0; i < feedInstanceCount; ++i)
        {
            FeedItemPtr feedItem = createFeedItem(feedMap);
            feedId = feedItem->itemId();
            feedItem->setFolders(folders);
            ret.insert(feedItem->itemId(), feedItem);
        }
        foreach(const QString& categoryId, categories)
            feedCategories->insert(categoryId, feedId);
    }
    return ret;
}

FeedItemPtr GoogleReaderClient::createFeedItem(const QVariantMap &feedMap) const
{
    FeedItemPtr ret(new FeedItem);
    ret->setItemId(generalItemId(feedMap.value(GoogleReaderClient::s_idValue).toByteArray()));
    ret->setTitle(toPlainText(QString::fromUtf8(feedMap.value(GoogleReaderClient::s_titleValue).toByteArray())));
    ret->setIconSource(feedIconUrl(QUrl(feedMap.value(GoogleReaderClient::s_htmlUrlValue).toString()).host()));
    ret->setSortId(feedMap.value(GoogleReaderClient::s_sortIdValue).toString());
    ret->setStartTime(feedMap.value(GoogleReaderClient::s_startTimeValue).toULongLong());
    ret->setCanFetchMore(true);
    return ret;
}

QStringList GoogleReaderClient::categoryIdList(const QVariantList &categoryList) const
{
    QStringList ret;
    foreach(const QVariant& category, categoryList)
    {
        QVariantMap categoryMap = category.toMap();
        QByteArray categoryId = categoryMap.value(GoogleReaderClient::s_idValue).toByteArray();
        if(!categoryId.contains(GoogleReaderClient::s_stateIdPattern))
            ret << generalItemId(categoryId);
    }
    return ret;
}

QHash<QString, int> GoogleReaderClient::createIdUnreadCounts(const QVariantList &unreadCountList) const
{
    QHash<QString, int> ret;
    foreach(const QVariant& unreadCount, unreadCountList)
    {
        QVariantMap unreadCountMap = unreadCount.toMap();
        ret.insert(generalItemId(unreadCountMap.value(GoogleReaderClient::s_idValue).toByteArray()),
                   unreadCountMap.value(GoogleReaderClient::s_countValue).toInt());
    }
    return ret;
}

QHash<QString, QStringList> GoogleReaderClient::createItemOrderingHash(const QVariantMap &preferenceMap)
{
    QHash<QString, QStringList> ret;
    foreach(const QString& itemId, preferenceMap.keys())
    {
        QVariantList preferenceList = preferenceMap.value(itemId).toList();
        foreach(const QVariant& preferenceListItem, preferenceList)
        {
            QVariantMap preferenceListItemMap = preferenceListItem.toMap();
            if(preferenceListItemMap.value(GoogleReaderClient::s_idValue).toString()
               == GoogleReaderClient::s_subscriptionOrderingValue)
            {
                QString ordering = preferenceListItemMap.value(GoogleReaderClient::s_valueValue).toString();
                int idCount = ordering.length() / GoogleReaderClient::s_itemIdLength;
                QStringList childIds;
                for(int i = 0; i < idCount; ++i)
                    childIds << ordering.mid(i * GoogleReaderClient::s_itemIdLength, GoogleReaderClient::s_itemIdLength);
                ret.insert(generalItemId(itemId.toAscii()), childIds);
            }
        }
    }
    return ret;
}

QString GoogleReaderClient::generalItemId(const QByteArray &itemId) const
{
    QString ret = QString::fromUtf8(itemId);
    ret.replace(QRegExp("/\\d{20}/"), "/-/");
    return ret;
}

QString GoogleReaderClient::toPlainText(const QString &richText) const
{
    return QTextDocumentFragment::fromHtml(richText).toPlainText();
}

QString GoogleReaderClient::sortTypeString(GoogleReaderEnum::ItemSortType sortType) const
{
    QString ret;
    if(sortType == GoogleReaderEnum::IstSortByNewest)
        ret = GoogleReaderClient::s_sortByNewestValue;
    else if(sortType == GoogleReaderEnum::IstSortByOldest)
        ret = GoogleReaderClient::s_sortByOldestValue;
    else if(sortType == GoogleReaderEnum::IstSortByMagic)
        ret = GoogleReaderClient::s_sortByMagicValue;
    return ret;
}

QString GoogleReaderClient::filterTypeString(GoogleReaderEnum::ItemFilterType filterType) const
{
    QString ret;
    if(filterType == GoogleReaderEnum::IftNewItems)
        ret = GoogleReaderClient::s_readStateId;
    return ret;
}

QString GoogleReaderClient::tagIdFromTitle(const QString &title) const
{
    return GoogleReaderClient::s_tagIdPrefix + title;
}

QString GoogleReaderClient::tagTitleFromId(const QString &id) const
{
    return id.section("/", -1);
}

QString GoogleReaderClient::feedIconUrl(const QString &feedUrl) const
{
    return QString("%1?domain=%2&alt=feed").arg(GoogleReaderClient::s_faviconUrl).arg(feedUrl);
}

bool GoogleReaderClient::itemSortLessThan(const GoogleReaderItemPtr &item1,
                                            const GoogleReaderItemPtr &item2)
{
    if(item1->itemType() != item2->itemType())
        return GoogleReaderClient::itemSortId(item1) < GoogleReaderClient::itemSortId(item2);
    else
        return item1->title().toLower() < item2->title().toLower();
}

int GoogleReaderClient::itemSortId(const GoogleReaderItemPtr &item)
{
    if(item->itemType() == GoogleReaderItem::ItFolderItem)
        return 1;
    else if(item->itemType() == GoogleReaderItem::ItFeedItem)
        return 2;
    else
        return 3;
}
} // namespace GoogleReader
