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

#include "nsSyncRunnableHelpers.h"
#include "nsImapCore.h"
#include "nsIMsgMailNewsUrl.h"
#include "nsIMsgIncomingServer.h"
#include "nsIMsgWindow.h"
#include "nsIImapMailFolderSink.h"

#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Monitor.h"

NS_IMPL_ISUPPORTS(StreamListenerProxy, nsIStreamListener)
NS_IMPL_ISUPPORTS(ImapMailFolderSinkProxy, nsIImapMailFolderSink)
NS_IMPL_ISUPPORTS(ImapServerSinkProxy, nsIImapServerSink)
NS_IMPL_ISUPPORTS(ImapMessageSinkProxy, nsIImapMessageSink)
NS_IMPL_ISUPPORTS(ImapProtocolSinkProxy, nsIImapProtocolSink)
namespace {

// Traits class for a reference type, specialized for parameters which are
// already references.
template <typename T>
struct RefType {
  typedef T& type;
};

template <>
struct RefType<nsAString&> {
  typedef nsAString& type;
};

template <>
struct RefType<const nsAString&> {
  typedef const nsAString& type;
};

template <>
struct RefType<nsACString&> {
  typedef nsACString& type;
};

template <>
struct RefType<const nsACString&> {
  typedef const nsACString& type;
};

template <>
struct RefType<const nsIID&> {
  typedef const nsIID& type;
};

class SyncRunnableBase : public mozilla::Runnable {
 public:
  nsresult Result() { return mResult; }

  mozilla::Monitor& Monitor() { return mMonitor; }

 protected:
  SyncRunnableBase()
      : mozilla::Runnable("SyncRunnableBase"),
        mResult(NS_ERROR_UNEXPECTED),
        mMonitor("SyncRunnableBase") {}

  nsresult mResult;
  mozilla::Monitor mMonitor;
};

template <typename Receiver>
class SyncRunnable0 : public SyncRunnableBase {
 public:
  typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)();

  SyncRunnable0(Receiver* receiver, ReceiverMethod method)
      : mReceiver(receiver), mMethod(method) {}

  NS_IMETHOD Run() {
    mResult = (mReceiver->*mMethod)();
    mozilla::MonitorAutoLock(mMonitor).Notify();
    return NS_OK;
  }

 private:
  Receiver* mReceiver;
  ReceiverMethod mMethod;
};

template <typename Receiver, typename Arg1>
class SyncRunnable1 : public SyncRunnableBase {
 public:
  typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1);
  typedef typename RefType<Arg1>::type Arg1Ref;

  SyncRunnable1(Receiver* receiver, ReceiverMethod method, Arg1Ref arg1)
      : mReceiver(receiver), mMethod(method), mArg1(arg1) {}

  NS_IMETHOD Run() {
    mResult = (mReceiver->*mMethod)(mArg1);
    mozilla::MonitorAutoLock(mMonitor).Notify();
    return NS_OK;
  }

 private:
  Receiver* mReceiver;
  ReceiverMethod mMethod;
  Arg1Ref mArg1;
};

template <typename Receiver, typename Arg1, typename Arg2>
class SyncRunnable2 : public SyncRunnableBase {
 public:
  typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1, Arg2);
  typedef typename RefType<Arg1>::type Arg1Ref;
  typedef typename RefType<Arg2>::type Arg2Ref;

  SyncRunnable2(Receiver* receiver, ReceiverMethod method, Arg1Ref arg1,
                Arg2Ref arg2)
      : mReceiver(receiver), mMethod(method), mArg1(arg1), mArg2(arg2) {}

  NS_IMETHOD Run() {
    mResult = (mReceiver->*mMethod)(mArg1, mArg2);
    mozilla::MonitorAutoLock(mMonitor).Notify();
    return NS_OK;
  }

 private:
  Receiver* mReceiver;
  ReceiverMethod mMethod;
  Arg1Ref mArg1;
  Arg2Ref mArg2;
};

