/*
  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 "incidencealarm.h"
#include "alarmdialog.h"
#include "alarmpresets.h"
#include "incidencedatetime.h"
#ifdef KDEPIM_MOBILE_UI
#include "ui_eventortodomoremobile.h"
#else
#include "ui_eventortododesktop.h"
#endif

#include <calendarsupport/kcalprefs.h>

#include <KDebug>

using namespace IncidenceEditorNG;
using namespace CalendarSupport;


#ifdef KDEPIM_MOBILE_UI
IncidenceAlarm::IncidenceAlarm( IncidenceDateTime *dateTime, Ui::EventOrTodoMore *ui )
#else
IncidenceAlarm::IncidenceAlarm( IncidenceDateTime *dateTime, Ui::EventOrTodoDesktop *ui )
#endif
  : mUi( ui ),
    mDateTime( dateTime ),
    mEnabledAlarmCount( 0 ),
    mIsTodo( false )
{
  setObjectName( "IncidenceAlarm" );

  mUi->mAlarmPresetCombo->insertItems( 0, AlarmPresets::availablePresets() );
  mUi->mAlarmPresetCombo->setCurrentIndex( AlarmPresets::defaultPresetIndex() );
  updateButtons();

  connect( mDateTime, SIGNAL(startDateTimeToggled(bool)),
           SLOT(handleDateTimeToggle()) );
  connect( mDateTime, SIGNAL(endDateTimeToggled(bool)),
           SLOT(handleDateTimeToggle()) );
  connect( mUi->mAlarmAddPresetButton, SIGNAL(clicked()),
           SLOT(newAlarmFromPreset()) );
  connect( mUi->mAlarmList, SIGNAL(itemSelectionChanged()),
           SLOT(updateButtons()) );
  connect( mUi->mAlarmNewButton, SIGNAL(clicked()),
           SLOT(newAlarm()));
  connect( mUi->mAlarmConfigureButton, SIGNAL(clicked()),
           SLOT(editCurrentAlarm()) );
  connect( mUi->mAlarmToggleButton, SIGNAL(clicked()),
           SLOT(toggleCurrentAlarm()) );
  connect( mUi->mAlarmRemoveButton, SIGNAL(clicked()),
           SLOT(removeCurrentAlarm()) );
}

void IncidenceAlarm::load( const KCalCore::Incidence::Ptr &incidence )
{
  mLoadedIncidence = incidence;
  // We must be sure that the date/time in mDateTime is the correct date time.
  // So don't depend on CombinedIncidenceEditor or whatever external factor to
  // load the date/time before loading the recurrence
  mDateTime->load( incidence );

  mAlarms.clear();
  foreach ( const KCalCore::Alarm::Ptr &alarm, incidence->alarms() ) {
    mAlarms.append( KCalCore::Alarm::Ptr( new KCalCore::Alarm( *alarm.data() ) ) );
  }

  mIsTodo = incidence->type() == KCalCore::Incidence::TypeTodo;
  if ( mIsTodo ) {
    mUi->mAlarmPresetCombo->clear();
    mUi->mAlarmPresetCombo->addItems( AlarmPresets::availablePresets( AlarmPresets::BeforeEnd ) );
  } else {
    mUi->mAlarmPresetCombo->clear();
    mUi->mAlarmPresetCombo->addItems( AlarmPresets::availablePresets( AlarmPresets::BeforeStart ) );
  }
  mUi->mAlarmPresetCombo->setCurrentIndex( AlarmPresets::defaultPresetIndex() );

  handleDateTimeToggle();
  mWasDirty = false;

  updateAlarmList();
}

void IncidenceAlarm::save( const KCalCore::Incidence::Ptr &incidence )
{
  incidence->clearAlarms();
  KCalCore::Alarm::List::ConstIterator it;
  for ( it = mAlarms.constBegin(); it != mAlarms.constEnd(); ++it ) {
    KCalCore::Alarm::Ptr al( new KCalCore::Alarm( *(*it) ) );
    al->setParent( incidence.data() );
    // We need to make sure that both lists are the same in the end for isDirty.
    Q_ASSERT( *al == *(*it) );
    incidence->addAlarm( al );
  }
}

bool IncidenceAlarm::isDirty() const
{
  if ( mLoadedIncidence->alarms().count() != mAlarms.count() ) {
    return true;
  }

  if ( !mLoadedIncidence->alarms().isEmpty() ) {
    const KCalCore::Alarm::List initialAlarms = mLoadedIncidence->alarms();

    if ( initialAlarms.count() != mAlarms.count() ) {
      return true; // The number of alarms has changed
    }

    // Note: Not the most efficient algorithm but I'm assuming that we're only
    //       dealing with a couple, at most tens of alarms. The idea is we check
    //       if all currently enabled alarms are also in the incidence. The
    //       disabled alarms are not changed by our code at all, so we assume that
    //       they're still there.
    foreach ( const KCalCore::Alarm::Ptr &alarm, mAlarms ) {
      bool found = false;
      foreach ( const KCalCore::Alarm::Ptr &initialAlarm, initialAlarms ) {
        if ( *alarm == *initialAlarm ) {
          found  = true;
          break;
        }
      }

      if ( !found ) {
        // There was an alarm in the mLoadedIncidence->alarms() that wasn't found
        // in mLastAlarms. This means that one of the alarms was modified.
        return true;
      }
    }
  }

 return false;
}

void IncidenceAlarm::editCurrentAlarm()
{
  KCalCore::Alarm::Ptr currentAlarm = mAlarms.at( mUi->mAlarmList->currentRow() );

#ifdef KDEPIM_MOBILE_UI
  QPointer<AlarmDialog> dialog( new AlarmDialog( mLoadedIncidence->type() ) );
#else
  QPointer<AlarmDialog> dialog( new AlarmDialog( mLoadedIncidence->type(), mUi->mTabWidget ) );
#endif
  dialog->load( currentAlarm );

  dialog->setAllowBeginReminders( mDateTime->startDateTimeEnabled() );
  dialog->setAllowEndReminders( mDateTime->endDateTimeEnabled() );

  if ( dialog->exec() == KDialog::Accepted ) {
    dialog->save( currentAlarm );
    updateAlarmList();
    checkDirtyStatus();
  }
  delete dialog;
}

void IncidenceAlarm::handleDateTimeToggle()
{
  QWidget *parent = mUi->mAlarmPresetCombo->parentWidget();  // the parent of a toplevel widget
  if ( parent ) {
    parent->setEnabled( mDateTime->startDateTimeEnabled() || mDateTime->endDateTimeEnabled() );
  }

  mUi->mAlarmPresetCombo->setEnabled( mDateTime->endDateTimeEnabled() );
  mUi->mAlarmAddPresetButton->setEnabled( mDateTime->endDateTimeEnabled() );

#ifndef KDEPIM_MOBILE_UI
  mUi->mQuickAddReminderLabel->setEnabled( mDateTime->endDateTimeEnabled() );
#endif
}

void IncidenceAlarm::newAlarm()
{
#ifdef KDEPIM_MOBILE_UI
  QPointer<AlarmDialog> dialog( new AlarmDialog( mLoadedIncidence->type() ) );
#else
  QPointer<AlarmDialog> dialog( new AlarmDialog( mLoadedIncidence->type(), mUi->mTabWidget ) );
#endif
  const int reminderOffset = KCalPrefs::instance()->reminderTime();

  if ( reminderOffset >= 0 ) {
    dialog->setOffset( reminderOffset );
  } else {
    dialog->setOffset( DEFAULT_REMINDER_OFFSET );
  }
  dialog->setUnit( AlarmDialog::Minutes );
  if ( mIsTodo && mDateTime->endDateTimeEnabled() ) {
    dialog->setWhen( AlarmDialog::BeforeEnd );
  } else {
    dialog->setWhen( AlarmDialog::BeforeStart );
  }

  dialog->setAllowBeginReminders( mDateTime->startDateTimeEnabled() );
  dialog->setAllowEndReminders( mDateTime->endDateTimeEnabled() );

  if ( dialog->exec() == KDialog::Accepted ) {
    KCalCore::Alarm::Ptr newAlarm( new KCalCore::Alarm( 0 ) );
    dialog->save( newAlarm );
    newAlarm->setEnabled( true );
    mAlarms.append( newAlarm );
    updateAlarmList();
    checkDirtyStatus();
  }
  delete dialog;
}

void IncidenceAlarm::newAlarmFromPreset()
{
  if ( mIsTodo ) {
    mAlarms.append(
      AlarmPresets::preset( AlarmPresets::BeforeEnd, mUi->mAlarmPresetCombo->currentText() ) );
  } else {
    mAlarms.append(
      AlarmPresets::preset( AlarmPresets::BeforeStart, mUi->mAlarmPresetCombo->currentText() ) );
  }

  updateAlarmList();
  checkDirtyStatus();
}

void IncidenceAlarm::removeCurrentAlarm()
{
  Q_ASSERT( mUi->mAlarmList->selectedItems().size() == 1 );
  const int curAlarmIndex = mUi->mAlarmList->currentRow();
  delete mUi->mAlarmList->takeItem( curAlarmIndex );
  mAlarms.remove( curAlarmIndex );

  updateAlarmList();
  updateButtons();
  checkDirtyStatus();
}

void IncidenceAlarm::toggleCurrentAlarm()
{
  Q_ASSERT( mUi->mAlarmList->selectedItems().size() == 1 );
  const int curAlarmIndex = mUi->mAlarmList->currentRow();
  KCalCore::Alarm::Ptr alarm = mAlarms.at( curAlarmIndex );
  alarm->setEnabled( !alarm->enabled() );

  updateButtons();
  updateAlarmList();
  checkDirtyStatus();
}

void IncidenceAlarm::updateAlarmList()
{
  const int prevEnabledAlarmCount = mEnabledAlarmCount;
  mEnabledAlarmCount = 0;

  const QModelIndex currentIndex = mUi->mAlarmList->currentIndex();
  mUi->mAlarmList->clear();
  foreach ( const KCalCore::Alarm::Ptr &alarm, mAlarms ) {
    mUi->mAlarmList->addItem( stringForAlarm( alarm ) );
    if ( alarm->enabled() ) {
      ++mEnabledAlarmCount;
    }
  }

  mUi->mAlarmList->setCurrentIndex( currentIndex );
  if ( prevEnabledAlarmCount != mEnabledAlarmCount ) {
    emit alarmCountChanged( mEnabledAlarmCount );
  }
}

void IncidenceAlarm::updateButtons()
{
  if ( mUi->mAlarmList->count() > 0 && mUi->mAlarmList->selectedItems().count() > 0 ) {
    mUi->mAlarmConfigureButton->setEnabled( true );
    mUi->mAlarmRemoveButton->setEnabled( true );
    mUi->mAlarmToggleButton->setEnabled( true );
    if ( mAlarms.at( mUi->mAlarmList->currentIndex().row() )->enabled() ) {
      mUi->mAlarmToggleButton->setText( i18nc( "Disable currently selected reminder", "Disable" ) );
    } else {
      mUi->mAlarmToggleButton->setText( i18nc( "Enable currently selected reminder", "Enable" ) );
    }
  } else {
    mUi->mAlarmConfigureButton->setEnabled( false );
    mUi->mAlarmRemoveButton->setEnabled( false );
    mUi->mAlarmToggleButton->setEnabled( false );
  }
}

QString IncidenceAlarm::stringForAlarm( const KCalCore::Alarm::Ptr &alarm )
{
  Q_ASSERT( alarm );

  QString action;
  switch( alarm->type() ) {
  case KCalCore::Alarm::Display:
    action = i18n( "Display a dialog" );
    break;
  case KCalCore::Alarm::Procedure:
    action = i18n( "Execute a script" );
    break;
  case KCalCore::Alarm::Email:
    action = i18n( "Send an email" );
    break;
  case KCalCore::Alarm::Audio:
    action = i18n( "Play an audio file" );
    break;
  default:
    action = i18n( "Invalid Reminder." );
    return action;
  }

  QString offsetUnit = i18nc( "The reminder is set to X minutes before/after the event", "minutes" );
  const int offset = alarm->hasStartOffset() ? alarm->startOffset().asSeconds() / 60 :
                     alarm->endOffset().asSeconds() / 60; // make minutes

  int useoffset = offset;
  if ( offset % ( 24 * 60 ) == 0 && offset != 0 ) { // divides evenly into days?
    useoffset =  offset / 60 / 24;
    offsetUnit = i18nc( "The reminder is set to X days before/after the event", "days" );
  } else if ( offset % 60 == 0 && offset != 0 ) { // divides evenly into hours?
    offsetUnit = i18nc( "The reminder is set to X hours before/after the event", "hours" );
    useoffset = offset / 60;
  }

  QString repeatStr;
  if ( alarm->repeatCount() > 0 ) {
    repeatStr = i18nc( "The reminder is configured to repeat after snooze", "(Repeats)" );
  }

  if ( alarm->enabled() ) {
    if ( useoffset > 0 && alarm->hasStartOffset() ) {
      if ( mIsTodo ) {
        return i18n( "%1 %2 %3 after the to-do started %4",
                     action, useoffset, offsetUnit, repeatStr );
      } else {
        return i18n( "%1 %2 %3 after the event started %4",
                     action, useoffset, offsetUnit, repeatStr );
      }
    } else if ( useoffset < 0 && alarm->hasStartOffset() ) {
      if ( mIsTodo ) {
        return i18n( "%1 %2 %3 before the to-do starts %4",
                     action, qAbs( useoffset ), offsetUnit, repeatStr );
      } else {
        return i18n( "%1 %2 %3 before the event starts %4",
                     action, qAbs( useoffset ), offsetUnit, repeatStr );
      }
    } else if ( useoffset > 0 && alarm->hasEndOffset() ) {
      if ( mIsTodo ) {
        return i18n( "%1 %2 %3 after the to-do is due %4",
                     action, useoffset, offsetUnit, repeatStr );
      } else {
        return i18n( "%1 %2 %3 after the event ends %4",
                     action, useoffset, offsetUnit, repeatStr );
      }
    } else if ( useoffset < 0 && alarm->hasEndOffset() ) {
      if ( mIsTodo ) {
        return i18n( "%1 %2 %3 before the to-do is due %4",
                     action, qAbs( useoffset ), offsetUnit, repeatStr );
      } else {
        return i18n( "%1 %2 %3 before the event ends %4",
                     action, qAbs( useoffset ), offsetUnit, repeatStr );
      }
    }
  } else {
    if ( useoffset > 0 ) {
      if ( mIsTodo ) {
        return i18n( "%1 %2 %3 after the to-do started %4 (Disabled)",
                     action, useoffset, offsetUnit, repeatStr );
      } else {
        return i18n( "%1 %2 %3 after the event started %4 (Disabled)",
                     action, useoffset, offsetUnit, repeatStr );
      }

    } else if ( useoffset < 0 ) {
      if ( mIsTodo ) {
        return i18n( "%1 %2 %3 before the to-do starts %4 (Disabled)",
                     action, qAbs( useoffset ), offsetUnit, repeatStr );
      } else {
        return i18n( "%1 %2 %3 before the event starts %4 (Disabled)",
                     action, qAbs( useoffset ), offsetUnit, repeatStr );
      }
    } else if ( useoffset > 0 && alarm->hasEndOffset() ) {
      if ( mIsTodo ) {
        return i18n( "%1 %2 %3 after the to-do is due %4 (Disabled)",
                     action, useoffset, offsetUnit, repeatStr );
      } else {
        return i18n( "%1 %2 %3 after the event ends %4 (Disabled)",
                     action, useoffset, offsetUnit, repeatStr );
      }
    } else if ( useoffset < 0 && alarm->hasEndOffset() ) {
      if ( mIsTodo ) {
        return i18n( "%1 %2 %3 before the to-do is due %4 (Disabled)",
                     action, qAbs( useoffset ), offsetUnit, repeatStr );
      } else {
        return i18n( "%1 %2 %3 before the event ends %4 (Disabled)",
                     action, qAbs( useoffset ), offsetUnit, repeatStr );
      }
    }
  }

  // useoffset == 0
  if ( alarm->enabled() ) {
    if ( mIsTodo && alarm->hasStartOffset() ) {
      return i18n( "%1 when the to-do starts", action );
    } else if ( alarm->hasStartOffset() ) {
      return i18n( "%1 when the event starts", action );
    } else if ( mIsTodo && alarm->hasEndOffset() ) {
      return i18n( "%1 when the to-do is due", action );
    } else {
      return i18n( "%1 when the event ends", action );
    }
  } else {
    if ( mIsTodo && alarm->hasStartOffset() ) {
      return i18n( "%1 when the to-do starts (Disabled)", action );
    } else if ( alarm->hasStartOffset() ) {
      return i18n( "%1 when the event starts (Disabled)", action );
    } else if ( mIsTodo && alarm->hasEndOffset() ) {
      return i18n( "%1 when the to-do is due (Disabled)", action );
    } else {
      return i18n( "%1 when the event ends (Disabled)", action );
    }
  }
}
