/* 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/. */

#ifdef MOZ_LOGGING
// this has to be before the pre-compiled header
#define FORCE_PR_LOG /* Allow logging in the release build */
#endif
#include <mapidefs.h>
#include <mapi.h>
#include "msgMapi.h"
#include "msgMapiImp.h"
#include "msgMapiFactory.h"
#include "msgMapiMain.h"

#include "nsIMsgCompFields.h"
#include "msgMapiHook.h"
#include "nsStringGlue.h"
#include "nsCOMPtr.h"
#include "nsISupports.h"
#include "nsMsgCompCID.h"
#include "nsIMsgDatabase.h"
#include "nsMsgFolderFlags.h"
#include "nsIMsgHdr.h"
#include "MailNewsTypes.h"
#include "nsMsgBaseCID.h"
#include "nsIMsgAccountManager.h"
#include "nsIMsgFolder.h"
#include "nsIMsgImapMailFolder.h"
#include <time.h>
#include "nsIInputStream.h"
#include "nsILineInputStream.h"
#include "nsISeekableStream.h"
#include "nsIFile.h"
#include "nsIFileStreams.h"
#include "nsNetCID.h"
#include "nsMsgMessageFlags.h"
#include "mozilla/mailnews/MimeHeaderParser.h"

using namespace mozilla::mailnews;

PRLogModuleInfo *MAPI;

CMapiImp::CMapiImp()
: m_cRef(1)
{
    m_Lock = PR_NewLock();
  if (!MAPI)
    MAPI = PR_NewLogModule("MAPI");
}

CMapiImp::~CMapiImp() 
{ 
    if (m_Lock)
        PR_DestroyLock(m_Lock);
}

STDMETHODIMP CMapiImp::QueryInterface(const IID& aIid, void** aPpv)
{    
    if (aIid == IID_IUnknown)
    {
        *aPpv = static_cast<nsIMapi*>(this); 
    }
    else if (aIid == IID_nsIMapi)
    {
        *aPpv = static_cast<nsIMapi*>(this);
    }
    else
    {
        *aPpv = nullptr;
        return E_NOINTERFACE;
    }

    reinterpret_cast<IUnknown*>(*aPpv)->AddRef();
    return S_OK;
}

STDMETHODIMP_(ULONG) CMapiImp::AddRef()
{
    return ++m_cRef;
}

STDMETHODIMP_(ULONG) CMapiImp::Release() 
{
    int32_t temp = --m_cRef;
    if (m_cRef == 0)
    {
        delete this;
        return 0;
    }

    return temp;
}

STDMETHODIMP CMapiImp::IsValid()
{
    return S_OK;
}

STDMETHODIMP CMapiImp::IsValidSession(unsigned long aSession)
{
    nsMAPIConfiguration *pConfig = nsMAPIConfiguration::GetMAPIConfiguration();
    if (pConfig && pConfig->IsSessionValid(aSession))
        return S_OK;

    return E_FAIL;
}

STDMETHODIMP CMapiImp::Initialize()
{
    HRESULT hr = E_FAIL;

    if (!m_Lock)
        return E_FAIL;

    PR_Lock(m_Lock);

    // Initialize MAPI Configuration

    nsMAPIConfiguration *pConfig = nsMAPIConfiguration::GetMAPIConfiguration();
    if (pConfig != nullptr)
      hr = S_OK;

    PR_Unlock(m_Lock);

    return hr;
}

