/*
  Copyright (c) 2009, 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
    Author: Frank Osterfeld <osterfeld@kde.org>
    Author: Andras Mantia <andras@kdab.com>

  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 "utils.h"
#include "kcalprefs.h"

#include <Akonadi/Collection>
#include <Akonadi/CollectionDialog>
#include <Akonadi/EntityDisplayAttribute>
#include <Akonadi/EntityTreeModel>
#include <Akonadi/Item>
#include <Akonadi/AgentManager>
#include <Akonadi/Calendar/ETMCalendar>
#include <akonadi/calendar/publishdialog.h>
#include <akonadi/calendar/calendarsettings.h>

#include <KHolidays/Holidays>

#include <KCalCore/CalFilter>
#include <KCalCore/Event>
#include <KCalCore/FreeBusy>
#include <KCalCore/Incidence>
#include <KCalCore/Journal>
#include <KCalCore/MemoryCalendar>
#include <KCalCore/Todo>
#include <KCalCore/ICalFormat>
#include <KCalCore/FileStorage>

#include <KCalUtils/DndFactory>
#include <KCalUtils/ICalDrag>
#include <KCalUtils/VCalDrag>

#include <Mailtransport/TransportManager>

#include <KIconLoader>
#include <KUrl>

#include <QAbstractItemModel>
#include <QDrag>
#include <QMimeData>
#include <QModelIndex>
#include <QPixmap>
#include <QPointer>

#include <boost/bind.hpp>
#include <KMessageBox>
#include <KPIMIdentities/IdentityManager>
#include <KFileDialog>
#include <KIO/NetAccess>

using namespace CalendarSupport;
using namespace KHolidays;
using namespace KCalCore;

KCalCore::Incidence::Ptr CalendarSupport::incidence( const Akonadi::Item &item )
{
  //relying on exception for performance reasons
  try {
    return item.payload<KCalCore::Incidence::Ptr>();
  } catch( Akonadi::PayloadException ) {
    return KCalCore::Incidence::Ptr();
  }
}

KCalCore::Event::Ptr CalendarSupport::event( const Akonadi::Item &item )
{
  //relying on exception for performance reasons
  try {
    KCalCore::Incidence::Ptr incidence = item.payload<KCalCore::Incidence::Ptr>();
    if ( incidence && incidence->type() == KCalCore::Incidence::TypeEvent ) {
      return item.payload<KCalCore::Event::Ptr>();
    }
  } catch( Akonadi::PayloadException ) {
    return KCalCore::Event::Ptr();
  }
  return KCalCore::Event::Ptr();
}

KCalCore::Event::List CalendarSupport::eventsFromItems( const Akonadi::Item::List &items )
{
  KCalCore::Event::List events;
  Q_FOREACH ( const Akonadi::Item &item, items ) {
    if ( const KCalCore::Event::Ptr e = CalendarSupport::event( item ) ) {
      events.push_back( e );
    }
  }
  return events;
}

KCalCore::Incidence::List CalendarSupport::incidencesFromItems( const Akonadi::Item::List &items )
{
  KCalCore::Incidence::List incidences;
  Q_FOREACH ( const Akonadi::Item &item, items ) {
    if ( const KCalCore::Incidence::Ptr e = CalendarSupport::incidence( item ) ) {
      incidences.push_back( e );
    }
  }
  return incidences;
}

KCalCore::Todo::Ptr CalendarSupport::todo( const Akonadi::Item &item )
{
  try {
    KCalCore::Incidence::Ptr incidence = item.payload<KCalCore::Incidence::Ptr>();
    if ( incidence && incidence->type() == KCalCore::Incidence::TypeTodo ) {
      return item.payload<KCalCore::Todo::Ptr>();
    }
  } catch( Akonadi::PayloadException ) {
    return KCalCore::Todo::Ptr();
  }
  return KCalCore::Todo::Ptr();
}

KCalCore::Journal::Ptr CalendarSupport::journal( const Akonadi::Item &item )
{
  try {
    KCalCore::Incidence::Ptr incidence = item.payload<KCalCore::Incidence::Ptr>();
    if ( incidence && incidence->type() == KCalCore::Incidence::TypeJournal ) {
      return item.payload<KCalCore::Journal::Ptr>();
    }
  } catch( Akonadi::PayloadException ) {
    return KCalCore::Journal::Ptr();
  }
  return KCalCore::Journal::Ptr();
}

bool CalendarSupport::hasIncidence( const Akonadi::Item &item )
{
  return item.hasPayload<KCalCore::Incidence::Ptr>();
}

bool CalendarSupport::hasEvent( const Akonadi::Item &item )
{
  return item.hasPayload<KCalCore::Event::Ptr>();
}

bool CalendarSupport::hasTodo( const Akonadi::Item &item )
{
  return item.hasPayload<KCalCore::Todo::Ptr>();
}

bool CalendarSupport::hasJournal( const Akonadi::Item &item )
{
  return item.hasPayload<KCalCore::Journal::Ptr>();
}

QMimeData *CalendarSupport::createMimeData( const Akonadi::Item::List &items,
                                            const KDateTime::Spec &timeSpec )
{
  if ( items.isEmpty() ) {
    return 0;
  }

  KCalCore::MemoryCalendar::Ptr cal( new KCalCore::MemoryCalendar( timeSpec ) );

  QList<QUrl> urls;
  int incidencesFound = 0;
  Q_FOREACH ( const Akonadi::Item &item, items ) {
    const KCalCore::Incidence::Ptr incidence( CalendarSupport::incidence( item ) );
    if ( !incidence ) {
      continue;
    }
    ++incidencesFound;
    urls.push_back( item.url() );
    KCalCore::Incidence::Ptr i( incidence->clone() );
    cal->addIncidence( i );
  }

  if ( incidencesFound == 0 ) {
    return 0;
  }

  std::auto_ptr<QMimeData> mimeData( new QMimeData );

  mimeData->setUrls( urls );

  KCalUtils::ICalDrag::populateMimeData( mimeData.get(), cal );
  KCalUtils::VCalDrag::populateMimeData( mimeData.get(), cal );

  return mimeData.release();
}

QMimeData *CalendarSupport::createMimeData( const Akonadi::Item &item,
                                            const KDateTime::Spec &timeSpec )
{
  return createMimeData( Akonadi::Item::List() << item, timeSpec );
}

#ifndef QT_NO_DRAGANDDROP
QDrag *CalendarSupport::createDrag( const Akonadi::Item &item,
                                    const KDateTime::Spec &timeSpec, QWidget *parent )
{
  return createDrag( Akonadi::Item::List() << item, timeSpec, parent );
}
#endif

static QByteArray findMostCommonType( const Akonadi::Item::List &items )
{
  QByteArray prev;
  if ( items.isEmpty() ) {
    return "Incidence";
  }

  Q_FOREACH ( const Akonadi::Item &item, items ) {
    if ( !CalendarSupport::hasIncidence( item ) ) {
      continue;
    }
    const QByteArray type = CalendarSupport::incidence( item )->typeStr();
    if ( !prev.isEmpty() && type != prev ) {
      return "Incidence";
    }
    prev = type;
  }
  return prev;
}

#ifndef QT_NO_DRAGANDDROP
QDrag *CalendarSupport::createDrag( const Akonadi::Item::List &items,
                                    const KDateTime::Spec &timeSpec, QWidget *parent )
{
  std::auto_ptr<QDrag> drag( new QDrag( parent ) );
  drag->setMimeData( CalendarSupport::createMimeData( items, timeSpec ) );

  const QByteArray common = findMostCommonType( items );
  if ( common == "Event" ) {
    drag->setPixmap( BarIcon( QLatin1String( "view-calendar-day" ) ) );
  } else if ( common == "Todo" ) {
    drag->setPixmap( BarIcon( QLatin1String( "view-calendar-tasks" ) ) );
  }

  return drag.release();
}
#endif

static bool itemMatches( const Akonadi::Item &item, const KCalCore::CalFilter *filter )
{
  assert( filter );
  KCalCore::Incidence::Ptr inc = CalendarSupport::incidence( item );
  if ( !inc ) {
    return false;
  }
  return filter->filterIncidence( inc );
}

Akonadi::Item::List CalendarSupport::applyCalFilter( const Akonadi::Item::List &items_,
                                                     const KCalCore::CalFilter *filter )
{
  Q_ASSERT( filter );
  Akonadi::Item::List items( items_ );
  items.erase( std::remove_if( items.begin(), items.end(),
                               !bind( itemMatches, _1, filter ) ), items.end() );
  return items;
}

bool CalendarSupport::isValidIncidenceItemUrl( const KUrl &url,
                                               const QStringList &supportedMimeTypes )
{
  if ( !url.isValid() ) {
    return false;
  }

  if ( url.scheme() != QLatin1String( "akonadi" ) ) {
    return false;
  }

  return supportedMimeTypes.contains( url.queryItem( QLatin1String( "type" ) ) );
}

bool CalendarSupport::isValidIncidenceItemUrl( const KUrl &url )
{
  return isValidIncidenceItemUrl( url,
                                  QStringList() << KCalCore::Event::eventMimeType()
                                                << KCalCore::Todo::todoMimeType()
                                                << KCalCore::Journal::journalMimeType()
                                                << KCalCore::FreeBusy::freeBusyMimeType() );
}

static bool containsValidIncidenceItemUrl( const QList<QUrl>& urls )
{
  return
    std::find_if( urls.begin(), urls.end(),
                  bind( CalendarSupport::isValidIncidenceItemUrl, _1 ) ) != urls.constEnd();
}

bool CalendarSupport::isValidTodoItemUrl( const KUrl &url )
{
  if ( !url.isValid() || url.scheme() != QLatin1String( "akonadi" ) ) {
    return false;
  }

  return url.queryItem( QLatin1String( "type" ) ) == KCalCore::Todo::todoMimeType();
}

bool CalendarSupport::canDecode( const QMimeData *md )
{
  Q_ASSERT( md );
  return
    containsValidIncidenceItemUrl( md->urls() ) ||
    KCalUtils::ICalDrag::canDecode( md ) ||
    KCalUtils::VCalDrag::canDecode( md );
}

QList<KUrl> CalendarSupport::incidenceItemUrls( const QMimeData *mimeData )
{
  QList<KUrl> urls;
  Q_FOREACH ( const KUrl &i, mimeData->urls() ) {
    if ( isValidIncidenceItemUrl( i ) ) {
      urls.push_back( i );
    }
  }
  return urls;
}

QList<KUrl> CalendarSupport::todoItemUrls( const QMimeData *mimeData )
{
  QList<KUrl> urls;

  Q_FOREACH ( const KUrl &i, mimeData->urls() ) {
    if ( isValidIncidenceItemUrl( i, QStringList() << KCalCore::Todo::todoMimeType() ) ) {
      urls.push_back( i );
    }
  }
  return urls;
}

bool CalendarSupport::mimeDataHasTodo( const QMimeData *mimeData )
{
  return !todoItemUrls( mimeData ).isEmpty() || !todos( mimeData, KDateTime::Spec() ).isEmpty();
}

bool CalendarSupport::mimeDataHasIncidence( const QMimeData *mimeData )
{
  return !incidenceItemUrls( mimeData ).isEmpty() ||
         !incidences( mimeData, KDateTime::Spec() ).isEmpty();
}

KCalCore::Todo::List CalendarSupport::todos( const QMimeData *mimeData,
                                             const KDateTime::Spec &spec )
{
  KCalCore::Todo::List todos;

#ifndef QT_NO_DRAGANDDROP
  KCalCore::Calendar::Ptr cal( KCalUtils::DndFactory::createDropCalendar( mimeData, spec ) );
  if ( cal ) {
    Q_FOREACH ( const KCalCore::Todo::Ptr &i, cal->todos() ) {
      todos.push_back( KCalCore::Todo::Ptr( i->clone() ) );
    }
  }
#endif

  return todos;
}

KCalCore::Incidence::List CalendarSupport::incidences( const QMimeData *mimeData,
                                                       const KDateTime::Spec &spec )
{
  KCalCore::Incidence::List incidences;

#ifndef QT_NO_DRAGANDDROP
  KCalCore::Calendar::Ptr cal( KCalUtils::DndFactory::createDropCalendar( mimeData, spec ) );
  if ( cal ) {
    KCalCore::Incidence::List calIncidences = cal->incidences();
    Q_FOREACH ( const KCalCore::Incidence::Ptr &i, calIncidences ) {
      incidences.push_back( KCalCore::Incidence::Ptr( i->clone() ) );
    }
  }
#endif

  return incidences;
}

Akonadi::Collection CalendarSupport::selectCollection( QWidget *parent,
                                                       int &dialogCode,
                                                       const QStringList &mimeTypes,
                                                       const Akonadi::Collection &defCollection )
{
  QPointer<Akonadi::CollectionDialog> dlg( new Akonadi::CollectionDialog( parent ) );
  dlg->setCaption( i18n( "Select Calendar" ) );
  dlg->setDescription( i18n( "Select the calendar where this item will be stored." ) );
  dlg->changeCollectionDialogOptions( Akonadi::CollectionDialog::KeepTreeExpanded );
  kDebug() << "selecting collections with mimeType in " << mimeTypes;

  dlg->setMimeTypeFilter( mimeTypes );
  dlg->setAccessRightsFilter( Akonadi::Collection::CanCreateItem );
  if ( defCollection.isValid() ) {
    dlg->setDefaultCollection( defCollection );
  }
  Akonadi::Collection collection;

  // FIXME: don't use exec.
  dialogCode = dlg->exec();
  if ( dlg && dialogCode == QDialog::Accepted ) {
    collection = dlg->selectedCollection();

    if ( !collection.isValid() ) {
      kWarning() << "An invalid collection was selected!";
    }
  }
  delete dlg;
  return collection;
}

Akonadi::Item CalendarSupport::itemFromIndex( const QModelIndex &idx )
{
  Akonadi::Item item = idx.data( Akonadi::EntityTreeModel::ItemRole ).value<Akonadi::Item>();
  item.setParentCollection(
    idx.data( Akonadi::EntityTreeModel::ParentCollectionRole ).value<Akonadi::Collection>() );
  return item;
}

Akonadi::Collection::List CalendarSupport::collectionsFromModel( const QAbstractItemModel *model,
                                                                 const QModelIndex &parentIndex,
                                                                 int start, int end )
{
  const int endRow = end >= 0 ? end : model->rowCount( parentIndex ) - 1;
  Akonadi::Collection::List collections;
  int row = start;
  QModelIndex i = model->index( row, 0, parentIndex );
  while ( row <= endRow ) {
    const Akonadi::Collection collection = collectionFromIndex( i );
    if ( collection.isValid() ) {
      collections << collection;
      QModelIndex childIndex = i.child( 0, 0 );
      if ( childIndex.isValid() ) {
        collections << collectionsFromModel( model, i );
      }
    }
    ++row;
    i = i.sibling( row, 0 );
  }
  return collections;
}

Akonadi::Item::List CalendarSupport::itemsFromModel( const QAbstractItemModel * model,
                                                     const QModelIndex &parentIndex,
                                                     int start, int end )
{
  const int endRow = end >= 0 ? end : model->rowCount( parentIndex ) - 1;
  Akonadi::Item::List items;
  int row = start;
  QModelIndex i = model->index( row, 0, parentIndex );
  while ( row <= endRow ) {
    const Akonadi::Item item = itemFromIndex( i );
    if ( CalendarSupport::hasIncidence( item ) ) {
      items << item;
    } else {
      QModelIndex childIndex = i.child( 0, 0 );
      if ( childIndex.isValid() ) {
        items << itemsFromModel( model, i );
      }
    }
    ++row;
    i = i.sibling( row, 0 );
  }
  return items;
}

Akonadi::Collection CalendarSupport::collectionFromIndex( const QModelIndex &index )
{
  return index.data( Akonadi::EntityTreeModel::CollectionRole ).value<Akonadi::Collection>();
}

Akonadi::Collection::Id CalendarSupport::collectionIdFromIndex( const QModelIndex &index )
{
  return index.data( Akonadi::EntityTreeModel::CollectionIdRole ).value<Akonadi::Collection::Id>();
}

Akonadi::Collection::List CalendarSupport::collectionsFromIndexes( const QModelIndexList &indexes )
{
  Akonadi::Collection::List l;
  Q_FOREACH ( const QModelIndex &idx, indexes ) {
    l.push_back( collectionFromIndex( idx ) );
  }
  return l;
}

QString CalendarSupport::displayName( Akonadi::ETMCalendar *calendar, const Akonadi::Collection &c )
{
  Akonadi::Collection fullCollection;
  if ( calendar && calendar->collection( c.id() ).isValid() ) {
    fullCollection = calendar->collection( c.id() );
  } else {
    fullCollection = c;
  }

  QString cName = fullCollection.name();
  const QString resourceName = fullCollection.resource();

  // Kolab Groupware
  if ( resourceName.contains( QLatin1String( "kolabproxy" ) ) ) {
    QString typeStr = cName; // contents type: "Calendar", "Tasks", etc
    QString ownerStr;        // folder owner: "fred", "ethel", etc
    QString nameStr;         // folder name: "Public", "Test", etc
    if ( calendar ) {
      Akonadi::Collection p = c.parentCollection();
      while ( p != Akonadi::Collection::root() ) {
        Akonadi::Collection tCol = calendar->collection( p.id() );
        const QString tName = tCol.name();
        if ( tName.toLower().startsWith( QLatin1String( "shared.cal" ) ) ) {
          ownerStr = QLatin1String("Shared");
          nameStr = cName;
          typeStr = QLatin1String("Calendar");
          break;
        } else if ( tName.toLower().startsWith( QLatin1String( "shared.tasks" ) ) ||
                    tName.toLower().startsWith( QLatin1String( "shared.todo" ) ) ) {
          ownerStr = QLatin1String("Shared");
          nameStr = cName;
          typeStr = QLatin1String("Tasks");
          break;
        } else if ( tName.toLower().startsWith( QLatin1String( "shared.journal" ) ) ) {
          ownerStr = QLatin1String("Shared");
          nameStr = cName;
          typeStr = QLatin1String("Journal");
          break;
        } else if ( tName.toLower().startsWith( QLatin1String( "shared.notes" ) ) ) {
          ownerStr = QLatin1String("Shared");
          nameStr = cName;
          typeStr = QLatin1String("Notes");
          break;
        } else if ( tName != i18n( "Calendar" ) &&
                    tName != i18n( "Tasks" ) &&
                    tName != i18n( "Journal" ) &&
                    tName != i18n( "Notes" ) ) {
          ownerStr = tName;
          break;
        } else {
          nameStr = typeStr;
          typeStr = tName;
        }
        p = p.parentCollection();
      }
    }

    if ( !ownerStr.isEmpty() ) {
      if ( ownerStr.toUpper() == QLatin1String( "INBOX" ) ) {
        return i18nc( "%1 is folder contents",
                      "My Kolab %1", typeStr );
      } else if ( ownerStr.toUpper() == QLatin1String( "SHARED" ) ) {
        return i18nc( "%1 is folder name, %2 is folder contents",
                      "Shared Kolab %1 %2", nameStr, typeStr );
      } else {
        if ( nameStr.isEmpty() ) {
          return i18nc( "%1 is folder owner name, %2 is folder contents",
                        "%1's Kolab %2", ownerStr, typeStr );
        } else {
          return i18nc( "%1 is folder owner name, %2 is folder name, %3 is folder contents",
                        "%1's %2 Kolab %3", ownerStr, nameStr, typeStr );
        }
      }
    } else {
      return i18nc( "%1 is folder contents",
                    "Kolab %1", typeStr );
    }
  } //end kolab section

  // Dav Groupware
  if ( resourceName.contains( QLatin1String( "davgroupware" ) ) ) {
    const QString resourceDisplayName = Akonadi::AgentManager::self()->instance(resourceName).name();
    return i18nc( "%1 is the folder name", "%1 in %2", fullCollection.displayName(), resourceDisplayName );
  } //end caldav section

  // Google
  if ( resourceName.contains( QLatin1String( "google" ) ) ) {
    QString ownerStr;        // folder owner: "user@gmail.com"
    if ( calendar ) {
      Akonadi::Collection p = c.parentCollection();
      ownerStr = calendar->collection( p.id() ).displayName();
    }

    const QString nameStr = c.displayName(); // folder name: can be anything

    QString typeStr;
    const QString mimeStr = c.contentMimeTypes().join( QLatin1String(",") );
    if ( mimeStr.contains( QLatin1String(".event") ) ) {
      typeStr = i18n( "Calendar" );
    } else if ( mimeStr.contains( QLatin1String(".todo") ) ) {
      typeStr = i18n( "Tasks" );
    } else if ( mimeStr.contains( QLatin1String(".journal") ) ) {
      typeStr = i18n( "Journal" );
    } else if ( mimeStr.contains( QLatin1String(".note") ) ) {
      typeStr = i18n( "Notes" );
    } else {
      typeStr = mimeStr;
    }

    if ( !ownerStr.isEmpty() ) {
      const int atChar = ownerStr.lastIndexOf( QLatin1Char('@') );
      ownerStr = ownerStr.left( atChar );
      if ( nameStr.isEmpty() ) {
        return i18nc( "%1 is folder owner name, %2 is folder contents",
                      "%1's Google %2", ownerStr, typeStr );
      } else {
        return i18nc( "%1 is folder owner name, %2 is folder name",
                      "%1's %2", ownerStr, nameStr );
      }
    } else {
      return i18nc( "%1 is folder contents",
                    "Google %1", typeStr );
    }
  } //end google section

  // Not groupware so the collection is "mine"
  const QString dName = fullCollection.displayName();

  if ( !dName.isEmpty() ) {
    return fullCollection.name().startsWith( QLatin1String( "akonadi_" ) ) ? i18n( "My %1", dName ) : fullCollection.name();
  } else {
    return i18nc( "unknown resource", "Unknown" );
  }
}

QString CalendarSupport::subMimeTypeForIncidence( const KCalCore::Incidence::Ptr &incidence )
{
  return incidence->mimeType();
}

QList<QDate> CalendarSupport::workDays( const QDate &startDate,
                                        const QDate &endDate )
{
  QList<QDate> result;

  const int mask( ~( KCalPrefs::instance()->mWorkWeekMask ) );
  const int numDays = startDate.daysTo( endDate ) + 1;

  for ( int i = 0; i < numDays; ++i ) {
    const QDate date = startDate.addDays( i );
    if ( !( mask & ( 1 << ( date.dayOfWeek() - 1 ) ) ) ) {
      result.append( date );
    }
  }

  if ( KCalPrefs::instance()->mExcludeHolidays ) {
    // NOTE: KOGlobals, where this method comes from, used to hold a pointer to
    //       a KHolidays object. I'm not sure about how expensive it is, just
    //       creating one here.
    const HolidayRegion holidays( KCalPrefs::instance()->mHolidays );
    const Holiday::List list = holidays.holidays( startDate, endDate );
    const int listCount( list.count() );
    for ( int i = 0; i < listCount; ++i ) {
      const Holiday &h = list.at( i );
      if ( h.dayType() == Holiday::NonWorkday ) {
        result.removeAll( h.date() );
      }
    }
  }

  return result;
}

QStringList CalendarSupport::holiday( const QDate &date )
{
  QStringList hdays;

  const HolidayRegion holidays( KCalPrefs::instance()->mHolidays );
  const Holiday::List list = holidays.holidays( date );
  const int listCount( list.count() );
  for ( int i = 0; i < listCount; ++i ) {
    hdays.append( list.at( i ).text() );
  }
  return hdays;
}

void CalendarSupport::saveAttachments( const Akonadi::Item &item, QWidget *parentWidget )
{
  Incidence::Ptr incidence = CalendarSupport::incidence( item );

  if ( !incidence ) {
    KMessageBox::sorry(
      parentWidget,
      i18n( "No item selected." ) );
    return;
  }

  Attachment::List attachments = incidence->attachments();

  if ( attachments.empty() ) {
    return;
  }

  QString targetFile, targetDir;
  if ( attachments.count() > 1 ) {
    // get the dir
    targetDir = KFileDialog::getExistingDirectory( KUrl( "kfiledialog:///saveAttachment" ),
                                                   parentWidget,
                                                   i18n( "Save Attachments To" ) );
    if ( targetDir.isEmpty() ) {
      return;
    }

    // we may not get a slash-terminated url out of KFileDialog
    if ( !targetDir.endsWith( QLatin1Char('/') ) ) {
      targetDir.append( QLatin1Char('/') );
    }
  } else {
    // only one item, get the desired filename
    QString fileName = attachments.first()->label();
    if ( fileName.isEmpty() ) {
      fileName = i18nc( "filename for an unnamed attachment", "attachment.1" );
    }
    targetFile = KFileDialog::getSaveFileName( KUrl( QLatin1String("kfiledialog:///saveAttachment/") + fileName ),
                                   QString(),
                                   parentWidget,
                                   i18n( "Save Attachment" ), KFileDialog::ConfirmOverwrite );
    if ( targetFile.isEmpty() ) {
      return;
    }

    targetDir = QFileInfo( targetFile ).absolutePath() + QLatin1Char('/');
  }

  Q_FOREACH ( Attachment::Ptr attachment, attachments ) {
    targetFile = targetDir + attachment->label();
    KUrl sourceUrl;
    if ( attachment->isUri() ) {
      sourceUrl = attachment->uri();
    } else {
      sourceUrl = incidence->writeAttachmentToTempFile( attachment );
    }
    // save the attachment url
    if ( !KIO::NetAccess::file_copy( sourceUrl, KUrl( targetFile ) ) &&
        KIO::NetAccess::lastError() ) {
      KMessageBox::error( parentWidget, KIO::NetAccess::lastErrorString() );
    }
  }
}

QStringList CalendarSupport::categories( const KCalCore::Incidence::List &incidences )
{
  QStringList cats, thisCats;
  // @TODO: For now just iterate over all incidences. In the future,
  // the list of categories should be built when reading the file.
  Q_FOREACH ( const KCalCore::Incidence::Ptr &incidence, incidences ) {
    thisCats = incidence->categories();
    for ( QStringList::ConstIterator si = thisCats.constBegin();
          si != thisCats.constEnd(); ++si ) {
      if ( !cats.contains( *si ) ) {
        cats.append( *si );
      }
    }
  }
  return cats;
}

bool CalendarSupport::mergeCalendar(const QString &srcFilename, const KCalCore::Calendar::Ptr &destCalendar)
{
    if (srcFilename.isEmpty()) {
        kError() << "Empty filename.";
        return false;
    }

    if (!QFile::exists(srcFilename)) {
        kError() << "File'" << srcFilename << "' doesn't exist.";
    }

    bool loadedSuccesfully = true;

    // merge in a file
    destCalendar->startBatchAdding();
    KCalCore::FileStorage storage(destCalendar);
    storage.setFileName(srcFilename);
    loadedSuccesfully = storage.load();
    destCalendar->endBatchAdding();

    return loadedSuccesfully;
}
