/*
  This file is part of the KDE reminder agent.

  Copyright (c) 2000,2003 Cornelius Schumacher <schumacher@kde.org>
  Copyright (c) 2008-2009 Allen Winter <winter@kde.org>
  Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 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 General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

  As a special exception, permission is given to link this program
  with any edition of Qt, and distribute the resulting executable,
  without including the source code for Qt in the source distribution.
*/

#include "alarmdialog.h"
#include "korganizer_interface.h"
#include "mailclient.h"

#include <calendarsupport/next/incidenceviewer.h>
#include <calendarsupport/kcalprefs.h>
#include <calendarsupport/identitymanager.h>
#include <calendarsupport/utils.h>

#include <incidenceeditor-ng/incidencedialog.h>
#include <incidenceeditor-ng/incidencedialogfactory.h>

#include <KCalCore/Event>
#include <KCalCore/Todo>
#include <KCalUtils/IncidenceFormatter>

#include <KPIMIdentities/Identity>
#include <KPIMIdentities/IdentityManager>

#include <Akonadi/Item>

#include <Mailtransport/TransportManager>

#include <KComboBox>
#include <KDebug>
#include <KHBox>
#include <KLocale>
#include <KMessageBox>
#include <KNotification>
#include <KSharedConfig>
#include <KSystemTimeZone>
#include <KToolInvocation>
#include <KWindowSystem>

#include <phonon/mediaobject.h>
#include <QLabel>
#include <QKeyEvent>
#include <QSpinBox>
#include <QTreeWidget>
#include <QVBoxLayout>

using namespace KPIMIdentities;
using namespace KCalCore;
using namespace KCalUtils;

static int defSuspendVal = 5;
static int defSuspendUnit = 0; // 0=>minutes, 1=>hours, 2=>days, 3=>weeks

class ReminderTree : public QTreeWidget
{
  public:
    ReminderTree( QWidget *parent ) : QTreeWidget( parent )
    {
    }

};

class ReminderTreeItem : public QTreeWidgetItem
{
  public:
    ReminderTreeItem( const Akonadi::Item &incidence, QTreeWidget *parent )
      : QTreeWidgetItem( parent ), mIncidence( incidence ), mNotified( false )
    {
    }
    bool operator<( const QTreeWidgetItem & other ) const;

    QString mDisplayText;

    const Akonadi::Item mIncidence;
    QDateTime mRemindAt;
    KDateTime mTrigger;
    KDateTime mHappening;
    bool mNotified;
};

struct ConfItem {
  QString uid;
  KUrl akonadiUrl;
  QDateTime remindAt;
};

bool ReminderTreeItem::operator<( const QTreeWidgetItem &other ) const
{
  switch( treeWidget()->sortColumn() ) {
  case 1: // happening datetime
  {
    const ReminderTreeItem *item = static_cast<const ReminderTreeItem *>( &other );
    return item->mHappening < mHappening;
  }
  case 2: // trigger datetime
  {
    const ReminderTreeItem *item = static_cast<const ReminderTreeItem *>( &other );
    return item->mTrigger < mTrigger;
  }
  default:
    return QTreeWidgetItem::operator < ( other );
  }
}