STDMETHODIMP CMapiImp::Login(unsigned long aUIArg, LOGIN_PW_TYPE aLogin, LOGIN_PW_TYPE aPassWord,
                unsigned long aFlags, unsigned long *aSessionId)
{
    HRESULT hr = E_FAIL;
     bool bNewSession = false;
    nsCString id_key;

    PR_LOG(MAPI, PR_LOG_DEBUG, ("CMapiImp::Login using flags %d\n", aFlags));
    if (aFlags & MAPI_NEW_SESSION)
        bNewSession = true;

    // Check For Profile Name
    if (aLogin != nullptr && aLogin[0] != '\0')
    {
        if (!nsMapiHook::VerifyUserName(nsString(aLogin), id_key))
        {
            *aSessionId = MAPI_E_LOGIN_FAILURE;
            PR_LOG(MAPI, PR_LOG_DEBUG, ("CMapiImp::Login failed for username %s\n", aLogin));
            NS_ASSERTION(false, "failed verifying user name");
            return hr;
        }
    }
    else
    {
      // get default account
      nsresult rv;
      nsCOMPtr <nsIMsgAccountManager> accountManager = 
        do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); 
      NS_ENSURE_SUCCESS(rv,MAPI_E_LOGIN_FAILURE);
      nsCOMPtr <nsIMsgAccount> account;
      nsCOMPtr <nsIMsgIdentity> identity;
       rv = accountManager->GetDefaultAccount(getter_AddRefs(account));
      NS_ENSURE_SUCCESS(rv,MAPI_E_LOGIN_FAILURE);
      account->GetDefaultIdentity(getter_AddRefs(identity));
      NS_ENSURE_SUCCESS(rv,MAPI_E_LOGIN_FAILURE);
      identity->GetKey(id_key);

    }

    // finally register(create) the session.
    uint32_t nSession_Id;
    int16_t nResult = 0;

    nsMAPIConfiguration *pConfig = nsMAPIConfiguration::GetMAPIConfiguration();
    if (pConfig != nullptr)
        nResult = pConfig->RegisterSession(aUIArg, aLogin, aPassWord,
                                           (aFlags & MAPI_FORCE_DOWNLOAD), bNewSession,
                                           &nSession_Id, id_key.get());
    switch (nResult)
    {
        case -1 :
        {
            *aSessionId = MAPI_E_TOO_MANY_SESSIONS;
            return hr;
        }
        case 0 :
        {
            *aSessionId = MAPI_E_INSUFFICIENT_MEMORY;
            return hr;
        }
        default :
        {
            *aSessionId = nSession_Id;
            PR_LOG(MAPI, PR_LOG_DEBUG, ("CMapiImp::Login succeeded\n"));
            break;
        }
    }

    return S_OK;
}

STDMETHODIMP CMapiImp::SendMail( unsigned long aSession, lpnsMapiMessage aMessage,
     short aRecipCount, lpnsMapiRecipDesc aRecips , short aFileCount, lpnsMapiFileDesc aFiles , 
     unsigned long aFlags, unsigned long aReserved)
{
    nsresult rv = NS_OK ;

    PR_LOG(MAPI, PR_LOG_DEBUG, ("CMapiImp::SendMail using flags %d\n", aFlags));
    // Assign the pointers in the aMessage struct to the array of Recips and Files
    // received here from MS COM. These are used in BlindSendMail and ShowCompWin fns 
    aMessage->lpRecips = aRecips ;
    aMessage->lpFiles = aFiles ;

    PR_LOG(MAPI, PR_LOG_DEBUG, ("CMapiImp::SendMail flags=%x subject: %s sender: %s\n", 
      aFlags, (char *) aMessage->lpszSubject, (aMessage->lpOriginator) ? aMessage->lpOriginator->lpszAddress : ""));

    /** create nsIMsgCompFields obj and populate it **/
    nsCOMPtr<nsIMsgCompFields> pCompFields = do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv) ;
    if (NS_FAILED(rv) || (!pCompFields) ) return MAPI_E_INSUFFICIENT_MEMORY ;

    if (aFlags & MAPI_UNICODE)
        rv = nsMapiHook::PopulateCompFields(aMessage, pCompFields) ;
    else
        rv = nsMapiHook::PopulateCompFieldsWithConversion(aMessage, pCompFields) ;
    
    if (NS_SUCCEEDED (rv))
    {
        // see flag to see if UI needs to be brought up
        if (!(aFlags & MAPI_DIALOG))
        {
            rv = nsMapiHook::BlindSendMail(aSession, pCompFields);
        }
        else
        {
            rv = nsMapiHook::ShowComposerWindow(aSession, pCompFields);
        }
    }
    
    return nsMAPIConfiguration::GetMAPIErrorFromNSError (rv) ;
}


