/* Copyright 2009 Klarälvdalens Datakonsult AB

   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) version 3 or any later version
   accepted by the membership of KDE e.V. (or its successor approved
   by the membership of KDE e.V.), which shall act as a proxy
   defined in Section 14 of version 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "importjob.h"

#include "kmfolder.h"
#include "folderutil.h"
#include "kmfolderdir.h"
#include "kmfolderimap.h"
#include "imapjob.h"

#include "progressmanager.h"

#include <kdebug.h>
#include <kzip.h>
#include <ktar.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kmimetype.h>

#include <tqwidget.h>
#include <tqtimer.h>
#include <tqfile.h>

using namespace KMail;

KMail::ImportJob::ImportJob( TQWidget *parentWidget )
  : TQObject( parentWidget ),
    mArchive( 0 ),
    mRootFolder( 0 ),
    mParentWidget( parentWidget ),
    mNumberOfImportedMessages( 0 ),
    mCurrentFolder( 0 ),
    mCurrentMessage( 0 ),
    mCurrentMessageFile( 0 ),
    mProgressItem( 0 ),
    mAborted( false )
{
}

KMail::ImportJob::~ImportJob()
{
  if ( mArchive && mArchive->isOpened() ) {
    mArchive->close();
  }
  delete mArchive;
  mArchive = 0;
}

void KMail::ImportJob::setFile( const KURL &archiveFile )
{
  mArchiveFile = archiveFile;
}

void KMail::ImportJob::setRootFolder( KMFolder *rootFolder )
{
  mRootFolder = rootFolder;
}

void KMail::ImportJob::finish()
{
  kdDebug(5006) << "Finished import job." << endl;
  mProgressItem->setComplete();
  mProgressItem = 0;
  TQString text = i18n( "Importing the archive file '%1' into the folder '%2' succeeded." )
                     .arg( mArchiveFile.path() ).arg( mRootFolder->name() );
  text += "\n" + i18n( "1 message was imported.", "%n messages were imported.", mNumberOfImportedMessages );
  KMessageBox::information( mParentWidget, text, i18n( "Import finished." ) );
  deleteLater();
}

void KMail::ImportJob::cancelJob()
{
  abort( i18n( "The operation was canceled by the user." ) );
}

void KMail::ImportJob::abort( const TQString &errorMessage )
{
  if ( mAborted )
    return;

  mAborted = true;
  TQString text = i18n( "Failed to import the archive into folder '%1'." ).arg( mRootFolder->name() );
  text += "\n" + errorMessage;
  if ( mProgressItem ) {
    mProgressItem->setComplete();
    mProgressItem = 0;
    // The progressmanager will delete it
  }
  KMessageBox::sorry( mParentWidget, text, i18n( "Importing archive failed." ) );
  deleteLater();
}

KMFolder * KMail::ImportJob::createSubFolder( KMFolder *parent, const TQString &folderName, mode_t permissions )
{
  KMFolder *newFolder = FolderUtil::createSubFolder( parent, parent->child(), folderName, TQString(),
                                                     KMFolderTypeMaildir );
  if ( !newFolder ) {
    abort( i18n( "Unable to create subfolder for folder '%1'." ).arg( parent->name() ) );
    return 0;
  }
  else {
    newFolder->createChildFolder(); // TODO: Just creating a child folder here is wasteful, only do
                                    //       that if really needed. We do it here so we can set the
                                    //       permissions
    chmod( newFolder->location().latin1(), permissions | S_IXUSR );
    chmod( newFolder->subdirLocation().latin1(), permissions | S_IXUSR );
    // TODO: chown?
    // TODO: what about subdirectories like "cur"?
    return newFolder;
  }
}

void KMail::ImportJob::enqueueMessages( const KArchiveDirectory *dir, KMFolder *folder )
{
  const KArchiveDirectory *messageDir = dynamic_cast<const KArchiveDirectory*>( dir->entry( "cur" ) );
  if ( messageDir ) {
    Messages messagesToQueue;
    messagesToQueue.parent = folder;
    const TQStringList entries = messageDir->entries();
    for ( uint i = 0; i < entries.size(); i++ ) {
      const KArchiveEntry *entry = messageDir->entry( entries[i] );
      Q_ASSERT( entry );
      if ( entry->isDirectory() ) {
        kdWarning(5006) << "Unexpected subdirectory in archive folder " << dir->name() << endl;
      }
      else {
        kdDebug(5006) << "Queueing message " << entry->name() << endl;
        const KArchiveFile *file = static_cast<const KArchiveFile*>( entry );
        messagesToQueue.files.append( file );
      }
    }
    mQueuedMessages.append( messagesToQueue );
  }
  else {
    kdWarning(5006) << "No 'cur' subdirectory for archive directory " << dir->name() << endl;
  }
}

void KMail::ImportJob::messageAdded()
{
  mNumberOfImportedMessages++;
  if ( mCurrentFolder->folderType() == KMFolderTypeMaildir ||
       mCurrentFolder->folderType() == KMFolderTypeCachedImap ) {
    const TQString messageFile = mCurrentFolder->location() + "/cur/" + mCurrentMessage->fileName();
    // TODO: what if the file is not in the "cur" subdirectory?
    if ( TQFile::exists( messageFile ) ) {
      chmod( messageFile.latin1(), mCurrentMessageFile->permissions() );
      // TODO: changing user/group he requires a bit more work, requires converting the strings
      //       to uid_t and gid_t
      //getpwnam()
      //chown( messageFile,
    }
    else {
      kdWarning(5006) << "Unable to change permissions for newly created file: " << messageFile << endl;
    }
  }
  // TODO: Else?

  mCurrentMessage = 0;
  mCurrentMessageFile = 0;
  TQTimer::singleShot( 0, this, TQ_SLOT( importNextMessage() ) );
}

void KMail::ImportJob::importNextMessage()
{
  if ( mAborted )
    return;

  if ( mQueuedMessages.isEmpty() ) {
    kdDebug(5006) << "importNextMessage(): Processed all messages in the queue." << endl;
    if ( mCurrentFolder ) {
      mCurrentFolder->close( "ImportJob" );
    }
    mCurrentFolder = 0;
    importNextDirectory();
    return;
  }

  Messages &messages = mQueuedMessages.front();
  if ( messages.files.isEmpty() ) {
    mQueuedMessages.pop_front();
    importNextMessage();
    return;
  }

  KMFolder *folder = messages.parent;
  if ( folder != mCurrentFolder ) {
    kdDebug(5006) << "importNextMessage(): Processed all messages in the current folder of the queue." << endl;
    if ( mCurrentFolder ) {
      mCurrentFolder->close( "ImportJob" );
    }
    mCurrentFolder = folder;
    if ( mCurrentFolder->open( "ImportJob" ) != 0 ) {
      abort( i18n( "Unable to open folder '%1'." ).arg( mCurrentFolder->name() ) );
      return;
    }
    kdDebug(5006) << "importNextMessage(): Current folder of queue is now: " << mCurrentFolder->name() << endl;
    mProgressItem->setStatus( i18n( "Importing folder %1" ).arg( mCurrentFolder->name() ) );
  }

  mProgressItem->setProgress( ( mProgressItem->progress() + 5 ) );

  mCurrentMessageFile = messages.files.first();
  Q_ASSERT( mCurrentMessageFile );
  messages.files.removeFirst();

  mCurrentMessage = new KMMessage();
  mCurrentMessage->fromByteArray( mCurrentMessageFile->data(), true /* setStatus */ );
  int retIndex;

  // If this is not an IMAP folder, we can add the message directly. Otherwise, the whole thing is
  // async, for online IMAP. While addMsg() fakes a sync call, we rather do it the async way here
  // ourselves, as otherwise the whole thing gets pretty much messed up with regards to folder
  // refcounting. Furthermore, the completion dialog would be shown before the messages are actually
  // uploaded.
  if ( mCurrentFolder->folderType() != KMFolderTypeImap ) {
    if ( mCurrentFolder->addMsg( mCurrentMessage, &retIndex ) != 0 ) {
      abort( i18n( "Failed to add a message to the folder '%1'." ).arg( mCurrentFolder->name() ) );
      return;
    }
    messageAdded();
  }
  else {
    ImapJob *imapJob = new ImapJob( mCurrentMessage, ImapJob::tPutMessage,
                                    dynamic_cast<KMFolderImap*>( mCurrentFolder->storage() ) );
    connect( imapJob, TQ_SIGNAL(result(KMail::FolderJob*)),
             TQ_SLOT(messagePutResult(KMail::FolderJob*)) );
    imapJob->start();
  }
}

