/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Alec Flett <alecf@netscape.com>
 *   Seth Spitzer <sspitzer@netscape.com>
 *   Bhuvan Racham <racham@netscape.com>
 *   David Bienvenu <bienvenu@mozilla.org>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/*
 * The account manager service - manages all accounts, servers, and identities
 */

#include "nsIComponentManager.h"
#include "nsIServiceManager.h"
#include "nsISupportsArray.h"
#include "nsMsgAccountManager.h"
#include "nsMsgBaseCID.h"
#include "nsMsgCompCID.h"
#include "nsMsgDBCID.h"
#include "prmem.h"
#include "prcmon.h"
#include "prthread.h"
#include "plstr.h"
#include "nsString.h"
#include "nsXPIDLString.h"
#include "nsUnicharUtils.h"
#include "nscore.h"
#include "nsEscape.h"
#include "nsCRT.h"  // for nsCRT::strtok
#include "prprf.h"
#include "nsIMsgFolderCache.h"
#include "nsFileStream.h"
#include "nsIFileStreams.h"
#include "nsMsgUtils.h"
#include "nsIFileSpec.h" 
#include "nsILocalFile.h"
#include "nsIURL.h"
#include "nsNetCID.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsISmtpService.h"
#include "nsIMsgBiffManager.h"
#include "nsIMsgPurgeService.h"
#include "nsIObserverService.h"
#include "nsIMsgMailSession.h"
#include "nsIEventQueueService.h"
#include "nsIDirectoryService.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsMsgFolderFlags.h"
#include "nsIRDFService.h"
#include "nsRDFCID.h"
#include "nsIImapIncomingServer.h" 
#include "nsIImapUrl.h"
#include "nsIMessengerOSIntegration.h"
#include "nsICategoryManager.h"
#include "nsISupportsPrimitives.h"
#include "nsMsgFilterService.h"
#include "nsIMsgFilter.h"
#include "nsIMsgSearchSession.h"
#include "nsIDBChangeListener.h"
#include "nsIDBFolderInfo.h"
#include "nsIMsgHdr.h"
#include "nsILineInputStream.h"
#include "nsNetUtil.h"
#include "nsEscape.h"

#define PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS "mail.accountmanager.accounts"
#define PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT "mail.accountmanager.defaultaccount"
#define PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER "mail.accountmanager.localfoldersserver"
#define PREF_MAIL_SERVER_PREFIX "mail.server."
#define ACCOUNT_PREFIX "account"
#define SERVER_PREFIX "server"
#define ID_PREFIX "id"
#define ABOUT_TO_GO_OFFLINE_TOPIC "network:offline-about-to-go-offline"
#define ACCOUNT_DELIMITER ","
#define APPEND_ACCOUNTS_VERSION_PREF_NAME "append_preconfig_accounts.version"
#define MAILNEWS_ROOT_PREF "mailnews."
#define PREF_MAIL_ACCOUNTMANAGER_APPEND_ACCOUNTS "mail.accountmanager.appendaccounts"

static NS_DEFINE_CID(kMsgAccountCID, NS_MSGACCOUNT_CID);
static NS_DEFINE_CID(kMsgFolderCacheCID, NS_MSGFOLDERCACHE_CID);
static NS_DEFINE_CID(kEventQueueServiceCID, NS_EVENTQUEUESERVICE_CID);

// use this to search for all servers with the given hostname/iid and
// put them in "servers"
typedef struct _findServerEntry {
  const char *hostname;
  const char *username;
  PRInt32 port;
  const char *type;
  PRBool useRealSetting;
  nsIMsgIncomingServer *server;
} findServerEntry;

typedef struct _findServerByKeyEntry {
  const char *key;
  PRInt32 index;
} findServerByKeyEntry;

// use this to search for all servers that match "server" and
// put all identities in "identities"
typedef struct _findIdentitiesByServerEntry {
  nsISupportsArray *identities;
  nsIMsgIncomingServer *server;
} findIdentitiesByServerEntry;

typedef struct _findServersByIdentityEntry {
  nsISupportsArray *servers;
  nsIMsgIdentity *identity;
} findServersByIdentityEntry;

typedef struct _findAccountByKeyEntry {
    const char* key;
    nsIMsgAccount* account;
} findAccountByKeyEntry;


NS_IMPL_THREADSAFE_ISUPPORTS5(nsMsgAccountManager,
                              nsIMsgAccountManager,
                              nsIObserver,
                              nsISupportsWeakReference,
                              nsIUrlListener,
                              nsIFolderListener)

nsMsgAccountManager::nsMsgAccountManager() :
  m_accountsLoaded(PR_FALSE),
  m_emptyTrashInProgress(PR_FALSE),
  m_cleanupInboxInProgress(PR_FALSE),
  m_haveShutdown(PR_FALSE),
  m_shutdownInProgress(PR_FALSE),
  m_userAuthenticated(PR_FALSE),
  m_loadingVirtualFolders(PR_FALSE),
  m_virtualFoldersLoaded(PR_FALSE)
{
}

nsMsgAccountManager::~nsMsgAccountManager()
{
  nsresult rv;

  if(!m_haveShutdown)
  {
    Shutdown();
    //Don't remove from Observer service in Shutdown because Shutdown also gets called
    //from xpcom shutdown observer.  And we don't want to remove from the service in that case.
    nsCOMPtr<nsIObserverService> observerService = 
         do_GetService("@mozilla.org/observer-service;1", &rv);
    if (NS_SUCCEEDED(rv))
    {    
      observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
      observerService->RemoveObserver(this, ABOUT_TO_GO_OFFLINE_TOPIC);
    }
  }
}

nsresult nsMsgAccountManager::Init()
{
  nsresult rv;

  rv = NS_NewISupportsArray(getter_AddRefs(m_accounts));
  if(NS_FAILED(rv)) return rv;

  rv = NS_NewISupportsArray(getter_AddRefs(mFolderListeners));

  nsCOMPtr<nsIObserverService> observerService = 
           do_GetService("@mozilla.org/observer-service;1", &rv);
  if (NS_SUCCEEDED(rv))
  {    
    observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_TRUE);
    observerService->AddObserver(this, "quit-application" , PR_TRUE);
    observerService->AddObserver(this, ABOUT_TO_GO_OFFLINE_TOPIC, PR_TRUE);
    observerService->AddObserver(this, "session-logout", PR_TRUE);
    observerService->AddObserver(this, "profile-before-change", PR_TRUE);
  }

  return NS_OK;
}

nsresult nsMsgAccountManager::Shutdown()
{
  if (m_haveShutdown)     // do not shutdown twice
    return NS_OK;

  nsresult rv;

  SaveVirtualFolders();
    
  nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
  if (msgDBService)
  {
    PRInt32 numVFListeners = m_virtualFolderListeners.Count();
    for(PRInt32 i = 0; i < numVFListeners; i++)
      msgDBService->UnregisterPendingListener(m_virtualFolderListeners[i]);
  }
  if(m_msgFolderCache)
    WriteToFolderCache(m_msgFolderCache);
  (void)ShutdownServers();
  (void)UnloadAccounts();
  
  //shutdown removes nsIIncomingServer listener from biff manager, so do it after accounts have been unloaded
  nsCOMPtr<nsIMsgBiffManager> biffService = do_GetService(NS_MSGBIFFMANAGER_CONTRACTID, &rv);
  if (NS_SUCCEEDED(rv) && biffService)
    biffService->Shutdown();

  //shutdown removes nsIIncomingServer listener from purge service, so do it after accounts have been unloaded
  nsCOMPtr<nsIMsgPurgeService> purgeService = do_GetService(NS_MSGPURGESERVICE_CONTRACTID, &rv);
  if (NS_SUCCEEDED(rv) && purgeService)
    purgeService->Shutdown();
  
  m_msgFolderCache = nsnull;

  m_haveShutdown = PR_TRUE;
  return NS_OK;
}

NS_IMETHODIMP
nsMsgAccountManager::GetShutdownInProgress(PRBool *_retval)
{
    NS_ENSURE_ARG_POINTER(_retval);
    *_retval = m_shutdownInProgress;
    return NS_OK;
}

NS_IMETHODIMP
nsMsgAccountManager::GetUserNeedsToAuthenticate(PRBool *aRetval)
{
  NS_ENSURE_ARG_POINTER(aRetval);
  if (!m_userAuthenticated)
    return m_prefs->GetBoolPref("mail.password_protect_local_cache", aRetval);
  *aRetval = !m_userAuthenticated;
  return NS_OK;
}

NS_IMETHODIMP
nsMsgAccountManager::SetUserNeedsToAuthenticate(PRBool aUserNeedsToAuthenticate)
{
    m_userAuthenticated = !aUserNeedsToAuthenticate;
    return NS_OK;
}

NS_IMETHODIMP nsMsgAccountManager::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *someData)
{
  if(!nsCRT::strcmp(aTopic,NS_XPCOM_SHUTDOWN_OBSERVER_ID))
  {
    Shutdown();
    return NS_OK;
  }
  
  if (!nsCRT::strcmp(aTopic,"quit-application"))
  {
    m_shutdownInProgress = PR_TRUE;
    return NS_OK;
  }
  
  if (!nsCRT::strcmp(aTopic, ABOUT_TO_GO_OFFLINE_TOPIC))
  {
    nsAutoString dataString(NS_LITERAL_STRING("offline"));
    if (someData)
    {
      nsAutoString someDataString(someData);
      if (dataString == someDataString)
        CloseCachedConnections();
    }
    return NS_OK;
  }
  
  if (!nsCRT::strcmp(aTopic, "session-logout"))
  {
    m_incomingServers.Enumerate(hashLogoutOfServer, nsnull);
    return NS_OK;
  }
  
  if (!nsCRT::strcmp(aTopic, "profile-before-change"))
  {
    Shutdown();
    return NS_OK;
  }
    
 return NS_OK;
}

nsresult
nsMsgAccountManager::getPrefService()
{

  // get the prefs service
  nsresult rv = NS_OK;
  
  if (!m_prefs)
    m_prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);

  if (NS_FAILED(rv)) return rv;
  /* m_prefs is good now */
  return NS_OK;
}

void
nsMsgAccountManager::getUniqueKey(const char* prefix,
                                  nsHashtable *hashTable,
                                  nsCString& aResult)
{
  PRInt32 i=1;
  PRBool unique=PR_FALSE;

  do {
    aResult=prefix;
    aResult.AppendInt(i++);
    nsCStringKey hashKey(aResult);
    void* hashElement = hashTable->Get(&hashKey);
    
    if (!hashElement) unique=PR_TRUE;
  } while (!unique);

}

void
nsMsgAccountManager::getUniqueAccountKey(const char *prefix,
                                         nsISupportsArray *accounts,
                                         nsCString& aResult)
{
  PRInt32 i=1;
  PRBool unique = PR_FALSE;
  
  findAccountByKeyEntry findEntry;
  findEntry.account = nsnull;
  
  do {
      aResult = prefix;
      aResult.AppendInt(i++);
      findEntry.key = aResult.get();
    
    accounts->EnumerateForwards(findAccountByKey, (void *)&findEntry);

    if (!findEntry.account) unique=PR_TRUE;
    findEntry.account = nsnull;
  } while (!unique);

}

nsresult
nsMsgAccountManager::CreateIdentity(nsIMsgIdentity **_retval)
{
  NS_ENSURE_ARG_POINTER(_retval);
  
  nsresult rv;

  nsCAutoString key;
  getUniqueKey(ID_PREFIX, &m_identities, key);

  rv = createKeyedIdentity(key.get(), _retval);

  return rv;
}

nsresult
nsMsgAccountManager::GetIdentity(const char* key,
                                 nsIMsgIdentity **_retval)
{
  if (!_retval) return NS_ERROR_NULL_POINTER;
  // null or empty key does not return an identity!
  if (!key || !key[0]) {
    *_retval = nsnull;
    return NS_OK;
  }

  nsresult rv;
  // check for the identity in the hash table
  nsCStringKey hashKey(key);
  nsISupports *idsupports = (nsISupports*)m_identities.Get(&hashKey);
  nsCOMPtr<nsIMsgIdentity> identity = do_QueryInterface(idsupports, &rv);

  if (NS_SUCCEEDED(rv)) {
    NS_ADDREF(*_retval = identity);
    return NS_OK;
  }

  // identity doesn't exist. create it.
  rv = createKeyedIdentity(key, _retval);

  return rv;
}

/*
 * the shared identity-creation code
 * create an identity and add it to the accountmanager's list.
 */
nsresult
nsMsgAccountManager::createKeyedIdentity(const char* key,
                                         nsIMsgIdentity ** aIdentity)
{
  nsresult rv;
  nsCOMPtr<nsIMsgIdentity> identity =
      do_CreateInstance(NS_MSGIDENTITY_CONTRACTID, &rv);
  if (NS_FAILED(rv)) return rv;
  
  identity->SetKey(key);
  
  nsCStringKey hashKey(key);

  // addref for the hash table`
  nsISupports* idsupports = identity;
  NS_ADDREF(idsupports);
  m_identities.Put(&hashKey, (void *)idsupports);

  NS_ADDREF(*aIdentity = identity);
  
  return NS_OK;
}

nsresult
nsMsgAccountManager::CreateIncomingServer(const char* username,
                                          const char* hostname,
                                          const char* type,
                                          nsIMsgIncomingServer **_retval)
{
  NS_ENSURE_ARG_POINTER(_retval);

  nsresult rv = LoadAccounts();
  if (NS_FAILED(rv)) return rv;

  nsCAutoString key;
  getUniqueKey(SERVER_PREFIX, &m_incomingServers, key);
  return createKeyedServer(key.get(), username, hostname, type, _retval);
}

