/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* directory server preferences (used to be dirprefs.c in 4.x) */

#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsDirPrefs.h"
#include "nsIPrefLocalizedString.h"
#include "nsIObserver.h"
#include "nsTArray.h"
#include "nsServiceManagerUtils.h"
#include "nsMemory.h"
#include "nsIAddrDatabase.h"
#include "nsAbBaseCID.h"
#include "nsIAbManager.h"
#include "nsIFile.h"
#include "nsWeakReference.h"
#include "nsIAbMDBDirectory.h"
#if defined(MOZ_LDAP_XPCOM)
#include "nsIAbLDAPDirectory.h"
#endif
#include "prmem.h"
#include "prprf.h"
#include "plstr.h"
#include "nsQuickSort.h"
#include "nsComponentManagerUtils.h"
#include "msgCore.h"
#include "nsStringGlue.h"

#include <ctype.h>

/*****************************************************************************
 * Private definitions
 */

/* Default settings for site-configurable prefs */
#define kDefaultPosition 1
static bool dir_IsServerDeleted(DIR_Server * server);

static char *DIR_GetStringPref(const char *prefRoot, const char *prefLeaf, const char *defaultValue);
static int32_t DIR_GetIntPref(const char *prefRoot, const char *prefLeaf, int32_t defaultValue);
static char *DIR_GetLocalizedStringPref(const char *prefRoot, const char *prefLeaf);

static char * dir_ConvertDescriptionToPrefName(DIR_Server * server);

void DIR_SetFileName(char** filename, const char* leafName);
static void DIR_SetIntPref(const char *prefRoot, const char *prefLeaf, int32_t value, int32_t defaultValue);
static DIR_Server *dir_MatchServerPrefToServer(nsTArray<DIR_Server*> *wholeList, const char *pref);
static bool dir_ValidateAndAddNewServer(nsTArray<DIR_Server*> *wholeList, const char *fullprefname);
static void DIR_DeleteServerList(nsTArray<DIR_Server*> *wholeList);

static char *dir_CreateServerPrefName(DIR_Server *server);
static void DIR_GetPrefsForOneServer(DIR_Server *server);

static void DIR_InitServer(DIR_Server *server, DirectoryType dirType = (DirectoryType)0);
static DIR_PrefId  DIR_AtomizePrefName(const char *prefname);

const int32_t DIR_POS_APPEND = -1;
const int32_t DIR_POS_DELETE = -2;
static bool DIR_SetServerPosition(nsTArray<DIR_Server*> *wholeList, DIR_Server *server, int32_t position);

/* These two routines should be called to initialize and save 
 * directory preferences from the XP Java Script preferences
 */
static nsresult DIR_GetServerPreferences(nsTArray<DIR_Server*>** list);
static void DIR_SaveServerPreferences(nsTArray<DIR_Server*> *wholeList);

static int32_t dir_UserId = 0;
nsTArray<DIR_Server*> *dir_ServerList = nullptr;

/*****************************************************************************
 * Functions for creating the new back end managed DIR_Server list.
 */
class DirPrefObserver final : public nsSupportsWeakReference,
                              public nsIObserver
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIOBSERVER

private:
  ~DirPrefObserver() {}
};

NS_IMPL_ISUPPORTS(DirPrefObserver, nsISupportsWeakReference, nsIObserver)

NS_IMETHODIMP DirPrefObserver::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
{
  nsCOMPtr<nsIPrefBranch> prefBranch(do_QueryInterface(aSubject));
  nsCString strPrefName;
  strPrefName.Assign(NS_ConvertUTF16toUTF8(aData));
  const char *prefname = strPrefName.get();

  DIR_PrefId id = DIR_AtomizePrefName(prefname);

  // Just get out if we get nothing here - we don't need to do anything
  if (id == idNone)
    return NS_OK;

  /* Check to see if the server is in the unified server list.
   */
  DIR_Server *server = dir_MatchServerPrefToServer(dir_ServerList, prefname);
  if (server)
  {
    /* If the server is in the process of being saved, just ignore this
     * change.  The DIR_Server structure is not really changing.
     */
    if (server->savingServer)
      return NS_OK;

    /* If the pref that changed is the position, read it in.  If the new
     * position is zero, remove the server from the list.
     */
    if (id == idPosition)
    {
      int32_t position;

      /* We must not do anything if the new position is the same as the
       * position in the DIR_Server.  This avoids recursion in cases
       * where we are deleting the server.
       */
      prefBranch->GetIntPref(prefname, &position);
      if (position != server->position)
      {
        server->position = position;
        if (dir_IsServerDeleted(server))
          DIR_SetServerPosition(dir_ServerList, server, DIR_POS_DELETE);
      }
    }

    if (id == idDescription) {
      // Ensure the local copy of the description is kept up to date.
      PR_FREEIF(server->description);
      server->description = DIR_GetLocalizedStringPref(prefname, nullptr);
    }
  }
  /* If the server is not in the unified list, we may need to add it.  Servers
   * are only added when the position, serverName and description are valid.
   */
  else if (id == idPosition || id == idType || id == idDescription)
  {
    dir_ValidateAndAddNewServer(dir_ServerList, prefname);
  }

  return NS_OK;
}

// A pointer to the pref observer
static DirPrefObserver *prefObserver = nullptr;

