/* -*- Mode: C++; tab-width: 2; 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):
 *   Pierre Phaneuf <pp@ludusdesign.com>
 *
 * 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 ***** */

#include "prprf.h"
#include "plstr.h"
#include "nsIImportService.h"
#include "nsIImportAddressBooks.h"
#include "nsIImportGeneric.h"
#include "nsISupportsPrimitives.h"
#include "nsIImportABDescriptor.h"
#include "nsIImportFieldMap.h"
#include "nsStringGlue.h"
#include "nsILocalFile.h"
#include "nsIAddrDatabase.h"
#include "nsIAbManager.h"
#include "nsIAbLDIFService.h"
#include "nsAbBaseCID.h"
#include "nsIStringBundle.h"
#include "nsImportStringBundle.h"
#include "nsTextFormatter.h"
#include "nsServiceManagerUtils.h"
#include "msgCore.h"
#include "ImportDebug.h"
#include "nsIAbMDBDirectory.h"
#include "nsComponentManagerUtils.h"
#include "nsISupportsArray.h"
#include "nsCOMArray.h"

static void ImportAddressThread( void *stuff);

class AddressThreadData;

class nsImportGenericAddressBooks : public nsIImportGeneric
{
public:

  nsImportGenericAddressBooks();
  virtual ~nsImportGenericAddressBooks();

  NS_DECL_ISUPPORTS

  /* nsISupports GetData (in string dataId); */
  NS_IMETHOD GetData(const char *dataId, nsISupports **_retval);

  NS_IMETHOD SetData(const char *dataId, nsISupports *pData);

  NS_IMETHOD GetStatus( const char *statusKind, PRInt32 *_retval);

  NS_IMETHOD WantsProgress(bool *_retval);

  NS_IMETHOD BeginImport(nsISupportsString *successLog, nsISupportsString *errorLog, bool *_retval) ;

  NS_IMETHOD ContinueImport(bool *_retval);

  NS_IMETHOD GetProgress(PRInt32 *_retval);

  NS_IMETHOD CancelImport(void);

private:
  void  GetDefaultLocation( void);
  void  GetDefaultBooks( void);
  void  GetDefaultFieldMap( void);

public:
  static void  SetLogs( nsString& success, nsString& error, nsISupportsString *pSuccess, nsISupportsString *pError);
  static void ReportError(const PRUnichar *pName, nsString *pStream,
                          nsIStringBundle *aBundle);

private:
  nsIImportAddressBooks *    m_pInterface;
  nsISupportsArray *m_pBooks;
  nsCOMArray<nsIAddrDatabase> m_DBs;
  nsCOMPtr <nsIFile>              m_pLocation;
  nsIImportFieldMap *      m_pFieldMap;
  bool              m_autoFind;
  PRUnichar *          m_description;
  bool              m_gotLocation;
  bool              m_found;
  bool              m_userVerify;
  nsISupportsString *    m_pSuccessLog;
  nsISupportsString *    m_pErrorLog;
  PRUint32          m_totalSize;
  bool              m_doImport;
  AddressThreadData *      m_pThreadData;
  char *            m_pDestinationUri;
  nsCOMPtr<nsIStringBundle>   m_stringBundle;
};

class AddressThreadData {
public:
  bool              driverAlive;
  bool              threadAlive;
  bool              abort;
  bool              fatalError;
  PRUint32          currentTotal;
  PRUint32          currentSize;
  nsISupportsArray *      books;
  nsCOMArray<nsIAddrDatabase>* dBs;
  nsCOMPtr<nsIAbLDIFService> ldifService;
  nsIImportAddressBooks *    addressImport;
  nsIImportFieldMap *      fieldMap;
  nsISupportsString *    successLog;
  nsISupportsString *    errorLog;
  char *            pDestinationUri;
    nsIStringBundle*            stringBundle;

  AddressThreadData();
  ~AddressThreadData();
};