nsresult
nsMsgAccountManager::GetIncomingServer(const char* key,
                                       nsIMsgIncomingServer **_retval)
{
  NS_ENSURE_ARG_POINTER(_retval);

  nsresult rv=NS_OK;
  
  nsCStringKey hashKey(key);
  nsCOMPtr<nsIMsgIncomingServer> server =
    do_QueryInterface((nsISupports*)m_incomingServers.Get(&hashKey), &rv);

  if (NS_SUCCEEDED(rv)) {
    NS_ADDREF(*_retval = server);
    return NS_OK;
  }

  // server doesn't exist, so create it
  // this is really horrible because we are doing our own prefname munging
  // instead of leaving it up to the incoming server.
  // this should be fixed somehow so that we can create the incoming server
  // and then read from the incoming server's attributes
  
  // in order to create the right kind of server, we have to look
  // at the pref for this server to get the username, hostname, and type
  nsCAutoString serverPrefPrefix(PREF_MAIL_SERVER_PREFIX);
  serverPrefPrefix += key;
  
  nsCAutoString serverPref;

  //
  // .type
  serverPref = serverPrefPrefix;
  serverPref += ".type";
  nsXPIDLCString serverType;
  rv = m_prefs->GetCharPref(serverPref.get(), getter_Copies(serverType));
  NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_INITIALIZED);
  
  //
  // .userName
  serverPref = serverPrefPrefix;
  serverPref += ".userName";
  nsXPIDLCString username;
  rv = m_prefs->GetCharPref(serverPref.get(), getter_Copies(username));

  // .hostname
  serverPref = serverPrefPrefix;
  serverPref += ".hostname";
  nsXPIDLCString hostname;
  rv = m_prefs->GetCharPref(serverPref.get(), getter_Copies(hostname));
  NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_INITIALIZED);
  
    // the server type doesn't exist. That's bad.

  rv = createKeyedServer(key, username, hostname, serverType, _retval);
  NS_ENSURE_SUCCESS(rv, rv);

  return rv;
}

/*
 * create a server when you know the key and the type
 */
nsresult
nsMsgAccountManager::createKeyedServer(const char* key,
                                       const char* username,
                                       const char* hostname,
                                       const char* type,
                                       nsIMsgIncomingServer ** aServer)
{
  nsresult rv;

  //construct the contractid
  nsCAutoString serverContractID(NS_MSGINCOMINGSERVER_CONTRACTID_PREFIX);
  serverContractID += type;
  
  // finally, create the server
  nsCOMPtr<nsIMsgIncomingServer> server =
           do_CreateInstance(serverContractID.get(), &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  
  server->SetKey(key);
  server->SetType(type);
  server->SetUsername(username);
  server->SetHostName(hostname);

  nsCStringKey hashKey(key);

  // addref for the hashtable
  nsISupports* serversupports = server;
  NS_ADDREF(serversupports);
  m_incomingServers.Put(&hashKey, serversupports);

  // now add all listeners that are supposed to be
  // waiting on root folders
  nsCOMPtr<nsIMsgFolder> rootFolder;
  rv = server->GetRootFolder(getter_AddRefs(rootFolder));
  NS_ENSURE_SUCCESS(rv, rv);
  mFolderListeners->EnumerateForwards(addListenerToFolder,
                                      (void *)(nsIMsgFolder*)rootFolder);
  NS_ADDREF(*aServer = server);
  
  return NS_OK;
}

PRBool
nsMsgAccountManager::addListenerToFolder(nsISupports *element, void *data)
{
  nsresult rv;
  nsIMsgFolder *rootFolder = (nsIMsgFolder *)data;
  nsCOMPtr<nsIFolderListener> listener = do_QueryInterface(element, &rv);
  NS_ENSURE_SUCCESS(rv, PR_TRUE);

  rootFolder->AddFolderListener(listener);
  return PR_TRUE;
}

PRBool
nsMsgAccountManager::removeListenerFromFolder(nsISupports *element, void *data)
{
  nsresult rv;
  nsIMsgFolder *rootFolder = (nsIMsgFolder *)data;
  nsCOMPtr<nsIFolderListener> listener = do_QueryInterface(element, &rv);
  NS_ENSURE_SUCCESS(rv, PR_TRUE);

  rootFolder->RemoveFolderListener(listener);
  return PR_TRUE;
}

NS_IMETHODIMP
nsMsgAccountManager::DuplicateAccount(nsIMsgAccount *aAccount)
{ 
  NS_ENSURE_ARG_POINTER(aAccount);
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsMsgAccountManager::RemoveIdentity(nsIMsgIdentity *aIdentity)
{
  // finish this
  return NS_OK;
}

NS_IMETHODIMP
nsMsgAccountManager::RemoveAccount(nsIMsgAccount *aAccount)
{
  NS_ENSURE_ARG_POINTER(aAccount);
  nsresult rv;
  rv = LoadAccounts();
  if (NS_FAILED(rv)) return rv;

  // order is important!
  // remove it from the prefs first
  nsXPIDLCString key;
  rv = aAccount->GetKey(getter_Copies(key));
  if (NS_FAILED(rv)) return rv;
  
  rv = removeKeyedAccount(key);
  if (NS_FAILED(rv)) return rv;

  // we were able to save the new prefs (i.e. not locked) so now remove it
  // from the account manager... ignore the error though, because the only
  // possible problem is that it wasn't in the hash table anyway... and if
  // so, it doesn't matter.
  m_accounts->RemoveElement(aAccount);

  // if it's the default, clear the default account
  if (m_defaultAccount.get() == aAccount) {
    SetDefaultAccount(nsnull);
  }

  // XXX - need to figure out if this is the last time this server is
  // being used, and only send notification then.
  // (and only remove from hashtable then too!)
  nsCOMPtr<nsIMsgIncomingServer> server;
  rv = aAccount->GetIncomingServer(getter_AddRefs(server));
  if (NS_SUCCEEDED(rv) && server) {
    nsXPIDLCString serverKey;
    rv = server->GetKey(getter_Copies(serverKey));
    NS_ENSURE_SUCCESS(rv,rv);

    LogoutOfServer(server); // close cached connections and forget session password

    // invalidate the FindServer() cache if we are removing the cached server
    if (m_lastFindServerResult) {
        nsXPIDLCString cachedServerKey;
        rv = m_lastFindServerResult->GetKey(getter_Copies(cachedServerKey));
        NS_ENSURE_SUCCESS(rv,rv);
    
        if (!nsCRT::strcmp((const char *)serverKey,(const char *)cachedServerKey)) {
            rv = SetLastServerFound(nsnull,"","",0,"");
            NS_ENSURE_SUCCESS(rv,rv);
        }
    }

    nsCStringKey hashKey(serverKey);
    
    nsIMsgIncomingServer* removedServer =
      (nsIMsgIncomingServer*) m_incomingServers.Remove(&hashKey);

    //NS_ASSERTION(server.get() == removedServer, "Key maps to different server. something wacky is going on");

    // remove reference from hashtable
    NS_IF_RELEASE(removedServer);
    
    nsCOMPtr<nsIMsgFolder> rootFolder;
    server->GetRootFolder(getter_AddRefs(rootFolder));
    nsCOMPtr<nsISupportsArray> allDescendents;
    NS_NewISupportsArray(getter_AddRefs(allDescendents));
    rootFolder->ListDescendents(allDescendents);
    PRUint32 cnt =0;
    rv = allDescendents->Count(&cnt);
    NS_ENSURE_SUCCESS(rv,rv);
    for (PRUint32 i=0; i< cnt;i++)
    {
      nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(allDescendents, i, &rv);
      folder->ForceDBClosed();
    }
   
    mFolderListeners->EnumerateForwards(removeListenerFromFolder,
                                        (void*)rootFolder);
    
    NotifyServerUnloaded(server);


    rv = server->RemoveFiles();
    NS_ASSERTION(NS_SUCCEEDED(rv), "failed to remove the files associated with server");

    
    // now clear out the server once and for all.
    // watch out! could be scary
    server->ClearAllValues();

    rootFolder->Shutdown(PR_TRUE);
  }
  nsCOMPtr<nsISupportsArray> identityArray;
  
  rv = aAccount->GetIdentities(getter_AddRefs(identityArray));
  if (NS_SUCCEEDED(rv)) {

    PRUint32 count=0;
    identityArray->Count(&count);

    PRUint32 i;
    for (i=0; i<count; i++) {
      nsCOMPtr<nsIMsgIdentity> identity;
      rv = identityArray->QueryElementAt(i, NS_GET_IID(nsIMsgIdentity),
                                         (void **)getter_AddRefs(identity));
      if (NS_SUCCEEDED(rv))
        // clear out all identity information.
        // watch out! could be scary
        identity->ClearAllValues();
    }

  }

  aAccount->ClearAllValues();
  return NS_OK;
}

// remove the account with the given key.
// note that this does NOT remove any of the related prefs
// (like the server, identity, etc)
nsresult
nsMsgAccountManager::removeKeyedAccount(const char *key)
{
  nsresult rv;
  rv = getPrefService();
  if (NS_FAILED(rv)) return rv;

  nsXPIDLCString accountList;
  rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS, getter_Copies(accountList));
  if (NS_FAILED(rv)) return rv;

  // reconstruct the new account list, re-adding all accounts except
  // the one with 'key'
  nsCAutoString newAccountList;
  char *newStr;
  char *rest = accountList.BeginWriting();
  
  char *token = nsCRT::strtok(rest, ",", &newStr);
  while (token) {
    nsCAutoString testKey(token);
    testKey.StripWhitespace();

    // re-add the candidate key only if it's not the key we're looking for
    if (!testKey.IsEmpty() && !testKey.Equals(key)) {
      if (!newAccountList.IsEmpty())
        newAccountList += ',';
      newAccountList += testKey;
    }

    token = nsCRT::strtok(newStr, ",", &newStr);
  }

  // Update mAccountKeyList to reflect the deletion
  mAccountKeyList = newAccountList;

  // now write the new account list back to the prefs
  rv = m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS,
                              newAccountList.get());
  if (NS_FAILED(rv)) return rv;


  return NS_OK;
}

/* get the default account. If no default account, pick the first account */
NS_IMETHODIMP
nsMsgAccountManager::GetDefaultAccount(nsIMsgAccount * *aDefaultAccount)
{
  NS_ENSURE_ARG_POINTER(aDefaultAccount);
  nsresult rv;
  rv = LoadAccounts();
  if (NS_FAILED(rv)) return rv;
  
  PRUint32 count;
  if (!m_defaultAccount) {
    m_accounts->Count(&count);
    if (count == 0) {
      *aDefaultAccount=nsnull;
      return NS_ERROR_FAILURE;
    }

    nsXPIDLCString defaultKey;
    rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT, getter_Copies(defaultKey));
    
    if (NS_SUCCEEDED(rv))
      GetAccount(defaultKey, getter_AddRefs(m_defaultAccount));

    if (!m_defaultAccount) {
      PRUint32 index;
      PRBool foundValidDefaultAccount = PR_FALSE;
      for (index = 0; index < count; index++) {
        nsCOMPtr<nsIMsgAccount> aAccount;
        rv = m_accounts->QueryElementAt(index, NS_GET_IID(nsIMsgAccount),
                                        (void **)getter_AddRefs(aAccount));
        if (NS_SUCCEEDED(rv)) {
          // get incoming server
          nsCOMPtr <nsIMsgIncomingServer> server;
          rv = aAccount->GetIncomingServer(getter_AddRefs(server));
          NS_ENSURE_SUCCESS(rv,rv);
          
          PRBool canBeDefaultServer = PR_FALSE;
          if (server)
            server->GetCanBeDefaultServer(&canBeDefaultServer);
          
          // if this can serve as default server, set it as default and
          // break outof the loop.
          if (canBeDefaultServer) {
            SetDefaultAccount(aAccount);
            foundValidDefaultAccount = PR_TRUE;
            break;
          }
        }
      }

      if (!foundValidDefaultAccount) {
        // get the first account and use it.
        // we need to fix this scenario.
        NS_WARNING("No valid default account found, just using first (FIXME)");
        nsCOMPtr<nsIMsgAccount> firstAccount;
        m_accounts->QueryElementAt(0, NS_GET_IID(nsIMsgAccount),
                                   (void **)getter_AddRefs(firstAccount));

        SetDefaultAccount(firstAccount);
      }
    }
  }
  
  *aDefaultAccount = m_defaultAccount;
  NS_IF_ADDREF(*aDefaultAccount);
  return NS_OK;
}


NS_IMETHODIMP
nsMsgAccountManager::SetDefaultAccount(nsIMsgAccount * aDefaultAccount)
{
  if (m_defaultAccount != aDefaultAccount)
  {
    nsCOMPtr<nsIMsgAccount> oldAccount = m_defaultAccount;

    m_defaultAccount = aDefaultAccount;

    // it's ok if this fails
    setDefaultAccountPref(aDefaultAccount);

    // ok if notifications fail
    notifyDefaultServerChange(oldAccount, aDefaultAccount);
  }
  return NS_OK;
}

// fire notifications
nsresult
nsMsgAccountManager::notifyDefaultServerChange(nsIMsgAccount *aOldAccount,
                                               nsIMsgAccount *aNewAccount)
{
  nsresult rv;

  nsCOMPtr<nsIMsgIncomingServer> server;
  nsCOMPtr<nsIMsgFolder> rootFolder;
  
  // first tell old server it's no longer the default
  if (aOldAccount) {
    rv = aOldAccount->GetIncomingServer(getter_AddRefs(server));
    if (NS_SUCCEEDED(rv) && server) {
      rv = server->GetRootFolder(getter_AddRefs(rootFolder));
      if (NS_SUCCEEDED(rv) && rootFolder)
        rootFolder->NotifyBoolPropertyChanged(kDefaultServerAtom,
                                                        PR_TRUE, PR_FALSE);
    }
  }
    
    // now tell new server it is.
  if (aNewAccount) {
    rv = aNewAccount->GetIncomingServer(getter_AddRefs(server));
    if (NS_SUCCEEDED(rv) && server) {
      rv = server->GetRootFolder(getter_AddRefs(rootFolder));
      if (NS_SUCCEEDED(rv) && rootFolder)
        rootFolder->NotifyBoolPropertyChanged(kDefaultServerAtom,
                                              PR_FALSE, PR_TRUE);
    }
  }

  if (aOldAccount && aNewAccount)  //only notify if the user goes and changes default account
  {
    nsCOMPtr<nsIObserverService> observerService = 
      do_GetService("@mozilla.org/observer-service;1", &rv);

    if (NS_SUCCEEDED(rv))
      observerService->NotifyObservers(nsnull,"mailDefaultAccountChanged",nsnull);
  }

  return NS_OK;
}

