/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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/. */

// this file implements the nsMsgFilterService interface

#include "msgCore.h"
#include "nsMsgFilterService.h"
#include "nsMsgFilterList.h"
#include "nsMsgSearchScopeTerm.h"
#include "nsDirectoryServiceDefs.h"
#include "nsIPrompt.h"
#include "nsIDocShell.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIStringBundle.h"
#include "nsIMsgSearchNotify.h"
#include "nsIUrlListener.h"
#include "nsIMsgCopyServiceListener.h"
#include "nsIMsgLocalMailFolder.h"
#include "nsIMsgDatabase.h"
#include "nsIMsgHdr.h"
#include "nsIDBFolderInfo.h"
#include "nsIRDFService.h"
#include "nsMsgBaseCID.h"
#include "nsIMsgCopyService.h"
#include "nsIInputStream.h"
#include "nsIOutputStream.h"
#include "nsISafeOutputStream.h"
#include "nsIMsgComposeService.h"
#include "nsMsgCompCID.h"
#include "nsNetUtil.h"
#include "nsMsgUtils.h"
#include "nsIMutableArray.h"
#include "nsIMsgMailSession.h"
#include "nsArrayUtils.h"
#include "nsCOMArray.h"
#include "nsIMsgFilterCustomAction.h"
#include "nsArrayEnumerator.h"
#include "nsMsgMessageFlags.h"
#include "nsIMsgWindow.h"
#include "nsIMsgSearchCustomTerm.h"
#include "nsIMsgSearchTerm.h"
#include "nsIMsgThread.h"
#include "nsAutoPtr.h"
#include "nsIMsgFilter.h"
#include "nsIMsgOperationListener.h"
#include "mozilla/Attributes.h"

#define BREAK_IF_FAILURE(_rv, _text) if (NS_FAILED(_rv)) { \
  NS_WARNING(_text); \
  mFinalResult = _rv; \
  break; \
}

#define CONTINUE_IF_FAILURE(_rv, _text) if (NS_FAILED(_rv)) { \
  NS_WARNING(_text); \
  mFinalResult = _rv; \
  if (m_msgWindow && !ContinueExecutionPrompt()) \
    return OnEndExecution(); \
  continue; \
}

#define BREAK_IF_FALSE(_assertTrue, _text) if (!(_assertTrue)) { \
  NS_WARNING(_text); \
  mFinalResult = NS_ERROR_FAILURE; \
  break; \
}

#define CONTINUE_IF_FALSE(_assertTrue, _text) if (!(_assertTrue)) { \
  NS_WARNING(_text); \
  mFinalResult = NS_ERROR_FAILURE; \
  if (m_msgWindow && !ContinueExecutionPrompt()) \
    return OnEndExecution(); \
  continue; \
}

NS_IMPL_ISUPPORTS(nsMsgFilterService, nsIMsgFilterService)

nsMsgFilterService::nsMsgFilterService()
{
}

nsMsgFilterService::~nsMsgFilterService()
{
}

NS_IMETHODIMP nsMsgFilterService::OpenFilterList(nsIFile *aFilterFile,
                                                 nsIMsgFolder *rootFolder,
                                                 nsIMsgWindow *aMsgWindow,
                                                 nsIMsgFilterList **resultFilterList)
{
  NS_ENSURE_ARG_POINTER(aFilterFile);
  NS_ENSURE_ARG_POINTER(resultFilterList);

  bool exists = false;
  nsresult rv = aFilterFile->Exists(&exists);
  if (NS_FAILED(rv) || !exists)
  {
    rv = aFilterFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  nsCOMPtr<nsIInputStream> fileStream;
  rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), aFilterFile);
  NS_ENSURE_SUCCESS(rv, rv);
  NS_ENSURE_TRUE(fileStream, NS_ERROR_OUT_OF_MEMORY);

  RefPtr<nsMsgFilterList> filterList = new nsMsgFilterList();
  NS_ENSURE_TRUE(filterList, NS_ERROR_OUT_OF_MEMORY);
  filterList->SetFolder(rootFolder);

  // temporarily tell the filter where its file path is
  filterList->SetDefaultFile(aFilterFile);

  int64_t size = 0;
  rv = aFilterFile->GetFileSize(&size);
  if (NS_SUCCEEDED(rv) && size > 0)
    rv = filterList->LoadTextFilters(fileStream);
  fileStream->Close();
  fileStream = nullptr;
  if (NS_SUCCEEDED(rv))
  {
    int16_t version;
    filterList->GetVersion(&version);
    if (version != kFileVersion)
      SaveFilterList(filterList, aFilterFile);
  }
  else
  {
    if (rv == NS_MSG_FILTER_PARSE_ERROR && aMsgWindow)
    {
      rv = BackUpFilterFile(aFilterFile, aMsgWindow);
      NS_ENSURE_SUCCESS(rv, rv);
      rv = aFilterFile->SetFileSize(0);
      NS_ENSURE_SUCCESS(rv, rv);
      return OpenFilterList(aFilterFile, rootFolder, aMsgWindow, resultFilterList);
    }
    else if (rv == NS_MSG_CUSTOM_HEADERS_OVERFLOW && aMsgWindow)
      ThrowAlertMsg("filterCustomHeaderOverflow", aMsgWindow);
    else if (rv == NS_MSG_INVALID_CUSTOM_HEADER && aMsgWindow)
      ThrowAlertMsg("invalidCustomHeader", aMsgWindow);
  }

  NS_ADDREF(*resultFilterList = filterList);
  return rv;
}

NS_IMETHODIMP nsMsgFilterService::CloseFilterList(nsIMsgFilterList *filterList)
{
  //NS_ASSERTION(false,"CloseFilterList doesn't do anything yet");
  return NS_OK;
}

