// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

#include "calendardemo.h"

#include "monthpage.h"
#include "daypage.h"
#include "eventeditpage.h"
#include "todoeditpage.h"
#include "journaleditpage.h"
#include "eventoccurrenceeditpage.h"
#include "addcalendarpage.h"
#include "editcalendarspage.h"
#ifdef BUILD_VERSIT
#include "qversitreader.h"
#include "qversitwriter.h"
#include "qversitorganizerimporter.h"
#include "qversitorganizerexporter.h"
#endif
#include <QtWidgets>
#include <QDesktopServices>
#include <QtOrganizer/qorganizer.h>

QTORGANIZER_USE_NAMESPACE

CalendarDemo::CalendarDemo(QWidget *parent)
    :QMainWindow(parent),
    m_stackedWidget(0),
    m_monthPage(0),
    m_dayPage(0),
    m_eventEditPage(0),
    m_eventOccurrenceEditPage(0)
{
    m_stackedWidget = new QStackedWidget(this);

    m_dayPage = new DayPage(m_stackedWidget);
    m_monthPage = new MonthPage(m_stackedWidget);
    m_eventEditPage = new EventEditPage(m_stackedWidget);
    m_todoEditPage = new TodoEditPage(m_stackedWidget);
    m_journalEditPage = new JournalEditPage(m_stackedWidget);
    m_eventOccurrenceEditPage = new EventOccurrenceEditPage(m_stackedWidget);
    m_addCalendarPage = new AddCalendarPage(m_stackedWidget);
    m_editCalendarsPage = new EditCalendarsPage(m_stackedWidget);

    //qRegisterMetaType<QOrganizerManager>("QOrganizerManager");
    qRegisterMetaType<QOrganizerItem>("QOrganizerItem");
    qRegisterMetaType<QOrganizerItemId>("QOrganizerItemId");
    qRegisterMetaType<QOrganizerCollection>("QOrganizerCollection");
    qRegisterMetaType<QOrganizerAbstractRequest::State>("QOrganizerAbstractRequest::State");

    connect(m_monthPage, SIGNAL(showDayPage(QDate)), this, SLOT(activateDayPage()), Qt::QueuedConnection);
    connect(m_monthPage, SIGNAL(showEditPage(const QOrganizerItem &)), this, SLOT(activateEditPage(const QOrganizerItem &)), Qt::QueuedConnection);
    connect(m_monthPage, SIGNAL(addNewEvent()), this, SLOT(addNewEvent()), Qt::QueuedConnection);
    connect(m_monthPage, SIGNAL(addNewTodo()), this, SLOT(addNewTodo()), Qt::QueuedConnection);
    connect(m_monthPage, SIGNAL(managerChanged(QOrganizerManager*)), this, SLOT(changeManager(QOrganizerManager*)), Qt::QueuedConnection);
    connect(m_monthPage, SIGNAL(managerChanged(QOrganizerManager*)), m_dayPage, SLOT(changeManager(QOrganizerManager*)), Qt::QueuedConnection);
    connect(m_monthPage, SIGNAL(currentDayChanged(QDate)), this, SLOT(updateSelectedDay(QDate)));
    connect(m_dayPage, SIGNAL(showMonthPage()), this, SLOT(activateMonthPage()), Qt::QueuedConnection);
    connect(m_dayPage, SIGNAL(showEditPage(const QOrganizerItem &)), this, SLOT(activateEditPage(const QOrganizerItem &)), Qt::QueuedConnection);
    connect(m_dayPage, SIGNAL(addNewEvent()), this, SLOT(addNewEvent()), Qt::QueuedConnection);
    connect(m_dayPage, SIGNAL(addNewTodo()), this, SLOT(addNewTodo()), Qt::QueuedConnection);
    connect(m_eventEditPage, SIGNAL(showDayPage()), this, SLOT(activateDayPage()), Qt::QueuedConnection);
    connect(m_todoEditPage, SIGNAL(showDayPage()), this, SLOT(activateDayPage()), Qt::QueuedConnection);
    connect(m_journalEditPage, SIGNAL(showDayPage()), this, SLOT(activateDayPage()), Qt::QueuedConnection);
    connect(m_eventOccurrenceEditPage, SIGNAL(showDayPage()), this, SLOT(activateDayPage()), Qt::QueuedConnection);
    connect(m_addCalendarPage, SIGNAL(showPreviousPage()), this, SLOT(activatePreviousPage()), Qt::QueuedConnection);
    connect(m_editCalendarsPage, SIGNAL(showAddCalendarPage(QOrganizerManager*,QOrganizerCollection*)), this, SLOT(editExistingCalendar(QOrganizerManager*,QOrganizerCollection*)), Qt::QueuedConnection);
    connect(m_editCalendarsPage, SIGNAL(showPreviousPage()), this, SLOT(activateMonthPage()), Qt::QueuedConnection);
    connect(m_editCalendarsPage, SIGNAL(addClicked()), this, SLOT(addCalendar()), Qt::QueuedConnection);

    // Connect to the save and remove request status change signals
    connect(&m_saveReq, SIGNAL(stateChanged(QOrganizerAbstractRequest::State)),
            this, SLOT(saveReqStateChanged(QOrganizerAbstractRequest::State)));
    connect(&m_remReq, SIGNAL(stateChanged(QOrganizerAbstractRequest::State)),
            this, SLOT(removeReqStateChanged(QOrganizerAbstractRequest::State)));

    m_monthPage->init();

    m_stackedWidget->addWidget(m_monthPage);
    m_stackedWidget->addWidget(m_dayPage);
    m_stackedWidget->addWidget(m_eventEditPage);
    m_stackedWidget->addWidget(m_todoEditPage);
    m_stackedWidget->addWidget(m_journalEditPage);
    m_stackedWidget->addWidget(m_eventOccurrenceEditPage);
    m_stackedWidget->addWidget(m_addCalendarPage);
    m_stackedWidget->addWidget(m_editCalendarsPage);
    m_stackedWidget->setCurrentIndex(0);

    setCentralWidget(m_stackedWidget);
    buildMenu();

    activateMonthPage();
}