nsresult
nsMsgAccountManager::setDefaultAccountPref(nsIMsgAccount* aDefaultAccount)
{
  nsresult rv;
  
  rv = getPrefService();
  NS_ENSURE_SUCCESS(rv,rv);

  if (aDefaultAccount) {
    nsXPIDLCString key;
    rv = aDefaultAccount->GetKey(getter_Copies(key));
    NS_ENSURE_SUCCESS(rv, rv);
    
    rv = m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT, key);
    NS_ENSURE_SUCCESS(rv,rv);
  }
  else
    // don't care if this fails
    m_prefs->ClearUserPref(PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT);

  return NS_OK;
}
    

// enumaration for sending unload notifications
PRBool
nsMsgAccountManager::hashUnloadServer(nsHashKey *aKey, void *aData,
                                          void *closure)
{
    nsresult rv;
    nsCOMPtr<nsIMsgIncomingServer> server =
      do_QueryInterface((nsISupports*)aData, &rv);
    if (NS_FAILED(rv)) return PR_TRUE;
    
    nsMsgAccountManager *accountManager = (nsMsgAccountManager*)closure;
    accountManager->NotifyServerUnloaded(server);

    nsCOMPtr<nsIMsgFolder> rootFolder;
    rv = server->GetRootFolder(getter_AddRefs(rootFolder));

    accountManager->mFolderListeners->EnumerateForwards(removeListenerFromFolder,
                                        (void *)(nsIMsgFolder*)rootFolder);

    if(NS_SUCCEEDED(rv))
        rootFolder->Shutdown(PR_TRUE);

    return PR_TRUE;

}

/* static */ void nsMsgAccountManager::LogoutOfServer(nsIMsgIncomingServer *aServer)
{
    nsresult rv = aServer->Shutdown();
    NS_ASSERTION(NS_SUCCEEDED(rv), "Shutdown of server failed");
    rv = aServer->ForgetSessionPassword();
    NS_ASSERTION(NS_SUCCEEDED(rv), "failed to remove the password associated with server");
}

/* static */ PRBool
nsMsgAccountManager::hashLogoutOfServer(nsHashKey *aKey, void *aData,
                                               void *closure)
{
    nsresult rv;
    nsCOMPtr<nsIMsgIncomingServer> server =
      do_QueryInterface((nsISupports*)aData, &rv);
    if (NS_SUCCEEDED(rv))
      LogoutOfServer(server);

    return PR_TRUE;
}

NS_IMETHODIMP nsMsgAccountManager::GetFolderCache(nsIMsgFolderCache* *aFolderCache)
{
  if (!aFolderCache) return NS_ERROR_NULL_POINTER;

  nsresult rv = NS_OK;

  if (!m_msgFolderCache)
  {
    m_msgFolderCache = do_CreateInstance(kMsgFolderCacheCID, &rv);
    if (NS_FAILED(rv))
        return rv;

    nsCOMPtr<nsIFile> cacheFile;
    nsCOMPtr <nsIFileSpec> cacheFileSpec;
    
    rv = NS_GetSpecialDirectory(NS_APP_MESSENGER_FOLDER_CACHE_50_DIR, getter_AddRefs(cacheFile));
    if (NS_FAILED(rv)) return rv;
    
    // TODO: Make nsIMsgFolderCache::Init take an nsIFile and
    // avoid this conversion.
    rv = NS_NewFileSpecFromIFile(cacheFile, getter_AddRefs(cacheFileSpec));
    if (NS_FAILED(rv)) return rv;
               
    m_msgFolderCache->Init(cacheFileSpec);
  }

  *aFolderCache = m_msgFolderCache;
  NS_IF_ADDREF(*aFolderCache);
  return rv;
}


// enumaration for writing out accounts to folder cache.
PRBool nsMsgAccountManager::writeFolderCache(nsHashKey *aKey, void *aData,
                                             void *closure)
{
    nsIMsgIncomingServer *server = (nsIMsgIncomingServer*)aData;
    nsIMsgFolderCache *folderCache = (nsIMsgFolderCache *)closure;

    server->WriteToFolderCache(folderCache);
    return PR_TRUE;
}

// enumeration for empty trash on exit
PRBool PR_CALLBACK nsMsgAccountManager::cleanupOnExit(nsHashKey *aKey, void *aData,
                                             void *closure)
{
  nsIMsgIncomingServer *server = (nsIMsgIncomingServer*)aData;
  PRBool emptyTrashOnExit = PR_FALSE;
  PRBool cleanupInboxOnExit = PR_FALSE;
  nsresult rv;
    
  if (WeAreOffline())
    return PR_TRUE;

  server->GetEmptyTrashOnExit(&emptyTrashOnExit);
  nsCOMPtr <nsIImapIncomingServer> imapserver = do_QueryInterface(server);
  if (imapserver)
  {
    imapserver->GetCleanupInboxOnExit(&cleanupInboxOnExit);
    imapserver->SetShuttingDown(PR_TRUE);
  }
  if (emptyTrashOnExit || cleanupInboxOnExit)
  {
    nsCOMPtr<nsIMsgFolder> root;
    server->GetRootFolder(getter_AddRefs(root));
    nsXPIDLCString type;
    server->GetType(getter_Copies(type));
    if (root)
    {
      nsCOMPtr<nsIMsgFolder> folder;
      folder = do_QueryInterface(root);
      if (folder)
      {
         nsXPIDLCString passwd;
         PRBool serverRequiresPasswordForAuthentication = PR_TRUE;
         PRBool isImap = (type ? PL_strcmp(type, "imap") == 0 :
                          PR_FALSE);
         if (isImap)
         {
           server->GetServerRequiresPasswordForBiff(&serverRequiresPasswordForAuthentication);
           server->GetPassword(getter_Copies(passwd));
         }
         if (!isImap || (isImap && (!serverRequiresPasswordForAuthentication || (passwd && 
                        strlen((const char*) passwd)))))
         {
           nsCOMPtr<nsIUrlListener> urlListener;
           nsCOMPtr<nsIMsgAccountManager> accountManager = 
                    do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
           if (NS_FAILED(rv)) return rv;
           nsCOMPtr<nsIEventQueueService> pEventQService = 
                    do_GetService(kEventQueueServiceCID, &rv);
           if (NS_FAILED(rv)) return rv;
           nsCOMPtr<nsIEventQueue> eventQueue;
           pEventQService->GetThreadEventQueue(NS_CURRENT_THREAD,
                                    getter_AddRefs(eventQueue)); 
           if (isImap)
             urlListener = do_QueryInterface(accountManager, &rv);
                    
           if (isImap && cleanupInboxOnExit)
           {
             nsCOMPtr<nsIEnumerator> aEnumerator;
             folder->GetSubFolders(getter_AddRefs(aEnumerator));
             nsCOMPtr<nsISupports> aSupport;
             rv = aEnumerator->First();
             while (NS_SUCCEEDED(rv))
             {  
               rv = aEnumerator->CurrentItem(getter_AddRefs(aSupport));
               nsCOMPtr<nsIMsgFolder>inboxFolder = do_QueryInterface(aSupport);
               PRUint32 flags;
               inboxFolder->GetFlags(&flags);
               if (flags & MSG_FOLDER_FLAG_INBOX)
               {
                 rv = inboxFolder->Compact(urlListener, nsnull /* msgwindow */);
                 if (NS_SUCCEEDED(rv))
                   accountManager->SetFolderDoingCleanupInbox(inboxFolder);
                 break;
                }
                else
                  rv = aEnumerator->Next();
             }
           }
                          
           if (emptyTrashOnExit)
           {
             rv = folder->EmptyTrash(nsnull, urlListener);
             if (isImap && NS_SUCCEEDED(rv))
               accountManager->SetFolderDoingEmptyTrash(folder);
           }
                    
           if (isImap && urlListener)
           {
             PRBool inProgress = PR_FALSE;
             if (cleanupInboxOnExit)
             {
               PRInt32 loopCount = 0; // used to break out after 5 seconds
               accountManager->GetCleanupInboxInProgress(&inProgress);
               while (inProgress && loopCount++ < 5000)
               {
                 accountManager->GetCleanupInboxInProgress(&inProgress);
                 PR_CEnterMonitor(folder);
                 PR_CWait(folder, PR_MicrosecondsToInterval(1000UL));
                 PR_CExitMonitor(folder);
                 if (eventQueue)
                 {
                   PLEvent *event;
                   do
                   {
                     eventQueue->GetEvent(&event);
                     if (event)
                       eventQueue->HandleEvent(event);
                   }
                   while (event);
                 }
               }
             }
             if (emptyTrashOnExit)
             {
               accountManager->GetEmptyTrashInProgress(&inProgress);
               PRInt32 loopCount = 0;
               while (inProgress && loopCount++ < 5000)
               {
                 accountManager->GetEmptyTrashInProgress(&inProgress);
                 PR_CEnterMonitor(folder);
                 PR_CWait(folder, PR_MicrosecondsToInterval(1000UL));
                 PR_CExitMonitor(folder);
                 if (eventQueue)
                 {
                   PLEvent *event;
                   do
                   {
                     eventQueue->GetEvent(&event);
                     if (event)
                       eventQueue->HandleEvent(event);
                   }
                   while (event);
                 }
               }
             }
           } 
         }
       }     
     }
   }
   return PR_TRUE;
}

PRBool nsMsgAccountManager::closeCachedConnections(nsHashKey *aKey, void *aData, void *closure)
{
  nsIMsgIncomingServer *server = (nsIMsgIncomingServer*)aData;

  server->CloseCachedConnections();

  return PR_TRUE;
}

PRBool nsMsgAccountManager::shutdown(nsHashKey *aKey, void *aData, void *closure)
{
  nsIMsgIncomingServer *server = (nsIMsgIncomingServer*)aData;

  server->Shutdown();

  return PR_TRUE;
}


/* readonly attribute nsISupportsArray accounts; */
NS_IMETHODIMP
nsMsgAccountManager::GetAccounts(nsISupportsArray **_retval)
{
  nsresult rv;
  
  rv = LoadAccounts();
  if (NS_FAILED(rv)) return rv;
  
  nsCOMPtr<nsISupportsArray> accounts;
  NS_NewISupportsArray(getter_AddRefs(accounts));

  accounts->AppendElements(m_accounts);

  NS_ADDREF(*_retval = accounts);

  return NS_OK;
}

PRBool
nsMsgAccountManager::hashElementToArray(nsHashKey *aKey, void *aData,
                                        void *closure)
{
    nsISupports* element = (nsISupports*)aData;
    nsISupportsArray* array = (nsISupportsArray*)closure;

    array->AppendElement(element);
    return PR_TRUE;
}

PRBool
nsMsgAccountManager::hashElementRelease(nsHashKey *aKey, void *aData,
                                        void *closure)
{
  nsISupports* element = (nsISupports*)aData;

  NS_RELEASE(element);

  return PR_TRUE;               // return true to remove this element
}

/* nsISupportsArray GetAllIdentities (); */
NS_IMETHODIMP
nsMsgAccountManager::GetAllIdentities(nsISupportsArray **_retval)
{
  nsresult rv;
  rv = LoadAccounts();
  if (NS_FAILED(rv)) return rv;
  
  nsCOMPtr<nsISupportsArray> identities;
  rv = NS_NewISupportsArray(getter_AddRefs(identities));
  if (NS_FAILED(rv)) return rv;

  // convert hash table->nsISupportsArray of identities
  m_accounts->EnumerateForwards(getIdentitiesToArray,
                                (void *)(nsISupportsArray*)identities);
  // convert nsISupportsArray->nsISupportsArray
  // when do we free the nsISupportsArray?
  NS_ADDREF(*_retval = identities);
  return rv;
}

PRBool
nsMsgAccountManager::addIdentityIfUnique(nsISupports *element, void *aData)
{
  nsresult rv;
  nsCOMPtr<nsIMsgIdentity> identity = do_QueryInterface(element, &rv);
  if (NS_FAILED(rv)) {
    printf("addIdentityIfUnique problem\n");
    return PR_TRUE;
  }
  
  nsISupportsArray *array = (nsISupportsArray*)aData;

  
  nsXPIDLCString key;
  rv = identity->GetKey(getter_Copies(key));
  if (NS_FAILED(rv)) return PR_TRUE;

  PRUint32 count=0;
  rv = array->Count(&count);
  if (NS_FAILED(rv)) return PR_TRUE;
  
  PRBool found=PR_FALSE;
  PRUint32 i;
  for (i=0; i<count; i++) {
    nsCOMPtr<nsISupports> thisElement;
    array->GetElementAt(i, getter_AddRefs(thisElement));

    nsCOMPtr<nsIMsgIdentity> thisIdentity =
      do_QueryInterface(thisElement, &rv);
    if (NS_FAILED(rv)) continue;

    nsXPIDLCString thisKey;
    thisIdentity->GetKey(getter_Copies(thisKey));
    if (PL_strcmp(key, thisKey)==0) {
      found = PR_TRUE;
      break;
    }
  }

  if (!found)
    array->AppendElement(identity);

  return PR_TRUE;
}

PRBool
nsMsgAccountManager::getIdentitiesToArray(nsISupports *element, void *aData)
{
  nsresult rv;
  nsCOMPtr<nsIMsgAccount> account = do_QueryInterface(element, &rv);
  if (NS_FAILED(rv)) return PR_TRUE;
  
  
  nsCOMPtr<nsISupportsArray> identities;
  rv = account->GetIdentities(getter_AddRefs(identities));
  if (NS_FAILED(rv)) return PR_TRUE;

  identities->EnumerateForwards(addIdentityIfUnique, aData);
  
  return PR_TRUE;
}

