/* -*- c++ -*-
 * imapaccountbase.h
 *
 * Copyright (c) 2000-2002 Michael Haeckel <haeckel@kde.org>
 * Copyright (c) 2002 Marc Mutz <mutz@kde.org>
 *
 * This file is based on work on pop3 and imap account implementations
 * by Don Sanders <sanders@kde.org> and Michael Haeckel <haeckel@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.
 */

#ifndef __KMAIL_IMAPACCOUNTBASE_H__
#define __KMAIL_IMAPACCOUNTBASE_H__

#include <set>

#include "networkaccount.h"

#include <tqtimer.h>
#include <tqguardedptr.h>
#include <tdeio/global.h>

namespace KMail {
    class AccountManager;
}
class KMFolder;
class TDEConfig/*Base*/;
class KMMessagePart;
class DwBodyPart;
class DwMessage;
class FolderStorage;
template <typename T> class TQValueVector;

namespace TDEIO {
  class Job;
}

namespace KPIM {
  class ProgressItem;
}

namespace KMail {
  struct ACLListEntry;
  struct QuotaInfo;
  typedef TQValueVector<KMail::ACLListEntry> ACLList;

  class AttachmentStrategy;

  class ImapAccountBase : public KMail::NetworkAccount {
    Q_OBJECT
  
  protected:
    ImapAccountBase( AccountManager * parent, const TQString & name, uint id );
  public:
    virtual ~ImapAccountBase();

    /** Set the config options to a decent state */
    virtual void init();

    /** A weak assignment operator */
    virtual void pseudoAssign( const KMAccount * a );

    /** @return whether to automatically expunge deleted messages when
        leaving the folder */
    bool autoExpunge() const { return mAutoExpunge; }
    virtual void setAutoExpunge( bool expunge );

    /** @return whether to show hidden files on the server */
    bool hiddenFolders() const { return mHiddenFolders; }
    virtual void setHiddenFolders( bool show );

    /** @return whether to show only subscribed folders */
    bool onlySubscribedFolders() const { return mOnlySubscribedFolders; }
    virtual void setOnlySubscribedFolders( bool show );

    /** @return whether to show only locally subscribed folders */
    bool onlyLocallySubscribedFolders() const { return mOnlyLocallySubscribedFolders; }
    virtual void setOnlyLocallySubscribedFolders( bool show );


    /** @return whether to load attachments on demand */
    bool loadOnDemand() const { return mLoadOnDemand; }
    virtual void setLoadOnDemand( bool load );

    /** @return whether to list only open folders */
    bool listOnlyOpenFolders() const { return mListOnlyOpenFolders; }
    virtual void setListOnlyOpenFolders( bool only );

    /** Configure the slave by adding to the meta data map */
    virtual TDEIO::MetaData slaveConfig() const;

    virtual void readConfig( TDEConfig& config );
    virtual void writeConfig( TDEConfig& config );

    /**
     * The state of the tdeioslave connection
     */
    enum ConnectionState { Error = 0, Connected, Connecting };

    // possible list types
    enum ListType {
      List,
      ListSubscribed,
      ListSubscribedNoCheck,
      ListFolderOnly,
      ListFolderOnlySubscribed
    };

    /**
     * Connect to the server, if no connection is active
     * Returns Connected (ok), Error (ko) or Connecting - which means
     * that one should wait for the slaveConnected signal from TDEIO::Scheduler
     * before proceeding.
     */
    ConnectionState makeConnection();

    // namespace defines
    enum imapNamespace { PersonalNS=0, OtherUsersNS=1, SharedNS=2 };

    // map a namespace type to a list of namespaces
    typedef TQMap<imapNamespace, TQStringList> nsMap;

    // map a namespace to a delimiter
    typedef TQMap<TQString, TQString> namespaceDelim;

    // map a namespace type to a map with the namespace and the delimiter
    typedef TQMap<imapNamespace, namespaceDelim> nsDelimMap;