CalendarDemo::~CalendarDemo()
{

}

void CalendarDemo::buildMenu()
{
    // Build Options menu
    QMenu *optionsMenu = new QMenu("&Options", this);
    menuBar()->addMenu(optionsMenu);
    // Add editing options in the menu
    QOrganizerManager defaultManager;
    QList<QOrganizerItemType::ItemType> supportedItemTypes = defaultManager.supportedItemTypes();
    if (supportedItemTypes.contains(QOrganizerItemType::TypeEvent)) {
        QAction* addEventAction = optionsMenu->addAction("Add E&vent");
        connect(addEventAction, SIGNAL(triggered(bool)), this, SLOT(addNewEvent()));
    }
    if (supportedItemTypes.contains(QOrganizerItemType::TypeTodo)) {
        QAction* addTodoAction = optionsMenu->addAction("Add &Todo");
        connect(addTodoAction, SIGNAL(triggered(bool)), this, SLOT(addNewTodo()));
    }
    if (supportedItemTypes.contains(QOrganizerItemType::TypeJournal)) {
        QAction* addJournalAction = optionsMenu->addAction("Add &Journal");
        connect(addJournalAction, SIGNAL(triggered(bool)), this, SLOT(addNewJournal()));
    }
    optionsMenu->addSeparator();
    QAction* editAction = optionsMenu->addAction("&Edit");
    connect(editAction, SIGNAL(triggered(bool)), this, SLOT(editItem()));
    QAction* removeAction = optionsMenu->addAction("&Remove");
    connect(removeAction, SIGNAL(triggered(bool)), this, SLOT(removeItem()));
    optionsMenu->addSeparator();
    m_switchViewAction = optionsMenu->addAction("&Open Day");
    connect(m_switchViewAction, SIGNAL(triggered(bool)), this, SLOT(switchView()));
    optionsMenu->addSeparator();
    QAction* addHugeEntries = optionsMenu->addAction("Add Test Events");
    connect(addHugeEntries, SIGNAL(triggered(bool)), this, SLOT(addEvents()));
    QAction* importItems = optionsMenu->addAction("&Import Items...");
    connect(importItems, SIGNAL(triggered(bool)), this, SLOT(importItems()));
    QAction* exportItems = optionsMenu->addAction("Ex&port Items...");
    connect(exportItems, SIGNAL(triggered(bool)), this, SLOT(exportItems()));
    QAction* deleteAllEntries = optionsMenu->addAction("Delete All Items");
    connect(deleteAllEntries, SIGNAL(triggered(bool)), this, SLOT(deleteAllEntries()));
    QAction* addCalendar = optionsMenu->addAction("New calendar");
    connect(addCalendar, SIGNAL(triggered(bool)), this, SLOT(addCalendar()));
    QAction* editCalendar = optionsMenu->addAction("Edit calendars");
    connect(editCalendar, SIGNAL(triggered(bool)), this, SLOT(editCalendar()));
}

