/*
  This file is part of the kcalcore library.

  Copyright (C) 2006-2007 Allen Winter <winter@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 "testmemorycalendar.h"
#include "filestorage.h"
#include "memorycalendar.h"
#include "utils.h"

#include <QDebug>

#include <QTest>
#include <QTimeZone>
QTEST_MAIN(MemoryCalendarTest)

using namespace KCalCore;

void MemoryCalendarTest::testValidity()
{
    MemoryCalendar::Ptr cal(new MemoryCalendar(QTimeZone::utc()));
    cal->setProductId(QStringLiteral("fredware calendar"));
    QVERIFY(cal->productId() == QLatin1String("fredware calendar"));
    QVERIFY(cal->timeZoneId() == QByteArrayLiteral("UTC"));
    QVERIFY(cal->timeZone() == QTimeZone::utc());
    cal->close();
}

void MemoryCalendarTest::testEvents()
{
    MemoryCalendar::Ptr cal(new MemoryCalendar(QTimeZone::utc()));
    cal->setProductId(QStringLiteral("fredware calendar"));
    QDate dt = QDate::currentDate();

    Event::Ptr event1 = Event::Ptr(new Event());
    event1->setUid(QStringLiteral("1"));
    event1->setDtStart(QDateTime(dt, {}));
    event1->setDtEnd(QDateTime(dt, {}).addDays(1));
    event1->setAllDay(true);
    event1->setSummary(QStringLiteral("Event1 Summary"));
    event1->setDescription(QStringLiteral("This is a description of the first event"));
    event1->setLocation(QStringLiteral("the place"));

    Event::Ptr event2 = Event::Ptr(new Event());
    event2->setUid(QStringLiteral("2"));
    event2->setDtStart(QDateTime(dt, {}).addDays(1));
    event2->setDtEnd(QDateTime(dt, {}).addDays(2));
    event2->setAllDay(true);
    event2->setSummary(QStringLiteral("Event2 Summary"));
    event2->setDescription(QStringLiteral("This is a description of the second event"));
    event2->setLocation(QStringLiteral("the other place"));

    QVERIFY(cal->addEvent(event1));
    QVERIFY(cal->addEvent(event2));

    FileStorage store(cal, QStringLiteral("foo.ics"));
    QVERIFY(store.save());
    cal->close();
    QFile::remove(QStringLiteral("foo.ics"));
}

void MemoryCalendarTest::testIncidences()
{
    MemoryCalendar::Ptr cal(new MemoryCalendar(QTimeZone::utc()));
    cal->setProductId(QStringLiteral("fredware calendar"));
    QDate dt = QDate::currentDate();

    Event::Ptr event1 = Event::Ptr(new Event());
    event1->setUid(QStringLiteral("1"));
    event1->setDtStart(QDateTime(dt, {}));
    event1->setDtEnd(QDateTime(dt, {}).addDays(1));
    event1->setAllDay(true);
    event1->setSummary(QStringLiteral("Event1 Summary"));
    event1->setDescription(QStringLiteral("This is a description of the first event"));
    event1->setLocation(QStringLiteral("the place"));

    Event::Ptr event2 = Event::Ptr(new Event());
    event2->setUid(QStringLiteral("2"));
    event2->setDtStart(QDateTime(dt, {}).addDays(1));
    event2->setDtEnd(QDateTime(dt, {}).addDays(2));
    event2->setAllDay(true);
    event2->setSummary(QStringLiteral("Event2 Summary"));
    event2->setDescription(QStringLiteral("This is a description of the second event"));
    event2->setLocation(QStringLiteral("the other place"));

    QVERIFY(cal->addEvent(event1));
    QVERIFY(cal->addEvent(event2));

    Todo::Ptr todo1 = Todo::Ptr(new Todo());
    todo1->setUid(QStringLiteral("3"));
    todo1->setDtStart(QDateTime(dt, {}).addDays(1));
    todo1->setDtDue(QDateTime(dt, {}).addDays(2));
    todo1->setAllDay(true);
    todo1->setSummary(QStringLiteral("Todo1 Summary"));
    todo1->setDescription(QStringLiteral("This is a description of a todo"));
    todo1->setLocation(QStringLiteral("this place"));

    Todo::Ptr todo2 = Todo::Ptr(new Todo());
    todo2->setUid(QStringLiteral("4"));
    todo2->setDtStart(QDateTime(dt, {}).addDays(1));
    todo2->setAllDay(true);
    todo2->setSummary(QStringLiteral("<qt><h1>Todo2 Summary</h1></qt>"), true);
    todo2->setDescription(QStringLiteral("This is a description of a todo"));
    todo2->setLocation(QStringLiteral("<html><a href=\"http://www.fred.com\">this place</a></html>"), true);

    QVERIFY(cal->addTodo(todo1));
    QVERIFY(cal->addTodo(todo2));

    FileStorage store(cal, QStringLiteral("foo.ics"));
    QVERIFY(store.save());
    cal->close();

    QVERIFY(store.load());
    Todo::Ptr todo = cal->incidence(QStringLiteral("4")).staticCast<Todo>();
    QVERIFY(todo->uid() == QLatin1String("4"));
    QVERIFY(todo->summaryIsRich());
    QVERIFY(todo->locationIsRich());
    cal->close();
    QFile::remove(QStringLiteral("foo.ics"));
}

void MemoryCalendarTest::testRelationsCrash()
{
    // Before, there was a crash that occurred only when reloading a calendar in which
    // the incidences had special relations.
    // This test tests that scenario, and will crash if it fails.
    MemoryCalendar::Ptr cal(new MemoryCalendar(QTimeZone::utc()));
    FileStorage store1(cal, QLatin1Literal(ICALTESTDATADIR) + QLatin1String("test_relations.ics"));
    QVERIFY(store1.load());
    const Todo::List oldTodos = cal->todos();
    qDebug() << "Loaded " << oldTodos.count() << " todos into oldTodos.";

    FileStorage store2(cal, QLatin1String(ICALTESTDATADIR) + QLatin1String("test_relations.ics"));
    QVERIFY(store2.load());
    const Todo::List newTodos = cal->todos();
    qDebug() << "Loaded " << newTodos.count() << " into newTodos.";

    // We can saftely access the old deleted todos here, since they are not really deleted
    // and are still kept in a map of deleted items somewhere.
    //
    // Here we make sure that non of the old items have connections to the new items, and
    // the other way around.

    // This doesn't makes sense so i commented it. when you load a calendar the second time
    // it reuses what it can, so oldTodo == newTodo

    /*  foreach (const Todo::Ptr &oldTodo, oldTodos ) {
        foreach (const Todo::Ptr &newTodo, newTodos ) {
          QVERIFY( oldTodo != newTodo );

          // Make sure that none of the new todos point to an old, deleted todo
          QVERIFY( newTodo->relatedTo() != oldTodo );
          QVERIFY( !newTodo->relations().contains( oldTodo ) );

          // Make sure that none of the old todos point to a new todo
          QVERIFY( oldTodo->relatedTo() != newTodo );
          QVERIFY( !oldTodo->relations().contains( newTodo ) );
        }
      }
    */
    cal->close();
}