STDMETHODIMP CMapiImp::SendDocuments( unsigned long aSession, LPTSTR aDelimChar,
                            LPTSTR aFilePaths, LPTSTR aFileNames, ULONG aFlags)
{
    nsresult rv = NS_OK ;

    PR_LOG(MAPI, PR_LOG_DEBUG, ("CMapiImp::SendDocument using flags %d\n", aFlags));
    /** create nsIMsgCompFields obj and populate it **/
    nsCOMPtr<nsIMsgCompFields> pCompFields = do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv) ;
    if (NS_FAILED(rv) || (!pCompFields) ) return MAPI_E_INSUFFICIENT_MEMORY ;

    if (aFilePaths)
    {
        rv = nsMapiHook::PopulateCompFieldsForSendDocs(pCompFields, aFlags, aDelimChar, aFilePaths) ;
    }

    if (NS_SUCCEEDED (rv)) 
        rv = nsMapiHook::ShowComposerWindow(aSession, pCompFields);
    else
      PR_LOG(MAPI, PR_LOG_DEBUG, ("CMapiImp::SendDocument error rv = %lx, paths = %s names = %s\n", rv, aFilePaths, aFileNames));

    return nsMAPIConfiguration::GetMAPIErrorFromNSError (rv) ;
}

nsresult CMapiImp::GetDefaultInbox(nsIMsgFolder **inboxFolder)
{
  // get default account
  nsresult rv;
  nsCOMPtr <nsIMsgAccountManager> accountManager = 
    do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); 
  NS_ENSURE_SUCCESS(rv,rv);

  nsCOMPtr <nsIMsgAccount> account;
  rv = accountManager->GetDefaultAccount(getter_AddRefs(account));
  NS_ENSURE_SUCCESS(rv,rv);

  // get incoming server
  nsCOMPtr <nsIMsgIncomingServer> server;
  rv = account->GetIncomingServer(getter_AddRefs(server));
  NS_ENSURE_SUCCESS(rv,rv);

  nsCString type;
  rv = server->GetType(type);
  NS_ENSURE_SUCCESS(rv,rv);

  // we only care about imap and pop3
  if (type.EqualsLiteral("imap") || type.EqualsLiteral("pop3"))
  {
    // imap and pop3 account should have an Inbox
    nsCOMPtr<nsIMsgFolder> rootMsgFolder;
    rv = server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
    NS_ENSURE_SUCCESS(rv,rv);
 
    if (!rootMsgFolder)
      return NS_ERROR_FAILURE;
 
    rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, inboxFolder);
    if (!*inboxFolder)
     return NS_ERROR_FAILURE;
 
  }
  return NS_OK;
}

//*****************************************************************************
// Encapsulate the XP DB stuff required to enumerate messages

class MsgMapiListContext 
{
public:
  MsgMapiListContext () {}
  ~MsgMapiListContext ();
  
  nsresult OpenDatabase (nsIMsgFolder *folder);
  
  nsMsgKey GetNext ();
  nsresult MarkRead (nsMsgKey key, bool read);
  
  lpnsMapiMessage GetMessage (nsMsgKey, unsigned long flFlags);
  bool IsIMAPHost(void);
  bool DeleteMessage(nsMsgKey key);
  
protected:
  
  char *ConvertDateToMapiFormat (time_t);
  char *ConvertBodyToMapiFormat (nsIMsgDBHdr *hdr);
  void ConvertRecipientsToMapiFormat(const nsCOMArray<msgIAddressObject> &ourRecips,
                                     lpnsMapiRecipDesc mapiRecips,
                                     int mapiRecipClass);
  
  nsCOMPtr <nsIMsgFolder> m_folder;
  nsCOMPtr <nsIMsgDatabase> m_db;
  nsCOMPtr <nsISimpleEnumerator> m_msgEnumerator;
};