void CalendarDemo::activateMonthPage()
{
    menuBar()->setVisible(true);
    m_monthPage->refresh();
    m_stackedWidget->setCurrentWidget(m_monthPage);
    m_switchViewAction->setText("&Open Day");
}

void CalendarDemo::activateDayPage()
{
    menuBar()->setVisible(true);
    m_dayPage->refresh();
    m_stackedWidget->setCurrentWidget(m_dayPage);
    m_switchViewAction->setText("View &Month");
}

void CalendarDemo::activateEditPage(const QOrganizerItem &item)
{
    m_previousItem = item;
    menuBar()->setVisible(false);
    if (item.type() == QOrganizerItemType::TypeEvent) {
        QOrganizerEvent event = static_cast<QOrganizerEvent>(item);
        m_dayPage->dayChanged(event.startDateTime().date()); // edit always comes back to day page
        m_eventEditPage->eventChanged(m_manager, event);
        m_stackedWidget->setCurrentWidget(m_eventEditPage);
    } else if (item.type() == QOrganizerItemType::TypeTodo) {
        QOrganizerTodo todo = static_cast<QOrganizerTodo>(item);
        m_dayPage->dayChanged(todo.startDateTime().date()); // edit always comes back to day page
        m_todoEditPage->todoChanged(m_manager, todo);
        m_stackedWidget->setCurrentWidget(m_todoEditPage);
    } else if (item.type() == QOrganizerItemType::TypeJournal) {
        QOrganizerJournal journal = static_cast<QOrganizerJournal>(item);
        m_dayPage->dayChanged(journal.dateTime().date()); // edit always comes back to day page
        m_journalEditPage->journalChanged(m_manager, journal);
        m_stackedWidget->setCurrentWidget(m_journalEditPage);
    } else if (item.type() == QOrganizerItemType::TypeEventOccurrence) {
        QOrganizerEventOccurrence eventOccurrence = static_cast<QOrganizerEventOccurrence>(item);
        QMessageBox msgBox;
        msgBox.setText(tr("This is a recurring event"));
        msgBox.setInformativeText(tr("Do you want to open this occurrence or the recurring event series?"));
        QAbstractButton *occurrenceButton = msgBox.addButton(tr("Occurrence"), QMessageBox::ActionRole);
        QAbstractButton *seriesButton = msgBox.addButton(tr("Series"), QMessageBox::ActionRole);

        msgBox.exec();
        if (msgBox.clickedButton() == seriesButton) {
            QOrganizerItemId parentEventId = eventOccurrence.parentId();
            QOrganizerEvent parentEvent = m_manager->item(parentEventId);
            m_dayPage->dayChanged(parentEvent.startDateTime().date()); // edit always comes back to day page
            m_eventEditPage->eventChanged(m_manager, parentEvent);
            m_stackedWidget->setCurrentWidget(m_eventEditPage);
        } else if (msgBox.clickedButton() == occurrenceButton) {
            m_dayPage->dayChanged(eventOccurrence.startDateTime().date()); // edit always comes back to day page
            m_eventOccurrenceEditPage->eventOccurrenceChanged(m_manager, eventOccurrence);
            m_stackedWidget->setCurrentWidget(m_eventOccurrenceEditPage);
        }

    }
    // TODO:
    //else if (item.type() == QOrganizerItemType::TypeNote)
}