nsresult NS_NewGenericAddressBooks(nsIImportGeneric** aImportGeneric)
{
    NS_PRECONDITION(aImportGeneric != nsnull, "null ptr");
    if (! aImportGeneric)
        return NS_ERROR_NULL_POINTER;

  nsImportGenericAddressBooks *pGen = new nsImportGenericAddressBooks();

  if (pGen == nsnull)
    return NS_ERROR_OUT_OF_MEMORY;

  NS_ADDREF( pGen);
  nsresult rv = pGen->QueryInterface( NS_GET_IID(nsIImportGeneric), (void **)aImportGeneric);
  NS_RELEASE( pGen);

    return( rv);
}

nsImportGenericAddressBooks::nsImportGenericAddressBooks()
{
  m_pInterface = nsnull;
  m_pBooks = nsnull;
  m_pSuccessLog = nsnull;
  m_pErrorLog = nsnull;
  m_totalSize = 0;
  m_doImport = PR_FALSE;
  m_pThreadData = nsnull;
  m_pDestinationUri = nsnull;
  m_pFieldMap = nsnull;

  m_autoFind = PR_FALSE;
  m_description = nsnull;
  m_gotLocation = PR_FALSE;
  m_found = PR_FALSE;
  m_userVerify = PR_FALSE;

  nsImportStringBundle::GetStringBundle(IMPORT_MSGS_URL, getter_AddRefs(m_stringBundle));
}


nsImportGenericAddressBooks::~nsImportGenericAddressBooks()
{
  if (m_pDestinationUri)
    NS_Free( m_pDestinationUri);

  if (m_description)
    NS_Free( m_description);

  NS_IF_RELEASE( m_pFieldMap);
  NS_IF_RELEASE( m_pInterface);
  NS_IF_RELEASE( m_pBooks);
  NS_IF_RELEASE( m_pSuccessLog);
  NS_IF_RELEASE( m_pErrorLog);
}



NS_IMPL_THREADSAFE_ISUPPORTS1(nsImportGenericAddressBooks, nsIImportGeneric)


NS_IMETHODIMP nsImportGenericAddressBooks::GetData(const char *dataId, nsISupports **_retval)
{
  NS_PRECONDITION(_retval != nsnull, "null ptr");
  if (!_retval)
    return NS_ERROR_NULL_POINTER;

  nsresult rv;
  *_retval = nsnull;
  if (!PL_strcasecmp( dataId, "addressInterface")) {
    *_retval = m_pInterface;
    NS_IF_ADDREF( m_pInterface);
  }

  if (!PL_strcasecmp( dataId, "addressLocation")) {
    if (!m_pLocation)
      GetDefaultLocation();
    NS_IF_ADDREF(*_retval = m_pLocation);
  }

  if (!PL_strcasecmp( dataId, "addressBooks")) {
    if (!m_pLocation)
      GetDefaultLocation();
    if (!m_pBooks)
      GetDefaultBooks();
    *_retval = m_pBooks;
    NS_IF_ADDREF( m_pBooks);
  }

  if (!PL_strcasecmp( dataId, "addressDestination")) {
    if (m_pDestinationUri) {
            nsCOMPtr<nsISupportsCString> abString = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
            NS_ENSURE_SUCCESS(rv, rv);
            abString->SetData(nsDependentCString(m_pDestinationUri));
            NS_IF_ADDREF( *_retval = abString);
    }
  }

  if (!PL_strcasecmp( dataId, "fieldMap")) {
    if (m_pFieldMap) {
      *_retval = m_pFieldMap;
      m_pFieldMap->AddRef();
    }
    else {
      if (m_pInterface && m_pLocation) {
        bool needsIt = false;
        m_pInterface->GetNeedsFieldMap( m_pLocation, &needsIt);
        if (needsIt) {
          GetDefaultFieldMap();
          if (m_pFieldMap) {
            *_retval = m_pFieldMap;
            m_pFieldMap->AddRef();
          }
        }
      }
    }
  }

  if (!PL_strncasecmp( dataId, "sampleData-", 11)) {
    // extra the record number
    const char *pNum = dataId + 11;
    PRInt32  rNum = 0;
    while (*pNum) {
      rNum *= 10;
      rNum += (*pNum - '0');
      pNum++;
    }
    IMPORT_LOG1( "Requesting sample data #: %ld\n", (long)rNum);
    if (m_pInterface) {
      nsCOMPtr<nsISupportsString>  data = do_CreateInstance( NS_SUPPORTS_STRING_CONTRACTID, &rv);
      if (NS_FAILED( rv))
        return( rv);
      PRUnichar *  pData = nsnull;
      bool      found = false;
      rv = m_pInterface->GetSampleData( rNum, &found, &pData);
      if (NS_FAILED( rv))
        return( rv);
      if (found) {
        data->SetData(nsDependentString(pData));
        *_retval = data;
        NS_ADDREF( *_retval);
      }
      NS_Free( pData);
    }
  }

  return( NS_OK);
}