    /**
     * Info Data for the Job
     */
    struct jobData
    {
      // Needed by TQMap, don't use
      jobData() : url(TQString()), parent(0), current(0), total(1), done(0), offset(0), progressItem(0),
                  onlySubscribed(false), quiet(false), cancellable(false) {}
      // Real constructor
      jobData( const TQString& _url, KMFolder *_parent = 0,
          int _total = 1, int _done = 0, bool _quiet = false,
          bool _cancelable = false )
        : url(_url), parent(_parent), current(0), total(_total), done(_done), offset(0),
          progressItem(0), quiet(_quiet), cancellable(_cancelable) {}

      TQString path;
      TQString url;
      TQString curNamespace;
      TQByteArray data;
      TQCString cdata;
      TQStringList items;
      KMFolder *parent, *current;
      TQPtrList<KMMessage> msgList;
      int total, done, offset;
      KPIM::ProgressItem *progressItem;
      bool onlySubscribed, quiet, cancellable;
    };

    typedef TQMap<TDEIO::Job *, jobData>::Iterator JobIterator;
    /**
     * Call this when starting a new job
     */
    void insertJob( TDEIO::Job* job, const jobData& data ) {
      mapJobData.insert( job, data );
    }
    /**
     * Look for the jobData related to a given job. Compare with end()
     */
    JobIterator findJob( TDEIO::Job* job ) { return mapJobData.find( job ); }
    JobIterator jobsEnd() { return mapJobData.end(); }
    /**
     * Call this when a job is finished.
     * Don't use @p *it afterwards!
     */
    void removeJob( JobIterator& it );

    void removeJob( TDEIO::Job* job );

    /**
     * Subscribe (@p subscribe = TRUE) / Unsubscribe the folder
     * identified by @p imapPath.
     * Emits subscriptionChanged signal on success.
     * Emits subscriptionChangeFailed signal when it fails.
     * @param quiet if false, an error message will be displayed if the job fails.
     */
    void changeSubscription(bool subscribe, const TQString& imapPath, bool quiet = false );

    /**
     * Returns whether the account is locally subscribed to the
     * folder @param imapPath. No relation to server side subscription above.
     */
    bool locallySubscribedTo( const TQString& imapPath );

    /**
     * Locally subscribe (@p subscribe = TRUE) / Unsubscribe the folder
     * identified by @p imapPath.
     */
    void changeLocalSubscription( const TQString& imapPath, bool subscribe );


    /**
     * Retrieve the users' right on the folder
     * identified by @p folder and @p imapPath.
     * Emits receivedUserRights signal on success/error.
     */
    void getUserRights( KMFolder* folder, const TQString& imapPath );

    /**
     * Retrieve the complete list of ACLs on the folder
     * identified by @p imapPath.
     * Emits receivedACL signal on success/error.
     */
    void getACL( KMFolder* folder, const TQString& imapPath );

    /**
     * Retrieve the the quota inforamiton on the folder
     * identified by @p imapPath.
     * Emits receivedQuotaInfo signal on success/error.
     */
    void getStorageQuotaInfo( KMFolder* folder, const TQString& imapPath );

    /**
     * Set the status on the server
     * Emits imapStatusChanged signal on success/error.
     */
    void setImapStatus( KMFolder* folder, const TQString& path, const TQCString& flags );

    /**
     * Set seen status on the server.
     * Emits imapStatusChanged signal on success/error.
     */
    void setImapSeenStatus( KMFolder* folder, const TQString& path, bool seen );

    /**
     * The TDEIO-Slave died
     */
    void slaveDied() { mSlave = 0; killAllJobs(); }

    /**
     * Kill the slave if any jobs are active
     */
    void killAllJobs( bool disconnectSlave=false ) = 0;

    /**
     * Abort all running mail checks. Used when exiting.
     */
    virtual void cancelMailCheck();

    /**
     * Init a new-mail-check for a single folder, and optionally its subfolders.
     */
    enum FolderListType { Single, Recursive };
    void processNewMailInFolder( KMFolder* folder, FolderListType type = Single );

    /**
     * Return true if we are processing a mailcheck for a single folder
     */
    bool checkingSingleFolder() { return mCheckingSingleFolder; }

    /**
     * Called when we're completely done checking mail for this account
     * When @p setStatusMsg is true a status msg is shown
     */
    void postProcessNewMail( bool setStatusMsg = true );