LONG CMapiImp::InitContext(unsigned long session, MsgMapiListContext **listContext)
{
  nsMAPIConfiguration * pMapiConfig = nsMAPIConfiguration::GetMAPIConfiguration() ;
  if (!pMapiConfig)
    return MAPI_E_FAILURE ;  // get the singelton obj
  *listContext = (MsgMapiListContext *) pMapiConfig->GetMapiListContext(session);
  // This is the first message
  if (!*listContext)
  {
    nsCOMPtr <nsIMsgFolder> inboxFolder;
    nsresult rv = GetDefaultInbox(getter_AddRefs(inboxFolder));
    if (NS_FAILED(rv))
    {
      NS_ASSERTION(false, "in init context, no inbox");
      return(MAPI_E_NO_MESSAGES);
    }

    *listContext = new MsgMapiListContext;
    if (!*listContext)
      return MAPI_E_INSUFFICIENT_MEMORY;

    rv = (*listContext)->OpenDatabase(inboxFolder);
    if (NS_FAILED(rv))
    {
      pMapiConfig->SetMapiListContext(session, NULL);
      delete *listContext;
      NS_ASSERTION(false, "in init context, unable to open db");
      return MAPI_E_NO_MESSAGES;
    }
    else
      pMapiConfig->SetMapiListContext(session, *listContext);
  }
  return SUCCESS_SUCCESS;
}

STDMETHODIMP CMapiImp::FindNext(unsigned long aSession, unsigned long ulUIParam, LPTSTR lpszMessageType,
                              LPTSTR lpszSeedMessageID, unsigned long flFlags, unsigned long ulReserved,
                              unsigned char lpszMessageID[64])

{
  //
  // If this is true, then this is the first call to this FindNext function
  // and we should start the enumeration operation.
  //

  *lpszMessageID = '\0';
  nsMAPIConfiguration * pMapiConfig = nsMAPIConfiguration::GetMAPIConfiguration() ;
  if (!pMapiConfig) 
  {
    NS_ASSERTION(false, "failed to get config in findnext");
    return MAPI_E_FAILURE ;  // get the singelton obj
  }
  MsgMapiListContext *listContext;
  LONG ret = InitContext(aSession, &listContext);
  if (ret != SUCCESS_SUCCESS)
  {
    NS_ASSERTION(false, "init context failed");
    return ret;
  }
  NS_ASSERTION(listContext, "initContext returned null context");
  if (listContext)
  {
//    NS_ASSERTION(false, "find next init context succeeded");
    nsMsgKey nextKey = listContext->GetNext();
    if (nextKey == nsMsgKey_None)
    {
      pMapiConfig->SetMapiListContext(aSession, NULL);
      delete listContext;
      return(MAPI_E_NO_MESSAGES);
    }

//    TRACE("MAPI: ProcessMAPIFindNext() Found message id = %d\n", nextKey);

    sprintf((char *) lpszMessageID, "%d", nextKey);
  }

  PR_LOG(MAPI, PR_LOG_DEBUG, ("CMapiImp::FindNext returning key %s\n", (char *) lpszMessageID));
  return(SUCCESS_SUCCESS);
}

STDMETHODIMP CMapiImp::ReadMail(unsigned long aSession, unsigned long ulUIParam, LPTSTR lpszMessageID,
                              unsigned long flFlags, unsigned long ulReserved, lpnsMapiMessage *lppMessage)
{
  nsresult irv;
  nsAutoCString keyString((char *) lpszMessageID);
  PR_LOG(MAPI, PR_LOG_DEBUG, ("CMapiImp::ReadMail asking for key %s\n", (char *) lpszMessageID));
  nsMsgKey msgKey = keyString.ToInteger(&irv);
  if (NS_FAILED(irv))
  {
    NS_ASSERTION(false, "invalid lpszMessageID");
    return MAPI_E_INVALID_MESSAGE;
  }
  MsgMapiListContext *listContext;
  LONG ret = InitContext(aSession, &listContext);
  if (ret != SUCCESS_SUCCESS)
  {
    NS_ASSERTION(false, "init context failed in ReadMail");
    return ret;
  }
  *lppMessage = listContext->GetMessage (msgKey, flFlags);
  NS_ASSERTION(*lppMessage, "get message failed");

  return (*lppMessage) ? SUCCESS_SUCCESS : E_FAIL;
}


