// -*- mode: C++; c-file-style: "gnu" -*-
/**
 * folderdiaacltab.cpp
 *
 * Copyright (c) 2004 David Faure <faure@kde.org>
 *
 *
 *  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; version 2 of the License
 *
 *  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.
 *
 *  In addition, as a special exception, the copyright holders give
 *  permission to link the code of this program with any edition of
 *  the TQt library by Trolltech AS, Norway (or with modified versions
 *  of TQt that use the same license as TQt), and distribute linked
 *  combinations including the two.  You must obey the GNU General
 *  Public License in all respects for all of the code used other than
 *  TQt.  If you modify this file, you may extend this exception to
 *  your version of the file, but you are not obligated to do so.  If
 *  you do not wish to do so, delete this exception statement from
 *  your version.
 */

#include <config.h> // FOR TDEPIM_NEW_DISTRLISTS

#include "folderdiaacltab.h"
#include "acljobs.h"
#include "kmfolderimap.h"
#include "kmfoldercachedimap.h"
#include "kmacctcachedimap.h"
#include "kmfolder.h"

#include <addressesdialog.h>
#include <tdeabc/addresseelist.h>
#ifdef TDEPIM_NEW_DISTRLISTS
#include <libtdepim/distributionlist.h> // libtdepim
#else
#include <tdeabc/distributionlist.h>
#endif
#include <tdeabc/stdaddressbook.h>
#include <kaddrbook.h>
#include <kpushbutton.h>
#include <kdebug.h>
#include <tdelocale.h>

#include <tqlayout.h>
#include <tqlabel.h>
#include <tqvbox.h>
#include <tqvbuttongroup.h>
#include <tqwidgetstack.h>
#include <tqradiobutton.h>
#include <tqwhatsthis.h>

#include <assert.h>
#include <tdemessagebox.h>

using namespace KMail;

// In case your tdelibs is < 3.3
#ifndef I18N_NOOP2
#define I18N_NOOP2( comment,x ) x
#endif

// The set of standard permission sets
static const struct {
  unsigned int permissions;
  const char* userString;
} standardPermissions[] = {
  { 0, I18N_NOOP2( "Permissions", "None" ) },
  { ACLJobs::List | ACLJobs::Read | ACLJobs::WriteSeenFlag, I18N_NOOP2( "Permissions", "Read" ) },
  { ACLJobs::List | ACLJobs::Read | ACLJobs::WriteSeenFlag | ACLJobs::Insert | ACLJobs::Post, I18N_NOOP2( "Permissions", "Append" ) },
  { ACLJobs::AllWrite, I18N_NOOP2( "Permissions", "Write" ) },
  { ACLJobs::All, I18N_NOOP2( "Permissions", "All" ) }
};


KMail::ACLEntryDialog::ACLEntryDialog( IMAPUserIdFormat userIdFormat, const TQString& caption, TQWidget* parent, const char* name )
  : KDialogBase( parent, name, true /*modal*/, caption,
                 KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Ok, true /*sep*/ )
  , mUserIdFormat( userIdFormat )
{
  TQWidget *page = new TQWidget( this );
  setMainWidget(page);
  TQGridLayout *topLayout = new TQGridLayout( page, 4 /*rows*/, 3 /*cols*/, 0, spacingHint() );

  TQLabel *label = new TQLabel( i18n( "&User identifier:" ), page );
  topLayout->addWidget( label, 0, 0 );

  mUserIdLineEdit = new KLineEdit( page );
  topLayout->addWidget( mUserIdLineEdit, 0, 1 );
  label->setBuddy( mUserIdLineEdit );
  TQWhatsThis::add( mUserIdLineEdit, i18n( "The User Identifier is the login of the user on the IMAP server. This can be a simple user name or the full email address of the user; the login for your own account on the server will tell you which one it is." ) );

  TQPushButton* kabBtn = new TQPushButton( i18n( "Se&lect..." ), page );
  topLayout->addWidget( kabBtn, 0, 2 );

  mButtonGroup = new TQVButtonGroup( i18n( "Permissions" ), page );
  topLayout->addMultiCellWidget( mButtonGroup, 1, 1, 0, 2 );

  for ( unsigned int i = 0;
        i < sizeof( standardPermissions ) / sizeof( *standardPermissions );
        ++i ) {
    TQRadioButton* cb = new TQRadioButton( i18n( "Permissions", standardPermissions[i].userString ), mButtonGroup );
    // We store the permission value (bitfield) as the id of the radiobutton in the group
    mButtonGroup->insert( cb, standardPermissions[i].permissions );
  }
  topLayout->setRowStretch(2, 10);

  TQLabel *noteLabel = new TQLabel( i18n( "<b>Note: </b>Renaming requires write permissions on the parent folder." ), page );
  topLayout->addMultiCellWidget( noteLabel, 2, 2, 0, 2 );

  connect( mUserIdLineEdit, TQT_SIGNAL( textChanged( const TQString& ) ), TQT_SLOT( slotChanged() ) );
  connect( kabBtn, TQT_SIGNAL( clicked() ), TQT_SLOT( slotSelectAddresses() ) );
  connect( mButtonGroup, TQT_SIGNAL( clicked( int ) ), TQT_SLOT( slotChanged() ) );
  enableButtonOK( false );

  mUserIdLineEdit->setFocus();
  // Ensure the lineedit is rather wide so that email addresses can be read in it
  incInitialSize( TQSize( 200, 0 ) );
}