AlarmDialog::AlarmDialog( const Akonadi::ETMCalendar::Ptr &calendar, QWidget *parent )
  : KDialog( parent, Qt::WindowStaysOnTopHint ),
    mCalendar( calendar ), mSuspendTimer( this )
{
  // User1 => Edit...
  // User2 => Dismiss All
  // User3 => Dismiss Selected
  //    Ok => Suspend

  connect( calendar.data(), SIGNAL(calendarChanged()), SLOT(slotCalendarChanged()) );

  KIconLoader::global()->addAppDir( QLatin1String("korgac") );

  KSharedConfig::Ptr config = KGlobal::config();
  KConfigGroup generalConfig( config, "General" );
  QPoint pos = generalConfig.readEntry( "Position", QPoint( 0, 0 ) );

  QWidget *topBox = new QWidget( this );
  if ( !pos.isNull() ) {
    mPos = pos;
    topBox->move( mPos );
  }
  setMainWidget( topBox );
  setCaption( i18nc( "@title:window", "Reminders" ) );
  setWindowIcon( KIcon( QLatin1String("korgac") ) );
  setButtons( Ok | User1 | User2 | User3 );
  setDefaultButton( NoDefault );
  setButtonText( User3, i18nc( "@action:button", "Dismiss Reminder" ) );
  setButtonToolTip( User3, i18nc( "@info:tooltip",
                                  "Dismiss the reminders for the selected incidences" ) );
  setButtonText( User2, i18nc( "@action:button", "Dismiss All" ) );
  setButtonToolTip( User2, i18nc( "@info:tooltip",
                                  "Dismiss the reminders for all listed incidences" ) );
  setButtonText( User1, i18nc( "@action:button", "Edit..." ) );
  setButtonToolTip( User1, i18nc( "@info:tooltip",
                                  "Edit the selected incidence" ) );
  setButtonText( Ok, i18nc( "@action:button", "Suspend" ) );
  setButtonToolTip( Ok, i18nc( "@info:tooltip",
                               "Suspend the reminders for the selected incidences "
                               "by the specified interval" ) );

  // Try to keep the dialog small and non-obtrusive.
  setMinimumWidth( 575 );
  setMinimumHeight( 300 );

  QVBoxLayout *mTopLayout = new QVBoxLayout( topBox );

  QLabel *label = new QLabel(
    i18nc( "@label",
           "Reminders: "
           "Click on a title to toggle the details viewer for that item" ),
    topBox );
  mTopLayout->addWidget( label );

  mIncidenceTree = new ReminderTree( topBox );
  mIncidenceTree->setColumnCount( 3 );
  mIncidenceTree->setSortingEnabled( true );
  const QStringList headerLabels =
    ( QStringList( i18nc( "@title:column reminder title", "Title" ) )
      << i18nc( "@title:column happens at date/time", "Date Time" )
      << i18nc( "@title:column trigger date/time", "Trigger Time" ) );
  mIncidenceTree->setHeaderLabels( headerLabels );
  mIncidenceTree->headerItem()->setToolTip(
    0,
    i18nc( "@info:tooltip", "The event or to-do title" ) );
  mIncidenceTree->headerItem()->setToolTip(
    1,
    i18nc( "@info:tooltip", "The reminder is set for this date/time" ) );
  mIncidenceTree->headerItem()->setToolTip(
    2,
    i18nc( "@info:tooltip", "The date/time the reminder was triggered" ) );

  mIncidenceTree->setWordWrap( true );
  mIncidenceTree->setAllColumnsShowFocus( true );
  mIncidenceTree->setSelectionMode( QAbstractItemView::ExtendedSelection );
  mIncidenceTree->setRootIsDecorated( false );

  mTopLayout->addWidget( mIncidenceTree );

  connect( mIncidenceTree, SIGNAL(itemClicked(QTreeWidgetItem*,int)),
           SLOT(update()) );
  connect( mIncidenceTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)),
           SLOT(slotUser1()) );
  connect( mIncidenceTree, SIGNAL(itemSelectionChanged()),
           SLOT(updateButtons()) );

  mDetailView = new CalendarSupport::IncidenceViewer( mCalendar.data(), topBox );
  QString s;
  s = i18nc( "@info default incidence details string",
             "<emphasis>Select an event or to-do from the list above "
             "to view its details here.</emphasis>" );
  mDetailView->setDefaultMessage( s );
  mTopLayout->addWidget( mDetailView );
  mDetailView->hide();
  mLastItem = 0;

  KHBox *suspendBox = new KHBox( topBox );
  suspendBox->setSpacing( spacingHint() );
  mTopLayout->addWidget( suspendBox );

  QLabel *l = new QLabel( i18nc( "@label:spinbox", "Suspend &duration:" ), suspendBox );

  mSuspendSpin = new QSpinBox( suspendBox );
  mSuspendSpin->setRange( 1, 9999 );
  mSuspendSpin->setValue( defSuspendVal );  // default suspend duration
  mSuspendSpin->setToolTip(
    i18nc( "@info:tooltip",
           "Suspend the reminders by this amount of time" ) );
  mSuspendSpin->setWhatsThis(
    i18nc( "@info:whatsthis",
           "Each reminder for the selected incidences will be suspended "
           "by this number of time units. You can choose the time units "
           "(typically minutes) in the adjacent selector." ) );

  l->setBuddy( mSuspendSpin );

  mSuspendUnit = new KComboBox( suspendBox );
  mSuspendUnit->addItem( i18nc( "@item:inlistbox suspend in terms of minutes", "minute(s)" ) );
  mSuspendUnit->addItem( i18nc( "@item:inlistbox suspend in terms of hours", "hour(s)" ) );
  mSuspendUnit->addItem( i18nc( "@item:inlistbox suspend in terms of days", "day(s)" ) );
  mSuspendUnit->addItem( i18nc( "@item:inlistbox suspend in terms of weeks", "week(s)" ) );
  mSuspendUnit->setToolTip(
    i18nc( "@info:tooltip",
           "Suspend the reminders using this time unit" ) );
  mSuspendUnit->setWhatsThis(
    i18nc( "@info:whatsthis",
           "Each reminder for the selected incidences will be suspended "
           "using this time unit. You can set the number of time units "
           "in the adjacent number entry input." ) );

  mSuspendUnit->setCurrentIndex( defSuspendUnit );

  connect( &mSuspendTimer, SIGNAL(timeout()), SLOT(wakeUp()) );

  connect( this, SIGNAL(okClicked()), this, SLOT(slotOk()) );
  connect( this, SIGNAL(user1Clicked()), this, SLOT(slotUser1()) );
  connect( this, SIGNAL(user2Clicked()), this, SLOT(slotUser2()) );
  connect( this, SIGNAL(user3Clicked()), this, SLOT(slotUser3()) );

  mIdentityManager = new CalendarSupport::IdentityManager;
}