STDMETHODIMP CMapiImp::DeleteMail(unsigned long aSession, unsigned long ulUIParam, LPTSTR lpszMessageID,
                              unsigned long flFlags, unsigned long ulReserved)
{
  nsresult irv;
  nsAutoCString keyString((char *) lpszMessageID);
  nsMsgKey msgKey = keyString.ToInteger(&irv);
  // XXX Why do we return success on failure?
  if (NS_FAILED(irv))
    return SUCCESS_SUCCESS;
  MsgMapiListContext *listContext;
  LONG ret = InitContext(aSession, &listContext);
  if (ret != SUCCESS_SUCCESS)
    return ret;
  return (listContext->DeleteMessage(msgKey)) ? SUCCESS_SUCCESS : MAPI_E_INVALID_MESSAGE;
}

STDMETHODIMP CMapiImp::SaveMail(unsigned long aSession, unsigned long ulUIParam,  lpnsMapiMessage lppMessage,
                              unsigned long flFlags, unsigned long ulReserved, LPTSTR lpszMessageID)
{
  MsgMapiListContext *listContext;
  LONG ret = InitContext(aSession, &listContext);
  if (ret != SUCCESS_SUCCESS)
    return ret;
  return S_OK;
}


STDMETHODIMP CMapiImp::Logoff (unsigned long aSession)
{
    nsMAPIConfiguration *pConfig = nsMAPIConfiguration::GetMAPIConfiguration();

    if (pConfig->UnRegisterSession((uint32_t)aSession))
        return S_OK;

    return E_FAIL;
}

STDMETHODIMP CMapiImp::CleanUp()
{
    nsMapiHook::CleanUp();
    return S_OK;
}


#define           MAX_NAME_LEN    256


MsgMapiListContext::~MsgMapiListContext ()
{
  if (m_db)
    m_db->Close(false);
}


nsresult MsgMapiListContext::OpenDatabase (nsIMsgFolder *folder)
{
  nsresult dbErr = NS_ERROR_FAILURE;
  if (folder)
  {
    m_folder = folder;
    dbErr = folder->GetMsgDatabase(getter_AddRefs(m_db));
    if (m_db)
      dbErr = m_db->EnumerateMessages(getter_AddRefs(m_msgEnumerator));
  }
  return dbErr;
}

bool 
MsgMapiListContext::IsIMAPHost(void)
{
  if (!m_folder) 
    return FALSE;
  nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_folder);

  return imapFolder != nullptr;
}

nsMsgKey MsgMapiListContext::GetNext ()
{
  nsMsgKey key = nsMsgKey_None;
  bool      keepTrying = TRUE;
  
//  NS_ASSERTION (m_msgEnumerator && m_db, "need enumerator and db");
  if (m_msgEnumerator && m_db)
  {
    
    do 
    {
      keepTrying = FALSE;  
      nsCOMPtr <nsISupports> hdrISupports;
      nsCOMPtr <nsIMsgDBHdr> msgHdr;
      if (NS_SUCCEEDED(m_msgEnumerator->GetNext(getter_AddRefs(hdrISupports))) && hdrISupports)
      {
        msgHdr = do_QueryInterface(hdrISupports);
        msgHdr->GetMessageKey(&key);
        
        // Check here for IMAP message...if not, just return...
        if (!IsIMAPHost())
          return key;
        
        // If this is an IMAP message, we have to make sure we have a valid
        // body to work with.
        uint32_t  flags = 0;
        
        (void) msgHdr->GetFlags(&flags);
        if (flags & nsMsgMessageFlags::Offline) 
          return key;
        
        // Ok, if we get here, we have an IMAP message without a body!
        // We need to keep trying by calling the GetNext member recursively...
        keepTrying = TRUE;
      }
    } while (keepTrying);
  }
  
  return key;
}


