// kmfoldermgr.cpp

#ifdef HAVE_CONFIG_H
	#include <config.h>
#endif

#include <sys/types.h>

#ifdef HAVE_SYS_STAT_H
	#include <sys/stat.h>
#endif

#include <assert.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

#include <tqdir.h>

#include <tdelocale.h>
#include <tdemessagebox.h>
#include <tdeconfig.h>
#include <kdebug.h>
#include <tdeapplication.h>

#include "kmmainwin.h"
#include "kmfiltermgr.h"
#include "kmfoldermgr.h"
#include "folderstorage.h"
#include "kmfolder.h"
#include "kmfoldercachedimap.h"
#include "kmacctcachedimap.h"
#include "renamejob.h"
#include "copyfolderjob.h"

using KMail::RenameJob;
using KMail::CopyFolderJob;

//-----------------------------------------------------------------------------
KMFolderMgr::KMFolderMgr(const TQString& aBasePath, KMFolderDirType dirType):
  TQObject(), mDir(this, TQString(), dirType)
{
  if ( dirType == KMStandardDir )
    mDir.setBaseURL( I18N_NOOP("Local Folders") );
  mQuiet = 0;
  mChanged = false;
  setBasePath(aBasePath);
  mRemoveOrig = 0;
}


//-----------------------------------------------------------------------------
KMFolderMgr::~KMFolderMgr()
{
  mBasePath = TQString();
}


//-----------------------------------------------------------------------------
void KMFolderMgr::expireAll() {
  TDEConfig             *config = KMKernel::config();
  TDEConfigGroupSaver   saver(config, "General");
  int                 ret = KMessageBox::Continue;

  if (config->readBoolEntry("warn-before-expire", true)) {
    ret = KMessageBox::warningContinueCancel(TDEMainWindow::memberList->first(),
			 i18n("Are you sure you want to expire old messages?"),
			 i18n("Expire Old Messages?"), i18n("Expire"));
  }

  if (ret == KMessageBox::Continue) {
    expireAllFolders( true /*immediate*/ );
  }

}

#define DO_FOR_ALL(function, folder_code) \
  KMFolderNode* node; \
  TQPtrListIterator<KMFolderNode> it(*dir); \
  for ( ; (node = it.current()); ) { \
    ++it; \
    if (node->isDir()) continue; \
    KMFolder *folder = static_cast<KMFolder*>(node); \
    folder_code \
    KMFolderDir *child = folder->child(); \
    if (child) \
       function \
  }

int KMFolderMgr::folderCount(KMFolderDir *dir)
{
  int count = 0;
  if (dir == 0)
    dir = &mDir;
  DO_FOR_ALL(
        {
          count += folderCount( child );
        },
        {
          count++;
        }
  )

  return count;
}



//-----------------------------------------------------------------------------
void KMFolderMgr::compactAllFolders(bool immediate, KMFolderDir* dir)
{
  if (dir == 0)
    dir = &mDir;
  DO_FOR_ALL(
        {
          compactAllFolders( immediate, child );
        },
        {
          if ( folder->needsCompacting() )
              folder->compact( immediate ? KMFolder::CompactNow : KMFolder::CompactLater );
        }
  )
}


//-----------------------------------------------------------------------------
void KMFolderMgr::setBasePath(const TQString& aBasePath)
{
  assert(!aBasePath.isNull());

  if (aBasePath[0] == '~')
  {
    mBasePath = TQDir::homeDirPath();
    mBasePath.append("/");
    mBasePath.append(aBasePath.mid(1));
  }
  else
    mBasePath = aBasePath;

  TQFileInfo info( mBasePath );

  // FIXME We should ask for an alternative dir, rather than bailing out,
  // I guess - till
  if ( info.exists() ) {
   if ( !info.isDir() ) {
      KMessageBox::sorry(0, i18n("'%1' does not appear to be a folder.\n"
                                 "Please move the file out of the way.")
                            .arg( mBasePath ) );
      ::exit(-1);
    }
    if ( !info.isReadable() || !info.isWritable() ) {
      KMessageBox::sorry(0, i18n("The permissions of the folder '%1' are "
                               "incorrect;\n"
                               "please make sure that you can view and modify "
                               "the content of this folder.")
                            .arg( mBasePath ) );
      ::exit(-1);
    }
   } else {
    // ~/Mail (or whatever the user specified) doesn't exist, create it
    if ( ::mkdir( TQFile::encodeName( mBasePath ) , S_IRWXU ) == -1 ) {
      KMessageBox::sorry(0, i18n("KMail could not create folder '%1';\n"
                                 "please make sure that you can view and "
                                 "modify the content of the folder '%2'.")
                            .arg( mBasePath ).arg( TQDir::homeDirPath() ) );
      ::exit(-1);
    }
  }
  mDir.setPath(mBasePath);
  mDir.reload();
  contentsChanged();
}