template <typename Receiver, typename Arg1, typename Arg2, typename Arg3>
class SyncRunnable3 : public SyncRunnableBase {
 public:
  typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1, Arg2, Arg3);
  typedef typename RefType<Arg1>::type Arg1Ref;
  typedef typename RefType<Arg2>::type Arg2Ref;
  typedef typename RefType<Arg3>::type Arg3Ref;

  SyncRunnable3(Receiver* receiver, ReceiverMethod method, Arg1Ref arg1,
                Arg2Ref arg2, Arg3Ref arg3)
      : mReceiver(receiver),
        mMethod(method),
        mArg1(arg1),
        mArg2(arg2),
        mArg3(arg3) {}

  NS_IMETHOD Run() {
    mResult = (mReceiver->*mMethod)(mArg1, mArg2, mArg3);
    mozilla::MonitorAutoLock(mMonitor).Notify();
    return NS_OK;
  }

 private:
  Receiver* mReceiver;
  ReceiverMethod mMethod;
  Arg1Ref mArg1;
  Arg2Ref mArg2;
  Arg3Ref mArg3;
};

template <typename Receiver, typename Arg1, typename Arg2, typename Arg3,
          typename Arg4>
class SyncRunnable4 : public SyncRunnableBase {
 public:
  typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1, Arg2, Arg3,
                                                          Arg4);
  typedef typename RefType<Arg1>::type Arg1Ref;
  typedef typename RefType<Arg2>::type Arg2Ref;
  typedef typename RefType<Arg3>::type Arg3Ref;
  typedef typename RefType<Arg4>::type Arg4Ref;

  SyncRunnable4(Receiver* receiver, ReceiverMethod method, Arg1Ref arg1,
                Arg2Ref arg2, Arg3Ref arg3, Arg4Ref arg4)
      : mReceiver(receiver),
        mMethod(method),
        mArg1(arg1),
        mArg2(arg2),
        mArg3(arg3),
        mArg4(arg4) {}

  NS_IMETHOD Run() {
    mResult = (mReceiver->*mMethod)(mArg1, mArg2, mArg3, mArg4);
    mozilla::MonitorAutoLock(mMonitor).Notify();
    return NS_OK;
  }

 private:
  Receiver* mReceiver;
  ReceiverMethod mMethod;
  Arg1Ref mArg1;
  Arg2Ref mArg2;
  Arg3Ref mArg3;
  Arg4Ref mArg4;
};

template <typename Receiver, typename Arg1, typename Arg2, typename Arg3,
          typename Arg4, typename Arg5>
class SyncRunnable5 : public SyncRunnableBase {
 public:
  typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1, Arg2, Arg3,
                                                          Arg4, Arg5);
  typedef typename RefType<Arg1>::type Arg1Ref;
  typedef typename RefType<Arg2>::type Arg2Ref;
  typedef typename RefType<Arg3>::type Arg3Ref;
  typedef typename RefType<Arg4>::type Arg4Ref;
  typedef typename RefType<Arg5>::type Arg5Ref;

  SyncRunnable5(Receiver* receiver, ReceiverMethod method, Arg1Ref arg1,
                Arg2Ref arg2, Arg3Ref arg3, Arg4Ref arg4, Arg5Ref arg5)
      : mReceiver(receiver),
        mMethod(method),
        mArg1(arg1),
        mArg2(arg2),
        mArg3(arg3),
        mArg4(arg4),
        mArg5(arg5) {}

  NS_IMETHOD Run() {
    mResult = (mReceiver->*mMethod)(mArg1, mArg2, mArg3, mArg4, mArg5);
    mozilla::MonitorAutoLock(mMonitor).Notify();
    return NS_OK;
  }

 private:
  Receiver* mReceiver;
  ReceiverMethod mMethod;
  Arg1Ref mArg1;
  Arg2Ref mArg2;
  Arg3Ref mArg3;
  Arg4Ref mArg4;
  Arg5Ref mArg5;
};