/* nsISupportsArray GetAllServers (); */
NS_IMETHODIMP
nsMsgAccountManager::GetAllServers(nsISupportsArray **_retval)
{
  nsresult rv;
  rv = LoadAccounts();
  if (NS_FAILED(rv)) return rv;
  
  nsCOMPtr<nsISupportsArray> servers;
  rv = NS_NewISupportsArray(getter_AddRefs(servers));
  if (NS_FAILED(rv)) return rv;

  // enumerate by going through the list of accounts, so that we
  // get the order correct
  m_incomingServers.Enumerate(getServersToArray,
                              (void *)(nsISupportsArray*)servers);
  NS_ADDREF(*_retval = servers);
  return rv;
}

PRBool PR_CALLBACK
nsMsgAccountManager::getServersToArray(nsHashKey *aKey,
                                       void *element,
                                       void *aData)
{
  nsresult rv;
  nsCOMPtr<nsIMsgIncomingServer> server =
    do_QueryInterface((nsISupports*)element, &rv);
  if (NS_FAILED(rv)) return PR_TRUE;
  
  nsISupportsArray *array = (nsISupportsArray*)aData;
  
  nsCOMPtr<nsISupports> serverSupports = do_QueryInterface(server);
  if (NS_SUCCEEDED(rv)) 
    array->AppendElement(serverSupports);

  return PR_TRUE;
}

nsresult
nsMsgAccountManager::LoadAccounts()
{
  nsresult rv;

  // for now safeguard multiple calls to this function
  if (m_accountsLoaded)
    return NS_OK;

  kDefaultServerAtom = do_GetAtom("DefaultServer");
  
  //Ensure biff service has started
  nsCOMPtr<nsIMsgBiffManager> biffService = 
           do_GetService(NS_MSGBIFFMANAGER_CONTRACTID, &rv);

  if (NS_SUCCEEDED(rv))
    biffService->Init();

  //Ensure purge service has started
  nsCOMPtr<nsIMsgPurgeService> purgeService = 
           do_GetService(NS_MSGPURGESERVICE_CONTRACTID, &rv);

  if (NS_SUCCEEDED(rv))
    purgeService->Init();
  
  // Ensure messenger OS integration service has started
  // note, you can't expect the integrationService to be there
  // we don't have OS integration on all platforms.
  nsCOMPtr<nsIMessengerOSIntegration> integrationService =
           do_GetService(NS_MESSENGEROSINTEGRATION_CONTRACTID, &rv);

  // mail.accountmanager.accounts is the main entry point for all accounts
  nsXPIDLCString accountList;
  rv = getPrefService();
  if (NS_SUCCEEDED(rv)) {
    rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS, getter_Copies(accountList));
    
    /** 
     * Check to see if we need to add pre-configured accounts.
     * Following prefs are important to note in understanding the procedure here.
     *
     * 1. pref("mailnews.append_preconfig_accounts.version", version number);
     * This pref registers the current version in the user prefs file. A default value 
     * is stored in mailnews.js file. If a given vendor needs to add more preconfigured 
     * accounts, the default version number can be increased. Comparing version 
     * number from user's prefs file and the default one from mailnews.js, we
     * can add new accounts and any other version level changes that need to be done.
     *
     * 2. pref("mail.accountmanager.appendaccounts", <comma separated account list>);
     * This pref contains the list of pre-configured accounts that ISP/Vendor wants to
     * to add to the existing accounts list. 
     */
    nsCOMPtr<nsIPrefService> prefservice(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
    NS_ENSURE_SUCCESS(rv,rv);
    
    nsCOMPtr<nsIPrefBranch> defaultsPrefBranch;
    rv = prefservice->GetDefaultBranch(MAILNEWS_ROOT_PREF, getter_AddRefs(defaultsPrefBranch));
    NS_ENSURE_SUCCESS(rv,rv);

    nsCOMPtr<nsIPrefBranch> prefBranch;
    rv = prefservice->GetBranch(MAILNEWS_ROOT_PREF, getter_AddRefs(prefBranch));
    NS_ENSURE_SUCCESS(rv,rv);

    PRInt32 appendAccountsCurrentVersion=0;
    PRInt32 appendAccountsDefaultVersion=0;
    rv = prefBranch->GetIntPref(APPEND_ACCOUNTS_VERSION_PREF_NAME, &appendAccountsCurrentVersion);
    NS_ENSURE_SUCCESS(rv,rv);

    rv = defaultsPrefBranch->GetIntPref(APPEND_ACCOUNTS_VERSION_PREF_NAME, &appendAccountsDefaultVersion);
    NS_ENSURE_SUCCESS(rv,rv);

    // Update the account list if needed
    if ((appendAccountsCurrentVersion <= appendAccountsDefaultVersion)) {

      // Get a list of pre-configured accounts
      nsXPIDLCString appendAccountList;
      rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_APPEND_ACCOUNTS, getter_Copies(appendAccountList));

      // If there are pre-configured accounts, we need to add them to the existing list.
      if (!appendAccountList.IsEmpty()) {
        if (!accountList.IsEmpty()) {
          nsCStringArray existingAccountsArray;
          existingAccountsArray.ParseString(accountList.get(), ACCOUNT_DELIMITER);

          // Tokenize the data and add each account if it is not already there 
          // in the user's current mailnews account list
          char *newAccountStr;
          char *preConfigAccountsStr = ToNewCString(appendAccountList);
  
          char *token = nsCRT::strtok(preConfigAccountsStr, ACCOUNT_DELIMITER, &newAccountStr);

          nsCAutoString newAccount;
          while (token) {
            if (token && *token) {
              newAccount.Assign(token);
              newAccount.StripWhitespace();

              if (existingAccountsArray.IndexOf(newAccount) == -1) {
                accountList += ",";
                accountList += newAccount;
              }
            }
            token = nsCRT::strtok(newAccountStr, ACCOUNT_DELIMITER, &newAccountStr);
          }
          PR_Free(preConfigAccountsStr);
        }
        else {
          accountList = appendAccountList;
        }
        // Increase the version number so that updates will happen as and when needed
        rv = prefBranch->SetIntPref(APPEND_ACCOUNTS_VERSION_PREF_NAME, appendAccountsCurrentVersion + 1);
      }
    }
  }

  m_accountsLoaded = PR_TRUE;  //It is ok to return null accounts like when we create new profile
  m_haveShutdown = PR_FALSE;
  
  if (!accountList || !accountList[0])
    return NS_OK;
  
  /* parse accountList and run loadAccount on each string, comma-separated */   
  nsCOMPtr<nsIMsgAccount> account;
  char *newStr;
  char *rest = accountList.BeginWriting();
  nsCAutoString str;
  
  for (char *token = nsCRT::strtok(rest, ",", &newStr);
       token;
       token = nsCRT::strtok(newStr, ",", &newStr))
  {
    str = token;
    str.StripWhitespace();
      
    if (str.IsEmpty() ||
        NS_FAILED(createKeyedAccount(str.get(), getter_AddRefs(account))) ||
        !account) {
      NS_WARNING("unexpected entry in account list; prefs corrupt?");
      continue;
    }

    // force load of accounts (need to find a better way to do this)
    nsCOMPtr<nsISupportsArray> identities;
    account->GetIdentities(getter_AddRefs(identities));
    
    nsCOMPtr<nsIMsgIncomingServer> server;
    account->GetIncomingServer(getter_AddRefs(server));
  }

  nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); 

  if (NS_SUCCEEDED(rv))
    mailSession->AddFolderListener(this, nsIFolderListener::added | nsIFolderListener::removed);
  /* finished loading accounts */
  return NS_OK;
}

PRBool
nsMsgAccountManager::getAccountList(nsISupports *element, void *aData)
{
  nsresult rv;
  nsCAutoString* accountList = (nsCAutoString*) aData;
  nsCOMPtr<nsIMsgAccount> account = do_QueryInterface(element, &rv);
  if (NS_FAILED(rv)) return PR_TRUE;
  
  nsXPIDLCString key;
  rv = account->GetKey(getter_Copies(key));
  if (NS_FAILED(rv)) return PR_TRUE;

  if ((*accountList).IsEmpty())
    (*accountList).Append(key);
  else {
    (*accountList) += ',';
    (*accountList).Append(key);
  }

  return PR_TRUE;
}

// this routine goes through all the identities and makes sure
// that the special folders for each identity have the
// correct special folder flags set, e.g, the Sent folder has
// the sent flag set.
//
// it also goes through all the spam settings for each account
// and makes sure the folder flags are set there, too
NS_IMETHODIMP
nsMsgAccountManager::SetSpecialFolders()
{
  nsresult rv;
  nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv);
  NS_ENSURE_SUCCESS(rv,rv); 

  nsCOMPtr<nsISupportsArray> identities;
  GetAllIdentities(getter_AddRefs(identities));

  PRUint32 idCount=0;
  identities->Count(&idCount);

  PRUint32 id;
  nsXPIDLCString identityKey;
  
  for (id=0; id<idCount; id++) 
  {
    nsCOMPtr<nsISupports> thisSupports;
    rv = identities->GetElementAt(id, getter_AddRefs(thisSupports));
    if (NS_FAILED(rv)) continue;
    
    nsCOMPtr<nsIMsgIdentity>
      thisIdentity = do_QueryInterface(thisSupports, &rv);

    if (NS_SUCCEEDED(rv) && thisIdentity)
    {
      nsXPIDLCString folderUri;
      nsCOMPtr<nsIRDFResource> res;
      nsCOMPtr<nsIMsgFolder> folder;
      thisIdentity->GetFccFolder(getter_Copies(folderUri));
      if (folderUri && NS_SUCCEEDED(rdf->GetResource(folderUri, getter_AddRefs(res))))
      {
        folder = do_QueryInterface(res, &rv);
        nsCOMPtr <nsIMsgFolder> parent;
        if (folder && NS_SUCCEEDED(rv))
        {
          rv = folder->GetParent(getter_AddRefs(parent));
          if (NS_SUCCEEDED(rv) && parent)
            rv = folder->SetFlag(MSG_FOLDER_FLAG_SENTMAIL);
        }
      }
      thisIdentity->GetDraftFolder(getter_Copies(folderUri));
      if (folderUri && NS_SUCCEEDED(rdf->GetResource(folderUri, getter_AddRefs(res))))
      {
        folder = do_QueryInterface(res, &rv);
        nsCOMPtr <nsIMsgFolder> parent;
        if (folder && NS_SUCCEEDED(rv))
        {
          rv = folder->GetParent(getter_AddRefs(parent));
          if (NS_SUCCEEDED(rv) && parent)
            rv = folder->SetFlag(MSG_FOLDER_FLAG_DRAFTS);
        }
      }
      thisIdentity->GetStationeryFolder(getter_Copies(folderUri));
      if (folderUri && NS_SUCCEEDED(rdf->GetResource(folderUri, getter_AddRefs(res))))
      {
        folder = do_QueryInterface(res, &rv);
        if (folder && NS_SUCCEEDED(rv))
        {
          nsCOMPtr <nsIMsgFolder> parent;
          rv = folder->GetParent(getter_AddRefs(parent));
          if (NS_SUCCEEDED(rv) && parent) // only set flag if folder is real
            rv = folder->SetFlag(MSG_FOLDER_FLAG_TEMPLATES);
        }
      }
    }
  }

  // XXX todo
  // get all servers
  // get all spam settings for each server
  // set the JUNK folder flag on the spam folders, right?
  return NS_OK;
}

NS_IMETHODIMP
nsMsgAccountManager::UnloadAccounts()
{
  // release the default account
  kDefaultServerAtom = nsnull;
  m_defaultAccount=nsnull;
  m_incomingServers.Enumerate(hashUnloadServer, this);

  m_accounts->Clear();          // will release all elements
  m_identities.Reset(hashElementRelease, nsnull);
  m_incomingServers.Reset(hashElementRelease, nsnull);
  m_accountsLoaded = PR_FALSE;
  mAccountKeyList.Truncate(0);
  SetLastServerFound(nsnull,"","",0,"");
  return NS_OK;
}

NS_IMETHODIMP
nsMsgAccountManager::ShutdownServers()
{
  m_incomingServers.Enumerate(shutdown, nsnull);
  return NS_OK;
}

NS_IMETHODIMP
nsMsgAccountManager::CloseCachedConnections()
{
  m_incomingServers.Enumerate(closeCachedConnections, nsnull);
  return NS_OK;
}

NS_IMETHODIMP
nsMsgAccountManager::CleanupOnExit()
{
  m_incomingServers.Enumerate(cleanupOnExit, nsnull);
  return NS_OK;
}

NS_IMETHODIMP
nsMsgAccountManager::WriteToFolderCache(nsIMsgFolderCache *folderCache)
{
    m_incomingServers.Enumerate(writeFolderCache, folderCache);
    return folderCache->Close();
}

nsresult
nsMsgAccountManager::createKeyedAccount(const char* key,
                                        nsIMsgAccount ** aAccount)
{
    
  nsresult rv;
  nsCOMPtr<nsIMsgAccount> account = do_CreateInstance(kMsgAccountCID, &rv);
  if (NS_FAILED(rv)) return rv;

  account->SetKey(key);

  // add to internal nsISupportsArray
  m_accounts->AppendElement(NS_STATIC_CAST(nsISupports*, account));

  // add to string list
  if (mAccountKeyList.IsEmpty())
    mAccountKeyList = key;
  else {
    mAccountKeyList += ",";
    mAccountKeyList += key;
  }

  rv = getPrefService();
  if (NS_SUCCEEDED(rv))
    m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS,
                         mAccountKeyList.get());

  NS_ADDREF(*aAccount = account);
  
  return NS_OK;
}