/* save without deleting */
NS_IMETHODIMP  nsMsgFilterService::SaveFilterList(nsIMsgFilterList *filterList, nsIFile *filterFile)
{
  NS_ENSURE_ARG_POINTER(filterFile);
  NS_ENSURE_ARG_POINTER(filterList);

  nsCOMPtr<nsIOutputStream> strm;
  nsresult rv = MsgNewSafeBufferedFileOutputStream(getter_AddRefs(strm),
                                                filterFile, -1, 0600);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = filterList->SaveToFile(strm);

  nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(strm);
  NS_ASSERTION(safeStream, "expected a safe output stream!");
  if (safeStream) {
    rv = safeStream->Finish();
    if (NS_FAILED(rv)) {
      NS_WARNING("failed to save filter file! possible data loss");
    }
  }
  return rv;
}

NS_IMETHODIMP nsMsgFilterService::CancelFilterList(nsIMsgFilterList *filterList)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult nsMsgFilterService::BackUpFilterFile(nsIFile *aFilterFile, nsIMsgWindow *aMsgWindow)
{
  AlertBackingUpFilterFile(aMsgWindow);

  nsCOMPtr<nsIFile> localParentDir;
  nsresult rv = aFilterFile->GetParent(getter_AddRefs(localParentDir));
  NS_ENSURE_SUCCESS(rv,rv);

  //if back-up file exists delete the back up file otherwise copy fails.
  nsCOMPtr <nsIFile> backupFile;
  rv = localParentDir->Clone(getter_AddRefs(backupFile));
  NS_ENSURE_SUCCESS(rv,rv);
  backupFile->AppendNative(NS_LITERAL_CSTRING("rulesbackup.dat"));
  bool exists;
  backupFile->Exists(&exists);
  if (exists)
    backupFile->Remove(false);

  return aFilterFile->CopyToNative(localParentDir, NS_LITERAL_CSTRING("rulesbackup.dat"));
}

nsresult nsMsgFilterService::AlertBackingUpFilterFile(nsIMsgWindow *aMsgWindow)
{
  return ThrowAlertMsg("filterListBackUpMsg", aMsgWindow);
}

nsresult //Do not use this routine if you have to call it very often because it creates a new bundle each time
nsMsgFilterService::GetStringFromBundle(const char *aMsgName, char16_t **aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);

  nsCOMPtr <nsIStringBundle> bundle;
  nsresult rv = GetFilterStringBundle(getter_AddRefs(bundle));
  if (NS_SUCCEEDED(rv) && bundle)
    rv = bundle->GetStringFromName(NS_ConvertASCIItoUTF16(aMsgName).get(), aResult);
  return rv;

}

nsresult
nsMsgFilterService::GetFilterStringBundle(nsIStringBundle **aBundle)
{
  NS_ENSURE_ARG_POINTER(aBundle);

  nsCOMPtr<nsIStringBundleService> bundleService =
         mozilla::services::GetStringBundleService();
  NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
  nsCOMPtr<nsIStringBundle> bundle;
  if (bundleService)
    bundleService->CreateBundle("chrome://messenger/locale/filter.properties",
                                 getter_AddRefs(bundle));
  NS_IF_ADDREF(*aBundle = bundle);
  return NS_OK;
}

nsresult
nsMsgFilterService::ThrowAlertMsg(const char*aMsgName, nsIMsgWindow *aMsgWindow)
{
  nsString alertString;
  nsresult rv = GetStringFromBundle(aMsgName, getter_Copies(alertString));
  nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryInterface(aMsgWindow));
  if (!msgWindow) {
    nsCOMPtr<nsIMsgMailSession> mailSession ( do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv));
    if (NS_SUCCEEDED(rv))
      rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
  }

  if (NS_SUCCEEDED(rv) && !alertString.IsEmpty() && msgWindow)
  {
    nsCOMPtr <nsIDocShell> docShell;
    msgWindow->GetRootDocShell(getter_AddRefs(docShell));
    if (docShell)
    {
      nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
      if (dialog && !alertString.IsEmpty())
        dialog->Alert(nullptr, alertString.get());
    }
  }
  return rv;
}

// this class is used to run filters after the fact, i.e., after new mail has been downloaded from the server.
// It can do the following:
// 1. Apply a single imap or pop3 filter on a single folder.
// 2. Apply multiple filters on a single imap or pop3 folder.
// 3. Apply a single filter on multiple imap or pop3 folders in the same account.
// 4. Apply multiple filters on multiple imap or pop3 folders in the same account.
// This will be called from the front end js code in the case of the apply filters to folder menu code,
// and from the filter dialog js code with the run filter now command.


// this class holds the list of filters and folders, and applies them in turn, first iterating
// over all the filters on one folder, and then advancing to the next folder and repeating.
// For each filter,we take the filter criteria and create a search term list. Then, we execute the search.
// We are a search listener so that we can build up the list of search hits.
// Then, when the search is done, we will apply the filter action(s) en-masse, so, for example, if the action is a move,
// we calls one method to move all the messages to the destination folder. Or, mark all the messages read.
// In the case of imap operations, or imap/local  moves, the action will be asynchronous, so we'll need to be a url listener
// as well, and kick off the next filter when the action completes.
class nsMsgFilterAfterTheFact : public nsIUrlListener, public nsIMsgSearchNotify, public nsIMsgCopyServiceListener
{
public:
  nsMsgFilterAfterTheFact(nsIMsgWindow *aMsgWindow,
                          nsIMsgFilterList *aFilterList, nsIArray *aFolderList,
                          nsIMsgOperationListener *aCallback);
  NS_DECL_ISUPPORTS
  NS_DECL_NSIURLLISTENER
  NS_DECL_NSIMSGSEARCHNOTIFY
  NS_DECL_NSIMSGCOPYSERVICELISTENER