nsresult DispatchSyncRunnable(SyncRunnableBase* r) {
  if (NS_IsMainThread()) {
    r->Run();
  } else {
    mozilla::MonitorAutoLock lock(r->Monitor());
    nsresult rv = NS_DispatchToMainThread(r);
    if (NS_FAILED(rv)) return rv;
    lock.Wait();
  }
  return r->Result();
}

}  // anonymous namespace

#define NS_SYNCRUNNABLEMETHOD0(iface, method)                          \
  NS_IMETHODIMP iface##Proxy::method() {                               \
    RefPtr<SyncRunnableBase> r =                                       \
        new SyncRunnable0<nsI##iface>(mReceiver, &nsI##iface::method); \
    return DispatchSyncRunnable(r);                                    \
  }

#define NS_SYNCRUNNABLEMETHOD1(iface, method, arg1)                   \
  NS_IMETHODIMP iface##Proxy::method(arg1 a1) {                       \
    RefPtr<SyncRunnableBase> r = new SyncRunnable1<nsI##iface, arg1>( \
        mReceiver, &nsI##iface::method, a1);                          \
    return DispatchSyncRunnable(r);                                   \
  }

#define NS_SYNCRUNNABLEMETHOD2(iface, method, arg1, arg2)                   \
  NS_IMETHODIMP iface##Proxy::method(arg1 a1, arg2 a2) {                    \
    RefPtr<SyncRunnableBase> r = new SyncRunnable2<nsI##iface, arg1, arg2>( \
        mReceiver, &nsI##iface::method, a1, a2);                            \
    return DispatchSyncRunnable(r);                                         \
  }

#define NS_SYNCRUNNABLEMETHOD3(iface, method, arg1, arg2, arg3)   \
  NS_IMETHODIMP iface##Proxy::method(arg1 a1, arg2 a2, arg3 a3) { \
    RefPtr<SyncRunnableBase> r =                                  \
        new SyncRunnable3<nsI##iface, arg1, arg2, arg3>(          \
            mReceiver, &nsI##iface::method, a1, a2, a3);          \
    return DispatchSyncRunnable(r);                               \
  }

#define NS_SYNCRUNNABLEMETHOD4(iface, method, arg1, arg2, arg3, arg4)      \
  NS_IMETHODIMP iface##Proxy::method(arg1 a1, arg2 a2, arg3 a3, arg4 a4) { \
    RefPtr<SyncRunnableBase> r =                                           \
        new SyncRunnable4<nsI##iface, arg1, arg2, arg3, arg4>(             \
            mReceiver, &nsI##iface::method, a1, a2, a3, a4);               \
    return DispatchSyncRunnable(r);                                        \
  }

#define NS_SYNCRUNNABLEMETHOD5(iface, method, arg1, arg2, arg3, arg4, arg5) \
  NS_IMETHODIMP iface##Proxy::method(arg1 a1, arg2 a2, arg3 a3, arg4 a4,    \
                                     arg5 a5) {                             \
    RefPtr<SyncRunnableBase> r =                                            \
        new SyncRunnable5<nsI##iface, arg1, arg2, arg3, arg4, arg5>(        \
            mReceiver, &nsI##iface::method, a1, a2, a3, a4, a5);            \
    return DispatchSyncRunnable(r);                                         \
  }

#define NS_SYNCRUNNABLEATTRIBUTE(iface, attribute, type)               \
  NS_IMETHODIMP iface##Proxy::Get##attribute(type* a1) {               \
    RefPtr<SyncRunnableBase> r = new SyncRunnable1<nsI##iface, type*>( \
        mReceiver, &nsI##iface::Get##attribute, a1);                   \
    return DispatchSyncRunnable(r);                                    \
  }                                                                    \
  NS_IMETHODIMP iface##Proxy::Set##attribute(type a1) {                \
    RefPtr<SyncRunnableBase> r = new SyncRunnable1<nsI##iface, type>(  \
        mReceiver, &nsI##iface::Set##attribute, a1);                   \
    return DispatchSyncRunnable(r);                                    \
  }