nsresult
nsMsgAccountManager::CreateAccount(nsIMsgAccount **_retval)
{
    NS_ENSURE_ARG_POINTER(_retval);

    nsCAutoString key;
    getUniqueAccountKey(ACCOUNT_PREFIX, m_accounts, key);

    return createKeyedAccount(key.get(), _retval);
}

nsresult
nsMsgAccountManager::GetAccount(const char* key,
                                nsIMsgAccount **_retval)
{
    if (!_retval) return NS_ERROR_NULL_POINTER;

    findAccountByKeyEntry findEntry;
    findEntry.key = key;
    findEntry.account = nsnull;
    
    m_accounts->EnumerateForwards(findAccountByKey, (void *)&findEntry);

    if (findEntry.account)
        NS_ADDREF(*_retval = findEntry.account);
    else
        *_retval = nsnull;

    // not found, create on demand
    return NS_OK;
}

nsresult
nsMsgAccountManager::FindServerIndex(nsIMsgIncomingServer* server,
                                     PRInt32* result)
{
  NS_ENSURE_ARG_POINTER(server);
  nsresult rv;
  
  nsXPIDLCString key;
  rv = server->GetKey(getter_Copies(key));

  findServerByKeyEntry findEntry;
  findEntry.key = key;
  findEntry.index = -1;
  
  // do this by account because the account list is in order
  m_accounts->EnumerateForwards(findServerIndexByServer, (void *)&findEntry);

  // even if the search failed, we can return index.
  // this means that all servers not in the array return an index higher
  // than all "registered" servers
  *result = findEntry.index;
  
  return NS_OK;
}

PRBool
nsMsgAccountManager::findServerIndexByServer(nsISupports *element, void *aData)
{
  nsresult rv;
  
  nsCOMPtr<nsIMsgAccount> account = do_QueryInterface(element);
  findServerByKeyEntry *entry = (findServerByKeyEntry*) aData;

  // increment the index;
  entry->index++;
  
  nsCOMPtr<nsIMsgIncomingServer> server;
  rv = account->GetIncomingServer(getter_AddRefs(server));
  if (!server || NS_FAILED(rv)) return PR_TRUE;
  
  nsXPIDLCString key;
  rv = server->GetKey(getter_Copies(key));
  if (NS_FAILED(rv)) return PR_TRUE;

  // stop when found,
  // index will be set to the current index 
  if (nsCRT::strcmp(key, entry->key)==0)
    return PR_FALSE;
  
  return PR_TRUE;
}

PRBool
nsMsgAccountManager::findAccountByKey(nsISupports* element, void *aData)
{
    nsresult rv;
    nsCOMPtr<nsIMsgAccount> account = do_QueryInterface(element, &rv);
    if (NS_FAILED(rv)) return PR_TRUE;
    
    findAccountByKeyEntry *entry = (findAccountByKeyEntry*) aData;

    nsXPIDLCString key;
    account->GetKey(getter_Copies(key));
    if (PL_strcmp(key, entry->key)==0) {
        entry->account = account;
        return PR_FALSE;        // stop when found
    }

    return PR_TRUE;
}

NS_IMETHODIMP nsMsgAccountManager::AddIncomingServerListener(nsIIncomingServerListener *serverListener)
{
   m_incomingServerListeners.AppendObject(serverListener);
   return NS_OK;
}

NS_IMETHODIMP nsMsgAccountManager::RemoveIncomingServerListener(nsIIncomingServerListener *serverListener)
{
    m_incomingServerListeners.RemoveObject(serverListener);
    return NS_OK;
}


NS_IMETHODIMP nsMsgAccountManager::NotifyServerLoaded(nsIMsgIncomingServer *server)
{
    PRInt32 count = m_incomingServerListeners.Count();
    
    for(PRInt32 i = 0; i < count; i++)
    {
        nsIIncomingServerListener* listener = m_incomingServerListeners[i];
        listener->OnServerLoaded(server);
    }

    return NS_OK;
}

NS_IMETHODIMP nsMsgAccountManager::NotifyServerUnloaded(nsIMsgIncomingServer *server)
{
    PRInt32 count = m_incomingServerListeners.Count();
    server->SetFilterList(nsnull); // clear this to cut shutdown leaks. we are always passing valid non-null server here. 
    
    for(PRInt32 i = 0; i < count; i++)
    {
        nsIIncomingServerListener* listener = m_incomingServerListeners[i];
        listener->OnServerUnloaded(server);
    }

    return NS_OK;
}

NS_IMETHODIMP nsMsgAccountManager::NotifyServerChanged(nsIMsgIncomingServer *server)
{
    PRInt32 count = m_incomingServerListeners.Count();
    
    for(PRInt32 i = 0; i < count; i++)
    {
        nsIIncomingServerListener* listener = m_incomingServerListeners[i];
        listener->OnServerChanged(server);
    }

    return NS_OK;
}

NS_IMETHODIMP
nsMsgAccountManager::FindServerByURI(nsIURI *aURI, PRBool aRealFlag,
                                nsIMsgIncomingServer** aResult)
{
  nsresult rv;
  nsCOMPtr<nsISupportsArray> servers;
  
  // Get username and hostname and port so we can get the server
  nsCAutoString username;
  rv = aURI->GetUserPass(username);
  if (NS_SUCCEEDED(rv) && !username.IsEmpty())
    NS_UnescapeURL(username);

  nsCAutoString hostname;
  rv = aURI->GetHost(hostname);
  if (NS_SUCCEEDED(rv) && !hostname.IsEmpty())
    NS_UnescapeURL(hostname);

  nsCAutoString type;
  rv = aURI->GetScheme(type);
  if (NS_SUCCEEDED(rv) && !type.IsEmpty())
  {
    // now modify type if pop or news
    if (type.EqualsLiteral("pop"))
      type.AssignLiteral("pop3");
    // we use "nntp" in the server list so translate it here.
    else if (type.EqualsLiteral("news"))
      type.AssignLiteral("nntp");
    // we use "any" as the wildcard type.
    else if (type.EqualsLiteral("any"))
      type.Truncate();
  }

  PRInt32 port = 0;
  // check the port of the scheme is not 'none' or blank
  if (!(type.EqualsLiteral("none") || type.IsEmpty()))
  {
    rv = aURI->GetPort(&port);
    // Set the port to zero if we got a -1 (use default)
    if (NS_SUCCEEDED(rv) && (port == -1))
      port = 0;
  }

  // If 'aRealFlag' is set then we want to scan all existing accounts
  // to make sure there's no duplicate including those whose host and/or
  // user names have been changed.
  if (!aRealFlag &&
      (m_lastFindServerHostName.Equals(hostname)) &&
      (m_lastFindServerUserName.Equals(username)) &&
      (port == m_lastFindServerPort) &&
      (m_lastFindServerType.Equals(type)) &&
      m_lastFindServerResult) 
  {
    NS_ADDREF(*aResult = m_lastFindServerResult);
    return NS_OK;
  }

  rv = GetAllServers(getter_AddRefs(servers));
  if (NS_FAILED(rv)) return rv;

  findServerEntry serverInfo;

  // "" or 0 (for the port only) act as the wild card.

  // hostname might be blank, pass "" instead
  serverInfo.hostname = (!hostname.IsEmpty()) ? hostname.get() : "";
  // username might be blank, pass "" instead
  serverInfo.username = (!username.IsEmpty()) ? username.get() : "";
  // port is initialized to zero if not specified in the url,
  // so use it no matter what
  serverInfo.port = port;
  // type might be blank, pass "" instead
  serverInfo.type = (!type.IsEmpty()) ? type.get() : "";
  serverInfo.useRealSetting = aRealFlag;

  serverInfo.server = *aResult = nsnull;
  
  servers->EnumerateForwards(findServerUrl, (void *)&serverInfo);

  if (!serverInfo.server) 
    return NS_ERROR_UNEXPECTED;

  // cache for next time
  rv = SetLastServerFound(serverInfo.server, hostname.get(), username.get(), port, type.get());
  NS_ENSURE_SUCCESS(rv,rv);

  NS_ADDREF(*aResult = serverInfo.server);
  
  return NS_OK;
}

nsresult
nsMsgAccountManager::InternalFindServer(const char* username,
                                const char* hostname,
                                const char* type,
                                PRBool useRealSetting,
                                nsIMsgIncomingServer** aResult)
{
  nsresult rv;
  nsCOMPtr<nsISupportsArray> servers;
 
  findServerEntry serverInfo;

  // "" acts as the wild card.

  // hostname might be blank, pass "" instead
  serverInfo.hostname = hostname ? hostname : "";
  // username might be blank, pass "" instead
  serverInfo.username = username ? username : "";
  // type might be blank, pass "" instead
  serverInfo.type = type ? type : "";
  serverInfo.useRealSetting = useRealSetting;

  // If 'useRealSetting' is set then we want to scan all existing accounts
  // to make sure there's no duplicate including those whose host and/or
  // user names have been changed.
  if (!useRealSetting &&
      (m_lastFindServerHostName.Equals(serverInfo.hostname)) &&
      (m_lastFindServerUserName.Equals(serverInfo.username)) &&
      (m_lastFindServerType.Equals(serverInfo.type)) &&
      m_lastFindServerResult) 
  {
    NS_ADDREF(*aResult = m_lastFindServerResult);
    return NS_OK;
  }

  rv = GetAllServers(getter_AddRefs(servers));
  if (NS_FAILED(rv)) return rv;

  serverInfo.server = *aResult = nsnull;
  
  servers->EnumerateForwards(findServer, (void *)&serverInfo);

  if (!serverInfo.server) return NS_ERROR_UNEXPECTED;

  // cache for next time
  rv = SetLastServerFound(serverInfo.server, hostname, username, 0, type);
  NS_ENSURE_SUCCESS(rv,rv);

  NS_ADDREF(*aResult = serverInfo.server);
  
  return NS_OK;

}

NS_IMETHODIMP
nsMsgAccountManager::FindServer(const char* username,
                                const char* hostname,
                                const char* type,
                                nsIMsgIncomingServer** aResult)
{
  return(InternalFindServer(username, hostname, type, PR_FALSE, aResult));
}

// Interface called by UI js only (always return true).
NS_IMETHODIMP
nsMsgAccountManager::FindRealServer(const char* username,
                                const char* hostname,
                                const char* type,
                                PRInt32 port,
                                nsIMsgIncomingServer** aResult)
{
  // Dummy string to initialize the URL
  // Needed so that we can use the Set....() items below (except SetSpec())
  nsCAutoString spec("http://user@hostname:1111");
  nsresult rv;
  nsCOMPtr<nsIURL> aUrl = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv);
  if (NS_FAILED(rv)) return NS_OK;
  

  aUrl->SetSpec(spec);

  if (!*type) 
    // empty type -- use 'any' as the magic scheme value
    aUrl->SetScheme(NS_LITERAL_CSTRING("any"));
  else
    aUrl->SetScheme(nsDependentCString(type));

  aUrl->SetHost(nsDependentCString(hostname));
  aUrl->SetUserPass(nsDependentCString(username));
  aUrl->SetPort(port);

#ifdef DEBUG_kteuscher
  aUrl->GetSpec(spec);
  printf("aUrl == %s\n", spec.get());
#endif
  FindServerByURI(aUrl, PR_TRUE, aResult);
  return NS_OK;
}

PRBool
nsMsgAccountManager::findAccountByServerKey(nsISupports *element,
                                          void *aData)
{
  nsresult rv;
  findAccountByKeyEntry *entry = (findAccountByKeyEntry*)aData;
  nsCOMPtr<nsIMsgAccount> account =
    do_QueryInterface(element, &rv);
  if (NS_FAILED(rv)) 
    return PR_TRUE;

  nsCOMPtr<nsIMsgIncomingServer> server;
  rv = account->GetIncomingServer(getter_AddRefs(server));
  if (!server || NS_FAILED(rv)) 
    return PR_TRUE;

  nsXPIDLCString key;
  rv = server->GetKey(getter_Copies(key));
  if (NS_FAILED(rv)) return PR_TRUE;

  // if the keys are equal, the servers are equal
  if (PL_strcmp(key, entry->key)==0) 
  {
    entry->account = account;
    return PR_FALSE;            // stop on first found account
  }

  return PR_TRUE;
}

NS_IMETHODIMP
nsMsgAccountManager::FindAccountForServer(nsIMsgIncomingServer *server,
                                            nsIMsgAccount **aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);
  
  if (!server) 
  {
    (*aResult) = nsnull;
    return NS_OK;
  }
  
  nsresult rv;

  nsXPIDLCString key;
  rv = server->GetKey(getter_Copies(key));
  if (NS_FAILED(rv)) return rv;
  
  findAccountByKeyEntry entry;
  entry.key = key;
  entry.account = nsnull;

  m_accounts->EnumerateForwards(findAccountByServerKey, (void *)&entry);

  if (entry.account) 
    NS_ADDREF(*aResult = entry.account);

  return NS_OK;
}

// if the aElement matches the given hostname, add it to the given array
PRBool
nsMsgAccountManager::findServerUrl(nsISupports *aElement, void *data)
{
  nsresult rv;
  
  nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(aElement);
  if (!server) return PR_TRUE;

  findServerEntry *entry = (findServerEntry*) data;
  
  nsXPIDLCString thisHostname;
  if (entry->useRealSetting)
    rv = server->GetRealHostName(getter_Copies(thisHostname));
  else
    rv = server->GetHostName(getter_Copies(thisHostname));
  if (NS_FAILED(rv)) return PR_TRUE;

  nsXPIDLCString thisUsername;
  if (entry->useRealSetting)
    rv = server->GetRealUsername(getter_Copies(thisUsername));
  else
    rv = server->GetUsername(getter_Copies(thisUsername));
  if (NS_FAILED(rv)) return PR_TRUE;
 
  nsXPIDLCString thisType;
  rv = server->GetType(getter_Copies(thisType));
  if (NS_FAILED(rv)) return PR_TRUE;
 
  PRInt32 thisPort = -1; // use the default port identifier
  // Don't try and get a port for the 'none' scheme 
  if (!thisType.EqualsLiteral("none")) 
  {
    rv = server->GetPort(&thisPort);
    NS_ENSURE_TRUE(NS_SUCCEEDED(rv), PR_TRUE);
  }

  // treat "" as a wild card, so if the caller passed in "" for the desired attribute
  // treat it as a match
  // NOTE: DO NOT REPLACE PL_strcmp OR PL_strcasecmp below because one or more
  // of these items may be null
  if ((!*entry->type || (PL_strcmp(entry->type, thisType)==0)) && 
      (!*entry->hostname || (PL_strcasecmp(entry->hostname, thisHostname)==0)) && 
      (!(entry->port != 0) || (entry->port == thisPort)) && 
      (!*entry->username || (PL_strcmp(entry->username, thisUsername)==0))) 
  {
    entry->server = server;
    return PR_FALSE;            // stop on first find 
  }
  
  return PR_TRUE;
}