static nsresult DIR_GetDirServers()
{
  nsresult rv = NS_OK;

  if (!dir_ServerList)
  {
    /* we need to build the DIR_Server list */ 
    rv = DIR_GetServerPreferences(&dir_ServerList);

    /* Register the preference call back if necessary. */
    if (NS_SUCCEEDED(rv) && !prefObserver)
    {
      nsCOMPtr<nsIPrefBranch> pbi(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
      if (NS_FAILED(rv))
        return rv;
      prefObserver = new DirPrefObserver();

      if (!prefObserver)
        return NS_ERROR_OUT_OF_MEMORY;

      NS_ADDREF(prefObserver);

      pbi->AddObserver(PREF_LDAP_SERVER_TREE_NAME, prefObserver, true);
    }
  }
  return rv;
}

nsTArray<DIR_Server*>* DIR_GetDirectories()
{
    if (!dir_ServerList)
        DIR_GetDirServers();
  return dir_ServerList;
}

DIR_Server* DIR_GetServerFromList(const char* prefName)
{
  DIR_Server* result = nullptr;

  if (!dir_ServerList)
    DIR_GetDirServers();

  if (dir_ServerList)
  {
    int32_t count = dir_ServerList->Length();
    int32_t i;
    for (i = 0; i < count; ++i)
    {
      DIR_Server *server = dir_ServerList->ElementAt(i);

      if (server && strcmp(server->prefName, prefName) == 0)
      {
        result = server;
        break;
      }
    }
  }
  return result;
}

static nsresult SavePrefsFile()
{
  nsresult rv;
  nsCOMPtr<nsIPrefService> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
  if (NS_FAILED(rv))
    return rv;
  return pPref->SavePrefFile(nullptr);
}

nsresult DIR_ShutDown()  /* FEs should call this when the app is shutting down. It frees all DIR_Servers regardless of ref count values! */
{
  nsresult rv = SavePrefsFile();
  NS_ENSURE_SUCCESS(rv, rv);

  DIR_DeleteServerList(dir_ServerList);
  dir_ServerList = nullptr;

  /* unregister the preference call back, if necessary.
  * we need to do this as DIR_Shutdown() is called when switching profiles
  * when using turbo.  (see nsAbDirectoryDataSource::Observe())
  * When switching profiles, prefs get unloaded and then re-loaded
  * we don't want our callback to get called for all that.
  * We'll reset our callback the first time DIR_GetDirServers() is called
  * after we've switched profiles.
  */
  NS_IF_RELEASE(prefObserver);
  
  return NS_OK;
}

nsresult DIR_ContainsServer(DIR_Server* pServer, bool *hasDir)
{
  if (dir_ServerList)
  {
    int32_t count = dir_ServerList->Length();
    int32_t i;
    for (i = 0; i < count; i++)
    {
      DIR_Server* server = dir_ServerList->ElementAt(i);
      if (server == pServer)
      {
        *hasDir = true;
        return NS_OK;
      }
    }
  }
  *hasDir = false;
  return NS_OK;
}

nsresult DIR_AddNewAddressBook(const nsAString &dirName,
                               const nsACString &fileName,
                               const nsACString &uri, 
                               DirectoryType dirType,
                               const nsACString &prefName,
                               DIR_Server** pServer)
{
  DIR_Server * server = (DIR_Server *) PR_Malloc(sizeof(DIR_Server));
  if (!server)
    return NS_ERROR_OUT_OF_MEMORY;

  DIR_InitServer(server, dirType);
  if (!dir_ServerList)
    DIR_GetDirServers();
  if (dir_ServerList)
  {
    server->description = ToNewCString(NS_ConvertUTF16toUTF8(dirName));
    server->position = kDefaultPosition; // don't set position so alphabetic sort will happen.
    
    if (!fileName.IsEmpty())
      server->fileName = ToNewCString(fileName);
    else if (dirType == PABDirectory) 
      DIR_SetFileName(&server->fileName, kPersonalAddressbook);
    else if (dirType == LDAPDirectory)
      DIR_SetFileName(&server->fileName, kMainLdapAddressBook);

    if (dirType != PABDirectory) {
      if (!uri.IsEmpty())
        server->uri = ToNewCString(uri);
    }

    if (!prefName.IsEmpty())
      server->prefName = ToNewCString(prefName);

    dir_ServerList->AppendElement(server);

    DIR_SavePrefsForOneServer(server); 

    *pServer = server;
    
    // save new address book into pref file 
    return SavePrefsFile();
  }
  return NS_ERROR_FAILURE;
}

/*****************************************************************************
 * Functions for creating DIR_Servers
 */
static void DIR_InitServer(DIR_Server *server, DirectoryType dirType)
{
  if (!server) {
    NS_WARNING("DIR_InitServer: server parameter not initialized");
    return;
  }

  memset(server, 0, sizeof(DIR_Server));
  server->position = kDefaultPosition;
  server->uri = nullptr;
  server->savingServer = false;
  server->dirType = dirType;
}

/* Function for setting the position of a server.  Can be used to append,
 * delete, or move a server in a server list.
 *
 * The third parameter specifies the new position the server is to occupy.
 * The resulting position may differ depending on the lock state of the
 * given server and other servers in the list.  The following special values
 * are supported:
 *   DIR_POS_APPEND - Appends the server to the end of the list.  If the server
 *                    is already in the list, does nothing.
 *   DIR_POS_DELETE - Deletes the given server from the list.  Note that this
 *                    does not cause the server structure to be freed.
 *
 * Returns true if the server list was re-sorted.
 */
static bool DIR_SetServerPosition(nsTArray<DIR_Server*> *wholeList, DIR_Server *server, int32_t position)
 {
   NS_ENSURE_TRUE(wholeList, false);

   int32_t    i, count, num;
   bool       resort = false;
   DIR_Server *s=nullptr;
   
   switch (position) {
   case DIR_POS_APPEND:
   /* Do nothing if the request is to append a server that is already
     * in the list.
     */
     count = wholeList->Length();
     for (i= 0; i < count; i++)
     {
       if  ((s = wholeList->ElementAt(i)) != nullptr)
         if (s == server)
           return false;
     }
     /* In general, if there are any servers already in the list, set the
     * position to the position of the last server plus one.  If there
     * are none, set it to position 1.
     */
     if (count > 0)
     {
       s = wholeList->ElementAt(count - 1);
       server->position = s->position + 1;
     }
     else
       server->position = 1;
     
     wholeList->AppendElement(server);
     break;
     
   case DIR_POS_DELETE:
       /* Remove the prefs corresponding to the given server.  If the prefName
       * value is nullptr, the server has never been saved and there are no
       * prefs to remove.
     */
     if (server->prefName)
     {
       nsresult rv;
       nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
       if (NS_FAILED(rv))
         return false;

       pPref->DeleteBranch(server->prefName);

       // mark the server as deleted by setting its position to 0
       DIR_SetIntPref(server->prefName, "position", 0, -1);
     }
     
     /* If the server is in the server list, remove it.
     */
     num = wholeList->IndexOf(server);
     if (num >= 0)
     {
     /* The list does not need to be re-sorted if the server is the
     * last one in the list.
       */
       count = wholeList->Length();
       if (num == count - 1)
       {
         wholeList->RemoveElementAt(num);
       }
       else
       {
         resort = true;
         wholeList->RemoveElement(server);
       }
     }
     break;
     
   default:
   /* See if the server is already in the list.
     */
     count = wholeList->Length();
     for (i= 0; i < count; i++)
     {
       if  ((s = wholeList->ElementAt(i)) != nullptr)
         if (s == server)
           break;
     }
     
     /* If the server is not in the list, add it to the beginning and re-sort.
     */
     if (s == nullptr)
     {
       server->position = position;
       wholeList->AppendElement(server);
       resort = true;
     }
     
       /* Don't re-sort if the server is already in the requested position.
     */
     else if (server->position != position)
     {
       server->position = position;
       wholeList->RemoveElement(server);
       wholeList->AppendElement(server);
       resort = true;
     }
     break;
        }
        
        /* Make sure our position changes get saved back to prefs
        */
        DIR_SaveServerPreferences(wholeList);
        
        return resort;
}

/*****************************************************************************
 * DIR_Server Callback Notification Functions
 */

/* dir_matchServerPrefToServer
 *
 * This function finds the DIR_Server in the unified DIR_Server list to which
 * the given preference string belongs.
 */
static DIR_Server *dir_MatchServerPrefToServer(nsTArray<DIR_Server*> *wholeList, const char *pref)
{
  DIR_Server *server;

  int32_t count = wholeList->Length();
  int32_t i;
  for (i = 0; i < count; i++)
  {
    if ((server = wholeList->ElementAt(i)) != nullptr)
    {
      if (server->prefName && PL_strstr(pref, server->prefName) == pref)
      {
        char c = pref[PL_strlen(server->prefName)];
        if (c == 0 || c == '.')
          return server;
      }
    }
  }
  return nullptr;
}

/* dir_ValidateAndAddNewServer
 *
 * This function verifies that the position, serverName and description values
 * are set for the given prefName.  If they are then it adds the server to the
 * unified server list.
 */
static bool dir_ValidateAndAddNewServer(nsTArray<DIR_Server*> *wholeList, const char *fullprefname)
{
  bool rc = false;

  const char *endname = PL_strchr(&fullprefname[PL_strlen(PREF_LDAP_SERVER_TREE_NAME) + 1], '.');
  if (endname)
  {
    char *prefname = (char *)PR_Malloc(endname - fullprefname + 1);
    if (prefname)
    {
      int32_t dirType;
      char *t1 = nullptr, *t2 = nullptr;

      PL_strncpyz(prefname, fullprefname, endname - fullprefname + 1);

      dirType = DIR_GetIntPref(prefname, "dirType", -1);
      if (dirType != -1 &&
          DIR_GetIntPref(prefname, "position", 0) != 0 &&
          (t1 = DIR_GetLocalizedStringPref(prefname, "description")) != nullptr)
      {
        if (dirType == PABDirectory ||
           (t2 = DIR_GetStringPref(prefname, "serverName",  nullptr)) != nullptr)
        {
          DIR_Server *server = (DIR_Server *)PR_Malloc(sizeof(DIR_Server));
          if (server)
          {
            DIR_InitServer(server, (DirectoryType)dirType);
            server->prefName = prefname;
            DIR_GetPrefsForOneServer(server);
            DIR_SetServerPosition(wholeList, server, server->position);
            rc = true;
          }
          PR_FREEIF(t2);
        }
        PR_Free(t1);
      }
      else
        PR_Free(prefname);
    }
  }

  return rc;
}

static DIR_PrefId DIR_AtomizePrefName(const char *prefname)
{
  if (!prefname)
    return idNone;

  DIR_PrefId rc = idNone;

  /* Skip the "ldap_2.servers.<server-name>." portion of the string.
   */
  if (PL_strstr(prefname, PREF_LDAP_SERVER_TREE_NAME) == prefname)
  {
    prefname = PL_strchr(&prefname[PL_strlen(PREF_LDAP_SERVER_TREE_NAME) + 1], '.');
    if (!prefname)
      return idNone;
    else
      prefname = prefname + 1;
  }

  switch (prefname[0]) {
  case 'd':
    switch (prefname[1]) {
    case 'e': /* description */
      rc = idDescription;
      break;
    case 'i': /* dirType */
      rc = idType;
      break;
    }
    break;

  case 'f':
    rc = idFileName;
    break;

  case 'p':
    switch (prefname[1]) {
    case 'o':
      switch (prefname[2]) {
      case 's': /* position */
        rc = idPosition;
        break;
      }
      break;
    }
    break;

  case 'u': /* uri */
    rc = idUri;
    break;
  }

  return rc;
}

/*****************************************************************************
 * Functions for destroying DIR_Servers 
 */

/* this function determines if the passed in server is no longer part of the of
   the global server list. */
static bool dir_IsServerDeleted(DIR_Server * server)
{
  return (server && server->position == 0);
}

/* when the back end manages the server list, deleting a server just decrements its ref count,
   in the old world, we actually delete the server */
static void DIR_DeleteServer(DIR_Server *server)
{
  if (server)
  {
    /* when destroying the server check its clear flag to see if things need cleared */
#ifdef XP_FileRemove
    if (DIR_TestFlag(server, DIR_CLEAR_SERVER))
    {
      if (server->fileName)
        XP_FileRemove (server->fileName, xpAddrBookNew);
    }
#endif /* XP_FileRemove */
    PR_Free(server->prefName);
    PR_Free(server->description);
    PR_Free(server->fileName);
    PR_Free(server->uri);
    PR_Free(server);
  }
}

nsresult DIR_DeleteServerFromList(DIR_Server *server)
{
  if (!server)
    return NS_ERROR_NULL_POINTER;

  nsresult rv = NS_OK;
  nsCOMPtr<nsIFile> dbPath;

  nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); 
  if (NS_SUCCEEDED(rv))
    rv = abManager->GetUserProfileDirectory(getter_AddRefs(dbPath));
  
  if (NS_SUCCEEDED(rv))
  {
    // close the database, as long as it isn't the special ones 
    // (personal addressbook and collected addressbook)
    // which can never be deleted.  There was a bug where we would slap in
    // "abook.mab" as the file name for LDAP directories, which would cause a crash
    // on delete of LDAP directories.  this is just extra protection.
    if (server->fileName &&
        strcmp(server->fileName, kPersonalAddressbook) && 
        strcmp(server->fileName, kCollectedAddressbook))
    {
      nsCOMPtr<nsIAddrDatabase> database;

      rv = dbPath->AppendNative(nsDependentCString(server->fileName));
      NS_ENSURE_SUCCESS(rv, rv);

      // close file before delete it
      nsCOMPtr<nsIAddrDatabase> addrDBFactory = 
               do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv);

      if (NS_SUCCEEDED(rv) && addrDBFactory)
        rv = addrDBFactory->Open(dbPath, false, true, getter_AddRefs(database));
      if (database)  /* database exists */
      {
        database->ForceClosed();
        rv = dbPath->Remove(false);
        NS_ENSURE_SUCCESS(rv, rv);
      }
    }

    nsTArray<DIR_Server*> *dirList = DIR_GetDirectories();
    DIR_SetServerPosition(dirList, server, DIR_POS_DELETE);
    DIR_DeleteServer(server);

    return SavePrefsFile();
  }

  return NS_ERROR_NULL_POINTER;
}