//-----------------------------------------------------------------------------
KMFolder* KMFolderMgr::createFolder(const TQString& fName, bool sysFldr,
				    KMFolderType aFolderType,
				    KMFolderDir *aFolderDir)
{
  KMFolder* fld;
  KMFolderDir *fldDir = aFolderDir;

  if (!aFolderDir)
    fldDir = &mDir;

  // check if this is a dimap folder and the folder we want to create has been deleted
  // since the last sync
  if ( fldDir->owner() && fldDir->owner()->folderType() == KMFolderTypeCachedImap ) {
    KMFolderCachedImap *storage = static_cast<KMFolderCachedImap*>( fldDir->owner()->storage() );
    KMAcctCachedImap *account = storage->account();
    // guess imap path
    TQString imapPath = storage->imapPath();
    if ( !imapPath.endsWith( "/" ) )
      imapPath += "/";
    imapPath += fName;
    if ( account->isDeletedFolder( imapPath ) || account->isDeletedFolder( imapPath + "/" )
       || account->isPreviouslyDeletedFolder( imapPath )
       || account->isPreviouslyDeletedFolder( imapPath + "/" ) ) {
      KMessageBox::error( 0, i18n("A folder with the same name has been deleted since the last mail check."
          "You need to check mails first before creating another folder with the same name."),
          i18n("Could Not Create Folder") );
      return 0;
    }
  }

  fld = fldDir->createFolder(fName, sysFldr, aFolderType);
  if (fld) {
    if ( fld->id() == 0 )
      fld->setId( createId() );
    contentsChanged();
    emit folderAdded(fld);
    if (kmkernel->filterMgr())
      kmkernel->filterMgr()->folderCreated(fld);
  }

  return fld;
}


//-----------------------------------------------------------------------------
KMFolder* KMFolderMgr::find(const TQString& folderName, bool foldersOnly)
{
  KMFolderNode* node;

  for (node=mDir.first(); node; node=mDir.next())
  {
    if (node->isDir() && foldersOnly) continue;
    if (node->name()==folderName) return (KMFolder*)node;
  }
  return 0;
}

//-----------------------------------------------------------------------------
KMFolder* KMFolderMgr::findById(const uint id)
{
  return findIdString( TQString(), id );
}

//-----------------------------------------------------------------------------
KMFolder* KMFolderMgr::findIdString( const TQString& folderId,
                                     const uint id,
                                     KMFolderDir *dir )
{
  if (!dir)
    dir = &mDir;

  DO_FOR_ALL(
        {
          KMFolder *folder = findIdString( folderId, id, child );
          if ( folder )
             return folder;
        },
        {
          if ( ( !folderId.isEmpty() && folder->idString() == folderId ) ||
               ( id != 0 && folder->id() == id ) )
             return folder;
        }
  )

  return 0;
}

void KMFolderMgr::getFolderURLS( TQStringList& flist, const TQString& prefix,
                                 KMFolderDir *adir )
{
  KMFolderDir* dir = adir ? adir : &mDir;

  DO_FOR_ALL(
             {
               getFolderURLS( flist, prefix + "/" + folder->name(), child );
             },
             {
               flist << prefix + "/" + folder->name();
             }
             )
}

KMFolder* KMFolderMgr::getFolderByURL( const TQString& vpath,
                                       const TQString& prefix,
                                       KMFolderDir *adir )
{
  KMFolderDir* dir = adir ? adir : &mDir;
  DO_FOR_ALL(
        {
          TQString a = prefix + "/" + folder->name();
          KMFolder * mfolder = getFolderByURL( vpath, a,child );
          if ( mfolder )
            return mfolder;
        },
        {
          TQString comp = prefix + "/" + folder->name();
          if ( comp  == vpath )
            return folder;
        }
  )
  return 0;
}