// if the aElement matches the given hostname, add it to the given array
PRBool
nsMsgAccountManager::findServer(nsISupports *aElement, void *data)
{
  nsresult rv;
  
  nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(aElement, &rv);
  if (NS_FAILED(rv)) return PR_TRUE;

  findServerEntry *entry = (findServerEntry*) data;
  
  nsXPIDLCString thisHostname;
  if (entry->useRealSetting)
    rv = server->GetRealHostName(getter_Copies(thisHostname));
  else
  rv = server->GetHostName(getter_Copies(thisHostname));
  if (NS_FAILED(rv)) return PR_TRUE;

  nsXPIDLCString thisUsername;
  if (entry->useRealSetting)
    rv = server->GetRealUsername(getter_Copies(thisUsername));
  else
  rv = server->GetUsername(getter_Copies(thisUsername));
  if (NS_FAILED(rv)) return PR_TRUE;
 
  nsXPIDLCString thisType;
  rv = server->GetType(getter_Copies(thisType));
  if (NS_FAILED(rv)) return PR_TRUE;
 
  // treat "" as a wild card, so if the caller passed in "" for the desired attribute
  // treat it as a match
  PRBool checkType = PL_strcmp(entry->type, "");
  PRBool checkHostname = PL_strcmp(entry->hostname,"");
  PRBool checkUsername = PL_strcmp(entry->username,"");
  if ((!checkType || (PL_strcmp(entry->type, thisType)==0)) && 
      (!checkHostname || (PL_strcasecmp(entry->hostname, thisHostname)==0)) && 
      (!checkUsername || (PL_strcmp(entry->username, thisUsername)==0))) 
  {
    entry->server = server;
    return PR_FALSE;            // stop on first find 
  }
  
  return PR_TRUE;
}

NS_IMETHODIMP
nsMsgAccountManager::GetFirstIdentityForServer(nsIMsgIncomingServer *aServer, nsIMsgIdentity **aIdentity)
{
  NS_ENSURE_ARG_POINTER(aServer);
  NS_ENSURE_ARG_POINTER(aIdentity);

  nsCOMPtr<nsISupportsArray> identities;
  nsresult rv = GetIdentitiesForServer(aServer, getter_AddRefs(identities));
  NS_ENSURE_SUCCESS(rv, rv);

  // not all servers have identities
  // for example, Local Folders
  PRUint32 numIdentities;
  rv = identities->Count(&numIdentities);
  NS_ENSURE_SUCCESS(rv, rv);

  if (numIdentities > 0) 
  {
    nsCOMPtr<nsIMsgIdentity> identity;
    rv = identities->QueryElementAt(0, NS_GET_IID(nsIMsgIdentity),
                                  (void **)getter_AddRefs(identity));
    NS_ENSURE_SUCCESS(rv, rv);
    NS_IF_ADDREF(*aIdentity = identity);
  }
  else
    *aIdentity = nsnull;
  return rv;
}

NS_IMETHODIMP
nsMsgAccountManager::GetIdentitiesForServer(nsIMsgIncomingServer *server,
                                            nsISupportsArray **_retval)
{
  NS_ENSURE_ARG_POINTER(server);
  NS_ENSURE_ARG_POINTER(_retval);
  nsresult rv;
  rv = LoadAccounts();
  if (NS_FAILED(rv)) return rv;
  
  nsCOMPtr<nsISupportsArray> identities;
  rv = NS_NewISupportsArray(getter_AddRefs(identities));
  if (NS_FAILED(rv)) return rv;
  
  findIdentitiesByServerEntry identityInfo;
  identityInfo.server = server;
  identityInfo.identities = identities;
  
  m_accounts->EnumerateForwards(findIdentitiesForServer,
                                (void *)&identityInfo);

  // do an addref for the caller.
  NS_ADDREF(*_retval = identities);

  return NS_OK;
}

PRBool
nsMsgAccountManager::findIdentitiesForServer(nsISupports* element, void *aData)
{
  nsresult rv;
  nsCOMPtr<nsIMsgAccount> account = do_QueryInterface(element, &rv);
  if (NS_FAILED(rv)) return PR_TRUE;
  
  findIdentitiesByServerEntry *entry = (findIdentitiesByServerEntry*)aData;
  
  nsCOMPtr<nsIMsgIncomingServer> thisServer;
  rv = account->GetIncomingServer(getter_AddRefs(thisServer));
  if (NS_FAILED(rv)) return PR_TRUE;
  nsXPIDLCString serverKey;
    
//  NS_ASSERTION(thisServer, "thisServer is null");
  NS_ASSERTION(entry, "entry is null");
  NS_ASSERTION(entry->server, "entry->server is null");
  // if this happens, bail.
  if (!thisServer || !entry || !(entry->server)) 
    return PR_TRUE;

  entry->server->GetKey(getter_Copies(serverKey));
  nsXPIDLCString thisServerKey;
  thisServer->GetKey(getter_Copies(thisServerKey));
  if (PL_strcmp(serverKey, thisServerKey)==0) 
  {
    // add all these elements to the nsISupports array
    nsCOMPtr<nsISupportsArray> theseIdentities;
    rv = account->GetIdentities(getter_AddRefs(theseIdentities));
    if (NS_SUCCEEDED(rv))
      rv = entry->identities->AppendElements(theseIdentities);
  }

  return PR_TRUE;
}

NS_IMETHODIMP
nsMsgAccountManager::GetServersForIdentity(nsIMsgIdentity *identity,
                                           nsISupportsArray **_retval)
{
  nsresult rv;
  rv = LoadAccounts();
  if (NS_FAILED(rv)) return rv;
  
  nsCOMPtr<nsISupportsArray> servers;
  rv = NS_NewISupportsArray(getter_AddRefs(servers));
  if (NS_FAILED(rv)) return rv;
  
  findServersByIdentityEntry serverInfo;
  serverInfo.identity = identity;
  serverInfo.servers = servers;
  
  m_accounts->EnumerateForwards(findServersForIdentity,
                                (void *)&serverInfo);

  // do an addref for the caller.
  NS_ADDREF(*_retval = servers);

  return NS_OK;
}
  
PRBool
nsMsgAccountManager::findServersForIdentity(nsISupports *element, void *aData)
{
  nsresult rv;
  nsCOMPtr<nsIMsgAccount> account = do_QueryInterface(element, &rv);
  if (NS_FAILED(rv)) return PR_TRUE;
  
  findServersByIdentityEntry *entry = (findServersByIdentityEntry*)aData;

  nsCOMPtr<nsISupportsArray> identities;
  account->GetIdentities(getter_AddRefs(identities));

  PRUint32 idCount=0;
  identities->Count(&idCount);

  PRUint32 id;
  nsXPIDLCString identityKey;
  rv = entry->identity->GetKey(getter_Copies(identityKey));

  
  for (id=0; id<idCount; id++) 
  {

    // convert supports->Identity
    nsCOMPtr<nsISupports> thisSupports;
    rv = identities->GetElementAt(id, getter_AddRefs(thisSupports));
    if (NS_FAILED(rv)) continue;
    
    nsCOMPtr<nsIMsgIdentity>
      thisIdentity = do_QueryInterface(thisSupports, &rv);

    if (NS_SUCCEEDED(rv)) 
    {

      nsXPIDLCString thisIdentityKey;
      rv = thisIdentity->GetKey(getter_Copies(thisIdentityKey));

      if (NS_SUCCEEDED(rv) && PL_strcmp(identityKey, thisIdentityKey) == 0) 
      {
        nsCOMPtr<nsIMsgIncomingServer> thisServer;
        rv = account->GetIncomingServer(getter_AddRefs(thisServer));
        
        if (thisServer && NS_SUCCEEDED(rv)) 
        {
          entry->servers->AppendElement(thisServer);
          break;
        }
      }
    }
  }

  return PR_TRUE;
}

nsresult
nsMsgAccountManager::AddRootFolderListener(nsIFolderListener *aListener)
{
  nsresult rv;
  NS_ENSURE_TRUE(aListener, NS_OK);
  // first add listener to the list
  rv = mFolderListeners->AppendElement(aListener);
  
  // now add the listener to all loaded accounts
  m_incomingServers.Enumerate(addListener, (void *)aListener);

  return NS_OK;
}

nsresult
nsMsgAccountManager::RemoveRootFolderListener(nsIFolderListener *aListener)
{
  nsresult rv;
  NS_ENSURE_TRUE(aListener, NS_OK);
  // remove the listener from the notification list
  rv = mFolderListeners->RemoveElement(aListener);
  
  // remove the listener from the individual folders
  m_incomingServers.Enumerate(removeListener, (void *)aListener);

  return NS_OK;
}

// enumeration functions
PRBool
nsMsgAccountManager::addListener(nsHashKey *aKey, void *element, void *aData)
{
  nsIMsgIncomingServer *server = (nsIMsgIncomingServer *)element;
  nsIFolderListener* listener = (nsIFolderListener *)aData;

  nsresult rv;
  
  nsCOMPtr<nsIMsgFolder> rootFolder;
  rv = server->GetRootFolder(getter_AddRefs(rootFolder));
  NS_ENSURE_SUCCESS(rv, PR_TRUE);

  rv = rootFolder->AddFolderListener(listener);
  NS_ENSURE_SUCCESS(rv, PR_TRUE);
  
  return PR_TRUE;
}

PRBool
nsMsgAccountManager::removeListener(nsHashKey *aKey, void *element, void *aData)
{
  nsIMsgIncomingServer *server = (nsIMsgIncomingServer *)element;
  nsIFolderListener* listener = (nsIFolderListener *)aData;

  nsresult rv;
  
  nsCOMPtr<nsIMsgFolder> rootFolder;
  rv = server->GetRootFolder(getter_AddRefs(rootFolder));
  NS_ENSURE_SUCCESS(rv, PR_TRUE);

  rv = rootFolder->RemoveFolderListener(listener);
  NS_ENSURE_SUCCESS(rv, PR_TRUE);
  
  return PR_TRUE;
}

NS_IMETHODIMP nsMsgAccountManager::SetLocalFoldersServer(nsIMsgIncomingServer *aServer)
{
    nsresult rv;
    if (!aServer) return NS_ERROR_NULL_POINTER;

    nsXPIDLCString key;
    rv = aServer->GetKey(getter_Copies(key));
    if (NS_FAILED(rv)) return rv;
    
    rv = m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER, (const char *)key);
    return rv;
}

NS_IMETHODIMP nsMsgAccountManager::GetLocalFoldersServer(nsIMsgIncomingServer **aServer)
{
  nsXPIDLCString serverKey;
  nsresult rv;

  if (!aServer) return NS_ERROR_NULL_POINTER;

  if (!m_prefs) 
  {
    rv = getPrefService();
    NS_ENSURE_SUCCESS(rv,rv);
  }
  rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER, getter_Copies(serverKey));

  if (NS_SUCCEEDED(rv) && ((const char *)serverKey)) 
  {
      rv = GetIncomingServer(serverKey, aServer);
      if (!*aServer) return NS_ERROR_FAILURE;
      return rv;
  }

  // try ("nobody","Local Folders","none"), and work down to any "none" server.
  rv = FindServer("nobody","Local Folders","none",aServer);
  if (NS_FAILED(rv) || !*aServer) 
  {
      rv = FindServer("nobody",nsnull,"none",aServer);
      if (NS_FAILED(rv) || !*aServer) 
      {
          rv = FindServer(nsnull,"Local Folders","none",aServer); 
          if (NS_FAILED(rv) || !*aServer) 
              rv = FindServer(nsnull,nsnull,"none",aServer);
      }
  }

  if (NS_FAILED(rv)) return rv;
  if (!*aServer) return NS_ERROR_FAILURE;
  
  rv = SetLocalFoldersServer(*aServer);
  return rv;
}
  // nsIUrlListener methods

NS_IMETHODIMP
nsMsgAccountManager::OnStartRunningUrl(nsIURI * aUrl)
{
    return NS_OK;
}

NS_IMETHODIMP
nsMsgAccountManager::OnStopRunningUrl(nsIURI * aUrl, nsresult aExitCode)
{
  if (aUrl)
  {
    nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(aUrl); 
    if (imapUrl)
    {
      nsImapAction imapAction = nsIImapUrl::nsImapTest;
      imapUrl->GetImapAction(&imapAction);
      switch(imapAction)
      {
        case nsIImapUrl::nsImapExpungeFolder:
          if (m_folderDoingCleanupInbox)
          {
            PR_CEnterMonitor(m_folderDoingCleanupInbox);
            PR_CNotifyAll(m_folderDoingCleanupInbox);
            m_cleanupInboxInProgress = PR_FALSE;
            PR_CExitMonitor(m_folderDoingCleanupInbox);
            m_folderDoingCleanupInbox=nsnull;   //reset to nsnull
          }
          break;
        case nsIImapUrl::nsImapDeleteAllMsgs:
          if (m_folderDoingEmptyTrash) 
          {
            PR_CEnterMonitor(m_folderDoingEmptyTrash);
            PR_CNotifyAll(m_folderDoingEmptyTrash);
            m_emptyTrashInProgress = PR_FALSE;
            PR_CExitMonitor(m_folderDoingEmptyTrash);
            m_folderDoingEmptyTrash = nsnull;  //reset to nsnull;
          }
          break;
        default:
          break;
       } 
     }
   }
   return NS_OK;
}