AlarmDialog::~AlarmDialog()
{
  mIncidenceTree->clear();
  delete mIdentityManager;
}

ReminderTreeItem *AlarmDialog::searchByItem( const Akonadi::Item &incidence )
{
  ReminderTreeItem *found = 0;
  QTreeWidgetItemIterator it( mIncidenceTree );
  while ( *it ) {
    ReminderTreeItem *item = static_cast<ReminderTreeItem *>( *it );
    if ( item->mIncidence == incidence ) {
      found = item;
      break;
    }
    ++it;
  }
  return found;
}

static QString cleanSummary( const QString &summary )
{
  static QString etc = i18nc( "@label an elipsis", "..." );
  int maxLen = 30;
  QString retStr = summary;
  retStr.replace( QLatin1Char('\n'), QLatin1Char(' ') );
  if ( retStr.length() > maxLen ) {
    maxLen -= etc.length();
    retStr = retStr.left( maxLen );
    retStr += etc;
  }
  return retStr;
}

void AlarmDialog::addIncidence( const Akonadi::Item &incidenceitem,
                                const QDateTime &reminderAt,
                                const QString &displayText )
{
  Incidence::Ptr incidence = CalendarSupport::incidence( incidenceitem );
  ReminderTreeItem *item = searchByItem( incidenceitem );
  if ( !item ) {
    item = new ReminderTreeItem( incidenceitem, mIncidenceTree );
  }
  item->mNotified = false;
  item->mHappening = KDateTime();
  item->mRemindAt = reminderAt;
  item->mTrigger = KDateTime::currentLocalDateTime();
  item->mDisplayText = displayText;
  item->setText( 0, cleanSummary( incidence->summary() ) );

  QString displayStr;
  const KDateTime dateTime = triggerDateForIncidence( incidence, reminderAt,
                                                      displayStr );

  if ( incidence->type() == Incidence::TypeEvent ) {
    item->setIcon( 0, SmallIcon( QLatin1String("view-calendar-day") ) );
  } else if ( incidence->type() == Incidence::TypeTodo ) {
    item->setIcon( 0, SmallIcon( QLatin1String("view-calendar-tasks")) );
  }

  item->mHappening = dateTime;
  item->setText( 1, displayStr );

  item->setText( 2, IncidenceFormatter::dateTimeToString(
                   item->mTrigger, false, true, KDateTime::Spec::LocalZone() ) );
  QString tip =
    IncidenceFormatter::toolTipStr(
      CalendarSupport::displayName( mCalendar.data(), incidenceitem.parentCollection() ),
      incidence,
      item->mRemindAt.date(), true,
      KDateTime::Spec::LocalZone() );
  if ( !item->mDisplayText.isEmpty() ) {
    tip += QLatin1String("<br>") + item->mDisplayText;
  }
  item->setToolTip( 0, tip );
  item->setToolTip( 1, tip );
  item->setToolTip( 2, tip );
  item->setData( 0, QTreeWidgetItem::UserType, false );

  mIncidenceTree->setCurrentItem( item );
  showDetails( item );
  slotSave();
}

void AlarmDialog::slotOk()
{
  suspend();
}

void AlarmDialog::slotUser1()
{
    const ReminderList selection = selectedItems();
    if ( !selection.isEmpty() ) {
        ReminderTreeItem  *item = selection.first();
        if(mCalendar->hasRight( item->mIncidence, Akonadi::Collection::CanChangeItem )){
            edit();
        }
    }
}