void KMail::ACLEntryDialog::slotChanged()
{
  enableButtonOK( !mUserIdLineEdit->text().isEmpty() && mButtonGroup->selected() != 0 );
}

static TQString addresseeToUserId( const TDEABC::Addressee& addr, IMAPUserIdFormat userIdFormat )
{
  TQString email = addr.preferredEmail();
  if ( userIdFormat == FullEmail )
    return email;
  else { // mUserIdFormat == UserName
    email.truncate( email.find( '@' ) );
    return email;
  }
}

void KMail::ACLEntryDialog::slotSelectAddresses()
{
  KPIM::AddressesDialog dlg( this );
  dlg.setShowCC( false );
  dlg.setShowBCC( false );
  if ( mUserIdFormat == FullEmail ) // otherwise we have no way to go back from userid to email
    dlg.setSelectedTo( userIds() );
  if ( dlg.exec() != TQDialog::Accepted )
    return;

  const TQStringList distrLists = dlg.toDistributionLists();
  TQString txt = distrLists.join( ", " );
  const TDEABC::Addressee::List lst = dlg.toAddresses();
  if ( !lst.isEmpty() ) {
    for( TQValueList<TDEABC::Addressee>::ConstIterator it = lst.begin(); it != lst.end(); ++it ) {
      if ( !txt.isEmpty() )
        txt += ", ";
      txt += addresseeToUserId( *it, mUserIdFormat );
    }
  }
  mUserIdLineEdit->setText( txt );
}

void KMail::ACLEntryDialog::setValues( const TQString& userId, unsigned int permissions )
{
  mUserIdLineEdit->setText( userId );
  mButtonGroup->setButton( permissions );
  enableButtonOK( !userId.isEmpty() );
}

TQString KMail::ACLEntryDialog::userId() const
{
  return mUserIdLineEdit->text();
}

TQStringList KMail::ACLEntryDialog::userIds() const
{
  return KPIM::splitEmailAddrList( mUserIdLineEdit->text() );
}

unsigned int KMail::ACLEntryDialog::permissions() const
{
  return mButtonGroup->selectedId();
}

// class KMail::FolderDiaACLTab::ListView : public TDEListView
// {
// public:
//   ListView( TQWidget* parent, const char* name = 0 ) : TDEListView( parent, name ) {}
// };

class KMail::FolderDiaACLTab::ListViewItem : public TDEListViewItem
{
public:
  ListViewItem( TQListView* listview )
    : TDEListViewItem( listview, listview->lastItem() ),
      mModified( false ), mNew( false ) {}

  void load( const ACLListEntry& entry );
  void save( ACLList& list,
#ifdef TDEPIM_NEW_DISTRLISTS
             TDEABC::AddressBook* abook,
#else
             TDEABC::DistributionListManager& manager,
#endif
             IMAPUserIdFormat userIdFormat );

  TQString userId() const { return text( 0 ); }
  void setUserId( const TQString& userId ) { setText( 0, userId ); }

  unsigned int permissions() const { return mPermissions; }
  void setPermissions( unsigned int permissions );

  bool isModified() const { return mModified; }
  void setModified( bool b ) { mModified = b; }

  // The fact that an item is new doesn't matter much.
  // This bool is only used to handle deletion differently
  bool isNew() const { return mNew; }
  void setNew( bool b ) { mNew = b; }

private:
  unsigned int mPermissions;
  TQString mInternalRightsList; ///< protocol-dependent string (e.g. IMAP rights list)
  bool mModified;
  bool mNew;
};