void CalendarDemo::activatePreviousPage()
{
    if (m_previousPage == m_stackedWidget->indexOf(m_monthPage))
        activateMonthPage();
    else if (m_previousPage == m_stackedWidget->indexOf(m_dayPage))
        activateDayPage();
    else if (m_previousPage == m_stackedWidget->indexOf(m_editCalendarsPage))
        editCalendar();
    else
        activateEditPage(m_previousItem);
}

void CalendarDemo::addNewEvent()
{
    QOrganizerEvent newEvent;
    QDateTime time(m_currentDate.startOfDay());
    newEvent.setStartDateTime(time);
    time = time.addSecs(60*30); // add 30 minutes to end time
    newEvent.setEndDateTime(time);
    activateEditPage(newEvent);
}

void CalendarDemo::addNewTodo()
{
    QOrganizerTodo newTodo;
    QDateTime time(m_currentDate.startOfDay());
    newTodo.setStartDateTime(time);
    time = time.addSecs(60*30); // add 30 minutes to due time
    newTodo.setDueDateTime(time);
    activateEditPage(newTodo);
}

void CalendarDemo::addNewJournal()
{
    QOrganizerJournal newJournal;
    QDateTime time(m_currentDate.startOfDay());
    newJournal.setDateTime(time);
    activateEditPage(newJournal);
}

void CalendarDemo::switchView()
{
    if (m_stackedWidget->currentWidget() == m_dayPage) {
        activateMonthPage();
    } else if (m_stackedWidget->currentWidget() == m_monthPage) {
        activateDayPage();
    }
}

void CalendarDemo::editItem()
{
    if (m_stackedWidget->currentWidget() == m_dayPage) {
        m_dayPage->editItem();
    } else if (m_stackedWidget->currentWidget() == m_monthPage) {
        m_monthPage->editItem();
    }
}

void CalendarDemo::removeItem()
{
    if (m_stackedWidget->currentWidget() == m_dayPage) {
        m_dayPage->removeItem();
    } else if (m_stackedWidget->currentWidget() == m_monthPage) {
        m_monthPage->removeItem();
    }
}

void CalendarDemo::addEvents()
{
    QList<QOrganizerItem> items;

    // Create a large number of events asynchronously
    QOrganizerCollectionId defaultCollectionId = m_manager->defaultCollectionId();
    for (int index=0 ; index <  100 ; index++) {
        QOrganizerItem item;
        item.setType(QOrganizerItemType::TypeEvent);
        item.setDescription(QString("Event %1").arg(index));
        item.setDisplayLabel(QString("Subject for event %1").arg(index + 1));

        // Set the start date to index to add events to next 5000 days
        QOrganizerEventTime timeRange;
        timeRange.setStartDateTime(QDateTime::currentDateTime().addDays(index));
        timeRange.setEndDateTime(QDateTime::currentDateTime().addDays(index).addSecs(60 * 60));
        item.saveDetail(&timeRange);

        item.setCollectionId(defaultCollectionId);

        items.append(item);
    }

    // Now create a save request and execute it
    m_saveReq.setItems(items);
    m_saveReq.setManager(m_manager);
    m_saveReq.start();
}