  nsresult  AdvanceToNextFolder();  // kicks off the process
protected:
  virtual ~nsMsgFilterAfterTheFact();
  virtual   nsresult  RunNextFilter();
  /**
   * apply filter actions to current search hits
   */
  nsresult  ApplyFilter();
  nsresult  OnEndExecution(); // do what we have to do to cleanup.
  bool      ContinueExecutionPrompt();
  nsresult  DisplayConfirmationPrompt(nsIMsgWindow *msgWindow, const char16_t *confirmString, bool *confirmed);
  nsCOMPtr<nsIMsgWindow>      m_msgWindow;
  nsCOMPtr<nsIMsgFilterList>  m_filters;
  nsCOMPtr<nsIArray>          m_folders;
  nsCOMPtr<nsIMsgFolder>      m_curFolder;
  nsCOMPtr<nsIMsgDatabase>    m_curFolderDB;
  nsCOMPtr<nsIMsgFilter>      m_curFilter;
  uint32_t                    m_curFilterIndex;
  uint32_t                    m_curFolderIndex;
  uint32_t                    m_numFilters;
  uint32_t                    m_numFolders;
  nsTArray<nsMsgKey>          m_searchHits;
  nsCOMPtr<nsIMutableArray>   m_searchHitHdrs;
  nsTArray<nsMsgKey>          m_stopFiltering;
  nsCOMPtr<nsIMsgSearchSession> m_searchSession;
  nsCOMPtr<nsIMsgOperationListener> m_callback;
  uint32_t                    m_nextAction; // next filter action to perform
  nsresult                    mFinalResult; // report of overall success or failure
  bool                        mNeedsRelease; // Did we need to release ourself?
};

NS_IMPL_ISUPPORTS(nsMsgFilterAfterTheFact, nsIUrlListener, nsIMsgSearchNotify, nsIMsgCopyServiceListener)

nsMsgFilterAfterTheFact::nsMsgFilterAfterTheFact(nsIMsgWindow *aMsgWindow,
                                                 nsIMsgFilterList *aFilterList,
                                                 nsIArray *aFolderList,
                                                 nsIMsgOperationListener *aCallback)
{
  m_curFilterIndex = m_curFolderIndex = m_nextAction = 0;
  m_msgWindow = aMsgWindow;
  m_filters = aFilterList;
  m_folders = aFolderList;
  m_filters->GetFilterCount(&m_numFilters);
  m_folders->GetLength(&m_numFolders);

  NS_ADDREF(this); // we own ourselves, and will release ourselves when execution is done.
  mNeedsRelease = true;

  m_searchHitHdrs = do_CreateInstance(NS_ARRAY_CONTRACTID);
  m_callback = aCallback;
  mFinalResult = NS_OK;
}

nsMsgFilterAfterTheFact::~nsMsgFilterAfterTheFact()
{
}

// do what we have to do to cleanup.
nsresult nsMsgFilterAfterTheFact::OnEndExecution()
{
  if (m_searchSession)
    m_searchSession->UnregisterListener(this);

  if (m_filters)
    (void)m_filters->FlushLogIfNecessary();

  if (m_callback)
    (void)m_callback->OnStopOperation(mFinalResult);

  nsresult rv = mFinalResult;
  // OnEndExecution() can be called a second time when a rule execution fails
  // and the user is prompted whether he wants to continue.
  if (mNeedsRelease)
  {
    Release(); // release ourselves.
    mNeedsRelease = false;
  }
  return rv;
}

nsresult nsMsgFilterAfterTheFact::RunNextFilter()
{
  nsresult rv = NS_OK;
  while (true)
  {
    m_curFilter = nullptr;
    if (m_curFilterIndex >= m_numFilters)
      break;
    BREAK_IF_FALSE(m_filters, "Missing filters");
    rv = m_filters->GetFilterAt(m_curFilterIndex++, getter_AddRefs(m_curFilter));
    CONTINUE_IF_FAILURE(rv, "Could not get filter at index");

    nsCOMPtr <nsISupportsArray> searchTerms;
    rv = m_curFilter->GetSearchTerms(getter_AddRefs(searchTerms));
    CONTINUE_IF_FAILURE(rv, "Could not get searchTerms");

    if (m_searchSession)
      m_searchSession->UnregisterListener(this);
    m_searchSession = do_CreateInstance(NS_MSGSEARCHSESSION_CONTRACTID, &rv);
    BREAK_IF_FAILURE(rv, "Failed to get search session");

    nsMsgSearchScopeValue searchScope = nsMsgSearchScope::offlineMail;
    uint32_t termCount;
    searchTerms->Count(&termCount);
    for (uint32_t termIndex = 0; termIndex < termCount; termIndex++)
    {
      nsCOMPtr <nsIMsgSearchTerm> term;
      nsresult rv = searchTerms->QueryElementAt(termIndex, NS_GET_IID(nsIMsgSearchTerm), getter_AddRefs(term));
      BREAK_IF_FAILURE(rv, "Could not get search term");
      rv = m_searchSession->AppendTerm(term);
      BREAK_IF_FAILURE(rv, "Could not append search term");
    }
    CONTINUE_IF_FAILURE(rv, "Failed to setup search terms");
    m_searchSession->RegisterListener(this,
                                      nsIMsgSearchSession::allNotifications);

    rv = m_searchSession->AddScopeTerm(searchScope, m_curFolder);
    CONTINUE_IF_FAILURE(rv, "Failed to add scope term");
    m_nextAction = 0;
    rv = m_searchSession->Search(m_msgWindow);
    CONTINUE_IF_FAILURE(rv, "Search failed");
    return NS_OK; // OnSearchDone will continue
  }
  m_curFilter = nullptr;
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Search failed");
  return AdvanceToNextFolder();
}

nsresult nsMsgFilterAfterTheFact::AdvanceToNextFolder()
{
  nsresult rv = NS_OK;
  // Advance through folders, making sure m_curFolder is null on errors
  while (true)
  {
    m_stopFiltering.Clear();
    m_curFolder = nullptr;
    if (m_curFolderIndex >= m_numFolders)
      // final end of nsMsgFilterAfterTheFact object
      return OnEndExecution();

    // reset the filter index to apply all filters to this new folder
    m_curFilterIndex = 0;
    m_nextAction = 0;
    rv = m_folders->QueryElementAt(m_curFolderIndex++, NS_GET_IID(nsIMsgFolder), getter_AddRefs(m_curFolder));
    CONTINUE_IF_FAILURE(rv, "Could not get next folder");

    // Note: I got rv = NS_OK but null m_curFolder after deleting a folder
    // outside of TB, when I select a single message and "run filter on message"
    // and the filter is to move the message to the deleted folder.

     // m_curFolder may be null when the folder is deleted externally.
    CONTINUE_IF_FALSE(m_curFolder, "Next folder returned null");

    rv = m_curFolder->GetMsgDatabase(getter_AddRefs(m_curFolderDB));
    if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
    {
      nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_curFolder, &rv);
      if (NS_SUCCEEDED(rv) && localFolder)
        // will continue with OnStopRunningUrl
        return localFolder->ParseFolder(m_msgWindow, this);
    }
    CONTINUE_IF_FAILURE(rv, "Could not get folder db");

    rv = RunNextFilter();
    // RunNextFilter returns success when either filters are done, or an async process has started.
    // It will call AdvanceToNextFolder itself if possible, so no need to call here.
    BREAK_IF_FAILURE(rv, "Failed to run next filter");
    break;
  }
  return rv;
}

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

