/*
  Copyright (c) 2002-2004 Klarälvdalens Datakonsult AB
        <info@klaralvdalens-datakonsult.se>

  Copyright (C) 2010 Bertjan Broeksema <broeksema@kde.org>
  Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>

  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 "itiphandlerhelper_p.h"
#include "calendarsettings.h"
#include "utils_p.h"

#include <kcalcore/calendar.h>
#include <kcalcore/icalformat.h>
#include <kcalutils/incidenceformatter.h>
#include <kcalutils/stringify.h>
#include <KDebug>
#include <KLocalizedString>
#include <KMessageBox>

#include <QWidget>

using namespace Akonadi;

QString proposalComment(const KCalCore::Incidence::Ptr &incidence)
{
    QString comment;

    // TODO: doesn't KCalUtils/IncidenceFormater already provide this?
    // if not, it should go there.

    switch (incidence->type()) {
    case KCalCore::IncidenceBase::TypeEvent:
    {
        const KDateTime dtEnd = incidence->dateTime(KCalCore::Incidence::RoleDisplayEnd);
        comment = i18n("Proposed new meeting time: %1 - %2",
                       KCalUtils::IncidenceFormatter::dateToString(incidence->dtStart()),
                       KCalUtils::IncidenceFormatter::dateToString(dtEnd));
    }
    break;
    case KCalCore::IncidenceBase::TypeTodo:
    {
        kWarning() << "NOT IMPLEMENTED: proposalComment called for to-do.";
    }
    break;
    default:
        kWarning() << "NOT IMPLEMENTED: proposalComment called for " << incidence->typeStr();
    }

    return comment;
}

int ITIPHandlerHelper::askUserIfNeeded(const QString &question,
                                       bool ignoreDefaultAction,
                                       const KGuiItem &buttonYes,
                                       const KGuiItem &buttonNo) const
{
    Q_ASSERT_X(!question.isEmpty(), "ITIPHandlerHelper::askUser", "ask what?");

    if (ignoreDefaultAction || mDefaultAction == ITIPHandlerHelper::ActionAsk)
        return KMessageBox::questionYesNo(mParent, question, i18n("Group Scheduling Email"),
                                          buttonYes, buttonNo);

    switch (mDefaultAction) {
    case ITIPHandlerHelper::ActionSendMessage:
        return KMessageBox::Yes;
    case ITIPHandlerHelper::ActionDontSendMessage:
        return KMessageBox::No;
    default:
        Q_ASSERT(false);
        return 0;
    }
}

ITIPHandlerHelper::SendResult
ITIPHandlerHelper::sentInvitation(int messageBoxReturnCode,
                                  const KCalCore::Incidence::Ptr &incidence,
                                  KCalCore::iTIPMethod method)
{
    // The value represented by messageBoxReturnCode is the answer on a question
    // which is a variant of: Do you want to send an email to the attendees?
    //
    // Where the email contains an invitation, modification notification or
    // deletion notification.

    if (messageBoxReturnCode == KMessageBox::Yes) {
        // We will be sending out a message here. Now make sure there is some summary
        // Yes, we do modify the incidence here, but we still keep the Ptr
        // semantics, because this change is only for sending and not stored int the
        // local calendar.
        KCalCore::Incidence::Ptr _incidence(incidence->clone());
        if (_incidence->summary().isEmpty()) {
            _incidence->setSummary(i18n("<placeholder>No summary given</placeholder>"));
        }

        // Send the mail
        m_status = StatusSendingInvitation;
        m_scheduler->performTransaction(_incidence, method);
        return ITIPHandlerHelper::ResultSuccess;

    } else if (messageBoxReturnCode == KMessageBox::No) {
        return ITIPHandlerHelper::ResultCanceled;
    } else {
        Q_ASSERT(false);   // TODO: Figure out if/how this can happen (by closing the dialog with x??)
        return ITIPHandlerHelper::ResultCanceled;
    }
}

bool ITIPHandlerHelper::weAreOrganizerOf(const KCalCore::Incidence::Ptr &incidence)
{
    const QString email = incidence->organizer()->email();
    return Akonadi::CalendarUtils::thatIsMe(email) || email.isEmpty()
           || email == QLatin1String("invalid@email.address");
}

bool ITIPHandlerHelper::weNeedToSendMailFor(const KCalCore::Incidence::Ptr &incidence)
{
    if (!weAreOrganizerOf(incidence)) {
        kError() << "We should be the organizer of this incidence."
                 << "; email= "       << incidence->organizer()->email()
                 << "; thatIsMe() = " << Akonadi::CalendarUtils::thatIsMe(incidence->organizer()->email());
        Q_ASSERT(false);
        return false;
    }

    if (incidence->attendees().isEmpty()) {
        return false;
    }

    // At least one attendee
    return
        incidence->attendees().count() > 1 ||
        incidence->attendees().first()->email() != incidence->organizer()->email();
}

ITIPHandlerHelper::ITIPHandlerHelper(QWidget *parent)
    : mDefaultAction(ITIPHandlerHelper::ActionAsk)
    , mParent(parent)
    , m_scheduler(new MailScheduler(parent))
    , m_status(StatusNone)
{
    connect(m_scheduler, SIGNAL(transactionFinished(Akonadi::Scheduler::Result,QString)),
            SLOT(onSchedulerFinished(Akonadi::Scheduler::Result,QString)));
}

ITIPHandlerHelper::~ITIPHandlerHelper()
{
}

void ITIPHandlerHelper::setDialogParent(QWidget *parent)
{
    mParent = parent;
}

void ITIPHandlerHelper::setDefaultAction(Action action)
{
    mDefaultAction = action;
}

ITIPHandlerHelper::SendResult
ITIPHandlerHelper::sendIncidenceCreatedMessage(KCalCore::iTIPMethod method,
        const KCalCore::Incidence::Ptr &incidence)
{
    /// When we created the incidence, we *must* be the organizer.

    if (!weAreOrganizerOf(incidence)) {
        kWarning() << "Creating incidence which has another organizer! Will skip sending invitations."
                 << "; email= "       << incidence->organizer()->email()
                 << "; thatIsMe() = " << Akonadi::CalendarUtils::thatIsMe(incidence->organizer()->email());
        return ITIPHandlerHelper::ResultFailAbortUpdate;
    }

    if (!weNeedToSendMailFor(incidence)) {
        return ITIPHandlerHelper::ResultNoSendingNeeded;
    }

    QString question;
    if (incidence->type() == KCalCore::Incidence::TypeEvent) {
        question = i18n("The event \"%1\" includes other people.\n"
                        "Do you want to email the invitation to the attendees?",
                        incidence->summary());
    } else if (incidence->type() == KCalCore::Incidence::TypeTodo) {
        question = i18n("The todo \"%1\" includes other people.\n"
                        "Do you want to email the invitation to the attendees?",
                        incidence->summary());
    } else {
        question = i18n("This incidence includes other people. "
                        "Should an email be sent to the attendees?");
    }

    const int messageBoxReturnCode = askUserIfNeeded(question, false);
    return sentInvitation(messageBoxReturnCode, incidence, method);
}

bool ITIPHandlerHelper::handleIncidenceAboutToBeModified(const KCalCore::Incidence::Ptr &incidence)
{
    Q_ASSERT(incidence);
    if (!weAreOrganizerOf(incidence)) {
        switch (incidence->type()) {
        case KCalCore::Incidence::TypeEvent:
        {
            const QString question =
                i18n("You are not the organizer of this event. Editing it will "
                     "bring your calendar out of sync with the organizer's calendar. "
                     "Do you really want to edit it?");
            const int messageBoxReturnCode = KMessageBox::warningYesNo(mParent, question);
            return messageBoxReturnCode != KMessageBox::No;
        }
        break;
        case KCalCore::Incidence::TypeJournal:
        case KCalCore::Incidence::TypeTodo:
            // Not sure why we handle to-dos differently regarding this
            return true;
            break;
        default:
            kError() << "Unknown incidence type: " << incidence->type() << incidence->typeStr();
            Q_ASSERT_X(false, "ITIPHandlerHelper::handleIncidenceAboutToBeModified()",
                       "Unknown incidence type");
            return false;
        }
    } else {
        return true;
    }
}

ITIPHandlerHelper::SendResult
ITIPHandlerHelper::sendIncidenceModifiedMessage(KCalCore::iTIPMethod method,
        const KCalCore::Incidence::Ptr &incidence,
        bool attendeeStatusChanged)
{
    // For a modified incidence, either we are the organizer or someone else.
    if (weAreOrganizerOf(incidence)) {
        if (weNeedToSendMailFor(incidence)) {
            const QString question = i18n("You changed the invitation \"%1\".\n"
                                          "Do you want to email the attendees an update message?",
                                          incidence->summary());

            const int messageBoxReturnCode = askUserIfNeeded(question, false, KGuiItem(i18n("Send Update")));
            return sentInvitation(messageBoxReturnCode, incidence, method);
        } else {
            return ResultNoSendingNeeded;
        }

    } else if (incidence->type() == KCalCore::Incidence::TypeTodo) {

        if (method == KCalCore::iTIPRequest) {
            // This is an update to be sent to the organizer
            method = KCalCore::iTIPReply;
        }

        const QString question = i18n("Do you want to send a status update to the "
                                      "organizer of this task?");
        const int messageBoxReturnCode = askUserIfNeeded(question, false, KGuiItem(i18n("Send Update")));
        return sentInvitation(messageBoxReturnCode, incidence, method);

    } else if (incidence->type() == KCalCore::Incidence::TypeEvent) {
        if (attendeeStatusChanged && method == KCalCore::iTIPRequest) {
            method = KCalCore::iTIPReply;
            const QString question =
                i18n("Your status as an attendee of this event changed. "
                     "Do you want to send a status update to the event organizer?");
            const int messageBoxReturnCode = askUserIfNeeded(question, false, KGuiItem(i18n("Send Update")));
            return sentInvitation(messageBoxReturnCode, incidence, method);
        } else {
            return sentInvitation(KMessageBox::Yes, incidence, method);
        }
    }
    Q_ASSERT(false);   // Shouldn't happen.
    return ResultNoSendingNeeded;
}

ITIPHandlerHelper::SendResult
ITIPHandlerHelper::sendIncidenceDeletedMessage(KCalCore::iTIPMethod method,
        const KCalCore::Incidence::Ptr &incidence)
{
    Q_ASSERT(incidence);
    // For a modified incidence, either we are the organizer or someone else.
    if (weAreOrganizerOf(incidence)) {
        if (weNeedToSendMailFor(incidence)) {
            QString question;
            if (incidence->type() == KCalCore::Incidence::TypeEvent) {
                question = i18n("You removed the invitation \"%1\".\n"
                                "Do you want to email the attendees that the event is canceled?",
                                incidence->summary());
            } else if (incidence->type() == KCalCore::Incidence::TypeTodo) {
                question = i18n("You removed the invitation \"%1\".\n"
                                "Do you want to email the attendees that the todo is canceled?",
                                incidence->summary());
            }  else if (incidence->type() == KCalCore::Incidence::TypeJournal) {
                question = i18n("You removed the invitation \"%1\".\n"
                                "Do you want to email the attendees that the journal is canceled?",
                                incidence->summary());
            }

            const int messageBoxReturnCode = askUserIfNeeded(question, false);
            return sentInvitation(messageBoxReturnCode, incidence, method);
        } else {
            return ResultNoSendingNeeded;
        }

    } else if (incidence->type() != KCalCore::Incidence::TypeEvent) {

        if (method == KCalCore::iTIPRequest) {
            // This is an update to be sent to the organizer
            method = KCalCore::iTIPReply;
        }

        const QString question = (incidence->type() == KCalCore::Incidence::TypeTodo) ?
                                 i18n("Do you want to send a status update to the "
                                      "organizer of this task?") :
                                 i18n("Do you want to send a status update to the "
                                      "organizer of this journal?");

        const int messageBoxReturnCode = askUserIfNeeded(question, false, KGuiItem(i18n("Send Update")));
        return sentInvitation(messageBoxReturnCode, incidence, method);
    } else if (incidence->type() == KCalCore::Incidence::TypeEvent) {

        const QStringList myEmails = Akonadi::CalendarUtils::allEmails();
        bool incidenceAcceptedBefore = false;
        foreach(const QString &email, myEmails) {
            KCalCore::Attendee::Ptr me = incidence->attendeeByMail(email);
            if (me &&
                    (me->status() == KCalCore::Attendee::Accepted ||
                     me->status() == KCalCore::Attendee::Delegated)) {
                incidenceAcceptedBefore = true;
                break;
            }
        }

        if (incidenceAcceptedBefore) {
            QString question = i18n("You had previously accepted an invitation to this event. "
                                    "Do you want to send an updated response to the organizer "
                                    "declining the invitation?");
            int messageBoxReturnCode = askUserIfNeeded(question, false, KGuiItem(i18n("Send Update")));
            return sentInvitation(messageBoxReturnCode, incidence, method);
        } else {
            // We did not accept the event before and delete it from our calendar agian,
            // so there is no need to notify people.
            return ITIPHandlerHelper::ResultNoSendingNeeded;
        }
    }

    Q_ASSERT(false);   // Shouldn't happen.
    return ResultNoSendingNeeded;
}

ITIPHandlerHelper::SendResult
ITIPHandlerHelper::sendCounterProposal(const KCalCore::Incidence::Ptr &oldEvent,
                                       const KCalCore::Incidence::Ptr &newEvent)
{
    Q_ASSERT(oldEvent);
    Q_ASSERT(newEvent);

    if (!oldEvent || !newEvent || *oldEvent == *newEvent)
        return ITIPHandlerHelper::ResultNoSendingNeeded;

    if (CalendarSettings::self()->outlookCompatCounterProposals()) {
        KCalCore::Incidence::Ptr tmp(oldEvent->clone());
        tmp->setSummary(i18n("Counter proposal: %1", newEvent->summary()));
        tmp->setDescription(newEvent->description());
        tmp->addComment(proposalComment(newEvent));

        return sentInvitation(KMessageBox::Yes, tmp, KCalCore::iTIPReply);
    } else {
        return sentInvitation(KMessageBox::Yes, newEvent, KCalCore::iTIPCounter);
    }
}

void ITIPHandlerHelper::onSchedulerFinished(Akonadi::Scheduler::Result result,
        const QString &errorMsg)
{
    const bool success = result == MailScheduler::ResultSuccess;

    if (m_status == StatusSendingInvitation) {
        m_status = StatusNone;
        if (!success) {
            const QString question(i18n("Sending group scheduling email failed."));
            const int code = askUserIfNeeded(question, true, KGuiItem(i18n("Abort Update")));
            if (code == KMessageBox::Yes) {
                emit finished(ResultFailAbortUpdate, QString());
            } else {
                emit finished(ResultFailKeepUpdate, QString());
            }
            return;
        } else {
            //fall through
        }
    }

    emit finished(success ? ResultSuccess : ResultError,
                  success ? QString() : i18n("Error: %1", errorMsg));
}