nsresult MsgMapiListContext::MarkRead (nsMsgKey key, bool read)
{
  nsresult err = NS_ERROR_FAILURE;
  NS_ASSERTION(m_db, "no db");
  if (m_db)
    err = m_db->MarkRead (key, read, nullptr);
  return err;
}


lpnsMapiMessage MsgMapiListContext::GetMessage (nsMsgKey key, unsigned long flFlags)
{
  lpnsMapiMessage message = (lpnsMapiMessage) CoTaskMemAlloc (sizeof(nsMapiMessage));
  memset(message, 0, sizeof(nsMapiMessage));
  if (message)
  {
    nsCString subject;
    nsCString author;
    nsCOMPtr <nsIMsgDBHdr> msgHdr;

    nsresult rv = m_db->GetMsgHdrForKey (key, getter_AddRefs(msgHdr));
    if (msgHdr)
    {
      msgHdr->GetSubject (getter_Copies(subject));
      message->lpszSubject = (char *) CoTaskMemAlloc(subject.Length() + 1);
      strcpy((char *) message->lpszSubject, subject.get());
      uint32_t date;
      (void) msgHdr->GetDateInSeconds(&date);
      message->lpszDateReceived = ConvertDateToMapiFormat (date);
      
      // Pull out the flags info
      // anything to do with MAPI_SENT? Since we're only reading the Inbox, I guess not
      uint32_t ourFlags;
      (void) msgHdr->GetFlags(&ourFlags);
      if (!(ourFlags & nsMsgMessageFlags::Read))
        message->flFlags |= MAPI_UNREAD;
      if (ourFlags & (nsMsgMessageFlags::MDNReportNeeded | nsMsgMessageFlags::MDNReportSent))
        message->flFlags |= MAPI_RECEIPT_REQUESTED;
      
      // Pull out the author/originator info
      message->lpOriginator = (lpnsMapiRecipDesc) CoTaskMemAlloc (sizeof(nsMapiRecipDesc));
      memset(message->lpOriginator, 0, sizeof(nsMapiRecipDesc));
      if (message->lpOriginator)
      {
        msgHdr->GetAuthor (getter_Copies(author));
        ConvertRecipientsToMapiFormat(EncodedHeader(author),
          message->lpOriginator, MAPI_ORIG);
      }
      // Pull out the To/CC info
      nsCString recipients, ccList;
      msgHdr->GetRecipients(getter_Copies(recipients));
      msgHdr->GetCcList(getter_Copies(ccList));

      nsCOMArray<msgIAddressObject> parsedToRecips = EncodedHeader(recipients);
      nsCOMArray<msgIAddressObject> parsedCCRecips = EncodedHeader(ccList);
      uint32_t numToRecips = parsedToRecips.Length();
      uint32_t numCCRecips = parsedCCRecips.Length();

      message->lpRecips = (lpnsMapiRecipDesc) CoTaskMemAlloc ((numToRecips + numCCRecips) * sizeof(MapiRecipDesc));
      memset(message->lpRecips, 0, (numToRecips + numCCRecips) * sizeof(MapiRecipDesc));
      if (message->lpRecips)
      {
        ConvertRecipientsToMapiFormat(parsedToRecips, message->lpRecips,
          MAPI_TO);
        ConvertRecipientsToMapiFormat(parsedCCRecips,
          &message->lpRecips[numToRecips], MAPI_CC);
      }
  
      PR_LOG(MAPI, PR_LOG_DEBUG, ("MsgMapiListContext::GetMessage flags=%x subject %s date %s sender %s\n", 
        flFlags, (char *) message->lpszSubject,(char *) message->lpszDateReceived, author.get()) );

      // Convert any body text that we have locally
      if (!(flFlags & MAPI_ENVELOPE_ONLY))
        message->lpszNoteText = (char *) ConvertBodyToMapiFormat (msgHdr);
      
    }
    if (! (flFlags & (MAPI_PEEK | MAPI_ENVELOPE_ONLY)))
      m_db->MarkRead(key, true, nullptr);
  }
  return message;
}