NS_IMETHODIMP nsImportGenericAddressBooks::SetData( const char *dataId, nsISupports *item)
{
  NS_PRECONDITION(dataId != nsnull, "null ptr");
    if (!dataId)
        return NS_ERROR_NULL_POINTER;

  if (!PL_strcasecmp( dataId, "addressInterface")) {
    NS_IF_RELEASE( m_pInterface);
    if (item)
      item->QueryInterface( NS_GET_IID(nsIImportAddressBooks), (void **) &m_pInterface);
  }
  if (!PL_strcasecmp( dataId, "addressBooks")) {
    NS_IF_RELEASE( m_pBooks);
    if (item)
      item->QueryInterface( NS_GET_IID(nsISupportsArray), (void **) &m_pBooks);
  }

  if (!PL_strcasecmp( dataId, "addressLocation")) {
    m_pLocation = nsnull;

    if (item) {
      nsresult rv;
      m_pLocation = do_QueryInterface(item, &rv);
      NS_ENSURE_SUCCESS(rv,rv);
    }

    if (m_pInterface)
      m_pInterface->SetSampleLocation(m_pLocation);
    }

  if (!PL_strcasecmp( dataId, "addressDestination")) {
    if (item) {
      nsCOMPtr<nsISupportsCString> abString;
      item->QueryInterface( NS_GET_IID(nsISupportsCString), getter_AddRefs( abString));
      if (abString) {
        if (m_pDestinationUri)
          NS_Free( m_pDestinationUri);
        m_pDestinationUri = nsnull;
                nsCAutoString tempUri;
                abString->GetData(tempUri);
                m_pDestinationUri = ToNewCString(tempUri);
      }
    }
  }

  if (!PL_strcasecmp( dataId, "fieldMap")) {
    NS_IF_RELEASE( m_pFieldMap);
    if (item)
      item->QueryInterface( NS_GET_IID(nsIImportFieldMap), (void **) &m_pFieldMap);
  }

  return( NS_OK);
}

NS_IMETHODIMP nsImportGenericAddressBooks::GetStatus( const char *statusKind, PRInt32 *_retval)
{
  NS_PRECONDITION(statusKind != nsnull, "null ptr");
  NS_PRECONDITION(_retval != nsnull, "null ptr");
  if (!statusKind || !_retval)
    return( NS_ERROR_NULL_POINTER);

  *_retval = 0;

  if (!PL_strcasecmp( statusKind, "isInstalled")) {
    GetDefaultLocation();
    *_retval = (PRInt32) m_found;
  }

  if (!PL_strcasecmp( statusKind, "canUserSetLocation")) {
    GetDefaultLocation();
    *_retval = (PRInt32) m_userVerify;
  }

  if (!PL_strcasecmp( statusKind, "autoFind")) {
    GetDefaultLocation();
    *_retval = (PRInt32) m_autoFind;
  }

  if (!PL_strcasecmp( statusKind, "supportsMultiple")) {
    bool      multi = false;
    if (m_pInterface)
      m_pInterface->GetSupportsMultiple( &multi);
    *_retval = (PRInt32) multi;
  }

  if (!PL_strcasecmp( statusKind, "needsFieldMap")) {
    bool      needs = false;
    if (m_pInterface && m_pLocation)
      m_pInterface->GetNeedsFieldMap( m_pLocation, &needs);
    *_retval = (PRInt32) needs;
  }

  return( NS_OK);
}