// This is the return from a folder parse
NS_IMETHODIMP nsMsgFilterAfterTheFact::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode)
{
  if (NS_SUCCEEDED(aExitCode))
    return RunNextFilter();

  mFinalResult = aExitCode;
   // If m_msgWindow then we are in a context where the user can deal with
   //  errors. Put up a prompt, and exit if user wants.
  if (m_msgWindow && !ContinueExecutionPrompt())
    return OnEndExecution();

  // folder parse failed, so stop processing this folder.
  return AdvanceToNextFolder();
}

NS_IMETHODIMP nsMsgFilterAfterTheFact::OnSearchHit(nsIMsgDBHdr *header, nsIMsgFolder *folder)
{
  NS_ENSURE_ARG_POINTER(header);
  NS_ENSURE_TRUE(m_searchHitHdrs, NS_ERROR_NOT_INITIALIZED);

  nsMsgKey msgKey;
  header->GetMessageKey(&msgKey);

  // Under various previous actions (a move, delete, or stopExecution)
  //  we do not want to process filters on a per-message basis.
  if (m_stopFiltering.Contains(msgKey))
    return NS_OK;

  m_searchHits.AppendElement(msgKey);
  m_searchHitHdrs->AppendElement(header, false);
  return NS_OK;
}

// Continue after an async operation.
NS_IMETHODIMP nsMsgFilterAfterTheFact::OnSearchDone(nsresult status)
{
  if (NS_SUCCEEDED(status))
    return m_searchHits.IsEmpty() ? RunNextFilter() : ApplyFilter();

  mFinalResult = status;
  if (m_msgWindow && !ContinueExecutionPrompt())
    return OnEndExecution();

  // The search failed, so move on to the next filter.
  return RunNextFilter();
}

NS_IMETHODIMP nsMsgFilterAfterTheFact::OnNewSearch()
{
  m_searchHits.Clear();
  m_searchHitHdrs->Clear();
  return NS_OK;
}