char *MsgMapiListContext::ConvertDateToMapiFormat (time_t ourTime)
{
  char *date = (char*) CoTaskMemAlloc(32);
  if (date)
  {
    // MAPI time format is YYYY/MM/DD HH:MM
    // Note that we're not using XP_StrfTime because that localizes the time format,
    // and the way I read the MAPI spec is that their format is canonical, not localized.
    struct tm *local = localtime (&ourTime);
    if (local)
      strftime (date, 32, "%Y/%m/%d %I:%M", local); //use %H if hours should be 24 hour format
  }
  return date;
}


void MsgMapiListContext::ConvertRecipientsToMapiFormat(
    const nsCOMArray<msgIAddressObject> &recipients,
    lpnsMapiRecipDesc mapiRecips, int mapiRecipClass)
{
  nsTArray<nsCString> names, addresses;
  ExtractAllAddresses(recipients, UTF16ArrayAdapter<>(names),
    UTF16ArrayAdapter<>(addresses));
  
  uint32_t numAddresses = names.Length();
  for (int i = 0; i < numAddresses; i++)
  {
    if (!names[i].IsEmpty())
    {
      mapiRecips[i].lpszName = (char *) CoTaskMemAlloc(names[i].Length() + 1);
      if (mapiRecips[i].lpszName)
        strcpy((char *)mapiRecips[i].lpszName, names[i].get());
    }
    if (!addresses[i].IsEmpty())
    {
      mapiRecips[i].lpszName = (char *) CoTaskMemAlloc(addresses[i].Length() + 1);
      if (mapiRecips[i].lpszName)
        strcpy((char *)mapiRecips[i].lpszName, addresses[i].get());
    }
    mapiRecips[i].ulRecipClass = mapiRecipClass;
  }
}


char *MsgMapiListContext::ConvertBodyToMapiFormat (nsIMsgDBHdr *hdr)
{
  const int kBufLen = 64000; // I guess we only return the first 64K of a message.
  int bytesUsed = 0;
#define EMPTY_MESSAGE_LINE(buf) (buf[0] == '\r' || buf[0] == '\n' || buf[0] == '\0')

  nsCOMPtr <nsIMsgFolder> folder;
  hdr->GetFolder(getter_AddRefs(folder));
  if (!folder)
    return nullptr;

  nsCOMPtr <nsIInputStream> inputStream;
  nsCOMPtr <nsIFile> localFile;
  folder->GetFilePath(getter_AddRefs(localFile));

  nsresult rv;
  nsCOMPtr<nsIFileInputStream> fileStream = do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, nullptr);

  rv = fileStream->Init(localFile,  PR_RDONLY, 0664, false);  //just have to read the messages
  inputStream = do_QueryInterface(fileStream);

  if (inputStream)
  {
    nsCOMPtr <nsILineInputStream> fileLineStream = do_QueryInterface(inputStream);
    if (!fileLineStream)
      return nullptr;
    // ### really want to skip past headers...
    uint64_t messageOffset;
    uint32_t lineCount;
    hdr->GetMessageOffset(&messageOffset);
    hdr->GetLineCount(&lineCount);
    nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(inputStream);
    seekableStream->Seek(PR_SEEK_SET, messageOffset);
    bool hasMore = true;
    nsAutoCString curLine;
    bool inMessageBody = false;
    nsresult rv = NS_OK;
    while (hasMore) // advance past message headers
    {
      nsresult rv = fileLineStream->ReadLine(curLine, &hasMore);
      if (NS_FAILED(rv) || EMPTY_MESSAGE_LINE(curLine))
        break;
    }
    uint32_t msgSize;
    hdr->GetMessageSize(&msgSize);
    if (msgSize > kBufLen)
      msgSize = kBufLen - 1;
    // this is too big, since it includes the msg hdr size...oh well
    char *body = (char*) CoTaskMemAlloc (msgSize + 1);

    if (!body)
      return nullptr;
    int32_t bytesCopied = 0;
    for (hasMore = TRUE; lineCount > 0 && hasMore && NS_SUCCEEDED(rv); lineCount--)
    {
      rv = fileLineStream->ReadLine(curLine, &hasMore);
      if (NS_FAILED(rv))
        break;
      curLine.Append(CRLF);
      // make sure we have room left
      if (bytesCopied + curLine.Length() < msgSize)
      {
        strcpy(body + bytesCopied, curLine.get());
        bytesCopied += curLine.Length();
      }
    }
    PR_LOG(MAPI, PR_LOG_DEBUG, ("ConvertBodyToMapiFormat size=%x allocated size %x body = %100.100s\n", 
        bytesCopied, msgSize + 1, (char *) body) );
    body[bytesCopied] = '\0';   // rhp - fix last line garbage...
    return body;
  }
  return nullptr;
}