NS_IMETHODIMP
nsMsgAccountManager::SetFolderDoingEmptyTrash(nsIMsgFolder *folder)
{
  m_folderDoingEmptyTrash = folder;
  m_emptyTrashInProgress = PR_TRUE;
  return NS_OK;
}

NS_IMETHODIMP
nsMsgAccountManager::GetEmptyTrashInProgress(PRBool *bVal)
{
  NS_ENSURE_ARG_POINTER(bVal);
  *bVal = m_emptyTrashInProgress;
  return NS_OK;
}

NS_IMETHODIMP
nsMsgAccountManager::SetFolderDoingCleanupInbox(nsIMsgFolder *folder)
{
  m_folderDoingCleanupInbox = folder;
  m_cleanupInboxInProgress = PR_TRUE;
  return NS_OK;
}

NS_IMETHODIMP
nsMsgAccountManager::GetCleanupInboxInProgress(PRBool *bVal)
{
  NS_ENSURE_ARG_POINTER(bVal);
  *bVal = m_cleanupInboxInProgress;
  return NS_OK;
}
nsresult 
nsMsgAccountManager::SetLastServerFound(nsIMsgIncomingServer *server, const char *hostname, const char *username, PRInt32 port, const char *type)
{
    m_lastFindServerResult = server;
    m_lastFindServerHostName = hostname;
    m_lastFindServerUserName = username;
    m_lastFindServerPort = port;
    m_lastFindServerType = type;
    
    return NS_OK;
}

NS_IMETHODIMP
nsMsgAccountManager::SaveAccountInfo()
{
  nsresult rv;
  nsCOMPtr<nsIPrefService> pref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
  NS_ENSURE_SUCCESS(rv,rv);
  return pref->SavePrefFile(nsnull);
}

NS_IMETHODIMP
nsMsgAccountManager::GetChromePackageName(const char *aExtensionName, char **aChromePackageName)
{
  NS_ENSURE_ARG_POINTER(aExtensionName);
  NS_ENSURE_ARG_POINTER(aChromePackageName);
 
  nsresult rv;
  nsCOMPtr<nsICategoryManager> catman = do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv,rv);

  nsCOMPtr<nsISimpleEnumerator> e;
  rv = catman->EnumerateCategory(MAILNEWS_ACCOUNTMANAGER_EXTENSIONS, getter_AddRefs(e));
  if(NS_SUCCEEDED(rv) && e) {
    while (PR_TRUE) {
      nsCOMPtr<nsISupportsCString> catEntry;
      rv = e->GetNext(getter_AddRefs(catEntry));
      if (NS_FAILED(rv) || !catEntry) 
        break;

      nsCAutoString entryString;
      rv = catEntry->GetData(entryString);
      if (NS_FAILED(rv))
         break;

      nsXPIDLCString contractidString;
      rv = catman->GetCategoryEntry(MAILNEWS_ACCOUNTMANAGER_EXTENSIONS, entryString.get(), getter_Copies(contractidString));
      if (NS_FAILED(rv)) 
        break;

      nsCOMPtr <nsIMsgAccountManagerExtension> extension = do_GetService(contractidString.get(), &rv);
      if (NS_FAILED(rv) || !extension)
        break;
      
      nsXPIDLCString name;
      rv = extension->GetName(getter_Copies(name));
      if (NS_FAILED(rv))
        break;
      
      if (!strcmp(name.get(), aExtensionName))
        return extension->GetChromePackageName(aChromePackageName);
    }
  }
  return NS_ERROR_UNEXPECTED;
}

class VirtualFolderChangeListener : public nsIDBChangeListener
{
public:
  VirtualFolderChangeListener();
  ~VirtualFolderChangeListener() {};

  NS_DECL_ISUPPORTS
  NS_DECL_NSIDBCHANGELISTENER

  nsresult Init();

  nsCOMPtr <nsIMsgFolder> m_virtualFolder; // folder we're listening to db changes on behalf of.
  nsCOMPtr <nsIMsgFolder> m_folderWatching; // folder whose db we're listening to.
  nsCOMPtr <nsISupportsArray> m_searchTerms;
  nsCOMPtr <nsIMsgSearchSession> m_searchSession;
  PRBool m_searchOnMsgStatus;
};

NS_IMPL_ISUPPORTS1(VirtualFolderChangeListener, nsIDBChangeListener)

VirtualFolderChangeListener::VirtualFolderChangeListener() : m_searchOnMsgStatus(PR_FALSE)
{
}

nsresult VirtualFolderChangeListener::Init()
{
  nsCOMPtr <nsIMsgDatabase> msgDB;
  nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;

  nsresult rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(msgDB));
  if (NS_SUCCEEDED(rv) && msgDB)
  {
    nsXPIDLCString searchTermString;
    dbFolderInfo->GetCharPtrProperty("searchStr", getter_Copies(searchTermString));
    nsCOMPtr<nsIMsgFilterService> filterService = do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
    nsCOMPtr<nsIMsgFilterList> filterList;
    rv = filterService->GetTempFilterList(m_virtualFolder, getter_AddRefs(filterList));
    NS_ENSURE_SUCCESS(rv, rv);
    nsCOMPtr <nsIMsgFilter> tempFilter;
    filterList->CreateFilter(NS_LITERAL_STRING("temp").get(), getter_AddRefs(tempFilter));
    NS_ENSURE_SUCCESS(rv, rv);
    filterList->ParseCondition(tempFilter, searchTermString);
    NS_ENSURE_SUCCESS(rv, rv);
    m_searchSession = do_CreateInstance(NS_MSGSEARCHSESSION_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr<nsISupportsArray> searchTerms;
    rv = tempFilter->GetSearchTerms(getter_AddRefs(searchTerms));
    NS_ENSURE_SUCCESS(rv, rv);

    // we add the search scope right before we match the header,
    // because we don't want the search scope caching the body input
    // stream, because that holds onto the mailbox file, breaking
    // compaction.

    // add each item in termsArray to the search session
    PRUint32 numTerms;
    searchTerms->Count(&numTerms);
    for (PRUint32 i = 0; i < numTerms; i++)
    {
      nsCOMPtr <nsIMsgSearchTerm> searchTerm (do_QueryElementAt(searchTerms, i));
      nsMsgSearchAttribValue attrib;
      searchTerm->GetAttrib(&attrib);
      if (attrib == nsMsgSearchAttrib::MsgStatus)
        m_searchOnMsgStatus = true;
      m_searchSession->AppendTerm(searchTerm);
    }
  }
  return rv;
}

  /**
   * nsIDBChangeListener
   */
NS_IMETHODIMP VirtualFolderChangeListener::OnHdrChange(nsIMsgDBHdr *aHdrChanged, PRUint32 aOldFlags, PRUint32 aNewFlags, nsIDBChangeListener *aInstigator)
{
  nsCOMPtr <nsIMsgDatabase> msgDB;

  nsresult rv = m_folderWatching->GetMsgDatabase(nsnull, getter_AddRefs(msgDB));
  PRBool oldMatch = PR_FALSE, newMatch = PR_FALSE;
  // we don't want any early returns from this function, until we've 
  // called ClearScopes 0n the search session.
  m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail, m_folderWatching);
  rv = m_searchSession->MatchHdr(aHdrChanged, msgDB, &newMatch);
  if (NS_SUCCEEDED(rv) && m_searchOnMsgStatus)
  {
    // if status is a search criteria, check if the header matched before
    // it changed, in order to determine if we need to bump the counts. 
    aHdrChanged->SetFlags(aOldFlags);
    rv = m_searchSession->MatchHdr(aHdrChanged, msgDB, &oldMatch);
    aHdrChanged->SetFlags(aNewFlags); // restore new flags even on match failure.
  }
  else
    oldMatch = newMatch;
  m_searchSession->ClearScopes();
  NS_ENSURE_SUCCESS(rv, rv);
  // we don't want to change the total counts if this virtual folder is open in a view,
  // because we won't remove the header from view while it's open. On the other hand,
  // it's hard to fix the count when the user clicks away to another folder, w/o re-running
  // the search, or setting some sort of pending count change.
  // Maybe this needs to be handled in the view code...the view could do the same calculation
  // and also keep track of the counts changed. Then, when the view was closed, if it's a virtual
  // folder, it could update the counts for the db.
  if (oldMatch != newMatch || (oldMatch && (aOldFlags & MSG_FLAG_READ) != (aNewFlags & MSG_FLAG_READ)))
  {
    nsCOMPtr <nsIMsgDatabase> virtDatabase;
    nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;

    rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase));
    NS_ENSURE_SUCCESS(rv, rv);
    PRInt32 totalDelta = 0,  unreadDelta = 0;
    if (oldMatch != newMatch)
    {
 //     PRBool isOpen = PR_FALSE;
//      nsCOMPtr <nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID);
//      if (mailSession && aFolder)
//        mailSession->IsFolderOpenInWindow(m_virtualFolder, &isOpen);
      // we can't remove headers that no longer match - but we might add headers that newly match, someday.
//      if (!isOpen)
        totalDelta = (oldMatch) ? -1 : 1;
    }
    PRBool msgHdrIsRead;
    aHdrChanged->GetIsRead(&msgHdrIsRead);
    if (oldMatch == newMatch) // read flag changed state
      unreadDelta = (msgHdrIsRead) ? -1 : 1; 
    else if (oldMatch) // else header should removed
      unreadDelta = (aOldFlags & MSG_FLAG_READ) ? 0 : -1;
    else               // header should be added
      unreadDelta = (aNewFlags & MSG_FLAG_READ) ? 0 : 1;
    if (unreadDelta)
      dbFolderInfo->ChangeNumUnreadMessages(unreadDelta);
    if (totalDelta)
      dbFolderInfo->ChangeNumMessages(totalDelta);
    if (unreadDelta == -1 && aOldFlags & MSG_FLAG_NEW)
    {
      PRInt32 numNewMessages;
      m_virtualFolder->GetNumNewMessages(PR_FALSE, &numNewMessages);
      m_virtualFolder->SetNumNewMessages(numNewMessages - 1);
      if (numNewMessages == 1)
        m_virtualFolder->SetHasNewMessages(PR_FALSE);
    }
    if (totalDelta)
    {
      nsXPIDLCString searchUri;
      m_virtualFolder->GetURI(getter_Copies(searchUri));
      msgDB->UpdateHdrInCache(searchUri, aHdrChanged, totalDelta == 1);
    }

    m_virtualFolder->UpdateSummaryTotals(PR_TRUE); // force update from db.
    virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
  }
  return rv;
}

NS_IMETHODIMP VirtualFolderChangeListener::OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey, PRInt32 aFlags, nsIDBChangeListener *aInstigator)
{
  nsCOMPtr <nsIMsgDatabase> msgDB;

  nsresult rv = m_folderWatching->GetMsgDatabase(nsnull, getter_AddRefs(msgDB));
  NS_ENSURE_SUCCESS(rv, rv);
  PRBool match = PR_FALSE;
  m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail, m_folderWatching);
  rv = m_searchSession->MatchHdr(aHdrDeleted, msgDB, &match);
  m_searchSession->ClearScopes();
  if (match)
  {
    nsCOMPtr <nsIMsgDatabase> virtDatabase;
    nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;

    rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase));
    PRBool msgHdrIsRead;
    aHdrDeleted->GetIsRead(&msgHdrIsRead);
    if (!msgHdrIsRead)
      dbFolderInfo->ChangeNumUnreadMessages(-1);
    dbFolderInfo->ChangeNumMessages(-1);
    PRUint32 hdrFlags;
    aHdrDeleted->GetFlags(&hdrFlags);
    if (hdrFlags & MSG_FLAG_NEW)
    {
      PRInt32 numNewMessages;
      m_virtualFolder->GetNumNewMessages(PR_FALSE, &numNewMessages);
      m_virtualFolder->SetNumNewMessages(numNewMessages - 1);
      if (numNewMessages == 1)
        m_virtualFolder->SetHasNewMessages(PR_FALSE);
    }
    nsXPIDLCString searchUri;
    m_virtualFolder->GetURI(getter_Copies(searchUri));
    msgDB->UpdateHdrInCache(searchUri, aHdrDeleted, PR_FALSE);

    m_virtualFolder->UpdateSummaryTotals(PR_TRUE); // force update from db.
    virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
  }
  return rv;
}

NS_IMETHODIMP VirtualFolderChangeListener::OnHdrAdded(nsIMsgDBHdr *aNewHdr, nsMsgKey aParentKey, PRInt32 aFlags, nsIDBChangeListener *aInstigator)
{
  nsCOMPtr <nsIMsgDatabase> msgDB;

  nsresult rv = m_folderWatching->GetMsgDatabase(nsnull, getter_AddRefs(msgDB));
  NS_ENSURE_SUCCESS(rv, rv);
  PRBool match = PR_FALSE;
  m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail, m_folderWatching);
  rv = m_searchSession->MatchHdr(aNewHdr, msgDB, &match);
  m_searchSession->ClearScopes();
  if (match)
  {
    nsCOMPtr <nsIMsgDatabase> virtDatabase;
    nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;

    rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase));
    PRBool msgHdrIsRead;
    PRUint32 msgFlags;
    aNewHdr->GetIsRead(&msgHdrIsRead);
    aNewHdr->GetFlags(&msgFlags);
    if (!msgHdrIsRead)
      dbFolderInfo->ChangeNumUnreadMessages(1);
    if (msgFlags & MSG_FLAG_NEW)
    {
      PRInt32 numNewMessages;
      m_virtualFolder->GetNumNewMessages(PR_FALSE, &numNewMessages);
      m_virtualFolder->SetHasNewMessages(PR_TRUE);
      m_virtualFolder->SetNumNewMessages(numNewMessages + 1);
    }
    nsXPIDLCString searchUri;
    m_virtualFolder->GetURI(getter_Copies(searchUri));
    msgDB->UpdateHdrInCache(searchUri, aNewHdr, PR_TRUE);
    dbFolderInfo->ChangeNumMessages(1);
    m_virtualFolder->UpdateSummaryTotals(true); // force update from db.
    virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
  }
  return rv;
}