#define NS_NOTIMPLEMENTED               \
  {                                     \
    NS_RUNTIMEABORT("Not implemented"); \
    return NS_ERROR_UNEXPECTED;         \
  }

NS_SYNCRUNNABLEMETHOD4(StreamListener, OnDataAvailable, nsIRequest*,
                       nsIInputStream*, uint64_t, uint32_t)

NS_SYNCRUNNABLEMETHOD1(StreamListener, OnStartRequest, nsIRequest*)

NS_SYNCRUNNABLEMETHOD2(StreamListener, OnStopRequest, nsIRequest*, nsresult)

NS_SYNCRUNNABLEMETHOD2(ImapProtocolSink, GetUrlWindow, nsIMsgMailNewsUrl*,
                       nsIMsgWindow**)

NS_SYNCRUNNABLEMETHOD0(ImapProtocolSink, CloseStreams)
NS_SYNCRUNNABLEMETHOD0(ImapProtocolSink, SetupMainThreadProxies)

NS_SYNCRUNNABLEATTRIBUTE(ImapMailFolderSink, FolderNeedsACLListed, bool)
NS_SYNCRUNNABLEATTRIBUTE(ImapMailFolderSink, FolderNeedsSubscribing, bool)
NS_SYNCRUNNABLEATTRIBUTE(ImapMailFolderSink, FolderNeedsAdded, bool)
NS_SYNCRUNNABLEATTRIBUTE(ImapMailFolderSink, AclFlags, uint32_t)
NS_SYNCRUNNABLEATTRIBUTE(ImapMailFolderSink, UidValidity, int32_t)
NS_SYNCRUNNABLEATTRIBUTE(ImapMailFolderSink, FolderQuotaCommandIssued, bool)
NS_SYNCRUNNABLEMETHOD4(ImapMailFolderSink, SetFolderQuotaData, uint32_t,
                       const nsACString&, uint64_t, uint64_t)
NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, GetShouldDownloadAllHeaders, bool*)
NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, GetOnlineDelimiter, char*)
NS_SYNCRUNNABLEMETHOD0(ImapMailFolderSink, OnNewIdleMessages)
NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, UpdateImapMailboxStatus,
                       nsIImapProtocol*, nsIMailboxSpec*)
NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, UpdateImapMailboxInfo,
                       nsIImapProtocol*, nsIMailboxSpec*)
NS_SYNCRUNNABLEMETHOD3(ImapMailFolderSink, GetMsgHdrsToDownload, bool*,
                       int32_t*, nsTArray<nsMsgKey>&)
NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, ParseMsgHdrs, nsIImapProtocol*,
                       nsIImapHeaderXferInfo*)
NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, AbortHeaderParseStream,
                       nsIImapProtocol*)
NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, OnlineCopyCompleted,
                       nsIImapProtocol*, ImapOnlineCopyState)
NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, StartMessage, nsIMsgMailNewsUrl*)
NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, EndMessage, nsIMsgMailNewsUrl*,
                       nsMsgKey)
NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, NotifySearchHit, nsIMsgMailNewsUrl*,
                       const char*)
NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, CopyNextStreamMessage, bool,
                       nsISupports*)
NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, CloseMockChannel,
                       nsIImapMockChannel*)
NS_SYNCRUNNABLEMETHOD5(ImapMailFolderSink, SetUrlState, nsIImapProtocol*,
                       nsIMsgMailNewsUrl*, bool, bool, nsresult)
NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, ReleaseUrlCacheEntry,
                       nsIMsgMailNewsUrl*)
NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, HeaderFetchCompleted,
                       nsIImapProtocol*)
NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, SetBiffStateAndUpdate, int32_t)
NS_SYNCRUNNABLEMETHOD3(ImapMailFolderSink, ProgressStatusString,
                       nsIImapProtocol*, const char*, const char16_t*)
NS_SYNCRUNNABLEMETHOD5(ImapMailFolderSink, PercentProgress, nsIImapProtocol*,
                       nsACString const&, nsAString const&, int64_t, int64_t)