void AlarmDialog::slotUser2()
{
  dismissAll();
}

void AlarmDialog::slotUser3()
{
  dismissCurrent();
}

void AlarmDialog::dismissCurrent()
{
  dismiss( selectedItems() );

  if ( activeCount() == 0 ) {
    accept();
  } else {
    update();
  }
  emit reminderCount( activeCount() );
}

void AlarmDialog::dismissAll()
{
  ReminderList selections;

  QTreeWidgetItemIterator it( mIncidenceTree );
  while ( *it ) {
    if ( !(*it)->isDisabled() ) { //do not disable suspended reminders
      selections.append( static_cast<ReminderTreeItem *>( *it ) );
    }
    ++it;
  }
  dismiss( selections );

  setTimer();
  accept();
  emit reminderCount( activeCount() );
}

void AlarmDialog::dismiss( ReminderList selections )
{
  QList<Akonadi::Item::Id> ids;
  for ( ReminderList::Iterator it = selections.begin(); it != selections.end(); ++it ) {
    kDebug() << "removing " << CalendarSupport::incidence( (*it)->mIncidence )->summary();
    if ( mIncidenceTree->itemBelow( *it ) ) {
      mIncidenceTree->setCurrentItem( mIncidenceTree->itemBelow( *it ) );
    } else if ( mIncidenceTree->itemAbove( *it ) ) {
      mIncidenceTree->setCurrentItem( mIncidenceTree->itemAbove( *it ) );
    }
    mIncidenceTree->removeItemWidget( *it, 0 );
    ids.append( (*it)->mIncidence.id() );
    delete *it;
  }

  removeFromConfig( ids );
}

void AlarmDialog::edit()
{
  ReminderList selection = selectedItems();
  if ( selection.count() != 1 ) {
    return;
  }
  Incidence::Ptr incidence = CalendarSupport::incidence( selection.first()->mIncidence );
  if ( !mCalendar->hasRight( selection.first()->mIncidence, Akonadi::Collection::CanChangeItem ) ) {
    KMessageBox::sorry(
      this,
      i18nc( "@info",
             "\"%1\" is a read-only item so modifications are not possible.",
             cleanSummary( incidence->summary() ) ) );
    return;
  }

#if !defined(KDEPIM_MOBILE_UI)
  openIncidenceEditorNG( selection.first()->mIncidence );
#else
  openIncidenceEditorThroughKOrganizer( incidence );
#endif
}

void AlarmDialog::suspend()
{
  if ( !isVisible() ) { //do nothing if the dialog is hidden
    return;
  }

  int unit = 1;
  switch ( mSuspendUnit->currentIndex() ) {
    case 3: // weeks
      unit *=  7;
    case 2: // days
      unit *= 24;
    case 1: // hours
      unit *= 60;
    case 0: // minutes
      unit *= 60;
    default:
      break;
  }

  ReminderTreeItem *selitem = 0;
  QTreeWidgetItemIterator it( mIncidenceTree );
  while ( *it ) {
    if ( (*it)->isSelected() && !(*it)->isDisabled() ) { //suspend selected, non-suspended reminders
      (*it)->setSelected( false );
      (*it)->setDisabled( true );
      ReminderTreeItem *item = static_cast<ReminderTreeItem *>( *it );
      item->mRemindAt = QDateTime::currentDateTime().addSecs( unit * mSuspendSpin->value() );
      item->mHappening = KDateTime( item->mRemindAt, KDateTime::Spec::LocalZone() );
      item->mNotified = false;
      (*it)->setText( 1, KGlobal::locale()->formatDateTime( item->mHappening ) );
      selitem = item;
    }
    ++it;
  }

  if ( selitem ) {
    if ( mIncidenceTree->itemBelow( selitem ) ) {
      mIncidenceTree->setCurrentItem( mIncidenceTree->itemBelow( selitem ) );
    } else if ( mIncidenceTree->itemAbove( selitem ) ) {
      mIncidenceTree->setCurrentItem( mIncidenceTree->itemAbove( selitem ) );
    }
  }

  // save suspended alarms too so they can be restored on restart
  // kolab/issue4108
  slotSave();

  setTimer();
  if ( activeCount() == 0 ) {
    accept();
  } else {
    update();
  }
  emit reminderCount( activeCount() );
}