void KMail::ImportJob::messagePutResult( KMail::FolderJob *job )
{
  if ( mAborted )
    return;

  if ( job->error() ) {
    abort( i18n( "Failed to upload a message to the IMAP server." ) );
    return;
  } else {

    KMFolderImap *imap = dynamic_cast<KMFolderImap*>( mCurrentFolder->storage() );
    Q_ASSERT( imap );

    // Ok, we uploaded the message, but we still need to add it to the folder. Use addMsgQuiet(),
    // otherwise it will be uploaded again.
    imap->addMsgQuiet( mCurrentMessage );
    messageAdded();
  }
}

// Input: .inbox.directory
// Output: inbox
// Can also return an empty string if this is no valid dir name
static TQString folderNameForDirectoryName( const TQString &dirName )
{
  Q_ASSERT( dirName.startsWith( "." ) );
  const TQString end = ".directory";
  const int expectedIndex = dirName.length() - end.length();
  if ( dirName.lower().find( end ) != expectedIndex )
    return TQString();
  TQString returnName = dirName.left( dirName.length() - end.length() );
  returnName = returnName.right( returnName.length() - 1 );
  return returnName;
}

KMFolder* KMail::ImportJob::getOrCreateSubFolder( KMFolder *parentFolder, const TQString &subFolderName,
                                           mode_t subFolderPermissions )
{
  if ( !parentFolder->createChildFolder() ) {
    abort( i18n( "Unable to create subfolder for folder '%1'." ).arg( parentFolder->name() ) );
    return 0;
  }

  KMFolder *subFolder = 0;
  subFolder = dynamic_cast<KMFolder*>( parentFolder->child()->hasNamedFolder( subFolderName ) );

  if ( !subFolder ) {
    subFolder = createSubFolder( parentFolder, subFolderName, subFolderPermissions );
  }
  return subFolder;
}