void nsImportGenericAddressBooks::GetDefaultLocation( void)
{
  if (!m_pInterface)
    return;

  if ((m_pLocation && m_gotLocation) || m_autoFind)
    return;

  if (m_description)
    NS_Free( m_description);
  m_description = nsnull;
  m_pInterface->GetAutoFind( &m_description, &m_autoFind);
  m_gotLocation = PR_TRUE;
  if (m_autoFind) {
    m_found = PR_TRUE;
    m_userVerify = PR_FALSE;
    return;
  }

  nsCOMPtr <nsIFile> pLoc;
  m_pInterface->GetDefaultLocation( getter_AddRefs(pLoc), &m_found, &m_userVerify);
  if (!m_pLocation)
    m_pLocation = pLoc;
}

void nsImportGenericAddressBooks::GetDefaultBooks( void)
{
  if (!m_pInterface || m_pBooks)
    return;

  if (!m_pLocation && !m_autoFind)
    return;

  nsresult rv = m_pInterface->FindAddressBooks( m_pLocation, &m_pBooks);
  if (NS_FAILED( rv)) {
    IMPORT_LOG0( "*** Error: FindAddressBooks failed\n");
  }
}

void nsImportGenericAddressBooks::GetDefaultFieldMap( void)
{
  if (!m_pInterface || !m_pLocation)
    return;

  NS_IF_RELEASE( m_pFieldMap);

  nsresult  rv;
  nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
  if (NS_FAILED(rv)) {
    IMPORT_LOG0( "*** Unable to get nsIImportService.\n");
    return;
  }

  rv = impSvc->CreateNewFieldMap( &m_pFieldMap);
  if (NS_FAILED( rv))
    return;

  PRInt32  sz = 0;
  rv = m_pFieldMap->GetNumMozFields( &sz);
  if (NS_SUCCEEDED( rv))
    rv = m_pFieldMap->DefaultFieldMap( sz);
    if (NS_SUCCEEDED( rv))
      rv = m_pInterface->InitFieldMap(m_pFieldMap);
  if (NS_FAILED( rv)) {
    IMPORT_LOG0( "*** Error: Unable to initialize field map\n");
    NS_IF_RELEASE( m_pFieldMap);
  }
}


NS_IMETHODIMP nsImportGenericAddressBooks::WantsProgress(bool *_retval)
{
  NS_PRECONDITION(_retval != nsnull, "null ptr");
    if (!_retval)
        return NS_ERROR_NULL_POINTER;

  GetDefaultLocation();
  GetDefaultBooks();

  PRUint32    totalSize = 0;
  bool        result = false;

  if (m_pBooks) {
    PRUint32    count = 0;
    nsresult     rv = m_pBooks->Count( &count);
    PRUint32    i;
    bool        import;
    PRUint32    size;

    for (i = 0; i < count; i++) {
      nsCOMPtr<nsIImportABDescriptor> book =
        do_QueryElementAt(m_pBooks, i);
      if (book) {
        import = PR_FALSE;
        size = 0;
        rv = book->GetImport( &import);
        if (import) {
          rv = book->GetSize( &size);
          result = PR_TRUE;
        }
        totalSize += size;
      }
    }

    m_totalSize = totalSize;
  }

  m_doImport = result;

  *_retval = result;

  return( NS_OK);
}

void nsImportGenericAddressBooks::SetLogs( nsString& success, nsString& error, nsISupportsString *pSuccess, nsISupportsString *pError)
{
  nsAutoString str;
  if (pSuccess) {
    pSuccess->GetData(str);
        str.Append(success);
        pSuccess->SetData(success);
  }
  if (pError) {
    pError->GetData(str);
        str.Append(error);
        pError->SetData(error);
  }
}

already_AddRefed<nsIAddrDatabase> GetAddressBookFromUri(const char *pUri)
{
  if (!pUri)
    return nsnull;

  nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID);
  if (!abManager)
    return nsnull;

  nsCOMPtr<nsIAbDirectory> directory;
  abManager->GetDirectory(nsDependentCString(pUri),
                          getter_AddRefs(directory));
  if (!directory)
    return nsnull;

  nsCOMPtr<nsIAbMDBDirectory> mdbDirectory = do_QueryInterface(directory);
  if (!mdbDirectory)
    return nsnull;

  nsIAddrDatabase *pDatabase = nsnull;
  mdbDirectory->GetDatabase(&pDatabase);
  return pDatabase;
}