void AlarmDialog::setTimer()
{
  int nextReminderAt = -1;

  QTreeWidgetItemIterator it( mIncidenceTree );
  while ( *it ) {
    ReminderTreeItem *item = static_cast<ReminderTreeItem *>( *it );
    if ( item->mRemindAt > QDateTime::currentDateTime() ) {
      const int secs = QDateTime::currentDateTime().secsTo( item->mRemindAt );
      nextReminderAt = nextReminderAt <= 0 ? secs : qMin( nextReminderAt, secs );
    }
    ++it;
  }

  if ( nextReminderAt >= 0 ) {
    mSuspendTimer.stop();
    mSuspendTimer.start( 1000 * ( nextReminderAt + 1 ) );
    mSuspendTimer.setSingleShot( true );
  }
}

void AlarmDialog::show()
{
  mIncidenceTree->resizeColumnToContents( 0 );
  mIncidenceTree->resizeColumnToContents( 1 );
  mIncidenceTree->resizeColumnToContents( 2 );
  mIncidenceTree->sortItems( 1, Qt::AscendingOrder );

  // select the first item that hasn't already been notified
  QTreeWidgetItemIterator it( mIncidenceTree );
  while ( *it ) {
    ReminderTreeItem *item = static_cast<ReminderTreeItem *>( *it );
    if ( !item->mNotified ) {
      (*it)->setSelected( true );
      break;
    }
    ++it;
  }

  // reset the default suspend time
// Allen: commented-out the following lines on 17 Sept 2013
//  mSuspendSpin->setValue( defSuspendVal );
//  mSuspendUnit->setCurrentIndex( defSuspendUnit );

  KDialog::show();
  if ( !mPos.isNull() ) {
    KDialog::move( mPos );
  }
  KWindowSystem::unminimizeWindow( winId(), false );
  KWindowSystem::setState( winId(), NET::KeepAbove | NET::DemandsAttention );
  KWindowSystem::setOnAllDesktops( winId(), true );
  KWindowSystem::activateWindow( winId() );

  // Audio, Procedure, and EMail alarms
  eventNotification();
}

void AlarmDialog::suspendAll()
{
  mIncidenceTree->clearSelection();
  QTreeWidgetItemIterator it( mIncidenceTree );

  // first, select all non-suspended reminders
  while ( *it ) {
    if ( !(*it)->isDisabled() ) { //do not suspend suspended reminders
      (*it)->setSelected( true );
    }
    ++it;
  }

  //suspend all selected reminders
  suspend();
}

void AlarmDialog::eventNotification()
{
  bool beeped = false;
  bool found = false;

  ReminderList list;

  QTreeWidgetItemIterator it( mIncidenceTree );
  while ( *it ) {
    ReminderTreeItem *item = static_cast<ReminderTreeItem *>( *it );
    ++it;
    if ( item->isDisabled() || item->mNotified ) {
      //skip suspended reminders or reminders that have been notified
      continue;
    }
    found = true;
    item->mNotified = true;
    Incidence::Ptr incidence = CalendarSupport::incidence( item->mIncidence );
    Alarm::List alarms = incidence->alarms();
    Alarm::List::ConstIterator ait;
    for ( ait = alarms.constBegin(); ait != alarms.constEnd(); ++ait ) {
      Alarm::Ptr alarm = *ait;
      // FIXME: Check whether this should be done for all multiple alarms
      if ( alarm->type() == Alarm::Procedure ) {
        // FIXME: Add a message box asking whether the procedure should really be executed
        kDebug() << "Starting program: '" << alarm->programFile() << "'";

        QString program = alarm->programFile();

        // if the program name contains spaces escape it
        if ( program.contains( QLatin1Char(' ') )   &&
             !( program.startsWith( QLatin1Char('\"') ) &&
                program.endsWith( QLatin1Char('\"') ) ) ) {
          program = QLatin1Char('\"') + program + QLatin1Char('\"');
        }

        QProcess::startDetached( program + QLatin1Char(' ') + alarm->programArguments() );
      } else if ( alarm->type() == Alarm::Audio ) {
        beeped = true;
        Phonon::MediaObject *player =
          Phonon::createPlayer( Phonon::NotificationCategory,
                                KUrl( alarm->audioFile() ) );
        player->setParent( this );
        connect( player, SIGNAL(finished()), player, SLOT(deleteLater()) );
        player->play();
      } else if ( alarm->type() == Alarm::Email ) {
        QString from = CalendarSupport::KCalPrefs::instance()->email();
        Identity id = mIdentityManager->identityForAddress( from );
        QString to;
        if ( alarm->mailAddresses().isEmpty() ) {
          to = from;
        } else {
          const Person::List addresses = alarm->mailAddresses();
          QStringList add;
          for ( Person::List::ConstIterator it = addresses.constBegin();
                it != addresses.constEnd(); ++it ) {
            add << (*it)->fullName();
          }
          to = add.join( QLatin1String(", ") );
        }

        QString subject;

        Akonadi::Item parentItem = mCalendar->item( alarm->parentUid() );
        Incidence::Ptr parent = CalendarSupport::incidence( parentItem );

        if ( alarm->mailSubject().isEmpty() ) {
          if ( parent->summary().isEmpty() ) {
            subject = i18nc( "@title", "Reminder" );
          } else {
            subject = i18nc( "@title", "Reminder: %1", cleanSummary( parent->summary() ) );
          }
        } else {
          subject = i18nc( "@title", "Reminder: %1", alarm->mailSubject() );
        }

        QString body =
          IncidenceFormatter::mailBodyStr(
            parent.staticCast<IncidenceBase>(), KSystemTimeZones::local() );
        if ( !alarm->mailText().isEmpty() ) {
          body += QLatin1Char('\n') + alarm->mailText();
        }
        //TODO: support attachments
        KOrg::MailClient mailer;
        mailer.send( id, from, to, QString(), subject, body, true, false, QString(),
                     MailTransport::TransportManager::self()->defaultTransportName() );
      }
    }
  }

  if ( !beeped && found ) {
    KNotification::beep();
  }
}