static void DIR_DeleteServerList(nsTArray<DIR_Server*> *wholeList)
{
  if (wholeList)
  {
    DIR_Server *server = nullptr;
  
    /* TBD: Send notifications? */
    int32_t count = wholeList->Length();
    int32_t i;
    for (i = count - 1; i >=0; i--)
    {
      server = wholeList->ElementAt(i);
      if (server != nullptr)
        DIR_DeleteServer(server);
    }
    delete wholeList;
  }
}

/*****************************************************************************
 * Functions for managing JavaScript prefs for the DIR_Servers 
 */

static int
comparePrefArrayMembers(const void* aElement1, const void* aElement2, void* aData)
{
    const char* element1 = *static_cast<const char* const *>(aElement1);
    const char* element2 = *static_cast<const char* const *>(aElement2);
    const uint32_t offset = *((const uint32_t*)aData);

    // begin the comparison at |offset| chars into the string -
    // this avoids comparing the "ldap_2.servers." portion of every element,
    // which will always remain the same.
    return strcmp(element1 + offset, element2 + offset);
}

static nsresult dir_GetChildList(const nsCString &aBranch,
                                 uint32_t *aCount, char ***aChildList)
{
    uint32_t branchLen = aBranch.Length();

    nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
    if (!prefBranch) {
        return NS_ERROR_FAILURE;
    }

    nsresult rv = prefBranch->GetChildList(aBranch.get(), aCount, aChildList);
    if (NS_FAILED(rv)) {
        return rv;
    }

    // traverse the list, and truncate all the descendant strings to just
    // one branch level below the root branch.
    for (uint32_t i = *aCount; i--; ) {
        // The prefname we passed to GetChildList was of the form
        // "ldap_2.servers." and we are returned the descendants
        // in the form of "ldap_2.servers.servername.foo"
        // But we want the prefbranch of the servername, so
        // write a NUL character in to terminate the string early.
        char *endToken = strchr((*aChildList)[i] + branchLen, '.');
        if (endToken)
            *endToken = '\0';
    }

    if (*aCount > 1) {
        // sort the list, in preparation for duplicate entry removal
        NS_QuickSort(*aChildList, *aCount, sizeof(char*), comparePrefArrayMembers, &branchLen);

        // traverse the list and remove duplicate entries.
        // we use two positions in the list; the current entry and the next
        // entry; and perform a bunch of in-place ptr moves. so |cur| points
        // to the last unique entry, and |next| points to some (possibly much
        // later) entry to test, at any given point. we know we have >= 2
        // elements in the list here, so we just init the two counters sensibly
        // to begin with.
        uint32_t cur = 0;
        for (uint32_t next = 1; next < *aCount; ++next) {
            // check if the elements are equal or unique
            if (!comparePrefArrayMembers(&((*aChildList)[cur]), &((*aChildList)[next]), &branchLen)) {
                // equal - just free & increment the next element ptr

                free((*aChildList)[next]);
            } else {
                // cur & next are unique, so we need to shift the element.
                // ++cur will point to the next free location in the
                // reduced array (it's okay if that's == next)
                (*aChildList)[++cur] = (*aChildList)[next];
            }
        }

        // update the unique element count
        *aCount = cur + 1;
    }

    return NS_OK;
}