    /**
     * Check whether we're checking for new mail
     * and the folder is included
     */
    bool checkingMail( KMFolder *folder );

    bool checkingMail() { return NetworkAccount::checkingMail(); }

    /**
     * Handles the result from a BODYSTRUCTURE fetch
     */
    void handleBodyStructure( TQDataStream & stream, KMMessage * msg,
                              const AttachmentStrategy *as );

    /**
     * Reimplemented. Additionally set the folder label
     */
    virtual void setFolder(KMFolder*, bool addAccount = false);

    /**
     * Returns false if the IMAP server for this account doesn't support ACLs.
     * (and true if it does, or if we didn't try yet).
     */
    bool hasACLSupport() const { return mACLSupport; }

    /**
     * Returns false if the IMAP server for this account doesn't support annotations.
     * (and true if it does, or if we didn't try yet).
     */
    bool hasAnnotationSupport() const { return mAnnotationSupport; }

    /**
     * Called if the annotation command failed due to 'unsupported'
     */
    void setHasNoAnnotationSupport() { mAnnotationSupport = false; }

    /**
     * Returns false if the IMAP server for this account doesn't support quotas.
     * (and true if it does, or if we didn't try yet).
     */
    bool hasQuotaSupport() const { return mQuotaSupport; }

    /**
     * Called if the quota command failed due to 'unsupported'
     */
    void setHasNoQuotaSupport() { mQuotaSupport = false; }

    /**
     * React to an error from the job. Uses job->error and job->errorString and calls
     * the protected virtual handleJobError with them. See handleError below for details.
     */
    bool handleJobError( TDEIO::Job* job, const TQString& context, bool abortSync = false );

    /**
     * Returns the root folder of this account
     */
    virtual FolderStorage* rootFolder() const = 0;

    /**
     * Progress item for listDir
     */
    KPIM::ProgressItem* listDirProgressItem();

    /**
     * @return the number of (subscribed, if applicable) folders in this
     * account.
     */
    virtual unsigned int folderCount() const;

    /**
     * @return defined namespaces
     */
    nsMap namespaces() const { return mNamespaces; }

    /**
     * Set defined namespaces
     */
    virtual void setNamespaces( nsMap map )
    { mNamespaces = map; }

    /**
     * Full blown section - namespace - delimiter map
     * Do not call this very often as the map is constructed on the fly
     */
    nsDelimMap namespacesWithDelimiter();

    /**
     * @return the namespace for the @p folder
     */
     TQString namespaceForFolder( FolderStorage* );

     /**
      * Adds "/" as needed to the given namespace
      */
     TQString addPathToNamespace( const TQString& ns );

     /**
      * @return the delimiter for the @p namespace
      */
     TQString delimiterForNamespace( const TQString& prefix );

     /**
      * @return the delimiter for the @p folderstorage
      */
     TQString delimiterForFolder( FolderStorage* );

     /**
      * @return the namespace - delimiter map
      */
     namespaceDelim namespaceToDelimiter() const
     { return mNamespaceToDelimiter; }

     /**
      * Set the namespace - delimiter map
      */
     void setNamespaceToDelimiter( namespaceDelim map )
     { mNamespaceToDelimiter = map; }

     /**
      * Returns true if the given string is a namespace
      */
     bool isNamespaceFolder( TQString& name );

     /**
      * Returns true if the account has the given capability
      */
     bool hasCapability( const TQString& capa ) {
      return mCapabilities.contains( capa ); }

     /**
      * Create an IMAP path for a parent folder and a foldername
      * Parent and folder are separated with the delimiter of the account
      * The path starts and ends with '/'
      */
     TQString createImapPath( FolderStorage* parent, const TQString& folderName );

     /**
      * Create an IMAP path for a parent imapPath and a folderName
      */
     TQString createImapPath( const TQString& parent, const TQString& folderName );


  public slots:
    /**
     * Call this to get the namespaces
     * You are notified by the signal namespacesFetched
     */
    void getNamespaces();