already_AddRefed<nsIAddrDatabase> GetAddressBook(const PRUnichar *name,
                                                 bool makeNew)
{
  if (!makeNew) {
    // FIXME: How do I get the list of address books and look for a
    // specific name.  Major bogosity!
    // For now, assume we didn't find anything with that name
  }

  IMPORT_LOG0( "In GetAddressBook\n");

  nsresult rv;
  nsIAddrDatabase *pDatabase = nsnull;
  nsCOMPtr<nsILocalFile> dbPath;
  nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
  if (NS_SUCCEEDED(rv))
  {
    /* Get the profile directory */
    rv = abManager->GetUserProfileDirectory(getter_AddRefs(dbPath));
    if (NS_SUCCEEDED(rv))
    {
      // Create a new address book file - we don't care what the file
      // name is, as long as it's unique
      rv = dbPath->Append(NS_LITERAL_STRING("impab.mab"));
      if (NS_SUCCEEDED(rv))
      {
        rv = dbPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);

        if (NS_SUCCEEDED(rv))
        {
          IMPORT_LOG0( "Getting the address database factory\n");

          nsCOMPtr<nsIAddrDatabase> addrDBFactory =
            do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv);
          if (NS_FAILED(rv))
            return nsnull;

          IMPORT_LOG0( "Opening the new address book\n");
          rv = addrDBFactory->Open(dbPath, PR_TRUE, PR_TRUE,
                                   &pDatabase);
        }
      }
    }
  }
  if (NS_FAILED(rv))
  {
    IMPORT_LOG0( "Failed to get the user profile directory from the address book session\n");
  }

  if (pDatabase && dbPath)
  {
    // We made a database, add it to the UI?!?!?!?!?!?!
    // This is major bogosity again!  Why doesn't the address book
    // just handle this properly for me?  Uggggg...

    nsCOMPtr<nsIAbDirectory> parentDir;
    abManager->GetDirectory(NS_LITERAL_CSTRING(kAllDirectoryRoot),
                            getter_AddRefs(parentDir));
    if (parentDir)
    {
      nsCAutoString URI("moz-abmdbdirectory://");
      nsCAutoString leafName;
      rv = dbPath->GetNativeLeafName(leafName);
      if (NS_FAILED(rv))
        IMPORT_LOG0( "*** Error: Unable to get name of database file\n");
      else
      {
        URI.Append(leafName);
        rv = parentDir->CreateDirectoryByURI(nsDependentString(name), URI);
        if (NS_FAILED(rv))
          IMPORT_LOG0( "*** Error: Unable to create address book directory\n");
      }
    }

    if (NS_SUCCEEDED(rv))
      IMPORT_LOG0( "Added new address book to the UI\n");
    else
      IMPORT_LOG0( "*** Error: An error occurred while adding the address book to the UI\n");
  }

  return pDatabase;
}