void AlarmDialog::wakeUp()
{
  bool activeReminders = false;
  QTreeWidgetItemIterator it( mIncidenceTree );
  QTreeWidgetItem *firstItem = 0;
  while ( *it ) {
    if ( !firstItem ) {
      firstItem = *it;
    }
    ReminderTreeItem *item = static_cast<ReminderTreeItem *>( *it );
    Incidence::Ptr incidence = CalendarSupport::incidence( item->mIncidence );

    if ( item->mRemindAt <= QDateTime::currentDateTime() ) {
      if ( item->isDisabled() ) { //do not wakeup non-suspended reminders
        item->setDisabled( false );
        item->setSelected( false );
      }
      activeReminders = true;
    } else {
      item->setDisabled( true );
    }

    ++it;
  }

  if ( activeReminders ) {
    show();
  }
  setTimer();
  showDetails( firstItem );
  emit reminderCount( activeCount() );
}

void AlarmDialog::slotSave()
{
  KSharedConfig::Ptr config = KGlobal::config();
  KConfigGroup generalConfig( config, "General" );
  int numReminders = 0;

  QTreeWidgetItemIterator it( mIncidenceTree );
  while ( *it ) {
    ReminderTreeItem *item = static_cast<ReminderTreeItem *>( *it );
    KConfigGroup incidenceConfig( config,
                                  QString::fromLatin1( "Incidence-%1" ).arg( numReminders + 1 ) );

    Incidence::Ptr incidence = CalendarSupport::incidence( item->mIncidence );
    incidenceConfig.writeEntry( "AkonadiUrl", item->mIncidence.url() );
    incidenceConfig.writeEntry( "RemindAt", item->mRemindAt );
    ++numReminders;
    ++it;
  }

  generalConfig.writeEntry( "Reminders", numReminders );
  generalConfig.writeEntry( "Position", pos() );
  config->sync();
}

AlarmDialog::ReminderList AlarmDialog::selectedItems() const
{
  ReminderList list;

  QTreeWidgetItemIterator it( mIncidenceTree );
  while ( *it ) {
    if ( (*it)->isSelected() ) {
      list.append( static_cast<ReminderTreeItem *>( *it ) );
    }
    ++it;
  }
  return list;
}

int AlarmDialog::activeCount()
{
  int count = 0;
  QTreeWidgetItemIterator it( mIncidenceTree );
  while ( *it ) {
    if ( !(*it)->isDisabled() ) { //suspended reminders are non-active
      ++count;
    }
    ++it;
  }
  kDebug() << "computed " << count << " active reminders";
  return count;
}

void AlarmDialog::closeEvent( QCloseEvent * )
{
  slotSave();
  accept();
}

void AlarmDialog::updateButtons()
{
  const ReminderList selection = selectedItems();
  const int count = selection.count();
  const bool enabled = (count > 0);
  kDebug() << "selected items=" << count;
  enableButton( User3, enabled );  // enable Dismiss, if >1 selected
  if ( count == 1 ) {
      ReminderTreeItem  *item = selection.first();
      enableButton( User1, (mCalendar->hasRight( item->mIncidence, Akonadi::Collection::CanChangeItem )));
  } else {
      enableButton( User1, false);
  }
  enableButton( Ok, enabled );     // enable Suspend, if >1 selected
}