  private slots:
    /**
     * is called when the changeSubscription has finished
     * emits subscriptionChanged
     */
    void slotSubscriptionResult(TDEIO::Job * job);

  protected slots:
    virtual void slotCheckQueuedFolders();

    /// Handle a message coming from the TDEIO scheduler saying that the slave is now connected
    void slotSchedulerSlaveConnected(TDEIO::Slave *aSlave);
    /// Handle an error coming from the TDEIO scheduler
    void slotSchedulerSlaveError(TDEIO::Slave *aSlave, int, const TQString &errorMsg);

    /**
     * Only delete information about the job and ignore write errors
     */
    void slotSetStatusResult(TDEIO::Job * job);

    /// Result of getUserRights() job
    void slotGetUserRightsResult( TDEIO::Job* _job );

    /// Result of getACL() job
    void slotGetACLResult( TDEIO::Job* _job );

    /// Result of getStorageQuotaInfo() job
    void slotGetStorageQuotaInfoResult( TDEIO::Job* _job );

    /**
     * Send a NOOP command regularly to keep the slave from disconnecting
     */
    void slotNoopTimeout();
    /**
     * Log out when idle
     */
    void slotIdleTimeout();

    /**
     * Kills all jobs
     */
    void slotAbortRequested( KPIM::ProgressItem* );

    /**
     * Only delete information about the job
     */
    void slotSimpleResult(TDEIO::Job * job);

    /** Gets and parses the namespaces */
    void slotNamespaceResult( TDEIO::Job*, const TQString& str );

    /**
     * Saves the fetched namespaces
     */
    void slotSaveNamespaces( const ImapAccountBase::nsDelimMap& map );

    /**
     * Saves the capabilities list
     */
    void slotCapabilitiesResult( TDEIO::Job*, const TQString& result );

  protected:

  /**
     * Handle an error coming from a TDEIO job or from a TDEIO slave (via the scheduler)
     * and abort everything (in all cases) if abortSync is true [this is for slotSchedulerSlaveError].
     * Otherwise (abortSync==false), dimap will only abort in case of severe errors (connection broken),
     * but on "normal" errors (no permission to delete, etc.) it will ask the user.
     *
     * @param error the error code, usually job->error())
     * @param errorMsg the error message, usually job->errorText()
     * @param job the tdeio job (can be 0). If set, removeJob will be called automatically.
     * This is important! It means you should not call removeJob yourself in case of errors.
     * We can't let the caller do that, since it should only be done afterwards, and only if we didn't abort.
     *
     * @param context a sentence that gives some context to the error, e.g. i18n("Error while uploading message [...]")
     * @param abortSync if true, abort sync in all cases (see above). If false, ask the user (when possible).
     * @return false when aborting, true when continuing
     */
    virtual bool handleError( int error, const TQString &errorMsg, TDEIO::Job* job, const TQString& context, bool abortSync = false );

    /** Handle an error during TDEIO::put - helper method */
    bool handlePutError( TDEIO::Job* job, jobData& jd, KMFolder* folder );

    virtual TQString protocol() const;
    virtual unsigned short int defaultPort() const;

    /**
     * Build KMMessageParts and DwBodyParts from the bodystructure-stream
     */
    void constructParts( TQDataStream & stream, int count, KMMessagePart* parentKMPart,
       DwBodyPart * parent, const DwMessage * dwmsg );

    /** Migrate the prefix */
    void migratePrefix();

    // used for writing the blacklist out to the config file
    TQStringList locallyBlacklistedFolders() const;
    void localBlacklistFromStringList( const TQStringList & );
    TQString prettifyQuotaError( const TQString& _error, TDEIO::Job * job );

  protected:
    TQPtrList<TQGuardedPtr<KMFolder> > mOpenFolders;
    TQStringList mSubfolderNames, mSubfolderPaths,
        mSubfolderMimeTypes, mSubfolderAttributes;
    TQMap<TDEIO::Job *, jobData> mapJobData;
    /** used to detect when the slave has not been used for a while */
    TQTimer mIdleTimer;
    /** used to send a noop to the slave in regular intervals to keep it from disonnecting */
    TQTimer mNoopTimer;
    int mTotal, mCountUnread, mCountLastUnread;
    TQMap<TQString, int> mUnreadBeforeCheck;
    bool mAutoExpunge : 1;
    bool mHiddenFolders : 1;
    bool mOnlySubscribedFolders : 1;
    bool mOnlyLocallySubscribedFolders : 1;
    bool mLoadOnDemand : 1;
    bool mListOnlyOpenFolders : 1;
    bool mProgressEnabled : 1;