static char *DIR_GetStringPref(const char *prefRoot, const char *prefLeaf, const char *defaultValue)
{
    nsresult rv;
    nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
    if (NS_FAILED(rv))
        return nullptr;

    nsCString value;
    nsAutoCString prefLocation(prefRoot);

    prefLocation.Append('.');
    prefLocation.Append(prefLeaf);

    if (NS_SUCCEEDED(pPref->GetCharPref(prefLocation.get(), getter_Copies(value))))
    {
        /* unfortunately, there may be some prefs out there which look like this */
        if (value.EqualsLiteral("(null)")) 
        {
            if (defaultValue)
                value = defaultValue;
            else
                value.Truncate();
        }

        if (value.IsEmpty())
        {
          rv = pPref->GetCharPref(prefLocation.get(), getter_Copies(value));
        }
    }
    else
        value = defaultValue; 

    return ToNewCString(value);
}

/**
 * Get localized unicode string pref from properties file, convert into an UTF8 string
 * since address book prefs store as UTF8 strings. So far there are 2 default
 * prefs stored in addressbook.properties.
 * "ldap_2.servers.pab.description"
 * "ldap_2.servers.history.description"
 */
static char *DIR_GetLocalizedStringPref(const char *prefRoot, const char *prefLeaf)
{
  nsresult rv;
  nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));

  if (NS_FAILED(rv))
    return nullptr;

  nsAutoCString prefLocation(prefRoot);
  if (prefLeaf) {
    prefLocation.Append('.');
    prefLocation.Append(prefLeaf);
  }

  nsString wvalue;
  nsCOMPtr<nsIPrefLocalizedString> locStr;

  rv = pPref->GetComplexValue(prefLocation.get(), NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(locStr));
  if (NS_SUCCEEDED(rv))
    rv = locStr->ToString(getter_Copies(wvalue));

  char *value = nullptr;
  if (!wvalue.IsEmpty())
  {
    value = ToNewCString(NS_ConvertUTF16toUTF8(wvalue));
  }
  else
  {
    // In TB 2 only some prefs had chrome:// URIs. We had code in place that would
    // only get the localized string pref for the particular address books that
    // were built-in.
    // Additionally, nsIPrefBranch::getComplexValue will only get a non-user-set,
    // non-locked pref value if it is a chrome:// URI and will get the string
    // value at that chrome URI. This breaks extensions/autoconfig that want to
    // set default pref values and allow users to change directory names.
    //
    // Now we have to support this, and so if for whatever reason we fail to get
    // the localized version, then we try and get the non-localized version
    // instead. If the string value is empty, then we'll just get the empty value
    // back here.
    rv = pPref->GetCharPref(prefLocation.get(), &value);
    if (NS_FAILED(rv))
      value = nullptr;
  }

  return value;
}