// internalRightsList is only used if permissions doesn't match the standard set
static TQString permissionsToUserString( unsigned int permissions, const TQString& internalRightsList )
{
  for ( unsigned int i = 0;
        i < sizeof( standardPermissions ) / sizeof( *standardPermissions );
        ++i ) {
    if ( permissions == standardPermissions[i].permissions )
      return i18n( "Permissions", standardPermissions[i].userString );
  }
  if ( internalRightsList.isEmpty() )
    return i18n( "Custom Permissions" ); // not very helpful, but shouldn't happen
  else
    return i18n( "Custom Permissions (%1)" ).arg( internalRightsList );
}

void KMail::FolderDiaACLTab::ListViewItem::setPermissions( unsigned int permissions )
{
  mPermissions = permissions;
  setText( 1, permissionsToUserString( permissions, TQString() ) );
}

void KMail::FolderDiaACLTab::ListViewItem::load( const ACLListEntry& entry )
{
  // Don't allow spaces in userids. If you need this, fix the slave->app communication,
  // since it uses space as a separator (imap4.cc, look for GETACL)
  // It's ok in distribution list names though, that's why this check is only done here
  // and also why there's no validator on the lineedit.
  if ( entry.userId.contains( ' ' ) )
    kdWarning(5006) << "Userid contains a space!!!  '" << entry.userId << "'" << endl;

  setUserId( entry.userId );
  mPermissions = entry.permissions;
  mInternalRightsList = entry.internalRightsList;
  setText( 1, permissionsToUserString( entry.permissions, entry.internalRightsList ) );
  mModified = entry.changed; // for dimap, so that earlier changes are still marked as changes
}