NS_IMETHODIMP nsImportGenericAddressBooks::BeginImport(nsISupportsString *successLog, nsISupportsString *errorLog, bool *_retval)
{
  NS_PRECONDITION(_retval != nsnull, "null ptr");
    if (!_retval)
        return NS_ERROR_NULL_POINTER;

  nsString  success;
  nsString  error;

  if (!m_doImport) {
    *_retval = PR_TRUE;
    nsImportStringBundle::GetStringByID(IMPORT_NO_ADDRBOOKS, m_stringBundle,
                                        success);
    SetLogs( success, error, successLog, errorLog);
    return( NS_OK);
  }

  if (!m_pInterface || !m_pBooks) {
    nsImportStringBundle::GetStringByID(IMPORT_ERROR_AB_NOTINITIALIZED,
                                        m_stringBundle, error);
    SetLogs( success, error, successLog, errorLog);
    *_retval = PR_FALSE;
    return( NS_OK);
  }

  bool needsFieldMap = false;

  if (NS_FAILED(m_pInterface->GetNeedsFieldMap(m_pLocation, &needsFieldMap)) ||
      (needsFieldMap && !m_pFieldMap)) {
    nsImportStringBundle::GetStringByID(IMPORT_ERROR_AB_NOTINITIALIZED,
                                        m_stringBundle, error);
    SetLogs(success, error, successLog, errorLog);
    *_retval = PR_FALSE;
    return NS_OK;
  }

  NS_IF_RELEASE( m_pSuccessLog);
  NS_IF_RELEASE( m_pErrorLog);
  m_pSuccessLog = successLog;
  m_pErrorLog = errorLog;
  NS_IF_ADDREF( m_pSuccessLog);
  NS_IF_ADDREF( m_pErrorLog);


  // create the info need to drive address book import. We're
  // not going to create a new thread for this since address books
  // don't tend to be large, and import is rare.
  m_pThreadData = new AddressThreadData();
  m_pThreadData->books = m_pBooks;
  NS_ADDREF( m_pBooks);
  m_pThreadData->addressImport = m_pInterface;
  NS_ADDREF( m_pInterface);
  m_pThreadData->fieldMap = m_pFieldMap;
  NS_IF_ADDREF( m_pFieldMap);
  m_pThreadData->errorLog = m_pErrorLog;
  NS_IF_ADDREF( m_pErrorLog);
  m_pThreadData->successLog = m_pSuccessLog;
  NS_IF_ADDREF( m_pSuccessLog);
  if (m_pDestinationUri)
    m_pThreadData->pDestinationUri = strdup( m_pDestinationUri);

  PRUint32 count = 0;
  m_pBooks->Count(&count);
  // Create/obtain any address books that we need here, so that we don't need
  // to do so inside the import thread which would just proxy the create
  // operations back to the main thread anyway.
  nsCOMPtr<nsIAddrDatabase> db = GetAddressBookFromUri(m_pDestinationUri);
  for (PRUint32 i = 0; i < count; ++i)
  {
    nsCOMPtr<nsIImportABDescriptor> book = do_QueryElementAt(m_pBooks, i);
    if (book)
    {
      if (!db)
      {
        nsString name;
        book->GetPreferredName(name);
        db = GetAddressBook(name.get(), PR_TRUE);
      }
      m_DBs.AppendObject(db);
    }
  }
  m_pThreadData->dBs = &m_DBs;

  NS_IF_ADDREF(m_pThreadData->stringBundle = m_stringBundle);

  nsresult rv;
  m_pThreadData->ldifService = do_GetService(NS_ABLDIFSERVICE_CONTRACTID, &rv);

  ImportAddressThread(m_pThreadData);
  delete m_pThreadData;
  m_pThreadData = nsnull;
  *_retval = PR_TRUE;

  return( NS_OK);
}

NS_IMETHODIMP nsImportGenericAddressBooks::ContinueImport(bool *_retval)
{
    NS_PRECONDITION(_retval != nsnull, "null ptr");
    if (!_retval)
        return NS_ERROR_NULL_POINTER;

  *_retval = PR_TRUE;
  if (m_pThreadData) {
    if (m_pThreadData->fatalError)
      *_retval = PR_FALSE;
  }

  return( NS_OK);
}


NS_IMETHODIMP nsImportGenericAddressBooks::GetProgress(PRInt32 *_retval)
{
  // This returns the progress from the the currently
  // running import mail or import address book thread.
    NS_PRECONDITION(_retval != nsnull, "null ptr");
    if (!_retval)
        return NS_ERROR_NULL_POINTER;

  if (!m_pThreadData || !(m_pThreadData->threadAlive)) {
    *_retval = 100;
    return( NS_OK);
  }

  PRUint32 sz = 0;
  if (m_pThreadData->currentSize && m_pInterface) {
    if (NS_FAILED( m_pInterface->GetImportProgress( &sz)))
      sz = 0;
  }

  if (m_totalSize)
    *_retval = ((m_pThreadData->currentTotal + sz) * 100) / m_totalSize;
  else
    *_retval = 0;

  // never return less than 5 so it looks like we are doing something!
  if (*_retval < 5)
    *_retval = 5;

  // as long as the thread is alive don't return completely
  // done.
  if (*_retval > 99)
    *_retval = 99;

  return( NS_OK);
}


NS_IMETHODIMP nsImportGenericAddressBooks::CancelImport(void)
{
  if (m_pThreadData) {
    m_pThreadData->abort = PR_TRUE;
    m_pThreadData = nsnull;
  }

  return( NS_OK);
}