static int32_t DIR_GetIntPref(const char *prefRoot, const char *prefLeaf, int32_t defaultValue)
{
  nsresult rv;
  nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));

  if (NS_FAILED(rv))
    return defaultValue;

  int32_t value;
  nsAutoCString prefLocation(prefRoot);

  prefLocation.Append('.');
  prefLocation.Append(prefLeaf);

  if (NS_FAILED(pPref->GetIntPref(prefLocation.get(), &value)))
    value = defaultValue;

  return value;
}

/* This will convert from the old preference that was a path and filename */
/* to a just a filename */
static void DIR_ConvertServerFileName(DIR_Server* pServer)
{
  char* leafName = pServer->fileName;
  char* newLeafName = nullptr;
#if defined(XP_WIN)
  /* jefft -- bug 73349 This is to allow users share same address book.
   * It only works if the user specify a full path filename.
   */
#ifdef XP_FileIsFullPath
  if (! XP_FileIsFullPath(leafName))
    newLeafName = XP_STRRCHR (leafName, '\\');
#endif /* XP_FileIsFullPath */
#else
  newLeafName = strrchr(leafName, '/');
#endif
    pServer->fileName = newLeafName ? strdup(newLeafName + 1) : strdup(leafName);
  if (leafName) PR_Free(leafName);
}

/* This will generate a correct filename and then remove the path.
 * Note: we are assuming that the default name is in the native
 * filesystem charset. The filename will be returned as a UTF8
 * string.
 */
void DIR_SetFileName(char** fileName, const char* defaultName)
{
  if (!fileName)
    return;

  nsresult rv = NS_OK;
  nsCOMPtr<nsIFile> dbPath;

  *fileName = nullptr;

  nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); 
  if (NS_SUCCEEDED(rv))
    rv = abManager->GetUserProfileDirectory(getter_AddRefs(dbPath));
  if (NS_SUCCEEDED(rv))
  {
    rv = dbPath->AppendNative(nsDependentCString(defaultName));
    if (NS_SUCCEEDED(rv))
    {
      rv = dbPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0664);

      nsAutoString realFileName;
      rv = dbPath->GetLeafName(realFileName);

      if (NS_SUCCEEDED(rv))
        *fileName = ToNewUTF8String(realFileName);
    }
  }
}

/****************************************************************
Helper function used to generate a file name from the description
of a directory. Caller must free returned string. 
An extension is not applied 
*****************************************************************/