void CalendarDemo::importItems()
{
#ifdef BUILD_VERSIT
    QString messageTitle(tr("Import of Items failed"));
    if (!m_manager) {
        QMessageBox::warning(this, messageTitle, tr("No manager selected; cannot import"));
        return;
    }
    QString docPath = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
    if (docPath.isEmpty())
        docPath = QDesktopServices::storageLocation(QDesktopServices::HomeLocation);
    if (docPath.isEmpty())
        docPath = ".";
    QString fileName = QFileDialog::getOpenFileName(this,
       tr("Select iCalendar file"), docPath, tr("iCalendar files (*.ics)"));

    // user chose to cancel file dialog?
    if (fileName.isEmpty())
         return;

    QFile file(fileName);
    if (!file.open(QIODevice::ReadOnly) || !file.isReadable()){
        QMessageBox::warning(this, messageTitle, tr("Unable to read from file: %1").arg(fileName));
        return;
    }
    QVersitReader reader;
    reader.setDevice(&file);
    if (!reader.startReading() || !reader.waitForFinished()) {
        QMessageBox::warning(this, messageTitle, tr("Versit reader failed: %1").arg(reader.error()));
        return;
    }
    QVersitOrganizerImporter importer;
    QList<QOrganizerItem> allItems;
    QString errorMessage;
    foreach (const QVersitDocument& document, reader.results()) {
        if (!importer.importDocument(document)) {
            errorMessage += tr("Import failed,");
            QMap<int, QVersitOrganizerImporter::Error>::const_iterator iterator = importer.errorMap().constBegin();
            while (iterator != importer.errorMap().constEnd()){
                switch (iterator.value()){
                case QVersitOrganizerImporter::InvalidDocumentError:
                    errorMessage += QString(" index %1:").arg(iterator.key());
                    errorMessage += tr("One of the documents is not an iCalendar file");
                break;
                case QVersitOrganizerImporter::EmptyDocumentError:
                    errorMessage += QString(" index %1:").arg(iterator.key());
                    errorMessage += tr("One of the documents is empty");
                break;
                default:
                    errorMessage += QString(" index %1:").arg(iterator.key());
                    errorMessage += tr("Unknown error");
                }
                ++iterator;
            }
            errorMessage += QLatin1String("\n");
            continue;
        }
        QList<QOrganizerItem> items = importer.items();
        foreach (const QOrganizerItem& item, items) {
            allItems.append(item);
        }
    }
    if (!errorMessage.isEmpty())
        QMessageBox::warning(this, messageTitle, errorMessage);

    m_manager->saveItems(&allItems);
    m_monthPage->refresh();
    m_dayPage->refresh();
#endif
}

void CalendarDemo::exportItems()
{
#ifdef BUILD_VERSIT
    QString messageTitle(tr("Export of Items failed"));
    if (!m_manager) {
        QMessageBox::warning(this, messageTitle, tr("No manager selected; cannot export"));
        return;
    }
    QString docPath = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
    if (docPath.isEmpty())
        docPath = QDesktopServices::storageLocation(QDesktopServices::HomeLocation);
    if (docPath.isEmpty())
        docPath = ".";
    docPath.append("/calendar.ics");
    QString fileName = QFileDialog::getSaveFileName(this, tr("Save iCalendar"),
                                                    docPath,
                                                    tr("iCalendar files (*.ics)"));
    // user chose to cancel file dialog?
    if (fileName.isEmpty())
         return;

    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly) || !file.isWritable()) {
        QMessageBox::warning(this, messageTitle, tr("Unable to write to file: %1").arg(fileName));
        return;
    }
    QList<QOrganizerItem> items(m_manager->itemsForExport());
    QVersitOrganizerExporter exporter;
    QString errorMessage;
    if (!exporter.exportItems(items)) {
            errorMessage += tr("Export failed,");
            QMap<int, QVersitOrganizerExporter::Error>::const_iterator iterator = exporter.errorMap().constBegin();
            while (iterator != exporter.errorMap().constEnd()){
                switch (iterator.value()){
                case QVersitOrganizerExporter::EmptyOrganizerError:
                    errorMessage += QString(" index %1:").arg(iterator.key());
                    errorMessage += tr("One of the documents is not an iCalendar file");
                break;
                case QVersitOrganizerExporter::UnknownComponentTypeError:
                    errorMessage += QString(" index %1:").arg(iterator.key());
                    errorMessage += tr("One of the components in the iCalendar file is"
                                       " not supported");
                break;
                case QVersitOrganizerExporter::UnderspecifiedOccurrenceError:
                    errorMessage += QString(" index %1:").arg(iterator.key());
                    errorMessage += tr("An event or todo exception was found which"
                                       " did not specify both its parent and a specifier for"
                                       " which instance to override");
                break;
                default:
                    errorMessage += QString(" index %1:%2 ").arg(iterator.key())
                                                           .arg(tr("Unknown error"));
                }
                ++iterator;
            }
            errorMessage += QLatin1String("\n");
        if (!errorMessage.isEmpty()){
            QMessageBox::warning(this, messageTitle, errorMessage);
            return;
        }
    }
    QVersitDocument document = exporter.document();
    QVersitWriter writer;
    writer.setDevice(&file);
    if (!writer.startWriting(QList<QVersitDocument>() << document)
        || !writer.waitForFinished()) {
        QMessageBox::warning(this, messageTitle,
            tr("Versit writing of organizer items failed: %1").arg(writer.error()));
    }