//-----------------------------------------------------------------------------
KMFolder* KMFolderMgr::findOrCreate(const TQString& aFolderName, bool sysFldr,
                                    const uint id)
{
  KMFolder* folder = 0;
  if ( id == 0 )
    folder = find(aFolderName);
  else
    folder = findById(id);

  if (!folder)
  {
    static bool know_type = false;
    static KMFolderType type = KMFolderTypeMaildir;
    if (know_type == false)
    {
      know_type = true;
      TDEConfig *config = KMKernel::config();
      TDEConfigGroupSaver saver(config, "General");
      if (config->hasKey("default-mailbox-format"))
      {
        if (config->readNumEntry("default-mailbox-format", 1) == 0)
          type = KMFolderTypeMbox;

      }
    }

    folder = createFolder(aFolderName, sysFldr, type);
    if (!folder) {
      KMessageBox::error(0,(i18n("Cannot create file `%1' in %2.\nKMail cannot start without it.").arg(aFolderName).arg(mBasePath)));
      exit(-1);
    }
    if ( id > 0 )
      folder->setId( id );
  }
  return folder;
}


//-----------------------------------------------------------------------------
void KMFolderMgr::remove(KMFolder* aFolder)
{
  if (!aFolder) return;
  // remember the original folder to trigger contentsChanged later
  if (!mRemoveOrig) mRemoveOrig = aFolder;
  if (aFolder->child())
  {
    // call remove for every child
    KMFolderNode* node;
    TQPtrListIterator<KMFolderNode> it(*aFolder->child());
    for ( ; (node = it.current()); )
    {
      ++it;
      if (node->isDir()) continue;
      KMFolder *folder = static_cast<KMFolder*>(node);
      remove(folder);
    }
  }
  emit folderRemoved(aFolder);
  removeFolder(aFolder);
}

void KMFolderMgr::removeFolder(KMFolder* aFolder)
{
  connect(aFolder, TQ_SIGNAL(removed(KMFolder*, bool)),
      this, TQ_SLOT(removeFolderAux(KMFolder*, bool)));
  aFolder->remove();
}

KMFolder* KMFolderMgr::parentFolder( KMFolder* folder )
{
  // find the parent folder by stripping "." and ".directory" from the name
  KMFolderDir* fdir = folder->parent();
  TQString parentName = fdir->name();
  parentName = parentName.mid( 1, parentName.length()-11 );
  KMFolderNode* parent = fdir->hasNamedFolder( parentName );
  if ( !parent && fdir->parent() ) // dimap obviously has a different structure
    parent = fdir->parent()->hasNamedFolder( parentName );

  KMFolder* parentF = 0;
  if ( parent )
    parentF = dynamic_cast<KMFolder*>( parent );
  return parentF;
}

void KMFolderMgr::removeFolderAux(KMFolder* aFolder, bool success)
{
  if (!success) {
    mRemoveOrig = 0;
    return;
  }

  KMFolderDir* fdir = aFolder->parent();
  KMFolderNode* fN;
  for (fN = fdir->first(); fN != 0; fN = fdir->next()) {
    if (fN->isDir() && (fN->name() == "." + aFolder->fileName() + ".directory")) {
      removeDirAux(static_cast<KMFolderDir*>(fN));
      break;
    }
  }
  KMFolder* parentF = parentFolder( aFolder );

  // aFolder will be deleted by the next call!
  aFolder->parent()->remove(aFolder);

  // update the children state
  if ( parentF )
  {
    if ( parentF != aFolder )
    {
      parentF->storage()->updateChildrenState();
    }
  }
  else
    kdWarning(5006) << "Can not find parent folder" << endl;

  if (aFolder == mRemoveOrig) {
    // call only if we're removing the original parent folder
    contentsChanged();
    mRemoveOrig = 0;
  }
}

void KMFolderMgr::removeDirAux(KMFolderDir* aFolderDir)
{
  TQDir dir;
  TQString folderDirLocation = aFolderDir->path();
  aFolderDir->clear();
  aFolderDir->parent()->remove(aFolderDir);
  dir.rmdir(folderDirLocation);
}

//-----------------------------------------------------------------------------
KMFolderRootDir& KMFolderMgr::dir(void)
{
  return mDir;
}


//-----------------------------------------------------------------------------
void KMFolderMgr::contentsChanged(void)
{
  if (mQuiet) mChanged = true;
  else emit changed();
}


//-----------------------------------------------------------------------------
void KMFolderMgr::reload(void)
{
}