    bool mErrorDialogIsActive : 1;
    bool mPasswordDialogIsActive : 1;
    bool mACLSupport : 1;
    bool mAnnotationSupport : 1;
    bool mQuotaSupport : 1;
    bool mSlaveConnected : 1;
    bool mSlaveConnectionError : 1;
    bool mCheckingSingleFolder : 1;

    // folders that should be checked for new mails
    TQValueList<TQGuardedPtr<KMFolder> > mMailCheckFolders;
    // folders that should be checked after the current check is done
    TQValueList<TQGuardedPtr<KMFolder> > mFoldersQueuedForChecking;
    // holds messageparts from the bodystructure
    TQPtrList<KMMessagePart> mBodyPartList;
    // the current message for the bodystructure
    KMMessage* mCurrentMsg;

    TQGuardedPtr<KPIM::ProgressItem> mListDirProgressItem;

    // our namespaces in the form section=namespaceList
    nsMap mNamespaces;

    // namespace - delimiter map
    namespaceDelim mNamespaceToDelimiter;

    // old prefix for migration
    TQString mOldPrefix;

    // capabilities
    TQStringList mCapabilities;

    std::set<TQString> mLocalSubscriptionBlackList;

  signals:
    /**
     * Emitted when the slave managed or failed to connect
     * This is always emitted at some point after makeConnection returned Connecting.
     * @param errorCode 0 for success, != 0 in case of error
     * @param errorMsg if errorCode is != 0, this goes with errorCode to call TDEIO::buildErrorString
     */
    void connectionResult( int errorCode, const TQString& errorMsg );

    /**
     * Emitted when the subscription has changed,
     * as a result of a changeSubscription call.
     */
    void subscriptionChanged(const TQString& imapPath, bool subscribed);

    /**
     * Emitted when changeSubscription() failed.
     * @param errorMessage the error message that contains the reason for the failure
     */
    void subscriptionChangeFailed( const TQString &errorMessage );

    /**
     * Emitted upon completion of the job for setting the status for a group of UIDs,
     * as a result of a setImapStatus call.
     * On error, if the user chooses abort (not continue), cont is set to false.
     */
    void imapStatusChanged( KMFolder*, const TQString& imapPath, bool cont );

    /**
     * Emitted when the get-user-rights job is done,
     * as a result of a getUserRights call.
     * Use userRights() to retrieve them after using userRightsState() to see if the results are
     * valid.
     */
    void receivedUserRights( KMFolder* folder );

    /**
     * Emitted when the get-the-ACLs job is done,
     * as a result of a getACL call.
     * @param folder the folder for which we were listing the ACLs (can be 0)
     * @param job the job that was used for doing so (can be used to display errors)
     * @param entries the ACL list. Make your copy of it, it comes from the job.
     */
    void receivedACL( KMFolder* folder, TDEIO::Job* job, const KMail::ACLList& entries );

    /**
     * Emitted when the getQuotaInfo job is done,
     * as a result of a getQuotaInfo call.
     * @param folder The folder for which we were getting quota info (can be 0)
     * @param job The job that was used for doing so (can be used to display errors)
     * @param info The quota information for this folder. Make your copy of it,
     * it comes from the job.
     */
    void receivedStorageQuotaInfo( KMFolder* folder, TDEIO::Job* job, const KMail::QuotaInfo& entries );

    /**
     * Emitted when we got the namespaces
     */
    void namespacesFetched( const ImapAccountBase::nsDelimMap& );

    /**
     * Emitted when we got the namespaces, and they were set on the object.
     */
    void namespacesFetched();
  };


} // namespace KMail

#endif // __KMAIL_IMAPACCOUNTBASE_H__