// This method will apply filters. It will continue to advance though headers,
//   filters, and folders until done, unless it starts an async operation with
//   a callback. The callback should call ApplyFilter again. It only returns
//   an error if it is impossible to continue after attempting to continue the
//   next filter action, filter, or folder.
nsresult nsMsgFilterAfterTheFact::ApplyFilter()
{
  nsresult rv;
  do { // error management block, break if unable to continue with filter.
    if (!m_curFilter)
      break; // Maybe not an error, we just need to call RunNextFilter();
    if (!m_curFolder)
      break; // Maybe not an error, we just need to call AdvanceToNextFolder();
    BREAK_IF_FALSE(m_searchHitHdrs, "No search headers object");
    // we're going to log the filter actions before firing them because some actions are async
    bool loggingEnabled = false;
    if (m_filters)
      (void)m_filters->GetLoggingEnabled(&loggingEnabled);

    nsCOMPtr<nsIArray> actionList;
    rv = m_curFilter->GetSortedActionList(getter_AddRefs(actionList));
    BREAK_IF_FAILURE(rv, "Could not get action list for filter");

    uint32_t numActions;
    actionList->GetLength(&numActions);

    // We start from m_nextAction to allow us to continue applying actions
    // after the return from an async copy.
    while (m_nextAction < numActions)
    {
      nsCOMPtr<nsIMsgRuleAction>filterAction(do_QueryElementAt(actionList, m_nextAction++, &rv));
      CONTINUE_IF_FAILURE(rv, "actionList cannot QI element");

      nsMsgRuleActionType actionType;
      rv = filterAction->GetType(&actionType);
      CONTINUE_IF_FAILURE(rv, "Could not get type for filter action");

      nsCString actionTargetFolderUri;
      if (actionType == nsMsgFilterAction::MoveToFolder ||
          actionType == nsMsgFilterAction::CopyToFolder)
      {
        rv = filterAction->GetTargetFolderUri(actionTargetFolderUri);
        CONTINUE_IF_FALSE(NS_SUCCEEDED(rv) && !actionTargetFolderUri.IsEmpty(),
                          "actionTargetFolderUri is empty");
      }

      if (loggingEnabled)
      {
        for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
        {
          nsCOMPtr <nsIMsgDBHdr> msgHdr;
          m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
          if (msgHdr)
            (void)m_curFilter->LogRuleHit(filterAction, msgHdr);
          else
            NS_WARNING("could not QI element to nsIMsgDBHdr");
        }
      }

      // all actions that pass "this" as a listener in order to chain filter execution
      // when the action is finished need to return before reaching the bottom of this
      // routine, because we run the next filter at the end of this routine.
      switch (actionType)
      {
      case nsMsgFilterAction::Delete:
        // we can't pass ourselves in as a copy service listener because the copy service
        // listener won't get called in several situations (e.g., the delete model is imap delete)
        // and we rely on the listener getting called to continue the filter application.
        // This means we're going to end up firing off the delete, and then subsequently
        // issuing a search for the next filter, which will block until the delete finishes.
        m_curFolder->DeleteMessages(m_searchHitHdrs, m_msgWindow, false, false, nullptr, false /*allow Undo*/ );

        // don't allow any more filters on this message
        m_stopFiltering.AppendElements(m_searchHits);
        for (uint32_t i = 0; i < m_searchHits.Length(); i++)
          m_curFolder->OrProcessingFlags(m_searchHits[i], nsMsgProcessingFlags::FilterToMove);
        //if we are deleting then we couldn't care less about applying remaining filter actions
        m_nextAction = numActions;
        break;

      case nsMsgFilterAction::MoveToFolder:
        // Even if move fails we will not run additional actions, as they
        //   would not have run if move succeeded.
        m_nextAction = numActions;
        // Fall through to the copy case.
        MOZ_FALLTHROUGH;
      case nsMsgFilterAction::CopyToFolder:
      {
        nsCString uri;
        m_curFolder->GetURI(uri);
        if (!actionTargetFolderUri.IsEmpty() &&
            !uri.Equals(actionTargetFolderUri))
        {
          nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1",&rv);
          nsCOMPtr<nsIRDFResource> res;
          rv = rdf->GetResource(actionTargetFolderUri, getter_AddRefs(res));
          CONTINUE_IF_FAILURE(rv, "Could not get resource for action folder");

          nsCOMPtr<nsIMsgFolder> destIFolder(do_QueryInterface(res, &rv));
          CONTINUE_IF_FAILURE(rv, "Could not QI resource to folder");

          bool canFileMessages = true;
          nsCOMPtr<nsIMsgFolder> parentFolder;
          destIFolder->GetParent(getter_AddRefs(parentFolder));
          if (parentFolder)
            destIFolder->GetCanFileMessages(&canFileMessages);
          if (!parentFolder || !canFileMessages)
          {
            m_curFilter->SetEnabled(false);
            destIFolder->ThrowAlertMsg("filterDisabled",m_msgWindow);
            // we need to explicitly save the filter file.
            m_filters->SaveToDefaultFile();
            // In the case of applying multiple filters
            // we might want to remove the filter from the list, but
            // that's a bit evil since we really don't know that we own
            // the list. Disabling it doesn't do a lot of good since
            // we still apply disabled filters. Currently, we don't
            // have any clients that apply filters to multiple folders,
            // so this might be the edge case of an edge case.
            m_nextAction = numActions;
            mFinalResult = NS_ERROR_FAILURE;
            break;
          }
          nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
          CONTINUE_IF_FAILURE(rv, "Could not get copy service")

          if (actionType == nsMsgFilterAction::MoveToFolder)
          {
            m_stopFiltering.AppendElements(m_searchHits);
            for (uint32_t i = 0; i < m_searchHits.Length(); i++)
              m_curFolder->OrProcessingFlags(m_searchHits[i],
                                             nsMsgProcessingFlags::FilterToMove);
          }

          rv = copyService->CopyMessages(m_curFolder, m_searchHitHdrs,
              destIFolder, actionType == nsMsgFilterAction::MoveToFolder,
              this, m_msgWindow, false);
          CONTINUE_IF_FAILURE(rv, "CopyMessages failed");
          return NS_OK; // OnStopCopy callback to continue;
        }
        else
          NS_WARNING("Move or copy failed, empty or unchanged destination");
      }
        break;
      case nsMsgFilterAction::MarkRead:
          // crud, no listener support here - we'll probably just need to go on and apply
          // the next filter, and, in the imap case, rely on multiple connection and url
          // queueing to stay out of trouble
        m_curFolder->MarkMessagesRead(m_searchHitHdrs, true);
        break;
      case nsMsgFilterAction::MarkUnread:
        m_curFolder->MarkMessagesRead(m_searchHitHdrs, false);
        break;
      case nsMsgFilterAction::MarkFlagged:
        m_curFolder->MarkMessagesFlagged(m_searchHitHdrs, true);
        break;
      case nsMsgFilterAction::KillThread:
      case nsMsgFilterAction::WatchThread:
        {
          for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
          {
            nsCOMPtr <nsIMsgDBHdr> msgHdr;
            m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
            CONTINUE_IF_FALSE(msgHdr, "Could not get msg header");

            nsCOMPtr<nsIMsgThread> msgThread;
            nsMsgKey threadKey;
            m_curFolderDB->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(msgThread));
            CONTINUE_IF_FALSE(msgThread, "Could not find msg thread");
            msgThread->GetThreadKey(&threadKey);
            if (actionType == nsMsgFilterAction::KillThread)
              m_curFolderDB->MarkThreadIgnored(msgThread, threadKey, true, nullptr);
            else
              m_curFolderDB->MarkThreadWatched(msgThread, threadKey, true, nullptr);
          }
        }
        break;
      case nsMsgFilterAction::KillSubthread:
        {
          for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
          {
            nsCOMPtr<nsIMsgDBHdr> msgHdr;
            m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
            CONTINUE_IF_FALSE(msgHdr, "Could not get msg header");
            m_curFolderDB->MarkHeaderKilled(msgHdr, true, nullptr);
          }
        }
        break;
      case nsMsgFilterAction::ChangePriority:
        {
          nsMsgPriorityValue filterPriority;
          filterAction->GetPriority(&filterPriority);
          for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
          {
            nsCOMPtr <nsIMsgDBHdr> msgHdr;
            m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
            CONTINUE_IF_FALSE(msgHdr, "Could not get msg header");
            msgHdr->SetPriority(filterPriority);
          }
        }
        break;
      case nsMsgFilterAction::Label:
        {
          nsMsgLabelValue filterLabel;
          filterAction->GetLabel(&filterLabel);
          m_curFolder->SetLabelForMessages(m_searchHitHdrs, filterLabel);
        }
        break;
      case nsMsgFilterAction::AddTag:
        {
          nsCString keyword;
          filterAction->GetStrValue(keyword);
          m_curFolder->AddKeywordsToMessages(m_searchHitHdrs, keyword);
        }
        break;
      case nsMsgFilterAction::JunkScore:
        {
          nsAutoCString junkScoreStr;
          int32_t junkScore;
          filterAction->GetJunkScore(&junkScore);
          junkScoreStr.AppendInt(junkScore);
          m_curFolder->SetJunkScoreForMessages(m_searchHitHdrs, junkScoreStr);
        }
        break;
      case nsMsgFilterAction::Forward:
        {
          nsCOMPtr<nsIMsgIncomingServer> server;
          rv = m_curFolder->GetServer(getter_AddRefs(server));
          CONTINUE_IF_FAILURE(rv, "Could not get server");
          nsCString forwardTo;
          filterAction->GetStrValue(forwardTo);
          CONTINUE_IF_FALSE(!forwardTo.IsEmpty(), "blank forwardTo URI");
          nsCOMPtr<nsIMsgComposeService> compService =
            do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
          CONTINUE_IF_FAILURE(rv, "Could not get compose service");

          for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
          {
            nsCOMPtr<nsIMsgDBHdr> msgHdr(do_QueryElementAt(m_searchHitHdrs,
                                         msgIndex));
            if (msgHdr)
              rv = compService->ForwardMessage(NS_ConvertASCIItoUTF16(forwardTo),
                                               msgHdr, m_msgWindow, server,
                                               nsIMsgComposeService::kForwardAsDefault);
            CONTINUE_IF_FALSE(msgHdr && NS_SUCCEEDED(rv), "Forward action failed");
          }
        }
        break;
      case nsMsgFilterAction::Reply:
        {
          nsCString replyTemplateUri;
          filterAction->GetStrValue(replyTemplateUri);
          CONTINUE_IF_FALSE(!replyTemplateUri.IsEmpty(), "Empty reply template URI");

          nsCOMPtr<nsIMsgIncomingServer> server;
          rv = m_curFolder->GetServer(getter_AddRefs(server));
          CONTINUE_IF_FAILURE(rv, "Could not get server");

          nsCOMPtr<nsIMsgComposeService> compService = do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
          CONTINUE_IF_FAILURE(rv, "Could not get compose service");
          for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
          {
            nsCOMPtr <nsIMsgDBHdr> msgHdr;
            m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
            CONTINUE_IF_FALSE(msgHdr, "Could not get msgHdr");
            rv = compService->ReplyWithTemplate(msgHdr, replyTemplateUri.get(), m_msgWindow, server);
            if (NS_FAILED(rv)) {
              if (rv == NS_ERROR_ABORT) {
                m_curFilter->LogRuleHitFail(filterAction, msgHdr, rv, "Sending reply aborted");
              } else {
                m_curFilter->LogRuleHitFail(filterAction, msgHdr, rv, "Error sending reply");
              }
            }
            CONTINUE_IF_FAILURE(rv, "ReplyWithTemplate failed");
          }
        }
        break;
      case nsMsgFilterAction::DeleteFromPop3Server:
        {
          nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_curFolder);
          CONTINUE_IF_FALSE(localFolder, "Current folder not a local folder");
          // This action ignores the deleteMailLeftOnServer preference
          rv = localFolder->MarkMsgsOnPop3Server(m_searchHitHdrs, POP3_FORCE_DEL);
          CONTINUE_IF_FAILURE(rv, "MarkMsgsOnPop3Server failed");

          nsCOMPtr<nsIMutableArray> partialMsgs;
          // Delete the partial headers. They're useless now
          //   that the server copy is being deleted.
          for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
          {
            nsCOMPtr <nsIMsgDBHdr> msgHdr;
            m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
            CONTINUE_IF_FALSE(msgHdr, "Could not get msgHdr");
            uint32_t flags;
            msgHdr->GetFlags(&flags);
            if (flags & nsMsgMessageFlags::Partial)
            {
              if (!partialMsgs)
                partialMsgs = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
              CONTINUE_IF_FALSE(partialMsgs, "Could not create partialMsgs array");
              partialMsgs->AppendElement(msgHdr, false);
              m_stopFiltering.AppendElement(m_searchHits[msgIndex]);
              m_curFolder->OrProcessingFlags(m_searchHits[msgIndex],
                                             nsMsgProcessingFlags::FilterToMove);
            }
          }
          if (partialMsgs)
          {
            m_curFolder->DeleteMessages(partialMsgs, m_msgWindow, true, false, nullptr, false);
            CONTINUE_IF_FAILURE(rv, "Delete messages failed");
          }
        }
        break;
      case nsMsgFilterAction::FetchBodyFromPop3Server:
        {
          nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_curFolder);
          CONTINUE_IF_FALSE(localFolder, "current folder not local");
          nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
          CONTINUE_IF_FAILURE(rv, "Could not create messages array");
          for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
          {
            nsCOMPtr<nsIMsgDBHdr> msgHdr;
            m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
            CONTINUE_IF_FALSE(msgHdr, "Could not get msgHdr");
            uint32_t flags = 0;
            msgHdr->GetFlags(&flags);
            if (flags & nsMsgMessageFlags::Partial)
              messages->AppendElement(msgHdr, false);
          }
          uint32_t msgsToFetch;
          messages->GetLength(&msgsToFetch);
          if (msgsToFetch > 0)
          {
            rv = m_curFolder->DownloadMessagesForOffline(messages, m_msgWindow);
            CONTINUE_IF_FAILURE(rv, "DownloadMessagesForOffline failed");
          }
        }
        break;

      case nsMsgFilterAction::StopExecution:
        {
          // don't apply any more filters
          m_stopFiltering.AppendElements(m_searchHits);
          m_nextAction = numActions;
        }
      break;

      case nsMsgFilterAction::Custom:
        {
          nsMsgFilterTypeType filterType;
          m_curFilter->GetFilterType(&filterType);
          nsCOMPtr<nsIMsgFilterCustomAction> customAction;
          rv = filterAction->GetCustomAction(getter_AddRefs(customAction));
          CONTINUE_IF_FAILURE(rv, "Could not get custom action");

          nsAutoCString value;
          filterAction->GetStrValue(value);
          bool isAsync = false;
          customAction->GetIsAsync(&isAsync);
          rv = customAction->Apply(m_searchHitHdrs, value, this,
                                   filterType, m_msgWindow);
          CONTINUE_IF_FAILURE(rv, "custom action failed to apply");
          if (isAsync)
            return NS_OK; // custom action should call ApplyFilter on callback
        }
      break;

      default:
        break;
      }
    }
  } while (false); // end error management block
  return RunNextFilter();
}