void KMail::ImportJob::importNextDirectory()
{
  if ( mAborted )
    return;

  if ( mQueuedDirectories.isEmpty() ) {
    finish();
    return;
  }

  Folder folder = mQueuedDirectories.first();
  KMFolder *currentFolder = folder.parent;
  mQueuedDirectories.pop_front();
  kdDebug(5006) << "importNextDirectory(): Working on directory " << folder.archiveDir->name() << endl;

  TQStringList entries = folder.archiveDir->entries();
  for ( uint i = 0; i < entries.size(); i++ ) {
    const KArchiveEntry *entry = folder.archiveDir->entry( entries[i] );
    Q_ASSERT( entry );
    kdDebug(5006) << "Queueing entry " << entry->name() << endl;
    if ( entry->isDirectory() ) {
      const KArchiveDirectory *dir = static_cast<const KArchiveDirectory*>( entry );
      if ( !dir->name().startsWith( "." ) ) {

        kdDebug(5006) << "Queueing messages in folder " << entry->name() << endl;
        KMFolder *subFolder = getOrCreateSubFolder( currentFolder, entry->name(), entry->permissions() );
        if ( !subFolder )
          return;

        enqueueMessages( dir, subFolder );
      }

      // Entry starts with a dot, so we assume it is a subdirectory
      else {

        const TQString folderName = folderNameForDirectoryName( entry->name() );
        if ( folderName.isEmpty() ) {
          abort( i18n( "Unexpected subdirectory named '%1'." ).arg( entry->name() ) );
          return;
        }
        KMFolder *subFolder = getOrCreateSubFolder( currentFolder, folderName, entry->permissions() );
        if ( !subFolder )
          return;

        Folder newFolder;
        newFolder.archiveDir = dir;
        newFolder.parent = subFolder;
        kdDebug(5006) << "Enqueueing directory " << entry->name() << endl;
        mQueuedDirectories.push_back( newFolder );
      }
    }
  }

  importNextMessage();
}

// TODO:
// BUGS:
//    Online IMAP can fail spectacular, for example when cancelling upload
//    Online IMAP: Inform that messages are still being uploaded on finish()!
void KMail::ImportJob::start()
{
  Q_ASSERT( mRootFolder );
  Q_ASSERT( mArchiveFile.isValid() );

  KMimeType::Ptr mimeType = KMimeType::findByURL( mArchiveFile, 0, true /* local file */ );
  if ( !mimeType->patterns().grep( "tar", false /* no case-sensitive */ ).isEmpty() )
    mArchive = new KTar( mArchiveFile.path() );
  else if ( !mimeType->patterns().grep( "zip", false ).isEmpty() )
    mArchive = new KZip( mArchiveFile.path() );
  else {
    abort( i18n( "The file '%1' does not appear to be a valid archive." ).arg( mArchiveFile.path() ) );
    return;
  }

  if ( !mArchive->open( IO_ReadOnly ) ) {
    abort( i18n( "Unable to open archive file '%1'" ).arg( mArchiveFile.path() ) );
    return;
  }

  mProgressItem = KPIM::ProgressManager::createProgressItem(
      "ImportJob",
      i18n( "Importing Archive" ),
      TQString(),
      true );
  mProgressItem->setUsesBusyIndicator( true );
  connect( mProgressItem, TQ_SIGNAL(progressItemCanceled(KPIM::ProgressItem*)),
           this, TQ_SLOT(cancelJob()) );

  Folder nextFolder;
  nextFolder.archiveDir = mArchive->directory();
  nextFolder.parent = mRootFolder;
  mQueuedDirectories.push_back( nextFolder );
  importNextDirectory();
}

#include "importjob.moc"