static char * dir_ConvertDescriptionToPrefName(DIR_Server * server)
{
#define MAX_PREF_NAME_SIZE 25
  char * fileName = nullptr;
  char fileNameBuf[MAX_PREF_NAME_SIZE];
  int32_t srcIndex = 0;
  int32_t destIndex = 0;
  int32_t numSrcBytes = 0;
  const char * descr = nullptr;
  if (server && server->description)
  {
    descr = server->description;
    numSrcBytes = PL_strlen(descr);
    while (srcIndex < numSrcBytes && destIndex < MAX_PREF_NAME_SIZE-1)
    {
      if (IS_DIGIT(descr[srcIndex]) || IS_ALPHA(descr[srcIndex]))
      {
        fileNameBuf[destIndex] = descr[srcIndex];
        destIndex++;
      }

      srcIndex++;
    }

    fileNameBuf[destIndex] = '\0'; /* zero out the last character */
  }

  if (destIndex) /* have at least one character in the file name? */
  fileName = strdup(fileNameBuf);

  return fileName;
}


void DIR_SetServerFileName(DIR_Server *server)
{
  char * tempName = nullptr; 
  const char * prefName = nullptr;
  uint32_t numHeaderBytes = 0; 

  if (server && (!server->fileName || !(*server->fileName)) )
  {
          PR_FREEIF(server->fileName); // might be one byte empty string.
    /* make sure we have a pref name...*/
    if (!server->prefName || !*server->prefName)
      server->prefName = dir_CreateServerPrefName(server);

    /* set default personal address book file name*/
    if ((server->position == 1) && (server->dirType == PABDirectory))
            server->fileName = strdup(kPersonalAddressbook);
    else
    {
      /* now use the pref name as the file name since we know the pref name
         will be unique */
      prefName = server->prefName;
      if (prefName && *prefName)
      {
        /* extract just the pref name part and not the ldap tree name portion from the string */
        numHeaderBytes = PL_strlen(PREF_LDAP_SERVER_TREE_NAME) + 1; /* + 1 for the '.' b4 the name */
        if (PL_strlen(prefName) > numHeaderBytes) 
                    tempName = strdup(prefName + numHeaderBytes);

        if (tempName)
        {
          server->fileName = PR_smprintf("%s%s", tempName, kABFileName_CurrentSuffix);
          PR_Free(tempName);
        }
      }
    }

    if (!server->fileName || !*server->fileName) /* when all else has failed, generate a default name */
    {
      if (server->dirType == LDAPDirectory)
        DIR_SetFileName(&(server->fileName), kMainLdapAddressBook); /* generates file name with an ldap prefix */
      else
        DIR_SetFileName(&(server->fileName), kPersonalAddressbook);
    }
  }
}

static char *dir_CreateServerPrefName (DIR_Server *server)
{
  /* we are going to try to be smart in how we generate our server
     pref name. We'll try to convert the description into a pref name
     and then verify that it is unique. If it is unique then use it... */
  char * leafName = dir_ConvertDescriptionToPrefName(server);
  char * prefName = nullptr;
  bool isUnique = false;

  if (!leafName || !*leafName)
  {
    // we need to handle this in case the description has no alphanumeric chars
    // it's very common for cjk users
    leafName = strdup("_nonascii");
  }

  if (leafName)
  {
    int32_t uniqueIDCnt = 0;
        char **children = nullptr;
    /* we need to verify that this pref string name is unique */
    prefName = PR_smprintf(PREF_LDAP_SERVER_TREE_NAME".%s", leafName);
    isUnique = false;
    uint32_t prefCount;
    nsresult rv = dir_GetChildList(NS_LITERAL_CSTRING(PREF_LDAP_SERVER_TREE_NAME "."),
                                   &prefCount, &children);
    if (NS_SUCCEEDED(rv))
    {
      while (!isUnique && prefName)
      {
        isUnique = true; /* now flip the logic and assume we are unique until we find a match */
        for (uint32_t i = 0; i < prefCount && isUnique; ++i)
        {
          if (!PL_strcasecmp(children[i], prefName)) /* are they the same branch? */
            isUnique = false;
        }
        if (!isUnique) /* then try generating a new pref name and try again */
        {
          PR_smprintf_free(prefName);
          prefName = PR_smprintf(PREF_LDAP_SERVER_TREE_NAME".%s_%d", leafName, ++uniqueIDCnt);
        }
      } /* if we have a list of pref Names */

      NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, children);
    } /* while we don't have a unique name */

    // fallback to "user_directory_N" form if we failed to verify
    if (!isUnique && prefName)
    {
      PR_smprintf_free(prefName);
      prefName = nullptr;
    }

    PR_Free(leafName);

  } /* if leafName */

  if (!prefName) /* last resort if we still don't have a pref name is to use user_directory string */
    return PR_smprintf(PREF_LDAP_SERVER_TREE_NAME".user_directory_%d", ++dir_UserId);
  else
    return prefName;
}