NS_IMETHODIMP nsMsgFilterService::GetTempFilterList(nsIMsgFolder *aFolder, nsIMsgFilterList **aFilterList)
{
  NS_ENSURE_ARG_POINTER(aFilterList);

  nsMsgFilterList *filterList = new nsMsgFilterList;
  NS_ENSURE_TRUE(filterList, NS_ERROR_OUT_OF_MEMORY);
  NS_ADDREF(*aFilterList = filterList);
  (*aFilterList)->SetFolder(aFolder);
  filterList->m_temporaryList = true;
  return NS_OK;
}

NS_IMETHODIMP
nsMsgFilterService::ApplyFiltersToFolders(nsIMsgFilterList *aFilterList,
                                          nsIArray *aFolders,
                                          nsIMsgWindow *aMsgWindow,
                                          nsIMsgOperationListener *aCallback)
{
  NS_ENSURE_ARG_POINTER(aFilterList);
  NS_ENSURE_ARG_POINTER(aFolders);

  RefPtr<nsMsgFilterAfterTheFact> filterExecutor =
    new nsMsgFilterAfterTheFact(aMsgWindow, aFilterList, aFolders, aCallback);
  if (filterExecutor)
    return filterExecutor->AdvanceToNextFolder();
  else
    return NS_ERROR_OUT_OF_MEMORY;
}