NS_SYNCRUNNABLEMETHOD0(ImapMailFolderSink, ClearFolderRights)
NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, SetCopyResponseUid, const char*,
                       nsIImapUrl*)
NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, SetAppendMsgUid, nsMsgKey,
                       nsIImapUrl*)
NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, GetMessageId, nsIImapUrl*,
                       nsACString&)

NS_SYNCRUNNABLEMETHOD2(ImapMessageSink, SetupMsgWriteStream, nsIFile*, bool)
NS_SYNCRUNNABLEMETHOD3(ImapMessageSink, ParseAdoptedMsgLine, const char*,
                       nsMsgKey, nsIImapUrl*)
NS_SYNCRUNNABLEMETHOD4(ImapMessageSink, NormalEndMsgWriteStream, nsMsgKey, bool,
                       nsIImapUrl*, int32_t)
NS_SYNCRUNNABLEMETHOD0(ImapMessageSink, AbortMsgWriteStream)
NS_SYNCRUNNABLEMETHOD0(ImapMessageSink, BeginMessageUpload)
NS_SYNCRUNNABLEMETHOD4(ImapMessageSink, NotifyMessageFlags, uint32_t,
                       const nsACString&, nsMsgKey, uint64_t)
NS_SYNCRUNNABLEMETHOD3(ImapMessageSink, NotifyMessageDeleted, const char*, bool,
                       const char*)
NS_SYNCRUNNABLEMETHOD2(ImapMessageSink, GetMessageSizeFromDB, const char*,
                       uint32_t*)
NS_SYNCRUNNABLEMETHOD4(ImapMessageSink, GetCurMoveCopyMessageInfo, nsIImapUrl*,
                       PRTime*, nsACString&, uint32_t*)

NS_SYNCRUNNABLEMETHOD4(ImapServerSink, PossibleImapMailbox, const nsACString&,
                       char, int32_t, bool*)
NS_SYNCRUNNABLEMETHOD2(ImapServerSink, FolderNeedsACLInitialized,
                       const nsACString&, bool*)
NS_SYNCRUNNABLEMETHOD3(ImapServerSink, AddFolderRights, const nsACString&,
                       const nsACString&, const nsACString&)
NS_SYNCRUNNABLEMETHOD1(ImapServerSink, RefreshFolderRights, const nsACString&)
NS_SYNCRUNNABLEMETHOD0(ImapServerSink, DiscoveryDone)
NS_SYNCRUNNABLEMETHOD1(ImapServerSink, OnlineFolderDelete, const nsACString&)
NS_SYNCRUNNABLEMETHOD1(ImapServerSink, OnlineFolderCreateFailed,
                       const nsACString&)
NS_SYNCRUNNABLEMETHOD3(ImapServerSink, OnlineFolderRename, nsIMsgWindow*,
                       const nsACString&, const nsACString&)
NS_SYNCRUNNABLEMETHOD2(ImapServerSink, FolderIsNoSelect, const nsACString&,
                       bool*)
NS_SYNCRUNNABLEMETHOD2(ImapServerSink, SetFolderAdminURL, const nsACString&,
                       const nsACString&)
NS_SYNCRUNNABLEMETHOD2(ImapServerSink, FolderVerifiedOnline, const nsACString&,
                       bool*)
NS_SYNCRUNNABLEMETHOD1(ImapServerSink, SetCapability, eIMAPCapabilityFlags)
NS_SYNCRUNNABLEMETHOD1(ImapServerSink, SetServerID, const nsACString&)
NS_SYNCRUNNABLEMETHOD2(ImapServerSink, LoadNextQueuedUrl, nsIImapProtocol*,
                       bool*)
NS_SYNCRUNNABLEMETHOD2(ImapServerSink, PrepareToRetryUrl, nsIImapUrl*,
                       nsIImapMockChannel**)
NS_SYNCRUNNABLEMETHOD1(ImapServerSink, SuspendUrl, nsIImapUrl*)
NS_SYNCRUNNABLEMETHOD2(ImapServerSink, RetryUrl, nsIImapUrl*,
                       nsIImapMockChannel*)
