/*
  This file is part of the kblog library.

  Copyright (c) 2006-2007 Christian Weilbach <christian_weilbach@web.de>

  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 "data.h"

#include "kblog/gdata.h"
#include "kblog/blogpost.h"
#include "kblog/blogcomment.h"

#include <QTest>
#include <QTimer>
#include <QDateTime>
#include <QTimeZone>
#include <QDebug>

#include <unistd.h>

#define TIMEOUT 10000
#define GLOBALTIMEOUT 70000
#define DOWNLOADCOUNT 5

using namespace KBlog;

class TestGData : public QObject
{
    Q_OBJECT

public Q_SLOTS:
    // use this functions as a chain to go through network traffic.
    void fetchProfileId(const QString &);
    void listBlogs(const QList<QMap<QString, QString> > &);
    void listRecentPosts(const QList<KBlog::BlogPost> &posts);
    void createPost(KBlog::BlogPost *post);
    void createComment(const KBlog::BlogPost *post, const KBlog::BlogComment *comment);
    void removeComment(const KBlog::BlogPost *post, const KBlog::BlogComment *comment);
    void modifyPost(KBlog::BlogPost *post);
    void fetchPost(KBlog::BlogPost *post);
    void removePost(KBlog::BlogPost *post);
    // end chain
    void error(KBlog::Blog::ErrorType type, const QString &errStr, KBlog::BlogPost *);
private Q_SLOTS:
    void testValidity();
    void testNetwork();
private:
    void dumpPost(const KBlog::BlogPost *);
    void dumpComment(const KBlog::BlogComment *);
    KBlog::GData *b;
    KBlog::BlogPost *p;
    KBlog::BlogComment *c;
    QEventLoop *eventLoop;
    QTimer *fetchProfileIdTimer;
    QTimer *listBlogsTimer;
    QTimer *listRecentPostsTimer;
    QTimer *fetchPostTimer;
    QTimer *modifyPostTimer;
    QTimer *createPostTimer;
    QTimer *createCommentTimer;
    QTimer *removeCommentTimer;
    QTimer *removePostTimer;
};

class TestGDataWarnings : public QObject
{
    Q_OBJECT
private Q_SLOTS:
    void fetchProfileIdTimeoutWarning();
    void listBlogsTimeoutWarning();
    void listRecentPostsTimeoutWarning();
    void fetchPostTimeoutWarning();
    void modifyPostTimeoutWarning();
    void createPostTimeoutWarning();
    void removePostTimeoutWarning();
    void createCommentTimeoutWarning();
    void removeCommentTimeoutWarning();
};

#include "testgdata.moc"

void TestGData::dumpPost(const BlogPost *post)
{
    qDebug() << "########### post ############";
    qDebug() << "# postId: " << post->postId();
    qDebug() << "# title: " << post->title();
    qDebug() << "# content: " << post->content();
    qDebug() << "# private: " << post->isPrivate();
    qDebug() << "# categories: " << post->categories().join(QLatin1Char(' '));
    qDebug() << "# error: " << post->error();
    qDebug() << "# journalId: " << post->journalId();
    switch (post->status()) {
    case BlogPost::New:
        qDebug() << "# status: New";
        break;
    case BlogPost::Fetched:
        qDebug() << "# status: Fetched";
        break;
    case BlogPost::Created:
        qDebug() << "# status: Created";
        break;
    case BlogPost::Modified:
        qDebug() << "# status: Modified";
        break;
    case BlogPost::Removed:
        qDebug() << "# status: Removed";
        break;
    case BlogPost::Error:
        qDebug() << "# status: Error";
        break;
    };
    qDebug() << "# creationDateTime(UTC): "
             << post->creationDateTime().toUTC().toString();
    qDebug() << "# modificationDateTime(UTC): "
             << post->modificationDateTime().toUTC().toString();
    qDebug() << "###########################";
}

void TestGData::dumpComment(const BlogComment *comment)
{
    qDebug() << "########### comment ############";
    qDebug() << "# commentId: " << comment->commentId();
    qDebug() << "# title: " << comment->title();
    qDebug() << "# content: " << comment->content();
    qDebug() << "# name: " << comment->name();
    qDebug() << "# email: " << comment->email();
    qDebug() << "# url: " << comment->url().url();
    qDebug() << "# error: " << comment->error();
    switch (comment->status()) {
    case BlogComment::New:
        qDebug() << "# status: New";
        break;
    case BlogComment::Fetched:
        qDebug() << "# status: Fetched";
        break;
    case BlogComment::Created:
        qDebug() << "# status: Created";
        break;
    case BlogComment::Removed:
        qDebug() << "# status: Removed";
        break;
    case BlogComment::Error:
        qDebug() << "# status: Error";
        break;
    };
    qDebug() << "# creationDateTime(UTC): "
             << comment->creationDateTime().toUTC().toString();
    qDebug() << "# modificationDateTime(UTC): "
             << comment->modificationDateTime().toUTC().toString();
    qDebug() << "###########################";
}

// the chain starts here

void TestGData::fetchProfileId(const QString &pid)
{
    fetchProfileIdTimer->stop();
    qDebug() << "########### fetchProfileId ###########";
    qDebug() << "# profileId: " << pid;
    qDebug() << "##############################\n";

    connect(b, SIGNAL(listedBlogs(QList<QMap<QString,QString> >)),
            this, SLOT(listBlogs(QList<QMap<QString,QString> >)));
    b->listBlogs();
    listBlogsTimer->start(TIMEOUT);
}

void TestGData::listBlogs(const QList<QMap<QString, QString> > &listedBlogs)
{
    listBlogsTimer->stop();
    qDebug() << "########### listBlogs ###########";
    QList<QMap<QString, QString> >::ConstIterator it = listedBlogs.begin();
    QList<QMap<QString, QString> >::ConstIterator end = listedBlogs.end();
    for (; it != end; ++it) {
        qDebug() << "# " << (*it).cbegin().value();
    }
    qDebug() << "###########################\n";

    connect(b, SIGNAL(listedRecentPosts(QList<KBlog::BlogPost>)),
            this, SLOT(listRecentPosts(QList<KBlog::BlogPost>)));
    b->listRecentPosts(DOWNLOADCOUNT);
    listRecentPostsTimer->start(TIMEOUT);
}

void TestGData::listRecentPosts(
    const QList<KBlog::BlogPost> &posts)
{
    listRecentPostsTimer->stop();
    qDebug() << "########### listRecentPosts ###########";
    QList<KBlog::BlogPost>::ConstIterator it = posts.begin();
    QList<KBlog::BlogPost>::ConstIterator end = posts.end();
    for (; it != end; ++it) {
        dumpPost(&(*it));
    }
    qDebug() << "#################################\n";

    connect(b, SIGNAL(createdPost(KBlog::BlogPost*)),
            this, SLOT(createPost(KBlog::BlogPost*)));
    b->createPost(p);   // start chain
    createPostTimer->start(TIMEOUT);
}

void TestGData::createPost(KBlog::BlogPost *post)
{
    createPostTimer->stop();
    qDebug() << "########### createPost ############";
    dumpPost(post);
    qDebug() << "################################\n";
    QVERIFY(post->status() == BlogPost::Created);

    connect(b, SIGNAL(createdComment(const KBlog::BlogPost*,const KBlog::BlogComment*)),
            this, SLOT(createComment(const KBlog::BlogPost*,const KBlog::BlogComment*)));
    b->createComment(p, c);
    createCommentTimer->start(TIMEOUT);
}

void TestGData::createComment(const KBlog::BlogPost *post, const KBlog::BlogComment *comment)
{
    createPostTimer->stop();
    qDebug() << "########### createComment ############";
    dumpPost(post);
    dumpComment(comment);
    qDebug() << "################################\n";
    QVERIFY(comment->status() == BlogComment::Created);

    connect(b, SIGNAL(removedComment(const KBlog::BlogPost*,const KBlog::BlogComment*)),
            this, SLOT(removeComment(const KBlog::BlogPost*,const KBlog::BlogComment*)));
    b->removeComment(p, c);
    removeCommentTimer->start(TIMEOUT);
}

void TestGData::removeComment(const KBlog::BlogPost *post, const KBlog::BlogComment *comment)
{
    createPostTimer->stop();
    qDebug() << "########### removeComment ############";
    dumpPost(post);
    dumpComment(comment);
    qDebug() << "################################\n";
    QVERIFY(comment->status() == BlogComment::Created);

    connect(b, SIGNAL(modifiedPost(KBlog::BlogPost*)),
            this, SLOT(modifyPost(KBlog::BlogPost*)));
    p->setContent(mModifiedContent);
    b->modifyPost(p);
    modifyPostTimer->start(TIMEOUT);
}

void TestGData::modifyPost(KBlog::BlogPost *post)
{
    modifyPostTimer->stop();
    qDebug() << "########### modifyPost ############";
    dumpPost(post);
    qDebug() << "################################\n";
    QVERIFY(post->status() == BlogPost::Modified);

    connect(b, SIGNAL(fetchedPost(KBlog::BlogPost*)),
            this, SLOT(fetchPost(KBlog::BlogPost*)));
    p->setContent(QLatin1String("TestGData: created content."));
    b->fetchPost(p);
    fetchPostTimer->start(TIMEOUT);
}

void TestGData::fetchPost(KBlog::BlogPost *post)
{
    fetchPostTimer->stop();
    qDebug() << "########### fetchPost ############";
    dumpPost(post);
    qDebug() << "###############################\n";
    QVERIFY(post->status() == BlogPost::Fetched);
//   QVERIFY( post->content() == mModifiedContent ); // changed by GData

    connect(b, SIGNAL(removedPost(KBlog::BlogPost*)),
            this, SLOT(removePost(KBlog::BlogPost*)));
    b->removePost(p);
    removePostTimer->start(TIMEOUT);
}

void TestGData::removePost(KBlog::BlogPost *post)
{
    removePostTimer->stop();
    qDebug() << "########### removePost ###########";
    dumpPost(post);
    qDebug() << "################################\n";
    QVERIFY(post->status() == BlogPost::Removed);
    eventLoop->quit();
}

void TestGData::error(KBlog::Blog::ErrorType type, const QString &errStr,
                      KBlog::BlogPost *post)
{
    qDebug() << "############ error #############";
    switch (type) {
    case Blog::Atom:
        qDebug() << "type: Atom";
        break;
    case Blog::XmlRpc:
        qDebug() << "type: xmlRpc";
        break;
    case Blog::ParsingError:
        qDebug() << "type: ParsingError";
        break;
    case Blog::AuthenticationError:
        qDebug() << "type: AuthenticationError";
        break;
    case Blog::NotSupported:
        qDebug() << "type: NotSupported";
        break;
    case Blog::Other:
        qDebug() << "type: Other";
        break;
    };
    qDebug() << "error: " << errStr;
    if (post != nullptr) {
        dumpPost(post);
    }
    qDebug() << "#############################\n";
}

// Warnings for Timouts:

void TestGDataWarnings::fetchProfileIdTimeoutWarning()
{
    QWARN("fetchProfileId() timeout. This can be caused by an error, too. Any following calls will fail.");
}

void TestGDataWarnings::listBlogsTimeoutWarning()
{
    QWARN("listBlogs()  timeout. This can be caused by an error, too. Any following calls will fail.");
}

void TestGDataWarnings::listRecentPostsTimeoutWarning()
{
    QWARN("listRecentPosts() timeout. This can be caused by an error, too. Any following calls will fail.");
}

void TestGDataWarnings::fetchPostTimeoutWarning()
{
    QWARN("fetchPost() timeout. This can be caused by an error, too. Any following calls will fail.");
}

void TestGDataWarnings::modifyPostTimeoutWarning()
{
    QWARN("modifyPost() timeout. This can be caused by an error, too. Any following calls will fail.");
}

void TestGDataWarnings::createPostTimeoutWarning()
{
    QWARN("createPost() timeout. This can be caused by an error, too. Any following calls will fail.");
}

void TestGDataWarnings::createCommentTimeoutWarning()
{
    QWARN("createComment() timeout. This can be caused by an error, too. Any following calls will fail.");
}

void TestGDataWarnings::removeCommentTimeoutWarning()
{
    QWARN("removeComment() timeout. This can be caused by an error, too. Any following calls will fail.");
}

void TestGDataWarnings::removePostTimeoutWarning()
{
    QWARN("removePost() timeout. This can be caused by an error, too. Any following calls will fail.");
}

void TestGData::testValidity()
{
    eventLoop = new QEventLoop(this);

    // we do not test the setUrl() function additionally here
    b = new GData(QUrl(QLatin1String("http://blogger2test.blogspot.com")));
    b->setUsername(QLatin1String("christian_weilbach@web.de"));
    b->setFullName(QLatin1String("Santa Claus"));
    b->setProfileId(QLatin1String("11235141638164909615"));
    b->setPassword(QLatin1String("Wo ist Hans?"));
    b->setBlogId(QLatin1String("4662848212819772532"));
    b->setTimeZone(QTimeZone("UTC"));
    QVERIFY(b->url() == QUrl(QLatin1String("http://blogger2test.blogspot.com")));
    QVERIFY(b->blogId() == QLatin1String("4662848212819772532"));
    QVERIFY(b->fullName() == QLatin1String("Santa Claus"));
    QVERIFY(b->username() == QLatin1String("christian_weilbach@web.de"));
    QVERIFY(b->profileId() == QLatin1String("11235141638164909615"));
    QVERIFY(b->password() == QLatin1String("Wo ist Hans?"));
    QVERIFY(b->interfaceName() == QLatin1String("Google Blogger Data"));
    QVERIFY(b->timeZone().id() == "UTC");
}

void TestGData::testNetwork()
{
    QDateTime mCDateTime(mCreationDateTime);
    QDateTime mMDateTime(mModificationDateTime);
    p = new BlogPost(); // no need to delete later ;-)
    p->setTitle(mTitle);
    p->setContent(mContent);
    p->setPrivate(mPrivate);
    p->setPostId(mPostId);
    p->setCreationDateTime(mCDateTime);
    p->setModificationDateTime(mMDateTime);

    c = new BlogComment(); // no need to delete later ;-)
    c->setTitle(mCommentTitle);
    c->setContent(mCommentContent);
    c->setName(mUsername);
    c->setEmail(mCommentEmail);
    c->setCreationDateTime(mCDateTime);
    c->setModificationDateTime(mMDateTime);

    connect(b, SIGNAL(errorPost(KBlog::Blog::ErrorType,QString,KBlog::BlogPost*)),
            this, SLOT(error(KBlog::Blog::ErrorType,QString,KBlog::BlogPost*)));

    TestGDataWarnings *warnings = new TestGDataWarnings();

    fetchProfileIdTimer = new QTimer(this);
    fetchProfileIdTimer->setSingleShot(true);
    connect(fetchProfileIdTimer, SIGNAL(timeout()),
            warnings, SLOT(fetchProfileIdTimeoutWarning()));

    listBlogsTimer = new QTimer(this);
    listBlogsTimer->setSingleShot(true);
    connect(listBlogsTimer, SIGNAL(timeout()),
            warnings, SLOT(listBlogsTimeoutWarning()));

    listRecentPostsTimer = new QTimer(this);
    listRecentPostsTimer->setSingleShot(true);
    connect(listRecentPostsTimer, SIGNAL(timeout()),
            warnings, SLOT(listRecentPostsTimeoutWarning()));

    fetchPostTimer = new QTimer(this);
    fetchPostTimer->setSingleShot(true);
    connect(fetchPostTimer, SIGNAL(timeout()),
            warnings, SLOT(fetchPostTimeoutWarning()));

    modifyPostTimer = new QTimer(this);
    modifyPostTimer->setSingleShot(true);
    connect(modifyPostTimer, SIGNAL(timeout()),
            warnings, SLOT(modifyPostTimeoutWarning()));

    createPostTimer = new QTimer(this);
    createPostTimer->setSingleShot(true);
    connect(createPostTimer, SIGNAL(timeout()),
            warnings, SLOT(createPostTimeoutWarning()));

    createCommentTimer = new QTimer(this);
    createCommentTimer->setSingleShot(true);
    connect(createCommentTimer, SIGNAL(timeout()),
            warnings, SLOT(createCommentTimeoutWarning()));

    removeCommentTimer = new QTimer(this);
    removeCommentTimer->setSingleShot(true);
    connect(removeCommentTimer, SIGNAL(timeout()),
            warnings, SLOT(removeCommentTimeoutWarning()));

    removePostTimer = new QTimer(this);
    removePostTimer->setSingleShot(true);
    connect(removePostTimer, SIGNAL(timeout()),
            warnings, SLOT(removePostTimeoutWarning()));

    // start the chain
    connect(b, SIGNAL(fetchedProfileId(QString)),
            this, SLOT(fetchProfileId(QString)));
    b->fetchProfileId();
    fetchProfileIdTimer->start(TIMEOUT);

// wait for all jobs to finish

    QTimer::singleShot(GLOBALTIMEOUT, eventLoop, SLOT(quit()));
    eventLoop->exec();
    delete b;
    delete p;
    delete c;
}

QTEST_GUILESS_MAIN(TestGData)