NS_IMETHODIMP nsMsgFilterService::AddCustomAction(nsIMsgFilterCustomAction *aAction)
{
  mCustomActions.AppendObject(aAction);
  return NS_OK;
}

NS_IMETHODIMP nsMsgFilterService::GetCustomActions(nsISimpleEnumerator** aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);

  return NS_NewArrayEnumerator(aResult, mCustomActions);
}

NS_IMETHODIMP
nsMsgFilterService::GetCustomAction(const nsACString & aId,
                                    nsIMsgFilterCustomAction** aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);

  for (int32_t i = 0; i < mCustomActions.Count(); i++)
  {
    nsAutoCString id;
    nsresult rv = mCustomActions[i]->GetId(id);
    if (NS_SUCCEEDED(rv) && aId.Equals(id))
    {
      NS_ADDREF(*aResult = mCustomActions[i]);
      return NS_OK;
    }
  }
  aResult = nullptr;
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP nsMsgFilterService::AddCustomTerm(nsIMsgSearchCustomTerm *aTerm)
{
  mCustomTerms.AppendObject(aTerm);
  return NS_OK;
}

NS_IMETHODIMP nsMsgFilterService::GetCustomTerms(nsISimpleEnumerator** aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);

  return NS_NewArrayEnumerator(aResult, mCustomTerms);
}

NS_IMETHODIMP
nsMsgFilterService::GetCustomTerm(const nsACString& aId,
                                    nsIMsgSearchCustomTerm** aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);

  for (int32_t i = 0; i < mCustomTerms.Count(); i++)
  {
    nsAutoCString id;
    nsresult rv = mCustomTerms[i]->GetId(id);
    if (NS_SUCCEEDED(rv) && aId.Equals(id))
    {
      NS_ADDREF(*aResult = mCustomTerms[i]);
      return NS_OK;
    }
  }
  aResult = nullptr;
  // we use a null result to indicate failure to find a term
  return NS_OK;
}

// nsMsgApplyFiltersToMessages overrides nsMsgFilterAfterTheFact in order to
// apply filters to a list of messages, rather than an entire folder
class nsMsgApplyFiltersToMessages : public nsMsgFilterAfterTheFact
{
public:
  nsMsgApplyFiltersToMessages(nsIMsgWindow *aMsgWindow,
                              nsIMsgFilterList *aFilterList,
                              nsIArray *aFolderList, nsIArray *aMsgHdrList,
                              nsMsgFilterTypeType aFilterType,
                              nsIMsgOperationListener *aCallback);

protected:
  virtual   nsresult  RunNextFilter();

  nsCOMArray<nsIMsgDBHdr> m_msgHdrList;
  nsMsgFilterTypeType     m_filterType;
};

nsMsgApplyFiltersToMessages::nsMsgApplyFiltersToMessages(nsIMsgWindow *aMsgWindow,
                                                         nsIMsgFilterList *aFilterList,
                                                         nsIArray *aFolderList,
                                                         nsIArray *aMsgHdrList,
                                                         nsMsgFilterTypeType aFilterType,
                                                         nsIMsgOperationListener *aCallback)
: nsMsgFilterAfterTheFact(aMsgWindow, aFilterList, aFolderList, aCallback),
  m_filterType(aFilterType)
{
  nsCOMPtr<nsISimpleEnumerator> msgEnumerator;
  if (NS_SUCCEEDED(aMsgHdrList->Enumerate(getter_AddRefs(msgEnumerator))))
  {
    uint32_t length;
    if (NS_SUCCEEDED(aMsgHdrList->GetLength(&length)))
      m_msgHdrList.SetCapacity(length);

    bool hasMore;
    while (NS_SUCCEEDED(msgEnumerator->HasMoreElements(&hasMore)) && hasMore)
    {
      nsCOMPtr<nsISupports> supports;
      nsCOMPtr<nsIMsgDBHdr> msgHdr;
      if (NS_SUCCEEDED(msgEnumerator->GetNext(getter_AddRefs(supports))) &&
          (msgHdr = do_QueryInterface(supports)))
        m_msgHdrList.AppendObject(msgHdr);
    }
  }
}