void KMail::FolderDiaACLTab::ListViewItem::save( ACLList& aclList,
#ifdef TDEPIM_NEW_DISTRLISTS
                                                 TDEABC::AddressBook* addressBook,
#else
                                                 TDEABC::DistributionListManager& manager,
#endif
                                                 IMAPUserIdFormat userIdFormat )
{
  // expand distribution lists
#ifdef TDEPIM_NEW_DISTRLISTS
  KPIM::DistributionList list = KPIM::DistributionList::findByName( addressBook, userId(), false );
  if ( !list.isEmpty() ) {
    Q_ASSERT( mModified ); // it has to be new, it couldn't be stored as a distr list name....
    KPIM::DistributionList::Entry::List entryList = list.entries(addressBook);
    KPIM::DistributionList::Entry::List::ConstIterator it;
    // (we share for loop with the old-distrlist-code)
#else
  // kaddrbook.cpp has a strange two-pass case-insensitive lookup; is it ok to be case sensitive?
  TDEABC::DistributionList* list = manager.list( userId() );
  if ( list ) {
    Q_ASSERT( mModified ); // it has to be new, it couldn't be stored as a distr list name....
    TDEABC::DistributionList::Entry::List entryList = list->entries();
    TDEABC::DistributionList::Entry::List::ConstIterator it; // nice number of "::"!
#endif
    for( it = entryList.begin(); it != entryList.end(); ++it ) {
      TQString email = (*it).email;
      if ( email.isEmpty() )
        email = addresseeToUserId( (*it).addressee, userIdFormat );
      ACLListEntry entry( email, TQString(), mPermissions );
      entry.changed = true;
      aclList.append( entry );
    }
  } else { // it wasn't a distribution list
    ACLListEntry entry( userId(), mInternalRightsList, mPermissions );
    if ( mModified ) {
      entry.internalRightsList = TQString();
      entry.changed = true;
    }
    aclList.append( entry );
  }
}

////

KMail::FolderDiaACLTab::FolderDiaACLTab( KMFolderDialog* dlg, TQWidget* parent, const char* name )
  : FolderDiaTab( parent, name ),
    mImapAccount( 0 ),
    mUserRights( 0 ),
    mUserRightsState( KMail::ACLJobs::NotFetchedYet ),
    mDlg( dlg ),
    mChanged( false ), mAccepting( false ), mSaving( false )
{
  TQVBoxLayout* topLayout = new TQVBoxLayout( this );
  // We need a widget stack to show either a label ("no acl support", "please wait"...)
  // or a listview.
  mStack = new TQWidgetStack( this );
  topLayout->addWidget( mStack );

  mLabel = new TQLabel( mStack );
  mLabel->setAlignment( AlignHCenter | AlignVCenter | WordBreak );
  mStack->addWidget( mLabel );

  mACLWidget = new TQHBox( mStack );
  mACLWidget->setSpacing( KDialog::spacingHint() );
  mListView = new TDEListView( mACLWidget );
  mListView->setAllColumnsShowFocus( true );
  mStack->addWidget( mACLWidget );
  mListView->addColumn( i18n( "User Id" ) );
  mListView->addColumn( i18n( "Permissions" ) );

  connect( mListView, TQT_SIGNAL(doubleClicked(TQListViewItem*,const TQPoint&,int)),
	   TQT_SLOT(slotEditACL(TQListViewItem*)) );
  connect( mListView, TQT_SIGNAL(returnPressed(TQListViewItem*)),
	   TQT_SLOT(slotEditACL(TQListViewItem*)) );
  connect( mListView, TQT_SIGNAL(currentChanged(TQListViewItem*)),
	   TQT_SLOT(slotSelectionChanged(TQListViewItem*)) );

  TQVBox* buttonBox = new TQVBox( mACLWidget );
  buttonBox->setSpacing( KDialog::spacingHint() );
  mAddACL = new KPushButton( i18n( "Add Entry..." ), buttonBox );
  mEditACL = new KPushButton( i18n( "Modify Entry..." ), buttonBox );
  mRemoveACL = new KPushButton( i18n( "Remove Entry" ), buttonBox );
  TQWidget *spacer = new TQWidget( buttonBox );
  spacer->setSizePolicy( TQSizePolicy::Minimum, TQSizePolicy::Expanding );

  connect( mAddACL, TQT_SIGNAL( clicked() ), TQT_SLOT( slotAddACL() ) );
  connect( mEditACL, TQT_SIGNAL( clicked() ), TQT_SLOT( slotEditACL() ) );
  connect( mRemoveACL, TQT_SIGNAL( clicked() ), TQT_SLOT( slotRemoveACL() ) );
  mEditACL->setEnabled( false );
  mRemoveACL->setEnabled( false );

  connect( this, TQT_SIGNAL( changed(bool) ), TQT_SLOT( slotChanged(bool) ) );
}

// Warning before save() this will return the url of the _parent_ folder, when creating a new one
KURL KMail::FolderDiaACLTab::imapURL() const
{
  KURL url = mImapAccount->getUrl();
  url.setPath( mImapPath );
  return url;
}

void KMail::FolderDiaACLTab::initializeWithValuesFromFolder( KMFolder* folder )
{
  // This can be simplified once KMFolderImap and KMFolderCachedImap have a common base class
  mFolderType = folder->folderType();
  if ( mFolderType == KMFolderTypeImap ) {
    KMFolderImap* folderImap = static_cast<KMFolderImap*>( folder->storage() );
    mImapPath = folderImap->imapPath();
    mImapAccount = folderImap->account();
    mUserRights = folderImap->userRights();
    mUserRightsState = folderImap->userRightsState();
  }
  else if ( mFolderType == KMFolderTypeCachedImap ) {
    KMFolderCachedImap* folderImap = static_cast<KMFolderCachedImap*>( folder->storage() );
    mImapPath = folderImap->imapPath();
    mImapAccount = folderImap->account();
    mUserRights = folderImap->userRights();
    mUserRightsState = folderImap->userRightsState();
  }
  else
    assert( 0 ); // see KMFolderDialog constructor
}

void KMail::FolderDiaACLTab::load()
{
  if ( mDlg->folder() ) {
    // existing folder
    initializeWithValuesFromFolder( mDlg->folder() );
  } else if ( mDlg->parentFolder() ) {
    // new folder
    initializeWithValuesFromFolder( mDlg->parentFolder() );
    mChanged = true; // ensure that saving happens
  }

  // KABC knows email addresses.
  // We want LDAP userids.
  // Depending on the IMAP server setup, the userid can be the full email address,
  // or just the username part of it.
  // To know which one it is, we currently have a hidden config option,
  // but the default value is determined from the current user's own id.
  TQString defaultFormat = "fullemail";
  // warning mImapAccount can be 0 if creating a subsubsubfolder with dimap...  (bug?)
  if ( mImapAccount && mImapAccount->login().find('@') == -1 )
    defaultFormat = "username"; // no @ found, so we assume it's just the username
  TDEConfigGroup configGroup( kmkernel->config(), "IMAP" );
  TQString str = configGroup.readEntry( "UserIdFormat", defaultFormat );
  mUserIdFormat = FullEmail;
  if ( str == "username" )
    mUserIdFormat = UserName;

  if ( mFolderType == KMFolderTypeCachedImap ) {
    KMFolder* folder = mDlg->folder() ? mDlg->folder() : mDlg->parentFolder();
    KMFolderCachedImap* folderImap = static_cast<KMFolderCachedImap*>( folder->storage() );
    if ( mUserRightsState == KMail::ACLJobs::FetchFailed ||
         folderImap->aclListState() == KMail::ACLJobs::FetchFailed ) {
      TQString text = i18n( "Error retrieving user permissions." );
      if ( mUserRightsState == KMail::ACLJobs::Ok ) {
        text += "\n" + i18n( "You might not have enough permissions to see the permissions of this folder." );
      }
      mLabel->setText( text );
    } else if ( mUserRightsState == KMail::ACLJobs::NotFetchedYet ||
                folderImap->aclListState() == KMail::ACLJobs::NotFetchedYet ) {
      mLabel->setText( i18n( "Information not retrieved from server, you need to use \"Check Mail\" and have administrative privileges on the folder."));
    } else {
      loadFinished( folderImap->aclList() );
    }
    return;
  }

  // Loading, for online IMAP, consists of four steps:
  // 1) connect
  // 2) get user rights
  // 3) load ACLs

  // First ensure we are connected
  mStack->raiseWidget( mLabel );
  if ( !mImapAccount ) { // hmmm?
    mLabel->setText( i18n( "Error: no IMAP account defined for this folder" ) );
    return;
  }
  KMFolder* folder = mDlg->folder() ? mDlg->folder() : mDlg->parentFolder();
  if ( folder && folder->storage() == mImapAccount->rootFolder() )
    return; // nothing to be done for the (virtual) account folder
  mLabel->setText( i18n( "Connecting to server %1, please wait..." ).arg( mImapAccount->host() ) );
  ImapAccountBase::ConnectionState state = mImapAccount->makeConnection();
  if ( state == ImapAccountBase::Error ) { // Cancelled by user, or slave can't start
    slotConnectionResult( -1, TQString() );
  } else if ( state == ImapAccountBase::Connecting ) {
    connect( mImapAccount, TQT_SIGNAL( connectionResult(int, const TQString&) ),
             this, TQT_SLOT( slotConnectionResult(int, const TQString&) ) );
  } else { // Connected
    slotConnectionResult( 0, TQString() );
  }
}

void KMail::FolderDiaACLTab::slotConnectionResult( int errorCode, const TQString& errorMsg )
{
  disconnect( mImapAccount, TQT_SIGNAL( connectionResult(int, const TQString&) ),
              this, TQT_SLOT( slotConnectionResult(int, const TQString&) ) );
  if ( errorCode ) {
    if ( errorCode == -1 ) // unspecified error
      mLabel->setText( i18n( "Error connecting to server %1" ).arg( mImapAccount->host() ) );
    else
      // Connection error (error message box already shown by the account)
      mLabel->setText( TDEIO::buildErrorString( errorCode, errorMsg ) );
    return;
  }

  if ( mUserRightsState != KMail::ACLJobs::Ok ) {
    connect( mImapAccount, TQT_SIGNAL( receivedUserRights( KMFolder* ) ),
             this, TQT_SLOT( slotReceivedUserRights( KMFolder* ) ) );
    KMFolder* folder = mDlg->folder() ? mDlg->folder() : mDlg->parentFolder();
    mImapAccount->getUserRights( folder, mImapPath );
  }
  else
    startListing();
}

void KMail::FolderDiaACLTab::slotReceivedUserRights( KMFolder* folder )
{
  if ( !mImapAccount->hasACLSupport() ) {
    mLabel->setText( i18n( "This IMAP server does not have support for access control lists (ACL)" ) );
    return;
  }

  if ( folder == mDlg->folder() ? mDlg->folder() : mDlg->parentFolder() ) {
    KMFolderImap* folderImap = static_cast<KMFolderImap*>( folder->storage() );
    mUserRights = folderImap->userRights();
    mUserRightsState = folderImap->userRightsState();
    startListing();
  }
}

void KMail::FolderDiaACLTab::startListing()
{
  // List ACLs of folder - or its parent, if creating a new folder
  mImapAccount->getACL( mDlg->folder() ? mDlg->folder() : mDlg->parentFolder(), mImapPath );
  connect( mImapAccount, TQT_SIGNAL(receivedACL( KMFolder*, TDEIO::Job*, const KMail::ACLList& )),
           this, TQT_SLOT(slotReceivedACL( KMFolder*, TDEIO::Job*, const KMail::ACLList& )) );
}

void KMail::FolderDiaACLTab::slotReceivedACL( KMFolder* folder, TDEIO::Job* job, const KMail::ACLList& aclList )
{
  if ( folder == ( mDlg->folder() ? mDlg->folder() : mDlg->parentFolder() ) ) {
    disconnect( mImapAccount, TQT_SIGNAL(receivedACL( KMFolder*, TDEIO::Job*, const KMail::ACLList& )),
                this, TQT_SLOT(slotReceivedACL( KMFolder*, TDEIO::Job*, const KMail::ACLList& )) );

    if ( job && job->error() ) {
      if ( job->error() == TDEIO::ERR_UNSUPPORTED_ACTION )
        mLabel->setText( i18n( "This IMAP server does not have support for access control lists (ACL)" ) );
      else
        mLabel->setText( i18n( "Error retrieving access control list (ACL) from server\n%1" ).arg( job->errorString() ) );
      return;
    }

    loadFinished( aclList );
  }
}

void KMail::FolderDiaACLTab::loadListView( const ACLList& aclList )
{
  mListView->clear();
  for( ACLList::const_iterator it = aclList.begin(); it != aclList.end(); ++it ) {
    // -1 means deleted (for cachedimap), don't show those
    if ( (*it).permissions > -1 ) {
      ListViewItem* item = new ListViewItem( mListView );
      item->load( *it );
      if ( !mDlg->folder() ) // new folder? everything is new then
          item->setModified( true );
    }
  }
}

void KMail::FolderDiaACLTab::loadFinished( const ACLList& aclList )
{
  loadListView( aclList );
  if ( mDlg->folder() ) // not when creating a new folder
    mInitialACLList = aclList;
  mStack->raiseWidget( mACLWidget );
  slotSelectionChanged( mListView->selectedItem() );
}

void KMail::FolderDiaACLTab::slotEditACL(TQListViewItem* item)
{
  if ( !item ) return;
  bool canAdmin = ( mUserRights & ACLJobs::Administer );
  // Same logic as in slotSelectionChanged, but this is also needed for double-click IIRC
  if ( canAdmin && mImapAccount && item ) {
    // Don't allow users to remove their own admin permissions - there's no way back
    ListViewItem* ACLitem = static_cast<ListViewItem *>( item );
    if ( mImapAccount->login() == ACLitem->userId() && ACLitem->permissions() == ACLJobs::All )
      canAdmin = false;
  }
  if ( !canAdmin ) return;

  ListViewItem* ACLitem = static_cast<ListViewItem *>( mListView->currentItem() );
  ACLEntryDialog dlg( mUserIdFormat, i18n( "Modify Permissions" ), this );
  dlg.setValues( ACLitem->userId(), ACLitem->permissions() );
  if ( dlg.exec() == TQDialog::Accepted ) {
    TQStringList userIds = dlg.userIds();
    Q_ASSERT( !userIds.isEmpty() ); // impossible, the OK button is disabled in that case
    ACLitem->setUserId( dlg.userIds().front() );
    ACLitem->setPermissions( dlg.permissions() );
    ACLitem->setModified( true );
    emit changed(true);
    if ( userIds.count() > 1 ) { // more emails were added, append them
      userIds.pop_front();
      addACLs( userIds, dlg.permissions() );
    }
  }
}

void KMail::FolderDiaACLTab::slotEditACL()
{
  slotEditACL( mListView->currentItem() );
}

void KMail::FolderDiaACLTab::addACLs( const TQStringList& userIds, unsigned int permissions )
{
  for( TQStringList::const_iterator it = userIds.begin(); it != userIds.end(); ++it ) {
    ListViewItem* ACLitem = new ListViewItem( mListView );
    ACLitem->setUserId( *it );
    ACLitem->setPermissions( permissions );
    ACLitem->setModified( true );
    ACLitem->setNew( true );
  }
}

void KMail::FolderDiaACLTab::slotAddACL()
{
  ACLEntryDialog dlg( mUserIdFormat, i18n( "Add Permissions" ), this );
  if ( dlg.exec() == TQDialog::Accepted ) {
    const TQStringList userIds = dlg.userIds();
    addACLs( dlg.userIds(), dlg.permissions() );
    emit changed(true);
  }
}

void KMail::FolderDiaACLTab::slotSelectionChanged(TQListViewItem* item)
{
  bool canAdmin = ( mUserRights & ACLJobs::Administer );
  bool canAdminThisItem = canAdmin;
  if ( canAdmin && mImapAccount && item ) {
    // Don't allow users to remove their own admin permissions - there's no way back
    ListViewItem* ACLitem = static_cast<ListViewItem *>( item );
    if ( mImapAccount->login() == ACLitem->userId() && ACLitem->permissions() == ACLJobs::All )
      canAdminThisItem = false;
  }

  bool lvVisible = mStack->visibleWidget() == mACLWidget;
  mAddACL->setEnabled( lvVisible && canAdmin && !mSaving );
  mEditACL->setEnabled( item && lvVisible && canAdminThisItem && !mSaving );
  mRemoveACL->setEnabled( item && lvVisible && canAdminThisItem && !mSaving );
}

void KMail::FolderDiaACLTab::slotRemoveACL()
{
  ListViewItem* ACLitem = static_cast<ListViewItem *>( mListView->currentItem() );
  if ( !ACLitem )
    return;
  if ( !ACLitem->isNew() ) {
    if ( mImapAccount && mImapAccount->login() == ACLitem->userId() ) {
      if ( KMessageBox::Cancel == KMessageBox::warningContinueCancel( topLevelWidget(),
         i18n( "Do you really want to remove your own permissions for this folder? You will not be able to access it afterwards." ), i18n( "Remove" ) ) )
        return;
    }
    mRemovedACLs.append( ACLitem->userId() );
  }
  delete ACLitem;
  emit changed(true);
}

KMail::FolderDiaTab::AccepStatus KMail::FolderDiaACLTab::accept()
{
  if ( !mChanged || !mImapAccount )
    return Accepted; // (no change made), ok for accepting the dialog immediately
  // If there were changes, we need to apply them first (which is async)
  save();
  if ( mFolderType == KMFolderTypeCachedImap )
    return Accepted; // cached imap: changes saved immediately into the folder
  // disconnected imap: async job[s] running
  mAccepting = true;
  return Delayed;
}

bool KMail::FolderDiaACLTab::save()
{
  if ( !mChanged || !mImapAccount ) // no changes
    return true;
  assert( mDlg->folder() ); // should have been created already

  // Expand distribution lists. This is necessary because after Apply
  // we would otherwise be able to "modify" the permissions for a distr list,
  // which wouldn't work since the ACLList and the server only know about the
  // individual addresses.
  // slotACLChanged would have trouble matching the item too.
  // After reloading we'd see the list expanded anyway,
  // so this is more consistent.
  // But we do it now and not when inserting it, because this allows to
  // immediately remove a wrongly inserted distr list without having to
  // remove 100 items.
  // Now, how to expand them? Playing with listviewitem iterators and inserting
  // listviewitems at the same time sounds dangerous, so let's just save into
  // ACLList and reload that.
  TDEABC::AddressBook *addressBook = TDEABC::StdAddressBook::self( true );
#ifndef TDEPIM_NEW_DISTRLISTS
  TDEABC::DistributionListManager manager( addressBook );
  manager.load();
#endif
  ACLList aclList;
  for ( TQListViewItem* item = mListView->firstChild(); item; item = item->nextSibling() ) {
    ListViewItem* ACLitem = static_cast<ListViewItem *>( item );
    ACLitem->save( aclList,
#ifdef TDEPIM_NEW_DISTRLISTS
                   addressBook,
#else
                   manager,
#endif
                   mUserIdFormat );
  }
  loadListView( aclList );

  // Now compare with the initial ACLList, because if the user renamed a userid
  // we have to add the old userid to the "to be deleted" list.
  for( ACLList::ConstIterator init = mInitialACLList.begin(); init != mInitialACLList.end(); ++init ) {
    bool isInNewList = false;
    TQString uid = (*init).userId;
    for( ACLList::ConstIterator it = aclList.begin(); it != aclList.end() && !isInNewList; ++it )
      isInNewList = uid == (*it).userId;
    if ( !isInNewList && !mRemovedACLs.contains(uid) )
      mRemovedACLs.append( uid );
  }

  for ( TQStringList::ConstIterator rit = mRemovedACLs.begin(); rit != mRemovedACLs.end(); ++rit ) {
    // We use permissions == -1 to signify deleting. At least on cyrus, setacl(0) or deleteacl are the same,
    // but I'm not sure if that's true for all servers.
    ACLListEntry entry( *rit, TQString(), -1 );
    entry.changed = true;
    aclList.append( entry );
  }

  // aclList is finally ready. We can save it (dimap) or apply it (imap).

  if ( mFolderType == KMFolderTypeCachedImap ) {
    // Apply the changes to the aclList stored in the folder.
    // We have to do this now and not before, so that cancel really cancels.
    KMFolderCachedImap* folderImap = static_cast<KMFolderCachedImap*>( mDlg->folder()->storage() );
    folderImap->setACLList( aclList );
    return true;
  }

  mACLList = aclList;

  KMFolderImap* parentImap = mDlg->parentFolder() ? static_cast<KMFolderImap*>( mDlg->parentFolder()->storage() ) : 0;

  if ( mDlg->isNewFolder() ) {
    // The folder isn't created yet, wait for it
    // It's a two-step process (mkdir+listDir) so we wait for the dir listing to be complete
    connect( parentImap, TQT_SIGNAL( directoryListingFinished(KMFolderImap*) ),
             this, TQT_SLOT( slotDirectoryListingFinished(KMFolderImap*) ) );
  } else {
      slotDirectoryListingFinished( parentImap );
  }
  return true;
}

void KMail::FolderDiaACLTab::slotDirectoryListingFinished(KMFolderImap* f)
{
  if ( !f ||
       f != static_cast<KMFolderImap*>( mDlg->parentFolder()->storage() ) ||
       !mDlg->folder() ||
       !mDlg->folder()->storage() ) {
    emit readyForAccept();
    return;
  }

  // When creating a new folder with online imap, update mImapPath
  KMFolderImap* folderImap = static_cast<KMFolderImap*>( mDlg->folder()->storage() );
  if ( !folderImap || folderImap->imapPath().isEmpty() )
    return;
  mImapPath = folderImap->imapPath();

  TDEIO::Job* job = ACLJobs::multiSetACL( mImapAccount->slave(), imapURL(), mACLList );
  ImapAccountBase::jobData jd;
  jd.total = 1; jd.done = 0; jd.parent = 0;
  mImapAccount->insertJob(job, jd);

  connect(job, TQT_SIGNAL(result(TDEIO::Job *)),
          TQT_SLOT(slotMultiSetACLResult(TDEIO::Job *)));
  connect(job, TQT_SIGNAL(aclChanged( const TQString&, int )),
          TQT_SLOT(slotACLChanged( const TQString&, int )) );
}

void KMail::FolderDiaACLTab::slotMultiSetACLResult(TDEIO::Job* job)
{
  ImapAccountBase::JobIterator it = mImapAccount->findJob( job );
  if ( it == mImapAccount->jobsEnd() ) return;
  mImapAccount->removeJob( it );

  if ( job->error() ) {
    job->showErrorDialog( this );
    if ( mAccepting ) {
      emit cancelAccept();
      mAccepting = false; // don't emit readyForAccept anymore
    }
  } else {
    if ( mAccepting )
      emit readyForAccept();
  }
}

void KMail::FolderDiaACLTab::slotACLChanged( const TQString& userId, int permissions )
{
  // The job indicates success in changing the permissions for this user
  // -> we note that it's been done.
  bool ok = false;
  if ( permissions > -1 ) {
    for ( TQListViewItem* item = mListView->firstChild(); item; item = item->nextSibling() ) {
      ListViewItem* ACLitem = static_cast<ListViewItem *>( item );
      if ( ACLitem->userId() == userId ) {
        ACLitem->setModified( false );
        ACLitem->setNew( false );
        ok = true;
        break;
      }
    }
  } else {
    uint nr = mRemovedACLs.remove( userId );
    ok = ( nr > 0 );
  }
  if ( !ok )
    kdWarning(5006) << k_funcinfo << " no item found for userId " << userId << endl;
}

void KMail::FolderDiaACLTab::slotChanged( bool b )
{
  mChanged = b;
}

bool KMail::FolderDiaACLTab::supports( KMFolder* refFolder )
{
  ImapAccountBase* imapAccount = 0;
  if ( refFolder->folderType() == KMFolderTypeImap )
    imapAccount = static_cast<KMFolderImap*>( refFolder->storage() )->account();
  else
    imapAccount = static_cast<KMFolderCachedImap*>( refFolder->storage() )->account();
  return imapAccount && imapAccount->hasACLSupport(); // support for ACLs (or not tried connecting yet)
}

#include "folderdiaacltab.moc"