static void DIR_GetPrefsForOneServer(DIR_Server *server)
{
  nsresult rv;
  nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
  if (NS_FAILED(rv))
    return;
  
  char    *prefstring = server->prefName;

  // this call fills in tempstring with the position pref, and
  // we then check to see if it's locked.
  server->position = DIR_GetIntPref (prefstring, "position", kDefaultPosition);

  // For default address books, this will get the name from the chrome
  // file referenced, for other address books it'll just retrieve it from prefs
  // as normal.
  server->description = DIR_GetLocalizedStringPref(prefstring, "description");

  server->dirType = (DirectoryType)DIR_GetIntPref (prefstring, "dirType", LDAPDirectory);

  server->fileName = DIR_GetStringPref (prefstring, "filename", "");
  // if we don't have a file name try and get one
  if (!server->fileName || !*(server->fileName)) 
    DIR_SetServerFileName (server);
  if (server->fileName && *server->fileName)
    DIR_ConvertServerFileName(server);

  // the string "s" is the default uri ( <scheme> + "://" + <filename> )
  nsCString s((server->dirType == PABDirectory || server->dirType == MAPIDirectory) ?
#if defined(MOZ_LDAP_XPCOM)
    kMDBDirectoryRoot : kLDAPDirectoryRoot);
#else
    // Fallback to the all directory root in the non-ldap enabled case.
    kMDBDirectoryRoot : kAllDirectoryRoot);
#endif
  s.Append (server->fileName);
  server->uri = DIR_GetStringPref (prefstring, "uri", s.get ());
}

static nsresult dir_GetPrefs(nsTArray<DIR_Server*> **list)
{
    nsresult rv;
    nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
    if (NS_FAILED(rv))
        return rv;

    (*list) = new nsTArray<DIR_Server*>();
    if (!(*list))
        return NS_ERROR_OUT_OF_MEMORY;

    char **children;
    uint32_t prefCount;

    rv = dir_GetChildList(NS_LITERAL_CSTRING(PREF_LDAP_SERVER_TREE_NAME "."),
                          &prefCount, &children);
    if (NS_FAILED(rv))
        return rv;

    /* TBD: Temporary code to read broken "ldap" preferences tree.
     *      Remove line with if statement after M10.
     */
    if (dir_UserId == 0)
        pPref->GetIntPref(PREF_LDAP_GLOBAL_TREE_NAME".user_id", &dir_UserId);

    for (uint32_t i = 0; i < prefCount; ++i)
    {
        DIR_Server *server;

        server = (DIR_Server *)PR_Calloc(1, sizeof(DIR_Server));
        if (server)
        {
            DIR_InitServer(server);
            server->prefName = strdup(children[i]);
            DIR_GetPrefsForOneServer(server);
            if (server->description && server->description[0] && 
                ((server->dirType == PABDirectory ||
                  server->dirType == MAPIDirectory ||
                  server->dirType == FixedQueryLDAPDirectory ||  // this one might go away
                  server->dirType == LDAPDirectory)))
            {
                if (!dir_IsServerDeleted(server))
                {
                    (*list)->AppendElement(server);
                }
                else
                    DIR_DeleteServer(server);
            }
            else
            {
                DIR_DeleteServer(server);
            }
        }
    }

    NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, children);

    return NS_OK;
}

// I don't think we care about locked positions, etc.
void DIR_SortServersByPosition(nsTArray<DIR_Server*> *serverList)
{
  int i, j;
  DIR_Server *server;
  
  int count = serverList->Length();
  for (i = 0; i < count - 1; i++)
  {
    for (j = i + 1; j < count; j++)
    {
      if (serverList->ElementAt(j)->position < serverList->ElementAt(i)->position)
      {
        server = serverList->ElementAt(i);
        serverList->ReplaceElementAt(i, serverList->ElementAt(j));
        serverList->ReplaceElementAt(j, server);
      }
    }
  }
}

static nsresult DIR_GetServerPreferences(nsTArray<DIR_Server*>** list)
{
  nsresult err;
  nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &err));
  if (NS_FAILED(err))
    return err;

  int32_t version = -1;
  nsTArray<DIR_Server*> *newList = nullptr;
  
  /* Update the ldap list version and see if there are old prefs to migrate. */
  err = pPref->GetIntPref(PREF_LDAP_VERSION_NAME, &version);
  NS_ENSURE_SUCCESS(err, err);

  /* Find the new-style "ldap_2.servers" tree in prefs */
  err = dir_GetPrefs(&newList);

  if (version < kCurrentListVersion)
  {
    pPref->SetIntPref(PREF_LDAP_VERSION_NAME, kCurrentListVersion);
  }
 
  DIR_SortServersByPosition(newList);

  *list = newList;

  return err;
}

static void DIR_SetStringPref(const char *prefRoot, const char *prefLeaf, const char *value, const char *defaultValue)
{
  nsresult rv;
  nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); 
  if (NS_FAILED(rv)) 
    return;

  nsCString defaultPref;
  nsAutoCString prefLocation(prefRoot);

  prefLocation.Append('.');
  prefLocation.Append(prefLeaf);

  if (NS_SUCCEEDED(pPref->GetCharPref(prefLocation.get(), getter_Copies(defaultPref))))
  {
    /* If there's a default pref, just set ours in and let libpref worry 
     * about potential defaults in all.js
     */
    if (value) /* added this check to make sure we have a value before we try to set it..*/
      rv = pPref->SetCharPref (prefLocation.get(), value);
    else
      rv = pPref->ClearUserPref(prefLocation.get());
  }
  else
  {
    /* If there's no default pref, look for a user pref, and only set our value in
     * if the user pref is different than one of them.
     */
    nsCString userPref;
    if (NS_SUCCEEDED(pPref->GetCharPref (prefLocation.get(), getter_Copies(userPref))))
    {
      if (value && (defaultValue ? PL_strcasecmp(value, defaultValue) : value != defaultValue))
        rv = pPref->SetCharPref (prefLocation.get(), value);
      else
        rv = pPref->ClearUserPref(prefLocation.get());
    }
    else
    {
      if (value && (defaultValue ? PL_strcasecmp(value, defaultValue) : value != defaultValue))
        rv = pPref->SetCharPref (prefLocation.get(), value); 
    }
  }

  NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref in DIR_SetStringPref");
}