NS_SYNCRUNNABLEMETHOD0(ImapServerSink, AbortQueuedUrls)
NS_SYNCRUNNABLEMETHOD2(ImapServerSink, GetImapStringByName, const char*,
                       nsAString&)
NS_SYNCRUNNABLEMETHOD2(ImapServerSink, PromptLoginFailed, nsIMsgWindow*,
                       int32_t*)
NS_SYNCRUNNABLEMETHOD2(ImapServerSink, FEAlert, const nsAString&,
                       nsIMsgMailNewsUrl*)
NS_SYNCRUNNABLEMETHOD2(ImapServerSink, FEAlertWithName, const char*,
                       nsIMsgMailNewsUrl*)
NS_SYNCRUNNABLEMETHOD2(ImapServerSink, FEAlertFromServer, const nsACString&,
                       nsIMsgMailNewsUrl*)
NS_SYNCRUNNABLEMETHOD0(ImapServerSink, CommitNamespaces)
NS_SYNCRUNNABLEMETHOD3(ImapServerSink, AsyncGetPassword, nsIImapProtocol*, bool,
                       nsAString&)
NS_SYNCRUNNABLEMETHOD1(ImapServerSink, SyncGetPassword, nsAString&)
NS_SYNCRUNNABLEATTRIBUTE(ImapServerSink, UserAuthenticated, bool)
NS_SYNCRUNNABLEMETHOD3(ImapServerSink, SetMailServerUrls, const nsACString&,
                       const nsACString&, const nsACString&)
NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetArbitraryHeaders, nsACString&)
NS_SYNCRUNNABLEMETHOD0(ImapServerSink, ForgetPassword)
NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetShowAttachmentsInline, bool*)
NS_SYNCRUNNABLEMETHOD3(ImapServerSink, CramMD5Hash, const char*, const char*,
                       char**)
NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetLoginUsername, nsACString&)
NS_SYNCRUNNABLEMETHOD1(ImapServerSink, UpdateTrySTARTTLSPref, bool)
NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetOriginalUsername, nsACString&)
NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetServerKey, nsACString&)
NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetServerPassword, nsAString&)
NS_SYNCRUNNABLEMETHOD1(ImapServerSink, RemoveServerConnection, nsIImapProtocol*)
NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetServerShuttingDown, bool*)
NS_SYNCRUNNABLEMETHOD1(ImapServerSink, ResetServerConnection, const nsACString&)
NS_SYNCRUNNABLEMETHOD1(ImapServerSink, SetServerDoingLsub, bool)
NS_SYNCRUNNABLEMETHOD1(ImapServerSink, SetServerUtf8AcceptEnabled, bool)

namespace mozilla {
namespace mailnews {

NS_IMPL_ISUPPORTS(OAuth2ThreadHelper, msgIOAuth2ModuleListener)

OAuth2ThreadHelper::OAuth2ThreadHelper(nsIMsgIncomingServer* aServer)
    : mMonitor("OAuth thread lock"), mServer(aServer) {}

OAuth2ThreadHelper::~OAuth2ThreadHelper() {
  if (mOAuth2Support) {
    NS_ReleaseOnMainThread("OAuth2ThreadHelper::mOAuth2Support",
                           mOAuth2Support.forget());
  }
}

bool OAuth2ThreadHelper::SupportsOAuth2() {
  // Acquire a lock early, before reading anything. Guarantees memory visibility
  // issues.
  MonitorAutoLock lockGuard(mMonitor);

  // If we don't have a server, we can't init, and therefore, we don't support
  // OAuth2.
  if (!mServer) return false;

  // If we have this, then we support OAuth2.
  if (mOAuth2Support) return true;

  // Initialize. This needs to be done on-main-thread: if we're off that thread,
  // synchronously dispatch to the main thread.
  if (NS_IsMainThread()) {
    MonitorAutoUnlock lockGuard(mMonitor);
    Init();
  } else {
    nsCOMPtr<nsIRunnable> runInit = NewRunnableMethod(
        "OAuth2ThreadHelper::SupportsOAuth2", this, &OAuth2ThreadHelper::Init);
    nsresult rv = NS_DispatchToMainThread(runInit);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return false;
    }
    mMonitor.Wait();
  }