AddressThreadData::AddressThreadData()
{
  fatalError = PR_FALSE;
  driverAlive = PR_TRUE;
  threadAlive = PR_TRUE;
  abort = PR_FALSE;
  currentTotal = 0;
  currentSize = 0;
  books = nsnull;
  addressImport = nsnull;
  successLog = nsnull;
  errorLog = nsnull;
  pDestinationUri = nsnull;
  fieldMap = nsnull;
  stringBundle = nsnull;
  ldifService = nsnull;
}

AddressThreadData::~AddressThreadData()
{
  if (pDestinationUri)
    NS_Free(pDestinationUri);

  NS_IF_RELEASE(books);
  NS_IF_RELEASE(addressImport);
  NS_IF_RELEASE(errorLog);
  NS_IF_RELEASE(successLog);
  NS_IF_RELEASE(fieldMap);
  NS_IF_RELEASE(stringBundle);
}

void nsImportGenericAddressBooks::ReportError(const PRUnichar *pName,
                                              nsString *pStream,
                                              nsIStringBundle* aBundle)
{
  if (!pStream)
    return;
  // load the error string
  PRUnichar *pFmt = nsImportStringBundle::GetStringByID( IMPORT_ERROR_GETABOOK, aBundle);
  PRUnichar *pText = nsTextFormatter::smprintf( pFmt, pName);
  pStream->Append( pText);
  nsTextFormatter::smprintf_free( pText);
  NS_Free(pFmt);
  pStream->AppendLiteral(MSG_LINEBREAK);
}

static void ImportAddressThread( void *stuff)
{
  IMPORT_LOG0( "In Begin ImportAddressThread\n");

  AddressThreadData *pData = (AddressThreadData *)stuff;
  PRUint32  count = 0;
  nsresult   rv = pData->books->Count( &count);
  PRUint32          i;
  bool              import;
  PRUint32          size;

  nsString          success;
  nsString          error;

  for (i = 0; (i < count) && !(pData->abort); i++) {
    nsCOMPtr<nsIImportABDescriptor> book =
      do_QueryElementAt(pData->books, i);
    if (book) {
      import = PR_FALSE;
      size = 0;
      rv = book->GetImport( &import);
      if (import)
        rv = book->GetSize( &size);

      if (size && import) {
        nsString name;
        book->GetPreferredName(name);

        nsCOMPtr<nsIAddrDatabase> db = pData->dBs->ObjectAt(i);

        bool fatalError = false;
        pData->currentSize = size;
        if (db) {
          PRUnichar *pSuccess = nsnull;
          PRUnichar *pError = nsnull;

          /*
          if (pData->fieldMap) {
            PRInt32    sz = 0;
            PRInt32    mapIndex;
            bool      active;
            pData->fieldMap->GetMapSize( &sz);
            IMPORT_LOG1( "**** Field Map Size: %d\n", (int) sz);
            for (PRInt32 i = 0; i < sz; i++) {
              pData->fieldMap->GetFieldMap( i, &mapIndex);
              pData->fieldMap->GetFieldActive( i, &active);
              IMPORT_LOG3( "Field map #%d: index=%d, active=%d\n", (int) i, (int) mapIndex, (int) active);
            }
          }
          */

          rv = pData->addressImport->ImportAddressBook(book,
                                                       db,
                                                       pData->fieldMap,
                                                       pData->ldifService,
                                                       &pError,
                                                       &pSuccess,
                                                       &fatalError);
          if (pSuccess) {
            success.Append( pSuccess);
            NS_Free( pSuccess);
          }
          if (pError) {
            error.Append( pError);
            NS_Free( pError);
          }
        }
        else {
          nsImportGenericAddressBooks::ReportError(name.get(), &error, pData->stringBundle);
        }

        pData->currentSize = 0;
        pData->currentTotal += size;

        if (db)
          db->Close(PR_TRUE);

        if (fatalError) {
          pData->fatalError = PR_TRUE;
          break;
        }
      }
    }
  }


  nsImportGenericAddressBooks::SetLogs( success, error, pData->successLog, pData->errorLog);

  if (pData->abort || pData->fatalError) {
    // FIXME: do what is necessary to get rid of what has been imported so far.
    // Nothing if we went into an existing address book!  Otherwise, delete
    // the ones we created?
  }

}
