/*
 * Copyright (C) 2012  Christian Mollekopf <mollekopf@kolabsys.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "testoccurrenceiterator.h"
#include "occurrenceiterator.h"
#include "memorycalendar.h"
#include "calfilter.h"

#include <QDebug>
#include <QTest>
#include <QTimeZone>

QTEST_MAIN(TestOccurrenceIterator)

void TestOccurrenceIterator::testIterationWithExceptions()
{
    KCalCore::MemoryCalendar calendar(QTimeZone::utc());

    QDateTime start(QDate(2013, 03, 10), QTime(10, 0, 0), Qt::UTC);
    QDateTime end(QDate(2013, 03, 10), QTime(11, 0, 0), Qt::UTC);

    QDateTime recurrenceId(QDate(2013, 03, 11), QTime(10, 0, 0), Qt::UTC);
    QDateTime exceptionStart(QDate(2013, 03, 11), QTime(12, 0, 0), Qt::UTC);
    QDateTime exceptionEnd(QDate(2013, 03, 11), QTime(13, 0, 0), Qt::UTC);

    QDateTime actualEnd(QDate(2013, 03, 12), QTime(11, 0, 0), Qt::UTC);

    KCalCore::Event::Ptr event1(new KCalCore::Event());
    event1->setUid(QStringLiteral("event1"));
    event1->setSummary(QStringLiteral("event1"));
    event1->setDtStart(start);
    event1->setDtEnd(end);
    event1->recurrence()->setDaily(1);
    calendar.addEvent(event1);

    KCalCore::Event::Ptr exception(new KCalCore::Event());
    exception->setUid(event1->uid());
    exception->setSummary(QStringLiteral("exception"));
    exception->setRecurrenceId(recurrenceId);
    exception->setDtStart(exceptionStart);
    exception->setDtEnd(exceptionEnd);
    calendar.addEvent(exception);

    int occurrence = 0;
    KCalCore::OccurrenceIterator rIt(calendar, start, actualEnd);
    while (rIt.hasNext()) {
        rIt.next();
        occurrence++;
        if (occurrence == 1) {
            QCOMPARE(rIt.occurrenceStartDate(), start);
            QCOMPARE(rIt.incidence()->summary(), event1->summary());
        }
        if (occurrence == 2) {
            QCOMPARE(rIt.occurrenceStartDate(), exceptionStart);
            QCOMPARE(rIt.incidence()->summary(), exception->summary());
        }
        if (occurrence == 3) {
            QCOMPARE(rIt.occurrenceStartDate(), start.addDays(2));
            QCOMPARE(rIt.incidence()->summary(), event1->summary());
        }
//     qDebug() << occurrence;
//     qDebug() << "occurrence: " << rIt.occurrenceStartDate().toString();
//     qDebug() << "uid: " << rIt.incidence()->uid();
//     qDebug() << "summary: " << rIt.incidence()->summary();
//     qDebug() << "start: " << rIt.incidence()->dtStart().toString();
//     qDebug();
    }
    QCOMPARE(occurrence, 3);
}

void TestOccurrenceIterator::testEventsAndTodos()
{
    KCalCore::MemoryCalendar calendar(QTimeZone::utc());

    QDateTime start(QDate(2013, 03, 10), QTime(10, 0, 0), Qt::UTC);
    QDateTime end(QDate(2013, 03, 10), QTime(11, 0, 0), Qt::UTC);

    QDateTime actualEnd(QDate(2013, 03, 13), QTime(11, 0, 0), Qt::UTC);

    KCalCore::Event::Ptr event(new KCalCore::Event());
    event->setUid(QStringLiteral("event"));
    event->setDtStart(start);
    event->recurrence()->setDaily(1);
    event->recurrence()->setDuration(2);
    calendar.addEvent(event);

    KCalCore::Todo::Ptr todo(new KCalCore::Todo());
    todo->setUid(QStringLiteral("todo"));
    todo->setDtStart(start);
    todo->recurrence()->setDaily(1);
    todo->recurrence()->setDuration(2);
    calendar.addTodo(todo);

    KCalCore::OccurrenceIterator rIt(calendar, start, actualEnd);
    QList<QDateTime> expectedTodoOccurrences;
    expectedTodoOccurrences << start << start.addDays(1);
    QList<QDateTime> expectedEventOccurrences;
    expectedEventOccurrences << start << start.addDays(1);
    while (rIt.hasNext()) {
        rIt.next();
        qDebug() << rIt.occurrenceStartDate().toString();
        if (rIt.incidence()->type() == KCalCore::Incidence::TypeTodo) {
            QCOMPARE(expectedTodoOccurrences.removeAll(rIt.occurrenceStartDate()), 1);
        } else {
            QCOMPARE(expectedEventOccurrences.removeAll(rIt.occurrenceStartDate()), 1);
        }
    }
    QCOMPARE(expectedTodoOccurrences.size(), 0);
    QCOMPARE(expectedEventOccurrences.size(), 0);
}

void TestOccurrenceIterator::testFilterCompletedTodos()
{
    KCalCore::MemoryCalendar calendar(QTimeZone::utc());
    calendar.filter()->setCriteria(KCalCore::CalFilter::HideCompletedTodos);

    QDateTime start(QDate(2013, 03, 10), QTime(10, 0, 0), Qt::UTC);
    QDateTime end(QDate(2013, 03, 10), QTime(11, 0, 0), Qt::UTC);

    QDateTime actualEnd(QDate(2013, 03, 13), QTime(11, 0, 0), Qt::UTC);

    KCalCore::Todo::Ptr todo(new KCalCore::Todo());
    todo->setUid(QStringLiteral("todo"));
    todo->setDtDue(start);
    todo->setDtStart(start);
    todo->recurrence()->setDaily(1);
    todo->recurrence()->setDuration(2);
    // Yes, recurring todos are weird... setting this says that all occurrences
    // until this one have been completed, and thus should be skipped.
    // that's what kontact did, so it's what we test now.
    todo->setDtRecurrence(start.addDays(2));
    calendar.addTodo(todo);

    KCalCore::OccurrenceIterator rIt(calendar, start, actualEnd);
    QVERIFY(!rIt.hasNext());
}

void TestOccurrenceIterator::testAllDayEvents()
{
    KCalCore::MemoryCalendar calendar(QTimeZone::utc());

    QDateTime start(QDate(2013, 03, 10), QTime(), Qt::UTC);
    QDateTime actualEnd(QDate(2013, 03, 13), QTime(11, 0, 0), Qt::UTC);

    KCalCore::Event::Ptr event(new KCalCore::Event());
    event->setUid(QStringLiteral("event"));
    event->setDtStart(start);
    event->setAllDay(true);
    event->recurrence()->setDaily(1);
    event->recurrence()->setDuration(2);
    calendar.addEvent(event);

    KCalCore::OccurrenceIterator rIt(calendar, start, actualEnd);
    QList<QDateTime> expectedEventOccurrences;
    expectedEventOccurrences << start << start.addDays(1);
    while (rIt.hasNext()) {
        rIt.next();
        qDebug() << rIt.occurrenceStartDate().toString();
        QCOMPARE(expectedEventOccurrences.removeAll(rIt.occurrenceStartDate()), 1);
    }
    QCOMPARE(expectedEventOccurrences.size(), 0);
}

void TestOccurrenceIterator::testWithExceptionThisAndFuture()
{
    KCalCore::MemoryCalendar calendar(QTimeZone::utc());

    QDateTime start(QDate(2013, 03, 10), QTime(10, 0, 0), Qt::UTC);
    QDateTime end(QDate(2013, 03, 10), QTime(11, 0, 0), Qt::UTC);

    QDateTime recurrenceId1(QDate(2013, 03, 11), QTime(10, 0, 0), Qt::UTC);
    QDateTime exceptionStart1(QDate(2013, 03, 11), QTime(12, 0, 0), Qt::UTC);
    QDateTime exceptionEnd1(QDate(2013, 03, 11), QTime(13, 0, 0), Qt::UTC);

    QDateTime recurrenceId2(QDate(2013, 03, 13), QTime(10, 0, 0), Qt::UTC);
    QDateTime exceptionStart2(QDate(2013, 03, 13), QTime(14, 0, 0), Qt::UTC);
    QDateTime exceptionEnd2(QDate(2013, 03, 13), QTime(15, 0, 0), Qt::UTC);

    QDateTime actualEnd(QDate(2013, 03, 14), QTime(11, 0, 0), Qt::UTC);

    KCalCore::Event::Ptr event1(new KCalCore::Event());
    event1->setUid(QStringLiteral("event1"));
    event1->setSummary(QStringLiteral("event1"));
    event1->setDtStart(start);
    event1->setDtEnd(end);
    event1->recurrence()->setDaily(1);
    calendar.addEvent(event1);

    KCalCore::Event::Ptr exception1(new KCalCore::Event());
    exception1->setUid(event1->uid());
    exception1->setSummary(QStringLiteral("exception1"));
    exception1->setRecurrenceId(recurrenceId1);
    exception1->setThisAndFuture(true);
    exception1->setDtStart(exceptionStart1);
    exception1->setDtEnd(exceptionEnd1);
    calendar.addEvent(exception1);

    KCalCore::Event::Ptr exception2(new KCalCore::Event());
    exception2->setUid(event1->uid());
    exception2->setSummary(QStringLiteral("exception2"));
    exception2->setRecurrenceId(recurrenceId2);
    exception2->setDtStart(exceptionStart2);
    exception2->setDtEnd(exceptionEnd2);
    calendar.addEvent(exception2);

    int occurrence = 0;
    KCalCore::OccurrenceIterator rIt(calendar, start, actualEnd);
    while (rIt.hasNext()) {
        rIt.next();
        occurrence++;
//     qDebug() << occurrence;
//     qDebug() << "occurrence: " << rIt.occurrenceStartDate().toString();
//     qDebug() << "uid: " << rIt.incidence()->uid();
//     qDebug() << "summary: " << rIt.incidence()->summary();
//     qDebug() << "start: " << rIt.incidence()->dtStart().toString();
//     qDebug();
        QCOMPARE(rIt.recurrenceId(), start.addDays(occurrence - 1));
        if (occurrence == 1) {
            QCOMPARE(rIt.occurrenceStartDate(), start);
            QCOMPARE(rIt.incidence()->summary(), event1->summary());
        }
        if (occurrence == 2) {
            QCOMPARE(rIt.occurrenceStartDate(), exceptionStart1);
            QCOMPARE(rIt.incidence()->summary(), exception1->summary());
        }
        if (occurrence == 3) {
            QCOMPARE(rIt.occurrenceStartDate(), exceptionStart1.addDays(1));
            QCOMPARE(rIt.incidence()->summary(), exception1->summary());
        }
        if (occurrence == 4) {
            QCOMPARE(rIt.occurrenceStartDate(), exceptionStart2);
            QCOMPARE(rIt.incidence()->summary(), exception2->summary());
        }
        if (occurrence == 5) {
            QCOMPARE(rIt.occurrenceStartDate(), exceptionStart1.addDays(3));
            QCOMPARE(rIt.incidence()->summary(), exception1->summary());
        }
    }
    QCOMPARE(occurrence, 5);
}

void TestOccurrenceIterator::testSubDailyRecurrences()
{
    KCalCore::MemoryCalendar calendar(QTimeZone::utc());

    QDateTime start(QDate(2013, 03, 10), QTime(10, 0, 0), Qt::UTC);
    QDateTime actualEnd(QDate(2013, 03, 10), QTime(13, 0, 0), Qt::UTC);

    KCalCore::Event::Ptr event(new KCalCore::Event());
    event->setUid(QStringLiteral("event"));
    event->setDtStart(start);
    event->recurrence()->setHourly(1);
    event->recurrence()->setDuration(2);
    calendar.addEvent(event);

    KCalCore::OccurrenceIterator rIt(calendar, start, actualEnd);
    QList<QDateTime> expectedEventOccurrences;
    expectedEventOccurrences << start << start.addSecs(60 * 60);
    while (rIt.hasNext()) {
        rIt.next();
        qDebug() << rIt.occurrenceStartDate().toString();
        QCOMPARE(expectedEventOccurrences.removeAll(rIt.occurrenceStartDate()), 1);
    }
    QCOMPARE(expectedEventOccurrences.size(), 0);
}

void TestOccurrenceIterator::testJournals()
{
    KCalCore::MemoryCalendar calendar(QTimeZone::utc());

    const QDateTime today = QDateTime::currentDateTimeUtc();
    const QDateTime yesterday = today.addDays(-1);
    const QDateTime tomorrow = today.addDays(1);

    KCalCore::Journal::Ptr journal(new KCalCore::Journal());
    journal->setUid(QStringLiteral("journal"));
    journal->setDtStart(today);
    calendar.addJournal(journal);

    KCalCore::OccurrenceIterator rIt(calendar, yesterday, tomorrow);
    QVERIFY(rIt.hasNext());
    rIt.next();
    QCOMPARE(rIt.occurrenceStartDate(), today);
    QVERIFY(!rIt.hasNext());

    KCalCore::OccurrenceIterator rIt2(calendar, tomorrow, tomorrow.addDays(1));
    QVERIFY(!rIt2.hasNext());
}