#endif
}

void CalendarDemo::deleteAllEntries()
{
    // Fetch all the entries
    QList<QOrganizerItemId> ids = m_manager->itemIds();

    if (ids.count()) {
        m_remReq.setItemIds(ids);
        m_remReq.setManager(m_manager);
        m_remReq.start();
    }
}

void CalendarDemo::addCalendar()
{
    // Get default collection
    QOrganizerCollection defaultCollection = m_manager->collection(m_manager->defaultCollectionId());

    QOrganizerCollection newCollection = defaultCollection;
    newCollection.setId(QOrganizerCollectionId()); // reset collection id
    m_addCalendarPage->calendarChanged(m_manager, newCollection);

    m_previousPage = m_stackedWidget->currentIndex();
    m_stackedWidget->setCurrentWidget(m_addCalendarPage);
}

void CalendarDemo::editCalendar()
{
    m_editCalendarsPage->showPage(m_manager);
    m_previousPage = m_stackedWidget->currentIndex();
    m_stackedWidget->setCurrentWidget(m_editCalendarsPage);
}

void CalendarDemo::editExistingCalendar(QOrganizerManager *manager, QOrganizerCollection* calendar)
{
    m_addCalendarPage->calendarChanged(manager, *calendar);
    m_previousPage = m_stackedWidget->currentIndex();
    m_stackedWidget->setCurrentWidget(m_addCalendarPage);
}

void CalendarDemo::saveReqStateChanged(QOrganizerAbstractRequest::State reqState)
{
    if (QOrganizerAbstractRequest::ActiveState == reqState) {
        // Request started. Show a progress or wait dialog
        m_progressDlg = new QProgressDialog("Saving events..", "Cancel", 100, 100, this);
        connect(m_progressDlg, SIGNAL(canceled()), &m_saveReq, SLOT(cancel()));
        m_progressDlg->show();
    } else if (QOrganizerAbstractRequest::FinishedState == reqState ||
               QOrganizerAbstractRequest::CanceledState == reqState) {
        // Request finished or cancelled. Stop showing the progress dialog and refresh
        m_progressDlg->hide();
        m_monthPage->refresh();
        m_dayPage->refresh();
    }
}

void CalendarDemo::removeReqStateChanged(QOrganizerAbstractRequest::State reqState)
{
    if (QOrganizerAbstractRequest::ActiveState == reqState) {
        // Request started. Show a progress or wait dialog
        m_progressDlg = new QProgressDialog("Removing events..", "Cancel", 100, 100, this);
        connect(m_progressDlg, SIGNAL(canceled()), &m_remReq, SLOT(cancel()));
        m_progressDlg->show();
    } else if (QOrganizerAbstractRequest::FinishedState == reqState ||
               QOrganizerAbstractRequest::CanceledState == reqState) {
        // Request finished or cancelled. Stop showing the progress dialog and refresh
        m_progressDlg->hide();
        m_monthPage->refresh();
        m_dayPage->refresh();
    }
}

void CalendarDemo::changeManager(QOrganizerManager *manager)
{
    m_manager = manager;
}

void CalendarDemo::updateSelectedDay(const QDate& date)
{
    m_dayPage->dayChanged(date);
    m_currentDate = date;
}