nsresult nsMsgApplyFiltersToMessages::RunNextFilter()
{
  nsresult rv = NS_OK;
  while (true)
  {
    m_curFilter = nullptr; // we are done with the current filter
    if (!m_curFolder || // Not an error, we just need to run AdvanceToNextFolder()
        m_curFilterIndex >= m_numFilters)
      break;
    BREAK_IF_FALSE(m_filters, "No filters");
    nsMsgFilterTypeType filterType;
    bool isEnabled;
    rv = m_filters->GetFilterAt(m_curFilterIndex++, getter_AddRefs(m_curFilter));
    CONTINUE_IF_FAILURE(rv, "Could not get filter");
    rv = m_curFilter->GetFilterType(&filterType);
    CONTINUE_IF_FAILURE(rv, "Could not get filter type");
    if (!(filterType & m_filterType))
      continue;
    rv = m_curFilter->GetEnabled(&isEnabled);
    CONTINUE_IF_FAILURE(rv, "Could not get isEnabled");
    if (!isEnabled)
      continue;

    nsCOMPtr<nsIMsgSearchScopeTerm> scope(new nsMsgSearchScopeTerm(nullptr, nsMsgSearchScope::offlineMail, m_curFolder));
    BREAK_IF_FALSE(scope, "Could not create scope, OOM?");
    m_curFilter->SetScope(scope);
    OnNewSearch();

    for (int32_t i = 0; i < m_msgHdrList.Count(); i++)
    {
      nsCOMPtr<nsIMsgDBHdr> msgHdr = m_msgHdrList[i];
      CONTINUE_IF_FALSE(msgHdr, "null msgHdr");

      bool matched;
      rv = m_curFilter->MatchHdr(msgHdr, m_curFolder, m_curFolderDB, nullptr, 0, &matched);
      if (NS_SUCCEEDED(rv) && matched)
      {
        // In order to work with nsMsgFilterAfterTheFact::ApplyFilter we initialize
        // nsMsgFilterAfterTheFact's information with a search hit now for the message
        // that we're filtering.
        OnSearchHit(msgHdr, m_curFolder);
      }
    }
    m_curFilter->SetScope(nullptr);

    if (m_searchHits.Length() > 0)
    {
      m_nextAction = 0;
      rv = ApplyFilter();
      if (NS_SUCCEEDED(rv))
        return NS_OK; // async callback will continue, or we are done.
    }
  }
  m_curFilter = nullptr;
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to run filters");
  // We expect the failure is already recorded through one of the macro
  // expressions, that will have console logging added to them.
  // So an additional console warning is not needed here.
  return AdvanceToNextFolder();
}

NS_IMETHODIMP nsMsgFilterService::ApplyFilters(nsMsgFilterTypeType aFilterType,
                                               nsIArray *aMsgHdrList,
                                               nsIMsgFolder *aFolder,
                                               nsIMsgWindow *aMsgWindow,
                                               nsIMsgOperationListener *aCallback)
{
  NS_ENSURE_ARG_POINTER(aFolder);

  nsCOMPtr<nsIMsgFilterList>    filterList;
  nsresult rv = aFolder->GetFilterList(aMsgWindow, getter_AddRefs(filterList));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIMutableArray> folderList(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  folderList->AppendElement(aFolder, false);

  // Create our nsMsgApplyFiltersToMessages object which will be called when ApplyFiltersToHdr
  // finds one or more filters that hit.
  RefPtr<nsMsgApplyFiltersToMessages> filterExecutor =
    new nsMsgApplyFiltersToMessages(aMsgWindow, filterList, folderList,
                                    aMsgHdrList, aFilterType, aCallback);

  if (filterExecutor)
    return filterExecutor->AdvanceToNextFolder();

  return NS_ERROR_OUT_OF_MEMORY;
}

/* void OnStartCopy (); */
NS_IMETHODIMP nsMsgFilterAfterTheFact::OnStartCopy()
{
  return NS_OK;
}

/* void OnProgress (in uint32_t aProgress, in uint32_t aProgressMax); */
NS_IMETHODIMP nsMsgFilterAfterTheFact::OnProgress(uint32_t aProgress, uint32_t aProgressMax)
{
  return NS_OK;
}

/* void SetMessageKey (in uint32_t aKey); */
NS_IMETHODIMP nsMsgFilterAfterTheFact::SetMessageKey(nsMsgKey /* aKey */)
{
  return NS_OK;
}

NS_IMETHODIMP nsMsgFilterAfterTheFact::GetMessageId(nsACString& messageId)
{
  return NS_OK;
}

/* void OnStopCopy (in nsresult aStatus); */
NS_IMETHODIMP nsMsgFilterAfterTheFact::OnStopCopy(nsresult aStatus)
{
  if (NS_SUCCEEDED(aStatus))
    return ApplyFilter();

  mFinalResult = aStatus;
  if (m_msgWindow && !ContinueExecutionPrompt())
    return OnEndExecution();

  // Copy failed, so run the next filter
  return RunNextFilter();
}

bool nsMsgFilterAfterTheFact::ContinueExecutionPrompt()
{
  if (!m_curFilter)
    return false;
  nsCOMPtr<nsIStringBundle> bundle;
  nsCOMPtr<nsIStringBundleService> bundleService =
    mozilla::services::GetStringBundleService();
  if (!bundleService)
    return false;
  bundleService->CreateBundle("chrome://messenger/locale/filter.properties",
                              getter_AddRefs(bundle));
  if (!bundle)
    return false;
  nsString filterName;
  m_curFilter->GetFilterName(filterName);
  nsString formatString;
  nsString confirmText;
  const char16_t *formatStrings[] =
  {
    filterName.get()
  };
  nsresult rv = bundle->FormatStringFromName(u"continueFilterExecution",
                                             formatStrings, 1, getter_Copies(confirmText));
  if (NS_FAILED(rv))
    return false;
  bool returnVal = false;
  (void) DisplayConfirmationPrompt(m_msgWindow, confirmText.get(), &returnVal);
  return returnVal;
}

nsresult
nsMsgFilterAfterTheFact::DisplayConfirmationPrompt(nsIMsgWindow *msgWindow, const char16_t *confirmString, bool *confirmed)
{
  if (msgWindow)
  {
    nsCOMPtr <nsIDocShell> docShell;
    msgWindow->GetRootDocShell(getter_AddRefs(docShell));
    if (docShell)
    {
      nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
      if (dialog && confirmString)
        dialog->Confirm(nullptr, confirmString, confirmed);
    }
  }
  return NS_OK;
}