void MemoryCalendarTest::testRecurrenceExceptions()
{
    MemoryCalendar::Ptr cal(new MemoryCalendar(QTimeZone::utc()));
    cal->setProductId(QStringLiteral("fredware calendar"));
    QDate dt = QDate::currentDate();
    QDateTime start(dt, {});

    Event::Ptr event1 = Event::Ptr(new Event());
    event1->setUid(QStringLiteral("1"));
    event1->setDtStart(start);
    event1->setDtEnd(start.addDays(1));
    event1->setSummary(QStringLiteral("Event1 Summary"));
    event1->recurrence()->setDaily(1);
    event1->recurrence()->setDuration(3);
    QVERIFY(cal->addEvent(event1));

    const QDateTime recurrenceId = event1->dtStart().addDays(1);
    Event::Ptr exception1 = cal->createException(event1, recurrenceId).staticCast<Event>();
    QCOMPARE(exception1->recurrenceId(), recurrenceId);
    QCOMPARE(exception1->uid(), event1->uid());
    exception1->setSummary(QStringLiteral("exception"));

    QVERIFY(exception1);
    QVERIFY(cal->addEvent(exception1));

    QCOMPARE(cal->event(event1->uid()), event1);
    QCOMPARE(cal->event(event1->uid(), recurrenceId), exception1);

    const Event::List incidences = cal->rawEvents(start.date(), start.addDays(3).date(), start.timeZone());
    //Contains incidence and exception
    QCOMPARE(incidences.size(), 2);

    //Returns only exceptions for an event
    const Event::List exceptions = cal->eventInstances(event1);
    QCOMPARE(exceptions.size(), 1);
    QCOMPARE(exceptions.first()->uid(), event1->uid());
    QCOMPARE(exceptions.first()->summary(), exception1->summary());
}

void MemoryCalendarTest::testChangeRecurId()
{
    // When we change the recurring id, internal hashtables should be updated.

    MemoryCalendar::Ptr cal(new MemoryCalendar(QTimeZone::utc()));
    QDateTime start(QDate::currentDate(), {});

    // Add main event
    Event::Ptr event1 = Event::Ptr(new Event());
    const QString uid = QStringLiteral("1");
    event1->setUid(uid);
    event1->setDtStart(start);
    event1->setDtEnd(start.addDays(1));
    event1->setAllDay(true);
    event1->setSummary(QStringLiteral("Event1 Summary"));
    event1->recurrence()->setDaily(1);
    event1->recurrence()->setDuration(3);
    QVERIFY(cal->addEvent(event1));

    // Add exception event:
    const QDateTime recurrenceId = event1->dtStart().addDays(1);
    Event::Ptr exception1 = cal->createException(event1, recurrenceId).staticCast<Event>();
    QCOMPARE(exception1->recurrenceId(), recurrenceId);
    QCOMPARE(exception1->uid(), event1->uid());
    exception1->setSummary(QStringLiteral("exception"));
    QVERIFY(exception1);
    QVERIFY(cal->addEvent(exception1));

    const QString oldIdentifier = exception1->instanceIdentifier();
    Incidence::Ptr foo = cal->instance(oldIdentifier);
    QVERIFY(foo && foo->hasRecurrenceId());
    // Now change the recurring id!
    exception1->setRecurrenceId(start.addDays(2));
    const QString newIdentifier = exception1->instanceIdentifier();
    QVERIFY(oldIdentifier != newIdentifier);

    foo = cal->instance(oldIdentifier);
    QVERIFY(!foo);

    foo = cal->instance(newIdentifier);
    QVERIFY(foo);

    // Test hashing
    Incidence::List incidences = cal->incidences();
    QVERIFY(incidences.count() == 2);

    QDateTime newRecId = start.addDays(2);
    Incidence::Ptr main      = cal->incidence(uid);
    Incidence::Ptr exception = cal->incidence(uid, newRecId);
    Incidence::Ptr noException = cal->incidence(uid, recurrenceId);
    QVERIFY(!noException);
    QVERIFY(main);
    QVERIFY(exception);
    QVERIFY(exception->recurrenceId() == newRecId);
    QVERIFY(exception->summary() == QLatin1String("exception"));
    QVERIFY(main->summary() == event1->summary());
}