void AlarmDialog::toggleDetails( QTreeWidgetItem *item )
{
  if ( !item ) {
    return;
  }

  if ( !mDetailView->isHidden() ) {
    if ( mLastItem == item ) {
      resize( size().width(), size().height() - mDetailView->height() - 50 );
      mDetailView->hide();
    } else {
      showDetails( item );
    }
  } else {
    resize( size().width(), size().height() + mDetailView->height() + 50 );
    showDetails( item );
    mDetailView->show();
  }
  mLastItem = item;
}

void AlarmDialog::showDetails( QTreeWidgetItem *item )
{
  if ( !item ) {
    return;
  }

  ReminderTreeItem *reminderItem = dynamic_cast<ReminderTreeItem *>( item );

   if ( !reminderItem ) {
    mDetailView->setIncidence( Akonadi::Item() );
  } else {
    if ( !reminderItem->mDisplayText.isEmpty() ) {
      QString txt = QLatin1String("<qt><p><b>") + reminderItem->mDisplayText + QLatin1String("</b></p></qt>");
      mDetailView->setHeaderText( txt );
    } else {
      mDetailView->setHeaderText( QString() );
    }
    mDetailView->setIncidence( reminderItem->mIncidence, reminderItem->mRemindAt.date() );
  }
}

void AlarmDialog::update()
{
  updateButtons();

  const ReminderList selection = selectedItems();
  if ( !selection.isEmpty() ) {
      ReminderTreeItem  *item = selection.first();
      enableButton( User1, (mCalendar->hasRight( item->mIncidence, Akonadi::Collection::CanChangeItem )) && (selection.count() == 1) );
      toggleDetails( item );
  }
}

void AlarmDialog::accept()
{
  if ( activeCount() == 0 ) {
    mPos = pos();
    hide();
  }
}

/** static */
KDateTime AlarmDialog::triggerDateForIncidence( const Incidence::Ptr &incidence,
                                                const QDateTime &reminderAt,
                                                QString &displayStr )
{
  KDateTime result;

  if ( incidence->alarms().isEmpty() ) {
    return result;
  }

  Alarm::Ptr alarm = incidence->alarms().first();

  if ( incidence->recurs() ) {
    result = incidence->recurrence()->getNextDateTime(
      KDateTime( reminderAt, KDateTime::Spec::LocalZone( ) ) );

      displayStr = KGlobal::locale()->formatDateTime( result.toLocalZone() );
  }

  if ( !result.isValid() ) {
    result = incidence->dateTime( Incidence::RoleAlarm );
    displayStr = IncidenceFormatter::dateTimeToString( result, false,
                                                       true,
                                                       KDateTime::Spec::LocalZone() );
  }

  return result;
}

void AlarmDialog::slotCalendarChanged()
{
  KCalCore::Incidence::List incidences = mCalendar->incidences();
  Akonadi::Item::List items = mCalendar->itemList( incidences );
  for ( Akonadi::Item::List::ConstIterator it = items.constBegin();
        it != items.constEnd(); ++it ) {
    ReminderTreeItem *item = searchByItem( *it );

    if ( item ) {
      Incidence::Ptr incidence = CalendarSupport::incidence( *it );
      QString displayStr;

      // Yes, alarms can be empty, if someone edited the incidence and removed all alarms
      if ( !incidence->alarms().isEmpty() ) {
        const KDateTime dateTime = triggerDateForIncidence( incidence,
                                                            item->mRemindAt,
                                                            displayStr );

        const QString summary = cleanSummary( incidence->summary() );

        if ( displayStr != item->text( 1 ) || summary != item->text( 0 ) ) {
          item->setText( 1, displayStr );
          item->setText( 0, summary );
        }
      }
    }
  }
}

void AlarmDialog::keyPressEvent( QKeyEvent *e )
{
  const int key = e->key() | e->modifiers();

  if ( key == Qt::Key_Enter || key == Qt::Key_Return ) {
    e->ignore();
    return;
  }

  KDialog::keyPressEvent( e );
}