//*****************************************************************************
// MSGMAPI API implementation



static void msg_FreeMAPIFile(lpMapiFileDesc f)
{
  if (f)
  {
    CoTaskMemFree(f->lpszPathName);
    CoTaskMemFree(f->lpszFileName);
  }
}

static void msg_FreeMAPIRecipient(lpMapiRecipDesc rd)
{
  if (rd)
  {
    if (rd->lpszName)
      CoTaskMemFree(rd->lpszName);
    if (rd->lpszAddress)
      CoTaskMemFree(rd->lpszAddress);
    // CoTaskMemFree(rd->lpEntryID);  
  }
}

extern "C" void MSG_FreeMapiMessage (lpMapiMessage msg)
{
  ULONG i;
  
  if (msg)
  {
    CoTaskMemFree(msg->lpszSubject);
    CoTaskMemFree(msg->lpszNoteText);
    CoTaskMemFree(msg->lpszMessageType);
    CoTaskMemFree(msg->lpszDateReceived);
    CoTaskMemFree(msg->lpszConversationID);
    
    if (msg->lpOriginator)
      msg_FreeMAPIRecipient(msg->lpOriginator);
    
    for (i=0; i<msg->nRecipCount; i++)
      if (&(msg->lpRecips[i]) != nullptr)
        msg_FreeMAPIRecipient(&(msg->lpRecips[i]));
      
      CoTaskMemFree(msg->lpRecips);
      
      for (i=0; i<msg->nFileCount; i++)
        if (&(msg->lpFiles[i]) != nullptr)
          msg_FreeMAPIFile(&(msg->lpFiles[i]));
        
      CoTaskMemFree(msg->lpFiles);
      
      CoTaskMemFree(msg);
  }
}


extern "C" bool MsgMarkMapiMessageRead (nsIMsgFolder *folder, nsMsgKey key, bool read)
{
  bool success = FALSE;
  MsgMapiListContext *context = new MsgMapiListContext();
  if (context)
  {
    if (NS_SUCCEEDED(context->OpenDatabase(folder)))
    {
      if (NS_SUCCEEDED(context->MarkRead (key, read)))
        success = TRUE;
    }
    delete context;
  }
  return success;
}

bool 
MsgMapiListContext::DeleteMessage(nsMsgKey key)
{
  if (!m_db)
    return FALSE;
  
  if ( !IsIMAPHost() )
  {
    return NS_SUCCEEDED((m_db->DeleteMessages(1, &key, nullptr)));
  }
#if 0 
  else if ( m_folder->GetIMAPFolderInfoMail() )
  {
    nsAutoTArray<nsMsgKey, 1> messageKeys;
    messageKeys.AppendElement(key);

    (m_folder->GetIMAPFolderInfoMail())->DeleteSpecifiedMessages(pane, messageKeys, nsMsgKey_None);
    m_db->DeleteMessage(key, nullptr, FALSE);
    return TRUE;
  }
#endif
  else
  {
    return FALSE;
  }
}

/* Return TRUE on success, FALSE on failure */
extern "C" bool MSG_DeleteMapiMessage(nsIMsgFolder *folder, nsMsgKey key)
{
  bool success = FALSE;
  MsgMapiListContext *context = new MsgMapiListContext();
  if (context) 
  {
    if (NS_SUCCEEDED(context->OpenDatabase(folder)))
    {
      success = context->DeleteMessage(key);
    }
    
    delete context;
  }
  
  return success;
}