NS_IMETHODIMP VirtualFolderChangeListener::OnParentChanged(nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeListener *aInstigator)
{
  return NS_OK;
}

NS_IMETHODIMP VirtualFolderChangeListener::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator)
{
  nsCOMPtr <nsIMsgDatabase> msgDB = do_QueryInterface(instigator);
  if (msgDB)
    msgDB->RemoveListener(this);
  return NS_OK;
}

NS_IMETHODIMP VirtualFolderChangeListener::OnReadChanged(nsIDBChangeListener *aInstigator)
{
  return NS_OK;
}

NS_IMETHODIMP VirtualFolderChangeListener::OnJunkScoreChanged(nsIDBChangeListener *aInstigator)
{
  return NS_OK;
}

nsresult nsMsgAccountManager::GetVirtualFoldersFile(nsCOMPtr<nsILocalFile>& file)
{
  nsCOMPtr<nsIFile> profileDir;
  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profileDir));
  NS_ENSURE_SUCCESS(rv, rv);

  rv = profileDir->AppendNative(nsDependentCString("virtualFolders.dat"));
  if (NS_SUCCEEDED(rv))
    file = do_QueryInterface(profileDir, &rv);
  return rv;
}

NS_IMETHODIMP nsMsgAccountManager::LoadVirtualFolders()
{
  nsCOMPtr <nsILocalFile> file;
  GetVirtualFoldersFile(file);
  if (!file)
    return NS_ERROR_FAILURE;

  m_loadingVirtualFolders = PR_TRUE;

  nsresult rv;
  nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
  if (msgDBService) 
  {
     NS_ENSURE_SUCCESS(rv, rv);
     nsCOMPtr<nsIFileInputStream> fileStream = do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);

     rv = fileStream->Init(file,  PR_RDONLY, 0664, PR_FALSE);  
     nsCOMPtr <nsILineInputStream> lineInputStream(do_QueryInterface(fileStream));

    PRBool isMore = PR_TRUE;
    nsCAutoString buffer;
    PRInt32 version = -1;
    nsCOMPtr <nsIMsgFolder> virtualFolder;
    nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
    nsCOMPtr<nsIRDFResource> resource;
    nsCOMPtr<nsIRDFService> rdf(do_GetService("@mozilla.org/rdf/rdf-service;1", &rv));
    NS_ENSURE_SUCCESS(rv, rv);

    while (isMore &&
           NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore)))
    {
      if (buffer.Length() > 0)
      {
        if (version == -1)
        {
          buffer.Cut(0, 8);
          PRInt32 irv;
          version = buffer.ToInteger(&irv);
          continue;
        }
        if (Substring(buffer, 0, 4).Equals("uri="))
        {
          buffer.Cut(0, 4);

          rv = rdf->GetResource(buffer, getter_AddRefs(resource));
          NS_ENSURE_SUCCESS(rv, rv);

          virtualFolder = do_QueryInterface(resource, &rv);
          NS_ENSURE_SUCCESS(rv, rv);
          if (virtualFolder)
          {
            nsCOMPtr <nsIMsgFolder> grandParent;
            nsCOMPtr <nsIMsgFolder> oldParent;
            nsCOMPtr <nsIMsgFolder> parentFolder;
            PRBool isServer;
            do
            {
              // need to add the folder as a sub-folder of its parent.
              PRInt32 lastSlash = buffer.RFindChar('/');
              if (lastSlash == kNotFound)
                break;
              nsDependentCSubstring parentUri(buffer, 0, lastSlash);
              // hold a reference so it won't get deleted before it's parented.
              oldParent = parentFolder; 
              
              rdf->GetResource(parentUri, getter_AddRefs(resource));
              parentFolder = do_QueryInterface(resource);
              if (parentFolder)
              {
                nsAutoString currentFolderNameStr;
                nsCAutoString currentFolderNameCStr(Substring(buffer, lastSlash + 1, buffer.Length()));
                nsUnescape(currentFolderNameCStr.BeginWriting());
                CopyUTF8toUTF16(currentFolderNameCStr, currentFolderNameStr);
                nsCOMPtr <nsIMsgFolder> childFolder;
                nsCOMPtr <nsIMsgDatabase> db;
                virtualFolder->GetMsgDatabase(nsnull, getter_AddRefs(db)); // force db to get created.
                if (db)
                  rv = db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
                else
                  break;

                parentFolder->AddSubfolder(currentFolderNameStr, getter_AddRefs(childFolder));
                virtualFolder->SetFlag(MSG_FOLDER_FLAG_VIRTUAL);
                if (childFolder)
                  parentFolder->NotifyItemAdded(childFolder);
                // here we make sure if our parent is rooted - if not, we're
                // going to loop and add our parent as a child of its grandparent
                // and repeat until we get to the server, or a folder that
                // has its parent set.
                parentFolder->GetParent(getter_AddRefs(grandParent));
                parentFolder->GetIsServer(&isServer);
                buffer.Truncate(lastSlash);
              }
              else
                break;
            } while (!grandParent && !isServer);
          }
        }
        else if (dbFolderInfo && Substring(buffer, 0, 6).Equals("scope="))
        {
          buffer.Cut(0, 6);
          // if this is a cross folder virtual folder, we have a list of folders uris,
          // and we have to add a pending listener for each of them.
          if (buffer.Length())
          {
            dbFolderInfo->SetCharPtrProperty("searchFolderUri", buffer.get());
            AddVFListenersForVF(virtualFolder, buffer.get(), rdf, msgDBService);
          }
          else // this folder is useless
          {
          }
        }
        else if (dbFolderInfo && Substring(buffer, 0, 6).Equals("terms="))
        {
          buffer.Cut(0, 6);
          dbFolderInfo->SetCharPtrProperty("searchStr", buffer.get());
        }
        else if (dbFolderInfo && Substring(buffer, 0, 13).Equals("searchOnline="))
        {
          buffer.Cut(0, 13);
          dbFolderInfo->SetBooleanProperty("searchOnline", buffer.Equals("true"));
        }
      }
    }
  }

  m_loadingVirtualFolders = PR_FALSE;
  m_virtualFoldersLoaded = PR_TRUE;
  return rv;
}



NS_IMETHODIMP nsMsgAccountManager::SaveVirtualFolders()
{
  if (!m_virtualFoldersLoaded)
    return NS_OK;
  nsCOMPtr<nsISupportsArray> allServers;
  nsresult rv = GetAllServers(getter_AddRefs(allServers));
  nsCOMPtr <nsILocalFile> file;
  if (allServers)
  {
    PRUint32 count = 0;
    allServers->Count(&count);
    PRUint32 i;
    nsCOMPtr <nsIOutputStream> outputStream;
    for (i = 0; i < count; i++) 
    {
      nsCOMPtr<nsIMsgIncomingServer> server = do_QueryElementAt(allServers, i);
      if (server)
      {
        nsCOMPtr <nsIMsgFolder> rootFolder;
        server->GetRootFolder(getter_AddRefs(rootFolder));
        if (rootFolder)
        {
          nsCOMPtr <nsISupportsArray> virtualFolders;
          rv = rootFolder->GetAllFoldersWithFlag(MSG_FOLDER_FLAG_VIRTUAL, getter_AddRefs(virtualFolders));
          NS_ENSURE_SUCCESS(rv, rv);
          PRUint32 vfCount;
          virtualFolders->Count(&vfCount);
          if (!outputStream)
          {
            GetVirtualFoldersFile(file);
            rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream),
                                             file,
                                             PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE,
                                             0664);
            NS_ENSURE_SUCCESS(rv, rv);
            WriteLineToOutputStream("version=", "1", outputStream);

          }
          for (PRUint32 folderIndex = 0; folderIndex < vfCount; folderIndex++)
          {
            nsCOMPtr <nsIRDFResource> folderRes (do_QueryElementAt(virtualFolders, folderIndex));
            nsCOMPtr <nsIMsgFolder> msgFolder = do_QueryInterface(folderRes);
            const char *uri;
            nsCOMPtr <nsIMsgDatabase> db;
            nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
            rv = msgFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(db)); // force db to get created.
            if (dbFolderInfo)
            {
              nsXPIDLCString srchFolderUri;
              nsXPIDLCString searchTerms; 
              PRBool searchOnline = PR_FALSE;
              dbFolderInfo->GetBooleanProperty("searchOnline", PR_FALSE, &searchOnline);
              dbFolderInfo->GetCharPtrProperty("searchFolderUri", getter_Copies(srchFolderUri));
              dbFolderInfo->GetCharPtrProperty("searchStr", getter_Copies(searchTerms));
              folderRes->GetValueConst(&uri);
              if (!srchFolderUri.IsEmpty() && !searchTerms.IsEmpty())
              {
                WriteLineToOutputStream("uri=", uri, outputStream);
                WriteLineToOutputStream("scope=", srchFolderUri.get(), outputStream);
                WriteLineToOutputStream("terms=", searchTerms.get(), outputStream);
                WriteLineToOutputStream("searchOnline=", searchOnline ? "true" : "false", outputStream);
              }
            }
          }
        }
      }
   }
   if (outputStream)
    outputStream->Close();
  }
  return rv;
}

nsresult nsMsgAccountManager::WriteLineToOutputStream(const char *prefix, const char * line, nsIOutputStream *outputStream)
{
  PRUint32 writeCount;
  outputStream->Write(prefix, strlen(prefix), &writeCount);
  outputStream->Write(line, strlen(line), &writeCount);
  outputStream->Write("\n", 1, &writeCount);
  return NS_OK;
}

nsresult nsMsgAccountManager::AddVFListenersForVF(nsIMsgFolder *virtualFolder, 
                                                  const char *srchFolderUris,
                                                  nsIRDFService *rdf,
                                                  nsIMsgDBService *msgDBService)
{
  nsCStringArray folderUris;
  folderUris.ParseString(srchFolderUris, "|");
  nsCOMPtr <nsIRDFResource> resource;

  for (PRInt32 i = 0; i < folderUris.Count(); i++)
  {
    rdf->GetResource(*(folderUris[i]), getter_AddRefs(resource));
    nsCOMPtr <nsIMsgFolder> realFolder = do_QueryInterface(resource);
    VirtualFolderChangeListener *dbListener = new VirtualFolderChangeListener();
    NS_ENSURE_TRUE(dbListener, NS_ERROR_OUT_OF_MEMORY);
    m_virtualFolderListeners.AppendObject(dbListener);
    dbListener->m_virtualFolder = virtualFolder;
    dbListener->m_folderWatching = realFolder;
    dbListener->Init();
    msgDBService->RegisterPendingListener(realFolder, dbListener);
  }
  return NS_OK;
}

NS_IMETHODIMP nsMsgAccountManager::OnItemAdded(nsIRDFResource *parentItem, nsISupports *item)
{
  nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(item);
  // just kick out with a success code if the item in question is not a folder
  if (!folder)
    return NS_OK;
  PRUint32 folderFlags;
  folder->GetFlags(&folderFlags);
  nsresult rv = NS_OK;
  // need to make sure this isn't happening during loading of virtualfolders.dat
  if (folderFlags & MSG_FOLDER_FLAG_VIRTUAL && !m_loadingVirtualFolders)
  {
    // When a new virtual folder is added, need to create a db Listener for it.
    nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
    if (msgDBService)
    {
      nsCOMPtr <nsIMsgDatabase> virtDatabase;
      nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
      rv = folder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase));
      NS_ENSURE_SUCCESS(rv, rv);
      nsXPIDLCString srchFolderUri;
      dbFolderInfo->GetCharPtrProperty("searchFolderUri", getter_Copies(srchFolderUri));
      nsCOMPtr<nsIRDFService> rdf(do_GetService("@mozilla.org/rdf/rdf-service;1", &rv));
      AddVFListenersForVF(folder, srchFolderUri, rdf, msgDBService);
    }
    rv = SaveVirtualFolders();
  }
  return rv;
}

NS_IMETHODIMP nsMsgAccountManager::OnItemRemoved(nsIRDFResource *parentItem, nsISupports *item)
{
  nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(item);
  // just kick out with a success code if the item in question is not a folder
  if (!folder)
    return NS_OK;
  nsresult rv = NS_OK;
  PRUint32 folderFlags;
  folder->GetFlags(&folderFlags);
  if (folderFlags & MSG_FOLDER_FLAG_VIRTUAL) // if we removed a VF, flush VF list to disk.
  {
    rv = SaveVirtualFolders();
    // clear flags on deleted folder if it's a virtual folder, so that creating a new folder
    // with the same name doesn't cause confusion.
    folder->SetFlags(0);
  }
  // need to check if the underlying folder for a VF was removed, in which case we need to
  // remove the virtual folder.

 return rv;
}

NS_IMETHODIMP nsMsgAccountManager::OnItemPropertyChanged(nsIRDFResource *item, nsIAtom *property, const char *oldValue, const char *newValue)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP nsMsgAccountManager::OnItemIntPropertyChanged(nsIRDFResource *item, nsIAtom *property, PRInt32 oldValue, PRInt32 newValue)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP nsMsgAccountManager::OnItemBoolPropertyChanged(nsIRDFResource *item, nsIAtom *property, PRBool oldValue, PRBool newValue)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP nsMsgAccountManager::OnItemUnicharPropertyChanged(nsIRDFResource *item, nsIAtom *property, const PRUnichar *oldValue, const PRUnichar *newValue)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}


NS_IMETHODIMP nsMsgAccountManager::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, PRUint32 oldFlag, PRUint32 newFlag)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP nsMsgAccountManager::OnItemEvent(nsIMsgFolder *aFolder, nsIAtom *aEvent)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}
