/*
    This file is part of KOrganizer.

    Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org>
    Copyright (c) 2005 Reinhold Kainhofer <reinhold@kainhofer.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 "koeditorattachments.h"

#include <libkcal/incidence.h>
#include <libkdepim/kpimurlrequesterdlg.h>
#include <libkdepim/kfileio.h>

#include <klocale.h>
#include <kdebug.h>
#include <kmdcodec.h>
#include <kmessagebox.h>
#include <kiconview.h>
#include <krun.h>
#include <kurldrag.h>
#include <ktempfile.h>
#include <ktempdir.h>
#include <kio/netaccess.h>
#include <kmimetype.h>
#include <kiconloader.h>
#include <kfiledialog.h>
#include <kstdaction.h>
#include <kactioncollection.h>
#include <kpopupmenu.h>

#include <qfile.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qlistview.h>
#include <qpushbutton.h>
#include <qdragobject.h>
#include <qtooltip.h>
#include <qwhatsthis.h>
#include <qapplication.h>
#include <qclipboard.h>

#include <cassert>
#include <set>

class AttachmentListItem : public KIconViewItem
{
  public:
    AttachmentListItem( KCal::Attachment*att, QIconView *parent ) :
        KIconViewItem( parent )
    {
      if ( att ) {
        mAttachment = new KCal::Attachment( *att );
      } else {
        mAttachment = new KCal::Attachment( QString::null );
      }
      readAttachment();
      setDragEnabled( true );
    }
    ~AttachmentListItem() { delete mAttachment; }
    KCal::Attachment *attachment() const { return mAttachment; }

    void setUri( const QString &uri )
    {
      mAttachment->setUri( uri );
      readAttachment();
    }
    void setData( const char *base64 )
    {
      mAttachment->setData( base64 );
      readAttachment();
    }
    void setMimeType( const QString &mime )
    {
      mAttachment->setMimeType( mime );
      readAttachment();
    }
    void setLabel( const QString &label )
    {
      mAttachment->setLabel( label );
      readAttachment();
    }

    void readAttachment()
    {
      if ( mAttachment->isUri() )
        setText( mAttachment->uri() );
      else {
        if ( mAttachment->label().isEmpty() )
          setText( i18n("[Binary data]") );
        else
          setText( mAttachment->label() );
      }
      KMimeType::Ptr mt = KMimeType::mimeType( mAttachment->mimeType() );
      if ( mt ) {
          const QString iconName( mt->icon( QString(), false ) );
          QPixmap pix = KGlobal::iconLoader( )->loadIcon( iconName, KIcon::Small );
          if ( pix.isNull() )
            pix = KGlobal::iconLoader( )->loadIcon( "unknown", KIcon::Small );
            if ( !pix.isNull() )
              setPixmap( pix );
      }
    }

  private:
    KCal::Attachment *mAttachment;
};

class AttachmentIconView : public KIconView
{
    friend class KOEditorAttachments;
    public:
        AttachmentIconView( KOEditorAttachments* parent=0 )
            :KIconView( parent ),
             mParent( parent )
        {
            setAcceptDrops( true );
            setSelectionMode( QIconView::Extended );
            setMode( KIconView::Select );
            setItemTextPos( QIconView::Right );
            setArrangement( QIconView::LeftToRight );
            setMaxItemWidth( QMAX(maxItemWidth(), 250) );
            setMinimumHeight( QMAX(fontMetrics().height(), 16) + 12 );
        }
        ~AttachmentIconView()
        {
            for ( std::set<KTempDir*>::iterator it = mTempDirs.begin() ; it != mTempDirs.end() ; ++it ) {
                delete *it;
            }
        }
    protected:
        QDragObject * dragObject()
        {
            KURL::List urls;
            for ( QIconViewItem *it = firstItem( ); it; it = it->nextItem( ) ) {
                if ( !it->isSelected() ) continue;
                AttachmentListItem * item = dynamic_cast<AttachmentListItem*>( it );
                if ( !item ) return 0;
                KCal::Attachment * att = item->attachment();
                assert( att );
                KURL url;
                if ( att->isUri() ) {
                    url.setPath( att->uri() );
                } else {
                    KTempDir * tempDir = new KTempDir(); // will be deleted on editor close
                    tempDir->setAutoDelete( true );
                    mTempDirs.insert( tempDir );
                    QByteArray encoded;
                    encoded.duplicate( att->data(), strlen(att->data()) );
                    QByteArray decoded;
                    KCodecs::base64Decode( encoded, decoded );
                    const QString fileName = tempDir->name( ) + "/" + att->label();
                    KPIM::kByteArrayToFile( decoded, fileName, false, false, false );
                    url.setPath( fileName );
                }
                urls << url;
            }
            KURLDrag *drag  = new KURLDrag( urls, this );
            return drag;
        }
        void contentsDropEvent( QDropEvent* event )
        {
          mParent->handlePasteOrDrop( event );
        }
    private:
        std::set<KTempDir*> mTempDirs;
        KOEditorAttachments* mParent;
};

KOEditorAttachments::KOEditorAttachments( int spacing, QWidget *parent,
                                          const char *name )
  : QWidget( parent, name )
{
  QBoxLayout *topLayout = new QHBoxLayout( this );
  topLayout->setSpacing( spacing );

  QLabel *label = new QLabel( i18n("Attachments:"), this );
  topLayout->addWidget( label );

  mAttachments = new AttachmentIconView( this );
  QWhatsThis::add( mAttachments,
                   i18n("Displays a list of current items (files, mail, etc.) "
                        "that have been associated with this event or to-do. ") );
  topLayout->addWidget( mAttachments );
  connect( mAttachments, SIGNAL( doubleClicked( QIconViewItem * ) ),
           SLOT( showAttachment( QIconViewItem * ) ) );
  connect( mAttachments, SIGNAL(selectionChanged()),
           SLOT(selectionChanged()) );
  connect( mAttachments, SIGNAL(contextMenuRequested(QIconViewItem*,const QPoint&)),
           SLOT(contextMenu(QIconViewItem*,const QPoint&)) );

  mAddMenu = new KPopupMenu( this );
  mContextMenu = new KPopupMenu( this );

  KActionCollection* ac = new KActionCollection( this, this );

  mOpenAction = new KAction( i18n("View"), 0, this, SLOT(slotShow()), ac );
  mOpenAction->plug( mContextMenu );
  mContextMenu->insertSeparator();

  mCopyAction = KStdAction::copy(this, SLOT(slotCopy( ) ), ac );
  mCopyAction->plug( mContextMenu );
  mCutAction = KStdAction::cut(this, SLOT(slotCut( ) ), ac );
  mCutAction->plug( mContextMenu );
  KAction *action = KStdAction::paste(this, SLOT(slotPaste( ) ), ac );
  action->plug( mContextMenu );

  action = new KAction( i18n("&Attach File..."), 0, this, SLOT(slotAddData()), ac );
  action->setWhatsThis( i18n("Shows a dialog used to select an attachment "
                        "to add to this event or to-do as link as inline data.") );
  action->plug( mAddMenu );
  action = new KAction( i18n("Attach &Link..."), 0, this, SLOT(slotAdd()), ac );
  action->setWhatsThis( i18n("Shows a dialog used to select an attachment "
                        "to add to this event or to-do as link.") );
  action->plug( mAddMenu );

  QPushButton *addButton = new QPushButton( this );
  addButton->setIconSet( SmallIconSet( "add" ) );
  addButton->setPopup( mAddMenu );
  topLayout->addWidget( addButton );

  mRemoveBtn = new QPushButton( this );
  mRemoveBtn->setIconSet( SmallIconSet( "remove" ) );
  QToolTip::add( mRemoveBtn, i18n("&Remove") );
  QWhatsThis::add( mRemoveBtn,
                   i18n("Removes the attachment selected in the list above "
                        "from this event or to-do.") );
  topLayout->addWidget( mRemoveBtn );
  connect( mRemoveBtn, SIGNAL( clicked() ), SLOT( slotRemove() ) );

  selectionChanged();
  setAcceptDrops( true );
}

KOEditorAttachments::~KOEditorAttachments()
{
}

bool KOEditorAttachments::hasAttachments()
{
  return mAttachments->count() != 0;
}

void KOEditorAttachments::dragEnterEvent( QDragEnterEvent* event )
{
  event->accept( KURLDrag::canDecode( event ) | QTextDrag::canDecode( event ) );
}

void KOEditorAttachments::handlePasteOrDrop( QMimeSource* source )
{
  KURL::List urls;
  QString text;
  if ( KURLDrag::decode( source, urls ) ) {
    const bool asUri = KMessageBox::questionYesNo( this,
            i18n("Do you want to link to the attachments, or include them in the event?"),
            i18n("Attach as link?"), i18n("As Link"), i18n("As File") ) == KMessageBox::Yes;
    for ( KURL::List::ConstIterator it = urls.begin(); it != urls.end(); ++it ) {
      addAttachment( (*it).url(), QString::null, asUri );
    }
  } else if ( QTextDrag::decode( source, text ) ) {
    QStringList lst = QStringList::split( '\n', text );
    for ( QStringList::ConstIterator it = lst.begin(); it != lst.end(); ++it ) {
      addAttachment( (*it)  );
    }
  }
}

void KOEditorAttachments::dropEvent( QDropEvent* event )
{
    handlePasteOrDrop( event );
}

void KOEditorAttachments::showAttachment( QIconViewItem *item )
{
  AttachmentListItem *attitem = static_cast<AttachmentListItem*>(item);
  if ( !attitem || !attitem->attachment() ) return;

  KCal::Attachment *att = attitem->attachment();
  if ( att->isUri() ) {
    emit openURL( att->uri() );
  } else {
    KTempFile f;
    if ( !f.file() )
      return;
    QByteArray encoded;
    encoded.duplicate( att->data(), strlen(att->data()) );
    QByteArray decoded;
    KCodecs::base64Decode( encoded, decoded );
    f.file()->writeBlock( decoded );
    f.file()->close();
    KRun::runURL( f.name(), att->mimeType(), true, false );
  }
}

void KOEditorAttachments::slotAdd()
{
  KURL uri = KPimURLRequesterDlg::getURL( QString::null, i18n(
         "URL (e.g. a web page) or file to be attached (only "
         "the link will be attached, not the file itself):"), this,
                                       i18n("Add Attachment") );
  if ( !uri.isEmpty() ) {
    addAttachment( uri );
  }
}

void KOEditorAttachments::slotAddData()
{
  KURL uri = KFileDialog::getOpenFileName( QString(), QString(), this, i18n("Add Attachment") );
  if ( !uri.isEmpty() ) {
    addAttachment( uri, QString::null, false );
  }
}

void KOEditorAttachments::slotEdit()
{
  QIconViewItem *item = mAttachments->currentItem();
  AttachmentListItem *attitem = static_cast<AttachmentListItem*>(item);
  if ( !attitem || !attitem->attachment() ) return;

  KCal::Attachment *att = attitem->attachment();
  if ( att->isUri() ) {
    KURL uri = KPimURLRequesterDlg::getURL( att->uri(), i18n(
         "URL (e.g. a web page) or file to be attached (only "
         "the link will be attached, not the file itself):"), this,
                                         i18n("Edit Attachment") );

    if ( !uri.isEmpty() )
      attitem->setUri( uri.url() );
  } else {
    KURL uri = KPimURLRequesterDlg::getURL( QString::null, i18n(
         "File to be attached:"), this, i18n("Add Attachment") );
    if ( !uri.isEmpty() ) {
          QString tmpFile;
      if ( KIO::NetAccess::download( uri, tmpFile, this ) ) {
        QFile f( tmpFile );
        if ( !f.open( IO_ReadOnly ) )
          return;
        QByteArray data = f.readAll();
        f.close();
        attitem->setData( KCodecs::base64Encode( data ) );
        attitem->setMimeType( KIO::NetAccess::mimetype( uri, this ) );
        QString label = uri.fileName();
        if ( label.isEmpty() )
          label = uri.prettyURL();
        attitem->setLabel( label );
        KIO::NetAccess::removeTempFile( tmpFile );
      }
    }
  }
}

void KOEditorAttachments::slotRemove()
{
    QValueList<QIconViewItem*> selected;
    for ( QIconViewItem *it = mAttachments->firstItem( ); it; it = it->nextItem( ) ) {
        if ( !it->isSelected() ) continue;
        selected << it;
    }
    if ( selected.isEmpty() || KMessageBox::warningContinueCancel(this,
                    selected.count() == 1?i18n("This item will be permanently deleted."):
                    i18n("The selected items will be permanently deleted."),
                    i18n("KOrganizer Confirmation"),KStdGuiItem::del()) != KMessageBox::Continue )
        return;

    for ( QValueList<QIconViewItem*>::iterator it( selected.begin() ), end( selected.end() ); it != end ; ++it ) {
        delete *it;
    }
}

void KOEditorAttachments::slotShow()
{
  for ( QIconViewItem *it = mAttachments->firstItem(); it; it = it->nextItem() ) {
    if ( !it->isSelected() )
      continue;
    showAttachment( it );
  }
}

void KOEditorAttachments::setDefaults()
{
  mAttachments->clear();
}

void KOEditorAttachments::addAttachment( const KURL &uri,
                                         const QString &mimeType, bool asUri )
{
  AttachmentListItem *item = new AttachmentListItem( 0, mAttachments );
  if ( asUri ) {
    item->setUri( uri.url() );
    if ( !mimeType.isEmpty() ) item->setMimeType( mimeType );
  } else {
    QString tmpFile;
    if ( KIO::NetAccess::download( uri, tmpFile, this ) ) {
      QFile f( tmpFile );
      if ( !f.open( IO_ReadOnly ) )
        return;
      QByteArray data = f.readAll();
      f.close();
      item->setData( KCodecs::base64Encode( data ) );
      if ( !mimeType.isEmpty() )
        item->setMimeType( mimeType );
      else
        item->setMimeType( KIO::NetAccess::mimetype( uri, this ) );
      QString label = uri.fileName();
      if ( label.isEmpty() )
        label = uri.prettyURL();
      item->setLabel( label );
      KIO::NetAccess::removeTempFile( tmpFile );
    }
  }
}


void KOEditorAttachments::addAttachment( KCal::Attachment *attachment )
{
  new AttachmentListItem( attachment, mAttachments );
}

void KOEditorAttachments::readIncidence( KCal::Incidence *i )
{
  mAttachments->clear();

  KCal::Attachment::List attachments = i->attachments();
  KCal::Attachment::List::ConstIterator it;
  for( it = attachments.begin(); it != attachments.end(); ++it ) {
    addAttachment( (*it) );
  }
  if ( mAttachments->count() > 0 ) {
    QTimer::singleShot( 0, mAttachments, SLOT(arrangeItemsInGrid()) );
  }
}

void KOEditorAttachments::writeIncidence( KCal::Incidence *i )
{
  i->clearAttachments();

  QIconViewItem *item;
  AttachmentListItem *attitem;
  for( item = mAttachments->firstItem(); item; item = item->nextItem() ) {
    attitem = static_cast<AttachmentListItem*>(item);
    if ( attitem )
      i->addAttachment( new KCal::Attachment( *(attitem->attachment() ) ) );
  }
}


void KOEditorAttachments::slotCopy()
{
    QApplication::clipboard()->setData( mAttachments->dragObject(), QClipboard::Clipboard );
}

void KOEditorAttachments::slotCut()
{
    slotCopy();
    slotRemove();
}

void KOEditorAttachments::slotPaste()
{
    handlePasteOrDrop( QApplication::clipboard()->data() );
}

void KOEditorAttachments::selectionChanged()
{
  bool selected = false;
  for ( QIconViewItem *item = mAttachments->firstItem(); item; item = item->nextItem() ) {
    if ( item->isSelected() ) {
      selected = true;
      break;
    }
  }
  mRemoveBtn->setEnabled( selected );
}

void KOEditorAttachments::contextMenu(QIconViewItem * item, const QPoint & pos)
{
  const bool enable = item != 0;
  mOpenAction->setEnabled( enable );
  mCopyAction->setEnabled( enable );
  mCutAction->setEnabled( enable );
  mContextMenu->exec( pos );
}

#include "koeditorattachments.moc"