  // After synchronously initializing, if we didn't get an object, then we don't
  // support XOAuth2.
  return mOAuth2Support != nullptr;
}

void OAuth2ThreadHelper::GetXOAuth2String(nsACString& base64Str) {
  MOZ_ASSERT(!NS_IsMainThread(), "This method cannot run on the main thread");

  // Acquire a lock early, before reading anything. Guarantees memory visibility
  // issues.
  MonitorAutoLock lockGuard(mMonitor);

  // Umm... what are you trying to do?
  if (!mOAuth2Support) return;

  nsCOMPtr<nsIRunnable> runInit =
      NewRunnableMethod("OAuth2ThreadHelper::GetXOAuth2String", this,
                        &OAuth2ThreadHelper::Connect);
  nsresult rv = NS_DispatchToMainThread(runInit);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }
  mMonitor.Wait();

  nsCOMPtr<nsIRunnable> shutdownNotify = NS_NewRunnableFunction(
      "GetXOAuth2StringShutdownNotifier", [self = RefPtr(this)]() {
        mozilla::RunOnShutdown([self]() {
          // Notify anyone waiting that we're done.
          self->mMonitor.Notify();
        });
      });
  rv = NS_DispatchToMainThread(shutdownNotify.forget());
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }

  // Now we either have the string, or we failed (in which case the string is
  // empty).
  base64Str = mOAuth2String;
}

void OAuth2ThreadHelper::Init() {
  MOZ_ASSERT(NS_IsMainThread(), "Can't touch JS off-main-thread");
  MonitorAutoLock lockGuard(mMonitor);

  // Create the OAuth2 helper module and initialize it. If the preferences are
  // not set up on this server, we don't support OAuth2, and we nullify our
  // members to indicate this.
  mOAuth2Support = do_CreateInstance(MSGIOAUTH2MODULE_CONTRACTID);
  if (mOAuth2Support) {
    bool supportsOAuth = false;
    mOAuth2Support->InitFromMail(mServer, &supportsOAuth);
    if (!supportsOAuth) mOAuth2Support = nullptr;
  }

  // There is now no longer any need for the server. Kill it now--this helps
  // prevent us from maintaining a refcount cycle.
  mServer = nullptr;

  // Notify anyone waiting that we're done.
  mMonitor.Notify();
}

void OAuth2ThreadHelper::Connect() {
  MOZ_ASSERT(NS_IsMainThread(), "Can't touch JS off-main-thread");
  MOZ_ASSERT(mOAuth2Support, "Should not be here if no OAuth2 support");

  // OK to delay lock since mOAuth2Support is only written on main thread.
  nsresult rv = mOAuth2Support->Connect(true, this);
  // If the method failed, we'll never get a callback, so notify the monitor
  // immediately so that IMAP can react.
  if (NS_FAILED(rv)) {
    MonitorAutoLock lockGuard(mMonitor);
    mMonitor.Notify();
  }
}

nsresult OAuth2ThreadHelper::OnSuccess(const nsACString& aOAuth2String) {
  MOZ_ASSERT(NS_IsMainThread(), "Can't touch JS off-main-thread");
  MonitorAutoLock lockGuard(mMonitor);

  MOZ_ASSERT(mOAuth2Support, "Should not be here if no OAuth2 support");
  mOAuth2String = aOAuth2String;
  mMonitor.Notify();
  return NS_OK;
}

nsresult OAuth2ThreadHelper::OnFailure(nsresult aError) {
  MOZ_ASSERT(NS_IsMainThread(), "Can't touch JS off-main-thread");
  MonitorAutoLock lockGuard(mMonitor);

  mOAuth2String.Truncate();
  mMonitor.Notify();
  return NS_OK;
}

}  // namespace mailnews
}  // namespace mozilla