bool AlarmDialog::openIncidenceEditorThroughKOrganizer( const Incidence::Ptr &incidence )
{
  if ( !QDBusConnection::sessionBus().interface()->isServiceRegistered( QLatin1String("org.kde.korganizer") ) ) {
    if ( KToolInvocation::startServiceByDesktopName( QLatin1String("korganizer"), QString() ) ) {
      KMessageBox::error(
        this,
        i18nc( "@info",
               "Could not start KOrganizer so editing is not possible." ) );
      return false;
    }
  }
  org::kde::korganizer::Korganizer korganizer(
    QLatin1String("org.kde.korganizer"), QLatin1String("/Korganizer"), QDBusConnection::sessionBus() );

  kDebug() << "editing incidence " << incidence->summary();
  if ( !korganizer.editIncidence( incidence->uid() ) ) {
    KMessageBox::error(
      this,
      i18nc( "@info",
             "An internal KOrganizer error occurred attempting to modify \"%1\"",
             cleanSummary( incidence->summary() ) ) );
  }

  // get desktop # where korganizer (or kontact) runs
  QString object =
    QDBusConnection::sessionBus().interface()->isServiceRegistered( QLatin1String("org.kde.kontact") ) ?
    QLatin1String("kontact/MainWindow_1") : QLatin1String("korganizer/MainWindow_1");
  QDBusInterface korganizerObj( QLatin1String("org.kde.korganizer"), QLatin1Char('/') + object );
#ifdef Q_WS_X11
  QDBusReply<int> reply = korganizerObj.call( QLatin1String("winId") );
  if ( reply.isValid() ) {
    int window = reply;
    int desktop = KWindowSystem::windowInfo( window, NET::WMDesktop ).desktop();
    if ( KWindowSystem::currentDesktop() == desktop ) {
      KWindowSystem::minimizeWindow( winId(), false );
    } else {
      KWindowSystem::setCurrentDesktop( desktop );
    }
    KWindowSystem::activateWindow( KWindowSystem::transientFor( window ) );
  }
#elif defined(Q_WS_WIN)
  // WId is a typedef to a void* on windows
  QDBusReply<qlonglong> reply = korganizerObj.call( QLatin1String("winId") );
  if ( reply.isValid() ) {
    qlonglong window = reply;
    KWindowSystem::minimizeWindow( winId(), false );
    KWindowSystem::allowExternalProcessWindowActivation();
    KWindowSystem::activateWindow( reinterpret_cast<WId>(window) );
  }
#else
  // TODO (mac)
#endif
return true;
}

bool AlarmDialog::openIncidenceEditorNG( const Akonadi::Item &item )
{
  Incidence::Ptr incidence = CalendarSupport::incidence( item );
  IncidenceEditorNG::IncidenceDialog *dialog =
    IncidenceEditorNG::IncidenceDialogFactory::create(
      false, /*doesn't need initial saving*/
      incidence->type(), 0, this );
  dialog->load( item );
  return true;
}

void AlarmDialog::removeFromConfig( const QList<Akonadi::Item::Id> &ids )
{
  KSharedConfig::Ptr config = KGlobal::config();
  KConfigGroup genGroup( config, "General" );

  const int oldNumReminders = genGroup.readEntry( "Reminders", 0 );

  QList<ConfItem> newReminders;
  // Delete everything
  for ( int i = 1; i <= oldNumReminders; ++i ) {
    const QString group( QString::fromLatin1( "Incidence-%1" ).arg( i ) );
    KConfigGroup incGroup( config, group );
    const QString uid = incGroup.readEntry( "UID" );
    const QDateTime remindAtDate = incGroup.readEntry( "RemindAt", QDateTime() );
    const KUrl akonadiUrl = incGroup.readEntry( "AkonadiUrl" );
    const Akonadi::Item::Id id = Akonadi::Item::fromUrl( akonadiUrl ).id();
    if ( !ids.contains( id ) ) {
      ConfItem ci;
      ci.akonadiUrl = akonadiUrl;
      ci.remindAt = remindAtDate;
      ci.uid = uid;
      newReminders.append( ci );
    }
    config->deleteGroup( group );
  }

  genGroup.writeEntry( "Reminders", newReminders.count() );

  //Write everything except those which have an uid we don't want
  for ( int i = 0; i < newReminders.count(); ++i ) {
    const QString group( QString::fromLatin1( "Incidence-%1" ).arg( i + 1 ) );
    KConfigGroup incGroup( config, group );
    incGroup.writeEntry( "UID", newReminders[i].uid );
    incGroup.writeEntry( "RemindAt", newReminders[i].remindAt );
    incGroup.writeEntry( "AkonadiUrl", newReminders[i].akonadiUrl );
    incGroup.sync();
  }
  genGroup.sync();
}

