/*
    Copyright (c) 2010 Klarälvdalens Datakonsult AB,
                       a KDAB Group company <info@kdab.com>
    Author: Kevin Ottens <kevin@kdab.com>

    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 "changecollectiontask.h"

#include <kimap/renamejob.h>
#include <kimap/setacljob.h>
#include <kimap/setmetadatajob.h>
#include <kimap/session.h>
#include <kimap/subscribejob.h>

#include "collectionannotationsattribute.h"
#include "imapaclattribute.h"
#include "imapquotaattribute.h"

#include <KLocalizedString>

ChangeCollectionTask::ChangeCollectionTask( ResourceStateInterface::Ptr resource, QObject *parent )
  : ResourceTask( DeferIfNoSession, resource, parent ), m_pendingJobs(0)
{
}

ChangeCollectionTask::~ChangeCollectionTask()
{
}

void ChangeCollectionTask::doStart( KIMAP::Session *session )
{
  if ( collection().remoteId().isEmpty() ) {
    emitError( i18n( "Cannot modify IMAP folder '%1', it does not exist on the server.",
                    collection().name() ) );
    changeProcessed();
    return;
  }

  m_collection = collection();
  m_pendingJobs = 0;

  if ( parts().contains( "AccessRights" ) ) {
    Akonadi::ImapAclAttribute *aclAttribute = collection().attribute<Akonadi::ImapAclAttribute>();

    if ( aclAttribute==0 ) {
      emitWarning( i18n( "ACLs for '%1' need to be retrieved from the IMAP server first. Skipping ACL change",
                         collection().name() ) );
    } else {
      KIMAP::Acl::Rights imapRights = aclAttribute->rights()[userName().toUtf8()];
      Akonadi::Collection::Rights newRights = collection().rights();

      if ( newRights & Akonadi::Collection::CanChangeItem ) {
        imapRights|= KIMAP::Acl::Write;
      } else {
        imapRights&= ~KIMAP::Acl::Write;
      }

      if ( newRights & Akonadi::Collection::CanCreateItem ) {
        imapRights|= KIMAP::Acl::Insert;
      } else {
        imapRights&= ~KIMAP::Acl::Insert;
      }

      if ( newRights & Akonadi::Collection::CanDeleteItem ) {
        imapRights|= KIMAP::Acl::DeleteMessage;
      } else {
        imapRights&= ~KIMAP::Acl::DeleteMessage;
      }

      if ( newRights & ( Akonadi::Collection::CanChangeCollection | Akonadi::Collection::CanCreateCollection ) ) {
        imapRights|= KIMAP::Acl::CreateMailbox;
        imapRights|= KIMAP::Acl::Create;
      } else {
        imapRights&= ~KIMAP::Acl::CreateMailbox;
        imapRights&= ~KIMAP::Acl::Create;
      }

      if ( newRights & Akonadi::Collection::CanDeleteCollection ) {
        imapRights|= KIMAP::Acl::DeleteMailbox;
      } else {
        imapRights&= ~KIMAP::Acl::DeleteMailbox;
      }

      if ( ( newRights & Akonadi::Collection::CanDeleteItem )
        && ( newRights & Akonadi::Collection::CanDeleteCollection ) ) {
        imapRights|= KIMAP::Acl::Delete;
      } else {
        imapRights&= ~KIMAP::Acl::Delete;
      }

      kDebug( 5327 ) << "imapRights:" << imapRights
                     << "newRights:" << newRights;

      KIMAP::SetAclJob *job = new KIMAP::SetAclJob( session );
      job->setMailBox( mailBoxForCollection( collection() ) );
      job->setRights( KIMAP::SetAclJob::Change, imapRights );
      job->setIdentifier( userName().toUtf8() );

      connect( job, SIGNAL(result(KJob*)),
               this, SLOT(onSetAclDone(KJob*)) );

      job->start();

      m_pendingJobs++;
    }
  }

  if ( parts().contains( "collectionannotations" ) && serverSupportsAnnotations() ) {
    Akonadi::CollectionAnnotationsAttribute *annotationsAttribute =
      collection().attribute<Akonadi::CollectionAnnotationsAttribute>();

    if ( annotationsAttribute ) { // No annotations it seems... server is lieing to us?
      QMap<QByteArray, QByteArray> annotations = annotationsAttribute->annotations();
      kDebug( 5327 ) << "All annotations: " << annotations;

      foreach ( const QByteArray &entry, annotations.keys() ) { //krazy:exclude=foreach
        KIMAP::SetMetaDataJob *job = new KIMAP::SetMetaDataJob( session );
        if ( serverCapabilities().contains( QLatin1String("METADATA") ) ) {
          job->setServerCapability( KIMAP::MetaDataJobBase::Metadata );
        } else {
          job->setServerCapability( KIMAP::MetaDataJobBase::Annotatemore );
        }
        job->setMailBox( mailBoxForCollection( collection() ) );

        if ( !entry.startsWith( "/shared" ) && !entry.startsWith( "/private" ) ) {
          //Support for legacy annotations that don't include the prefix
          job->addMetaData( QByteArray("/shared") + entry, annotations[entry] );
        } else {
          job->addMetaData( entry, annotations[entry] );
        }

        kDebug( 5327 ) << "Job got entry:" << entry << "value:" << annotations[entry];

        connect( job, SIGNAL(result(KJob*)),
                 this, SLOT(onSetMetaDataDone(KJob*)) );

        job->start();

        m_pendingJobs++;
      }
    }
  }

  if ( parts().contains( "imapacl" ) ) {
    Akonadi::ImapAclAttribute *aclAttribute = collection().attribute<Akonadi::ImapAclAttribute>();

    if ( aclAttribute ) {
      const QMap<QByteArray, KIMAP::Acl::Rights> rights = aclAttribute->rights();
      const QMap<QByteArray, KIMAP::Acl::Rights> oldRights = aclAttribute->oldRights();
      const QList<QByteArray> oldIds = oldRights.keys();
      const QList<QByteArray> ids = rights.keys();

      // remove all ACL entries that have been deleted
      foreach ( const QByteArray &oldId, oldIds ) {
        if ( !ids.contains( oldId ) ) {
          KIMAP::SetAclJob *job = new KIMAP::SetAclJob( session );
          job->setMailBox( mailBoxForCollection( collection() ) );
          job->setIdentifier( oldId );
          job->setRights( KIMAP::SetAclJob::Remove, oldRights[oldId] );

          connect( job, SIGNAL(result(KJob*)),
                   this, SLOT(onSetAclDone(KJob*)) );

          job->start();

          m_pendingJobs++;
        }
      }

      foreach ( const QByteArray &id, ids ) {
        KIMAP::SetAclJob *job = new KIMAP::SetAclJob( session );
        job->setMailBox( mailBoxForCollection( collection() ) );
        job->setIdentifier( id );
        job->setRights( KIMAP::SetAclJob::Change, rights[id] );

        connect( job, SIGNAL(result(KJob*)),
                 this, SLOT(onSetAclDone(KJob*)) );

        job->start();

        m_pendingJobs++;
      }
    }
  }

  // Check if we need to rename the mailbox
  // This one goes last on purpose, we don't want the previous jobs
  // we triggered to act on the wrong mailbox name
  if ( parts().contains( "NAME" ) ) {
    const QChar separator = separatorCharacter();
    m_collection.setName( m_collection.name().replace( separator, QString() ) );
    m_collection.setRemoteId( separator + m_collection.name() );

    const QString oldMailBox = mailBoxForCollection( collection() );
    const QString newMailBox = mailBoxForCollection( m_collection );

    if ( oldMailBox != newMailBox ) {
      KIMAP::RenameJob *renameJob = new KIMAP::RenameJob( session );
      renameJob->setSourceMailBox( oldMailBox );
      renameJob->setDestinationMailBox( newMailBox );
      connect( renameJob, SIGNAL(result(KJob*)),
               this, SLOT(onRenameDone(KJob*)) );

      renameJob->start();

      m_pendingJobs++;
    }
  }

  // we scheduled no change on the server side, probably we got only
  // unsupported part, so just declare the task done
  if ( m_pendingJobs == 0 ) {
    changeCommitted( collection() );
  }
}

void ChangeCollectionTask::onRenameDone( KJob *job )
{
  if ( job->error() ) {
    const QString prevRid = collection().remoteId();
    Q_ASSERT( !prevRid.isEmpty() );

    emitWarning( i18n( "Failed to rename the folder, restoring folder list." ) );

    m_collection.setName( prevRid.mid( 1 ) );
    m_collection.setRemoteId( prevRid );

    endTaskIfNeeded();
  } else {
    KIMAP::RenameJob *renameJob = static_cast<KIMAP::RenameJob*>( job );
    KIMAP::SubscribeJob *subscribeJob = new KIMAP::SubscribeJob( renameJob->session() );
    subscribeJob->setMailBox( renameJob->destinationMailBox() );
    connect( subscribeJob, SIGNAL(result(KJob*)),
             this, SLOT(onSubscribeDone(KJob*)) );
    subscribeJob->start();
  }
}

void ChangeCollectionTask::onSubscribeDone( KJob *job )
{
  if ( job->error() && isSubscriptionEnabled() ) {
    emitWarning( i18n( "Failed to subscribe to the renamed folder '%1' on the IMAP server. "
                       "It will disappear on next sync. Use the subscription dialog to overcome that",
                       m_collection.name() ) );
  }

  endTaskIfNeeded();
}

void ChangeCollectionTask::onSetAclDone( KJob *job )
{
  if ( job->error() ) {
    emitWarning( i18n( "Failed to write some ACLs for '%1' on the IMAP server. %2",
                       collection().name(), job->errorText() ) );
  }

  endTaskIfNeeded();
}

void ChangeCollectionTask::onSetMetaDataDone( KJob *job )
{
  if ( job->error() ) {
    emitWarning( i18n( "Failed to write some annotations for '%1' on the IMAP server. %2",
                       collection().name(), job->errorText() ) );
  }

  endTaskIfNeeded();
}

void ChangeCollectionTask::endTaskIfNeeded()
{
  if ( --m_pendingJobs == 0 ) {
    // the others have ended, we're done, the next one can go
    changeCommitted( m_collection );
  }
}