static void DIR_SetLocalizedStringPref
(const char *prefRoot, const char *prefLeaf, const char *value)
{
  nsresult rv;
  nsCOMPtr<nsIPrefService> prefSvc(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));

  if (NS_FAILED(rv))
    return;

  nsAutoCString prefLocation(prefRoot);
  prefLocation.Append('.');

  nsCOMPtr<nsIPrefBranch> prefBranch;
  rv = prefSvc->GetBranch(prefLocation.get(), getter_AddRefs(prefBranch));
  if (NS_FAILED(rv))
    return;

  nsString wvalue;
  nsCOMPtr<nsIPrefLocalizedString> newStr(
    do_CreateInstance(NS_PREFLOCALIZEDSTRING_CONTRACTID, &rv));
  if (NS_FAILED(rv))
  {
    NS_ASSERTION(NS_SUCCEEDED(rv), "Could not createInstance in DIR_SetLocalizedStringPref");
    return;
  }

  NS_ConvertUTF8toUTF16 newValue(value);

  rv = newStr->SetData(newValue.get());
  if (NS_FAILED(rv))
  {
    NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref data in DIR_SetLocalizedStringPref");
    return;
  }
  nsCOMPtr<nsIPrefLocalizedString> locStr;
  if (NS_SUCCEEDED(prefBranch->GetComplexValue(prefLeaf,
                                               NS_GET_IID(nsIPrefLocalizedString),
                                               getter_AddRefs(locStr))))
  {
    nsString data;
    locStr->GetData(getter_Copies(data));

    // Only set the pref if the data values aren't the same (i.e. don't change
    // unnecessarily, but also, don't change in the case that its a chrome
    // string pointing to the value we want to set the pref to).
    if (newValue != data)
      rv = prefBranch->SetComplexValue(prefLeaf,
                                       NS_GET_IID(nsIPrefLocalizedString),
                                       newStr);
  }
  else {
    // No value set, but check the default pref branch (i.e. user may have
    // cleared the pref)
    nsCOMPtr<nsIPrefBranch> dPB;
    rv = prefSvc->GetDefaultBranch(prefLocation.get(),
                                   getter_AddRefs(dPB));

    if (NS_SUCCEEDED(dPB->GetComplexValue(prefLeaf,
                                          NS_GET_IID(nsIPrefLocalizedString),
                                          getter_AddRefs(locStr))))
    {
      // Default branch has a value
      nsString data;
      locStr->GetData(getter_Copies(data));

      if (newValue != data)
        // If the vales aren't the same, set the data on the main pref branch
        rv = prefBranch->SetComplexValue(prefLeaf,
                                         NS_GET_IID(nsIPrefLocalizedString),
                                         newStr);
      else
        // Else if they are, kill the user pref
        rv = prefBranch->ClearUserPref(prefLeaf);
    }
    else
      // No values set anywhere, so just set the pref
      rv = prefBranch->SetComplexValue(prefLeaf,
                                       NS_GET_IID(nsIPrefLocalizedString),
                                       newStr);
  }

  NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref in DIR_SetLocalizedStringPref");
}


static void DIR_SetIntPref(const char *prefRoot, const char *prefLeaf, int32_t value, int32_t defaultValue)
{
  nsresult rv;
  nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); 
  if (NS_FAILED(rv)) 
    return;

  int32_t defaultPref;
  nsAutoCString prefLocation(prefRoot);

  prefLocation.Append('.');
  prefLocation.Append(prefLeaf);

  if (NS_SUCCEEDED(pPref->GetIntPref(prefLocation.get(), &defaultPref)))
  {
    /* solve the problem where reordering user prefs must override default prefs */
    rv = pPref->SetIntPref(prefLocation.get(), value);
  }
  else
  {
    int32_t userPref;
    if (NS_SUCCEEDED(pPref->GetIntPref(prefLocation.get(), &userPref)))
    {
      if (value != defaultValue)
        rv = pPref->SetIntPref(prefLocation.get(), value);
      else
        rv = pPref->ClearUserPref(prefLocation.get());
    }
    else
    {
      if (value != defaultValue)
        rv = pPref->SetIntPref(prefLocation.get(), value); 
    }
  }

  NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref in DIR_SetIntPref");
}

void DIR_SavePrefsForOneServer(DIR_Server *server)
{
  if (!server)
    return;

  char *prefstring;

  if (server->prefName == nullptr)
    server->prefName = dir_CreateServerPrefName(server);
  prefstring = server->prefName;

  server->savingServer = true;

  DIR_SetIntPref (prefstring, "position", server->position, kDefaultPosition);

  // Only save the non-default address book name
  DIR_SetLocalizedStringPref(prefstring, "description", server->description);

  DIR_SetStringPref(prefstring, "filename", server->fileName, "");
  DIR_SetIntPref(prefstring, "dirType", server->dirType, LDAPDirectory);

  if (server->dirType != PABDirectory)
    DIR_SetStringPref(prefstring, "uri", server->uri, "");

  server->savingServer = false;
}

static void DIR_SaveServerPreferences(nsTArray<DIR_Server*> *wholeList)
{
  if (wholeList)
  {
    nsresult rv;
    nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); 
    if (NS_FAILED(rv)) {
      NS_WARNING("DIR_SaveServerPreferences: Failed to get the pref service\n");
      return;
    }

    int32_t  i;
    int32_t  count = wholeList->Length();
    DIR_Server *server;

    for (i = 0; i < count; i++)
    {
      server = wholeList->ElementAt(i);
      if (server)
        DIR_SavePrefsForOneServer(server);
    }
    pPref->SetIntPref(PREF_LDAP_GLOBAL_TREE_NAME".user_id", dir_UserId);
  }
}