//-----------------------------------------------------------------------------
void KMFolderMgr::createFolderList(TQStringList *str,
				   TQValueList<TQGuardedPtr<KMFolder> > *folders)
{
  createFolderList( str, folders, 0, "" );
}

//-----------------------------------------------------------------------------
void KMFolderMgr::createI18nFolderList(TQStringList *str,
				   TQValueList<TQGuardedPtr<KMFolder> > *folders)
{
  createFolderList( str, folders, 0, TQString(), true );
}

//-----------------------------------------------------------------------------
void KMFolderMgr::createFolderList(TQStringList *str,
				   TQValueList<TQGuardedPtr<KMFolder> > *folders,
				   KMFolderDir *adir,
				   const TQString& prefix,
				   bool i18nized)
{
  KMFolderDir* dir = adir ? adir : &mDir;

  DO_FOR_ALL(
        {
          createFolderList(str, folders, child, "  " + prefix, i18nized );
        },
        {
          if (i18nized)
            str->append(prefix + folder->label());
          else
            str->append(prefix + folder->name());
          folders->append( folder );
        }
  )
}

//-----------------------------------------------------------------------------
void KMFolderMgr::syncAllFolders( KMFolderDir *adir )
{
  KMFolderDir* dir = adir ? adir : &mDir;
  DO_FOR_ALL(
             {
               syncAllFolders(child);
             },
             {
               if (folder->isOpened())
	         folder->sync();
             }
  )
}


//-----------------------------------------------------------------------------
/**
 * Check each folder in turn to see if it is configured to
 * AutoExpire. If so, expire old messages.
 *
 * Should be called with 0 first time around.
 */
void KMFolderMgr::expireAllFolders(bool immediate, KMFolderDir *adir) {
  KMFolderDir   *dir = adir ? adir : &mDir;

  DO_FOR_ALL(
             {
               expireAllFolders(immediate, child);
             },
             {
               if (folder->isAutoExpire()) {
                 folder->expireOldMessages( immediate );
               }
             }
  )
}

//-----------------------------------------------------------------------------
void KMFolderMgr::quiet(bool beQuiet)
{
  if (beQuiet)
    mQuiet++;
  else {
    mQuiet--;
    if (mQuiet <= 0)
    {
      mQuiet = 0;
      if (mChanged) emit changed();
      mChanged = false;
    }
  }
}

//-----------------------------------------------------------------------------
void KMFolderMgr::tryReleasingFolder(KMFolder* f, KMFolderDir* adir)
{
  KMFolderDir* dir = adir ? adir : &mDir;
  DO_FOR_ALL(
             {
               tryReleasingFolder(f, child);
             },
             {
               if (folder->isOpened())
	         folder->storage()->tryReleasingFolder(f);
             }
  )
}

//-----------------------------------------------------------------------------
uint KMFolderMgr::createId()
{
  int newId;
  do
  {
    newId = kapp->random();
  } while ( findById( newId ) != 0 );

  return newId;
}

//-----------------------------------------------------------------------------
void KMFolderMgr::moveFolder( KMFolder* folder, KMFolderDir *newParent )
{
  renameFolder( folder, folder->name(), newParent );
}

//-----------------------------------------------------------------------------
void KMFolderMgr::renameFolder( KMFolder* folder, const TQString& newName,
                                KMFolderDir *newParent )
{
  RenameJob* job = new RenameJob( folder->storage(), newName, newParent );
  connect( job, TQ_SIGNAL( renameDone( TQString, bool ) ),
      this, TQ_SLOT( slotRenameDone( TQString, bool ) ) );
  connect( job, TQ_SIGNAL( renameDone( TQString, bool ) ),
	   this, TQ_SIGNAL( folderMoveOrCopyOperationFinished() ) );
  job->start();
}

//-----------------------------------------------------------------------------
void KMFolderMgr::copyFolder( KMFolder* folder, KMFolderDir *newParent )
{
  kdDebug(5006) << "Copy folder: " << folder->prettyURL() << endl;
  CopyFolderJob* job = new CopyFolderJob( folder->storage(), newParent );
  connect( job, TQ_SIGNAL( folderCopyComplete( bool ) ),
	   this, TQ_SIGNAL( folderMoveOrCopyOperationFinished() ) );
  job->start();
}

//-----------------------------------------------------------------------------
void KMFolderMgr::slotRenameDone( TQString, bool success )
{
  kdDebug(5006) << k_funcinfo << success << endl;
}

#include "kmfoldermgr.moc"
