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

#include <windows.h>
#include <shellapi.h>

#include "nsMessengerWinIntegration.h"
#include "nsIMsgAccountManager.h"
#include "nsIMsgMailSession.h"
#include "nsIMsgIncomingServer.h"
#include "nsIMsgIdentity.h"
#include "nsIMsgAccount.h"
#include "nsIMsgFolder.h"
#include "nsIMsgWindow.h"
#include "nsCOMPtr.h"
#include "nsMsgBaseCID.h"
#include "nsMsgFolderFlags.h"
#include "nsDirectoryServiceDefs.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsIDirectoryService.h"
#include "nsIWindowWatcher.h"
#include "nsIWindowMediator.h"
#include "mozIDOMWindow.h"
#include "nsPIDOMWindow.h"
#include "nsIDocShell.h"
#include "nsIBaseWindow.h"
#include "nsIWidget.h"
#include "nsWidgetsCID.h"
#include "MailNewsTypes.h"
#include "nsIMessengerWindowService.h"
#include "prprf.h"
#include "nsIWeakReference.h"
#include "nsIStringBundle.h"
#include "nsIAlertsService.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsIProperties.h"
#include "nsISupportsPrimitives.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIWeakReferenceUtils.h"
#include "nsComponentManagerUtils.h"
#include "nsNativeCharsetUtils.h"
#include "nsMsgUtils.h"
#ifdef MOZILLA_INTERNAL_API
#include "mozilla/LookAndFeel.h"
#endif
#include "mozilla/Services.h"
#include "nsIMutableArray.h"
#include "nsArrayUtils.h"

#include "nsToolkitCompsCID.h"
#include <stdlib.h>
#define PROFILE_COMMANDLINE_ARG " -profile "

#define NOTIFICATIONCLASSNAME "MailBiffNotificationMessageWindow"
#define UNREADMAILNODEKEY "Software\\Microsoft\\Windows\\CurrentVersion\\UnreadMail\\"
#define DOUBLE_QUOTE "\""
#define MAIL_COMMANDLINE_ARG " -mail"
#define IDI_MAILBIFF 32576
#define UNREAD_UPDATE_INTERVAL	(20 * 1000)	// 20 seconds
#define ALERT_CHROME_URL "chrome://messenger/content/newmailalert.xul"
#define NEW_MAIL_ALERT_ICON "chrome://messenger/skin/icons/new-mail-alert.png"
#define SHOW_ALERT_PREF     "mail.biff.show_alert"
#define SHOW_TRAY_ICON_PREF "mail.biff.show_tray_icon"
#define SHOW_BALLOON_PREF   "mail.biff.show_balloon"
#define SHOW_NEW_ALERT_PREF "mail.biff.show_new_alert"
#define ALERT_ORIGIN_PREF   "ui.alertNotificationOrigin"

// since we are including windows.h in this file, undefine get user name....
#ifdef GetUserName
#undef GetUserName
#endif

#ifndef NIIF_USER
#define NIIF_USER       0x00000004
#endif

#ifndef NIIF_NOSOUND
#define NIIF_NOSOUND    0x00000010
#endif

#ifndef NIN_BALOONUSERCLICK
#define NIN_BALLOONUSERCLICK (WM_USER + 5)
#endif

#ifndef MOZILLA_INTERNAL_API
// from LookAndFeel.h
#define NS_ALERT_HORIZONTAL 1
#define NS_ALERT_LEFT       2
#define NS_ALERT_TOP        4
#endif

using namespace mozilla;

// begin shameless copying from nsNativeAppSupportWin
HWND hwndForDOMWindow( mozIDOMWindowProxy *window )
{
  if ( !window ) {
    return 0;
  }
  nsCOMPtr<nsPIDOMWindowOuter> pidomwindow = nsPIDOMWindowOuter::From(window);

  nsCOMPtr<nsIBaseWindow> ppBaseWindow =
    do_QueryInterface( pidomwindow->GetDocShell() );
  if (!ppBaseWindow)
    return 0;

  nsCOMPtr<nsIWidget> ppWidget;
  ppBaseWindow->GetMainWidget( getter_AddRefs( ppWidget ) );

  return (HWND)( ppWidget->GetNativeData( NS_NATIVE_WIDGET ) );
}

static void activateWindow( mozIDOMWindowProxy *win )
{
  // Try to get native window handle.
  HWND hwnd = hwndForDOMWindow( win );
  if ( hwnd )
  {
    // Restore the window if it is minimized.
    if ( ::IsIconic( hwnd ) )
      ::ShowWindow( hwnd, SW_RESTORE );
    // Use the OS call, if possible.
    ::SetForegroundWindow( hwnd );
  } else {
    // Use internal method.
    nsCOMPtr<nsPIDOMWindowOuter> privateWindow = nsPIDOMWindowOuter::From(win);
    privateWindow->Focus();
  }
}
// end shameless copying from nsNativeAppWinSupport.cpp

static void openMailWindow(const nsACString& aFolderUri)
{
  nsresult rv;
  nsCOMPtr<nsIMsgMailSession> mailSession ( do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv));
  if (NS_FAILED(rv))
    return;

  nsCOMPtr<nsIMsgWindow> topMostMsgWindow;
  rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(topMostMsgWindow));
  if (topMostMsgWindow)
  {
    nsCOMPtr<mozIDOMWindowProxy> domWindow;
    topMostMsgWindow->GetDomWindow(getter_AddRefs(domWindow));
    if (domWindow)
    {
      if (!aFolderUri.IsEmpty())
      {
        nsCOMPtr<nsIMsgWindowCommands> windowCommands;
        topMostMsgWindow->GetWindowCommands(getter_AddRefs(windowCommands));
        if (windowCommands)
          windowCommands->SelectFolder(aFolderUri);
      }
      activateWindow(domWindow);
      return;
    }
  }

  {
    // the user doesn't have a mail window open already so open one for them...
    nsCOMPtr<nsIMessengerWindowService> messengerWindowService =
      do_GetService(NS_MESSENGERWINDOWSERVICE_CONTRACTID);
    // if we want to preselect the first account with new mail,
    // here is where we would try to generate a uri to pass in
    // (and add code to the messenger window service to make that work)
    if (messengerWindowService)
      messengerWindowService->OpenMessengerWindowWithUri(
                                "mail:3pane", nsCString(aFolderUri).get(), nsMsgKey_None);
  }
}

static void CALLBACK delayedSingleClick(HWND msgWindow, UINT msg, INT_PTR idEvent, DWORD dwTime)
{
  ::KillTimer(msgWindow, idEvent);

  // single clicks on the biff icon should re-open the alert notification
  nsresult rv = NS_OK;
  nsCOMPtr<nsIMessengerOSIntegration> integrationService =
    do_GetService(NS_MESSENGEROSINTEGRATION_CONTRACTID, &rv);
  if (NS_SUCCEEDED(rv))
  {
    // we know we are dealing with the windows integration object
    nsMessengerWinIntegration * winIntegrationService = static_cast<nsMessengerWinIntegration*>
                                                                   (static_cast<nsIMessengerOSIntegration*>(integrationService.get()));
    winIntegrationService->ShowNewAlertNotification(true, EmptyString(), EmptyString());
  }
}

// Window proc.
static LRESULT CALLBACK MessageWindowProc( HWND msgWindow, UINT msg, WPARAM wp, LPARAM lp )
{
  if (msg == WM_USER)
  {
    if (lp == WM_LBUTTONDOWN)
    {
      // the only way to tell a single left click
      // from a double left click is to fire a timer which gets cleared if we end up getting
      // a WM_LBUTTONDBLK event.
      ::SetTimer(msgWindow, 1, GetDoubleClickTime(), (TIMERPROC) delayedSingleClick);
    }
    else if (lp == WM_LBUTTONDBLCLK || lp == NIN_BALLOONUSERCLICK)
    {
      ::KillTimer(msgWindow, 1);
      openMailWindow(EmptyCString());
    }
  }

  return TRUE;
}

static HWND msgWindow;

// Create: Register class and create window.
static nsresult Create()
{
  if (msgWindow)
    return NS_OK;

  WNDCLASS classStruct = { 0,                          // style
                           &MessageWindowProc,         // lpfnWndProc
                           0,                          // cbClsExtra
                           0,                          // cbWndExtra
                           0,                          // hInstance
                           0,                          // hIcon
                           0,                          // hCursor
                           0,                          // hbrBackground
                           0,                          // lpszMenuName
                           NOTIFICATIONCLASSNAME };    // lpszClassName

  // Register the window class.
  NS_ENSURE_TRUE( ::RegisterClass( &classStruct ), NS_ERROR_FAILURE );
  // Create the window.
  NS_ENSURE_TRUE( msgWindow = ::CreateWindow( NOTIFICATIONCLASSNAME,
                                              0,          // title
                                              WS_CAPTION, // style
                                              0,0,0,0,    // x, y, cx, cy
                                              0,          // parent
                                              0,          // menu
                                              0,          // instance
                                              0 ),        // create struct
                  NS_ERROR_FAILURE );
  return NS_OK;
}


nsMessengerWinIntegration::nsMessengerWinIntegration()
{
  mDefaultServerAtom = MsgGetAtom("DefaultServer");
  mTotalUnreadMessagesAtom = MsgGetAtom("TotalUnreadMessages");

  mUnreadTimerActive = false;

  mBiffStateAtom = MsgGetAtom("BiffState");
  mBiffIconVisible = false;
  mSuppressBiffIcon = false;
  mAlertInProgress = false;
  mBiffIconInitialized = false;
  mFoldersWithNewMail = do_CreateInstance(NS_ARRAY_CONTRACTID);
}

nsMessengerWinIntegration::~nsMessengerWinIntegration()
{
  if (mUnreadCountUpdateTimer) {
    mUnreadCountUpdateTimer->Cancel();
    mUnreadCountUpdateTimer = nullptr;
  }

  // one last attempt, update the registry
  nsresult rv = UpdateRegistryWithCurrent();
  NS_ASSERTION(NS_SUCCEEDED(rv), "failed to update registry on shutdown");
  DestroyBiffIcon();
}

NS_IMPL_ADDREF(nsMessengerWinIntegration)
NS_IMPL_RELEASE(nsMessengerWinIntegration)

NS_INTERFACE_MAP_BEGIN(nsMessengerWinIntegration)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMessengerOSIntegration)
   NS_INTERFACE_MAP_ENTRY(nsIMessengerOSIntegration)
   NS_INTERFACE_MAP_ENTRY(nsIFolderListener)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_END


nsresult
nsMessengerWinIntegration::ResetCurrent()
{
  mInboxURI.Truncate();
  mEmail.Truncate();

  mCurrentUnreadCount = -1;
  mLastUnreadCountWrittenToRegistry = -1;

  mDefaultAccountMightHaveAnInbox = true;
  return NS_OK;
}

NOTIFYICONDATAW sBiffIconData = { NOTIFYICONDATAW_V2_SIZE,
                                  0,
                                  2,
                                  NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO,
                                  WM_USER,
                                  0,
                                  L"",
                                  0,
                                  0,
                                  L"",
                                  30000,
                                  L"",
                                  NIIF_USER | NIIF_NOSOUND };
// allow for the null terminator
static const uint32_t kMaxTooltipSize = sizeof(sBiffIconData.szTip) /
                                        sizeof(sBiffIconData.szTip[0]) - 1;
static const uint32_t kMaxBalloonSize = sizeof(sBiffIconData.szInfo) /
                                        sizeof(sBiffIconData.szInfo[0]) - 1;
static const uint32_t kMaxBalloonTitle = sizeof(sBiffIconData.szInfoTitle) /
                                         sizeof(sBiffIconData.szInfoTitle[0]) - 1;

void nsMessengerWinIntegration::InitializeBiffStatusIcon()
{
  // initialize our biff status bar icon
  Create();

  sBiffIconData.hWnd = (HWND) msgWindow;
  sBiffIconData.hIcon = ::LoadIcon( ::GetModuleHandle( NULL ), MAKEINTRESOURCE(IDI_MAILBIFF) );

  mBiffIconInitialized = true;
}

nsresult
nsMessengerWinIntegration::Init()
{
  nsresult rv;

  nsCOMPtr <nsIMsgAccountManager> accountManager =
    do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv,rv);

  // because we care if the default server changes
  rv = accountManager->AddRootFolderListener(this);
  NS_ENSURE_SUCCESS(rv,rv);

  nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv,rv);

  // because we care if the unread total count changes
  rv = mailSession->AddFolderListener(this, nsIFolderListener::boolPropertyChanged | nsIFolderListener::intPropertyChanged);
  NS_ENSURE_SUCCESS(rv,rv);

  // get current profile path for the commandliner
  nsCOMPtr<nsIProperties> directoryService =
    do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv,rv);

  nsCOMPtr<nsIFile> profilePath;
  rv = directoryService->Get(NS_APP_USER_PROFILE_50_DIR,
                             NS_GET_IID(nsIFile),
                             getter_AddRefs(profilePath));
  NS_ENSURE_SUCCESS(rv,rv);

  rv = profilePath->GetPath(mProfilePath);
  NS_ENSURE_SUCCESS(rv, rv);

  // get application path
  WCHAR appPath[_MAX_PATH] = {0};
  ::GetModuleFileNameW(nullptr, appPath, sizeof(appPath));
  mAppName.Assign((char16_t *)appPath);

  rv = ResetCurrent();
  NS_ENSURE_SUCCESS(rv,rv);

  return NS_OK;
}

NS_IMETHODIMP
nsMessengerWinIntegration::OnItemPropertyChanged(nsIMsgFolder *, nsIAtom *, char const *, char const *)
{
  return NS_OK;
}

NS_IMETHODIMP
nsMessengerWinIntegration::OnItemUnicharPropertyChanged(nsIMsgFolder *, nsIAtom *, const char16_t *, const char16_t *)
{
  return NS_OK;
}

NS_IMETHODIMP
nsMessengerWinIntegration::OnItemRemoved(nsIMsgFolder *, nsISupports *)
{
  return NS_OK;
}

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

#ifndef MOZ_THUNDERBIRD
nsresult nsMessengerWinIntegration::ShowAlertMessage(const nsString& aAlertTitle,
                                                     const nsString& aAlertText,
                                                     const nsACString& aFolderURI)
{
  nsresult rv;

  // if we are already in the process of showing an alert, don't try to show another....
  if (mAlertInProgress)
    return NS_OK;

  nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  bool showBalloon = false;
  prefBranch->GetBoolPref(SHOW_BALLOON_PREF, &showBalloon);
  sBiffIconData.szInfo[0] = '\0';
  if (showBalloon) {
    ::wcsncpy( sBiffIconData.szInfoTitle, aAlertTitle.get(), kMaxBalloonTitle);
    ::wcsncpy( sBiffIconData.szInfo, aAlertText.get(), kMaxBalloonSize);
  }

  bool showAlert = true;
  prefBranch->GetBoolPref(SHOW_ALERT_PREF, &showAlert);

  if (showAlert)
  {
    nsCOMPtr<nsIAlertsService> alertsService (do_GetService(NS_ALERTSERVICE_CONTRACTID, &rv));
    if (NS_SUCCEEDED(rv))
    {
      rv = alertsService->ShowAlertNotification(NS_LITERAL_STRING(NEW_MAIL_ALERT_ICON), aAlertTitle,
                                                aAlertText, true,
                                                NS_ConvertASCIItoUTF16(aFolderURI), this,
                                                EmptyString(),
                                                NS_LITERAL_STRING("auto"),
                                                EmptyString(), EmptyString(),
                                                nullptr,
                                                false,
                                                false);
      mAlertInProgress = true;
    }
  }

  if (!showAlert || NS_FAILED(rv)) // go straight to showing the system tray icon.
    AlertFinished();

  return rv;
}
#endif
// Opening Thunderbird's new mail alert notification window
// aUserInitiated --> true if we are opening the alert notification in response to a user action
//                    like clicking on the biff icon
nsresult nsMessengerWinIntegration::ShowNewAlertNotification(bool aUserInitiated, const nsString& aAlertTitle, const nsString& aAlertText)
{
  nsresult rv;

  // if we are already in the process of showing an alert, don't try to show another....
  if (mAlertInProgress)
    return NS_OK;

  nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  bool showBalloon = false;
  prefBranch->GetBoolPref(SHOW_BALLOON_PREF, &showBalloon);
  sBiffIconData.szInfo[0] = '\0';
  if (showBalloon) {
    ::wcsncpy( sBiffIconData.szInfoTitle, aAlertTitle.get(), kMaxBalloonTitle);
    ::wcsncpy( sBiffIconData.szInfo, aAlertText.get(), kMaxBalloonSize);
  }

  bool showAlert = true;

  if (prefBranch)
    prefBranch->GetBoolPref(SHOW_ALERT_PREF, &showAlert);

  // check if we are allowed to show a notification
  if (showAlert) {
    QUERY_USER_NOTIFICATION_STATE qstate;

    if (SUCCEEDED(SHQueryUserNotificationState(&qstate))) {
      if (qstate != QUNS_ACCEPTS_NOTIFICATIONS) {
        showAlert = false;
      }
    }
  }

  if (showAlert)
  {
    nsCOMPtr<nsIMutableArray> argsArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
    NS_ENSURE_SUCCESS(rv, rv);

    // pass in the array of folders with unread messages
    nsCOMPtr<nsISupportsInterfacePointer> ifptr = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    ifptr->SetData(mFoldersWithNewMail);
    ifptr->SetDataIID(&NS_GET_IID(nsIArray));
    rv = argsArray->AppendElement(ifptr, false);
    NS_ENSURE_SUCCESS(rv, rv);

    // pass in the observer
    ifptr = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    nsCOMPtr <nsISupports> supports = do_QueryInterface(static_cast<nsIMessengerOSIntegration*>(this));
    ifptr->SetData(supports);
    ifptr->SetDataIID(&NS_GET_IID(nsIObserver));
    rv = argsArray->AppendElement(ifptr, false);
    NS_ENSURE_SUCCESS(rv, rv);

    // pass in the animation flag
    nsCOMPtr<nsISupportsPRBool> scriptableUserInitiated (do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID, &rv));
    NS_ENSURE_SUCCESS(rv, rv);
    scriptableUserInitiated->SetData(aUserInitiated);
    rv = argsArray->AppendElement(scriptableUserInitiated, false);
    NS_ENSURE_SUCCESS(rv, rv);

    // pass in the alert origin
    nsCOMPtr<nsISupportsPRUint8> scriptableOrigin (do_CreateInstance(NS_SUPPORTS_PRUINT8_CONTRACTID));
    NS_ENSURE_TRUE(scriptableOrigin, NS_ERROR_FAILURE);
    scriptableOrigin->SetData(0);
    int32_t origin = 0;
#ifdef MOZILLA_INTERNAL_API
    origin = LookAndFeel::GetInt(LookAndFeel::eIntID_AlertNotificationOrigin);
#else
    // Get task bar window handle
    HWND shellWindow = FindWindowW(L"Shell_TrayWnd", NULL);

    rv = prefBranch->GetIntPref(ALERT_ORIGIN_PREF, &origin);
    if (NS_FAILED(rv) && (shellWindow != NULL))
    {
      // Determine position
      APPBARDATA appBarData;
      appBarData.hWnd = shellWindow;
      appBarData.cbSize = sizeof(appBarData);
      if (SHAppBarMessage(ABM_GETTASKBARPOS, &appBarData))
      {
        // Set alert origin as a bit field - see LookAndFeel.h
        // 0 represents bottom right, sliding vertically.
        switch(appBarData.uEdge)
        {
          case ABE_LEFT:
            origin = NS_ALERT_HORIZONTAL | NS_ALERT_LEFT;
            break;
          case ABE_RIGHT:
            origin = NS_ALERT_HORIZONTAL;
            break;
          case ABE_TOP:
            origin = NS_ALERT_TOP;
            // fall through for the right-to-left handling.
          case ABE_BOTTOM:
            // If the task bar is right-to-left,
            // move the origin to the left
            if (::GetWindowLong(shellWindow, GWL_EXSTYLE) &
                  WS_EX_LAYOUTRTL)
              origin |= NS_ALERT_LEFT;
            break;
        }
      }
    }
#endif
    scriptableOrigin->SetData(origin);

    rv = argsArray->AppendElement(scriptableOrigin, false);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
    nsCOMPtr<mozIDOMWindowProxy> newWindow;
    rv = wwatch->OpenWindow(0, ALERT_CHROME_URL, "_blank",
                "chrome,dialog=yes,titlebar=no,popup=yes", argsArray,
                 getter_AddRefs(newWindow));

    mAlertInProgress = true;
  }

  // if the user has turned off the mail alert, or  openWindow generated an error,
  // then go straight to the system tray.
  if (!showAlert || NS_FAILED(rv))
    AlertFinished();

  return rv;
}

nsresult nsMessengerWinIntegration::AlertFinished()
{
  // okay, we are done showing the alert
  // now put an icon in the system tray, if allowed
  bool showTrayIcon = !mSuppressBiffIcon || sBiffIconData.szInfo[0];
  if (showTrayIcon)
  {
    nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
    if (prefBranch)
      prefBranch->GetBoolPref(SHOW_TRAY_ICON_PREF, &showTrayIcon);
  }
  if (showTrayIcon)
  {
    GenericShellNotify(NIM_ADD);
    mBiffIconVisible = true;
  }
  mSuppressBiffIcon = false;
  mAlertInProgress = false;
  return NS_OK;
}

nsresult nsMessengerWinIntegration::AlertClicked()
{
  nsresult rv;
  nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv,rv);
  nsCOMPtr<nsIMsgWindow> topMostMsgWindow;
  rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(topMostMsgWindow));
  if (topMostMsgWindow)
  {
    nsCOMPtr<mozIDOMWindowProxy> domWindow;
    topMostMsgWindow->GetDomWindow(getter_AddRefs(domWindow));
    if (domWindow)
    {
      activateWindow(domWindow);
      return NS_OK;
    }
  }
  // make sure we don't insert the icon in the system tray since the user clicked on the alert.
  mSuppressBiffIcon = true;
  nsCString folderURI;
  GetFirstFolderWithNewMail(folderURI);
  openMailWindow(folderURI);
  return NS_OK;
}

#ifdef MOZ_SUITE
nsresult nsMessengerWinIntegration::AlertClickedSimple()
{
  mSuppressBiffIcon = true;
  return NS_OK;
}
#endif MOZ_SUITE

NS_IMETHODIMP
nsMessengerWinIntegration::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
{
  if (strcmp(aTopic, "alertfinished") == 0)
      return AlertFinished();

  if (strcmp(aTopic, "alertclickcallback") == 0)
      return AlertClicked();

#ifdef MOZ_SUITE
  // SeaMonkey does most of the GUI work in JS code when clicking on a mail
  // notification, so it needs an extra function here
  if (strcmp(aTopic, "alertclicksimplecallback") == 0)
      return AlertClickedSimple();
#endif

  return NS_OK;
}

static void EscapeAmpersands(nsString& aToolTip)
{
  // First, check to see whether we have any ampersands.
  int32_t pos = aToolTip.FindChar('&');
  if (pos == kNotFound)
    return;

  // Next, see if we only have bare ampersands.
  pos = MsgFind(aToolTip, "&&", false, pos);

  // Windows tooltip code removes one ampersand from each run,
  // then collapses pairs of amperands. This means that in the easy case,
  // we need to replace each ampersand with three.
  MsgReplaceSubstring(aToolTip, NS_LITERAL_STRING("&"), NS_LITERAL_STRING("&&&"));
  if (pos == kNotFound)
    return;

  // We inserted too many ampersands. Remove some.
  for (;;) {
    pos = MsgFind(aToolTip, "&&&&&&", false, pos);
    if (pos == kNotFound)
      return;

    aToolTip.Cut(pos, 1);
    pos += 2;
  }
}

void nsMessengerWinIntegration::FillToolTipInfo()
{
  // iterate over all the folders in mFoldersWithNewMail
  nsString accountName;
  nsCString hostName;
  nsString toolTipLine;
  nsAutoString toolTipText;
  nsAutoString animatedAlertText;
  nsCOMPtr<nsIMsgFolder> folder;
  nsCOMPtr<nsIWeakReference> weakReference;
  int32_t numNewMessages = 0;

  uint32_t count = 0;
  NS_ENSURE_SUCCESS_VOID(mFoldersWithNewMail->GetLength(&count));

  for (uint32_t index = 0; index < count; index++)
  {
    weakReference = do_QueryElementAt(mFoldersWithNewMail, index);
    folder = do_QueryReferent(weakReference);
    if (folder)
    {
      folder->GetPrettiestName(accountName);

      numNewMessages = 0;
      folder->GetNumNewMessages(true, &numNewMessages);
      nsCOMPtr<nsIStringBundle> bundle;
      GetStringBundle(getter_AddRefs(bundle));
      if (bundle)
      {
        nsAutoString numNewMsgsText;
        numNewMsgsText.AppendInt(numNewMessages);

        const char16_t *formatStrings[] =
        {
          numNewMsgsText.get(),
        };

        nsString finalText;
        if (numNewMessages == 1)
          bundle->FormatStringFromName(u"biffNotification_message", formatStrings, 1, getter_Copies(finalText));
        else
          bundle->FormatStringFromName(u"biffNotification_messages", formatStrings, 1, getter_Copies(finalText));

        // the alert message is special...we actually only want to show the first account with
        // new mail in the alert.
        if (animatedAlertText.IsEmpty()) // if we haven't filled in the animated alert text yet
          animatedAlertText = finalText;

        toolTipLine.Append(accountName);
        toolTipLine.Append(' ');
        toolTipLine.Append(finalText);
        EscapeAmpersands(toolTipLine);

        // only add this new string if it will fit without truncation....
        if (toolTipLine.Length() + toolTipText.Length() <= kMaxTooltipSize)
          toolTipText.Append(toolTipLine);

        // clear out the tooltip line for the next folder
        toolTipLine.Assign('\n');
      } // if we got a bundle
    } // if we got a folder
  } // for each folder

  ::wcsncpy( sBiffIconData.szTip, toolTipText.get(), kMaxTooltipSize);

  if (!mBiffIconVisible)
  {
#ifndef MOZ_THUNDERBIRD
  nsresult rv;
  bool showNewAlert = false;
  nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
  NS_ENSURE_SUCCESS_VOID(rv);

  prefBranch->GetBoolPref(SHOW_NEW_ALERT_PREF, &showNewAlert);
  if (!showNewAlert)
    ShowAlertMessage(accountName, animatedAlertText, EmptyCString());
  else
#endif
    ShowNewAlertNotification(false, accountName, animatedAlertText);
  }
  else
   GenericShellNotify( NIM_MODIFY);
}

// Get the first top level folder which we know has new mail, then enumerate over
// all the subfolders looking for the first real folder with new mail.
// Return the folderURI for that folder.
nsresult nsMessengerWinIntegration::GetFirstFolderWithNewMail(nsACString& aFolderURI)
{
  NS_ENSURE_TRUE(mFoldersWithNewMail, NS_ERROR_FAILURE);

  nsCOMPtr<nsIMsgFolder> folder;
  nsCOMPtr<nsIWeakReference> weakReference;
  int32_t numNewMessages = 0;

  uint32_t count = 0;
  nsresult rv = mFoldersWithNewMail->GetLength(&count);
  if (NS_FAILED(rv) || !count)  // kick out if we don't have any folders with new mail
    return NS_OK;

  weakReference = do_QueryElementAt(mFoldersWithNewMail, 0);
  folder = do_QueryReferent(weakReference);

  if (folder)
  {
    nsCOMPtr<nsIMsgFolder> msgFolder;
    // enumerate over the folders under this root folder till we find one with new mail....
    nsCOMPtr<nsIArray> allFolders;
    rv = folder->GetDescendants(getter_AddRefs(allFolders));
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr<nsISimpleEnumerator> enumerator;
    rv = allFolders->Enumerate(getter_AddRefs(enumerator));
    if (NS_SUCCEEDED(rv) && enumerator)
    {
      nsCOMPtr<nsISupports> supports;
      bool hasMore = false;
      while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
      {
        rv = enumerator->GetNext(getter_AddRefs(supports));
        if (NS_SUCCEEDED(rv) && supports)
        {
          msgFolder = do_QueryInterface(supports, &rv);
          if (msgFolder)
          {
            numNewMessages = 0;
            msgFolder->GetNumNewMessages(false, &numNewMessages);
            if (numNewMessages)
              break; // kick out of the while loop
          }
        } // if we have a folder
      }  // if we have more potential folders to enumerate
    }  // if enumerator

    if (msgFolder)
      msgFolder->GetURI(aFolderURI);
  }

  return NS_OK;
}

void nsMessengerWinIntegration::DestroyBiffIcon()
{
  GenericShellNotify(NIM_DELETE);
  // Don't call DestroyIcon().  see http://bugzilla.mozilla.org/show_bug.cgi?id=134745
}

void nsMessengerWinIntegration::GenericShellNotify(DWORD aMessage)
{
  ::Shell_NotifyIconW( aMessage, &sBiffIconData );
}

NS_IMETHODIMP
nsMessengerWinIntegration::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, uint32_t oldFlag, uint32_t newFlag)
{
  return NS_OK;
}

NS_IMETHODIMP
nsMessengerWinIntegration::OnItemAdded(nsIMsgFolder *, nsISupports *)
{
  return NS_OK;
}

NS_IMETHODIMP
nsMessengerWinIntegration::OnItemBoolPropertyChanged(nsIMsgFolder *aItem,
                                                         nsIAtom *aProperty,
                                                         bool aOldValue,
                                                         bool aNewValue)
{
  if (aProperty == mDefaultServerAtom) {
    nsresult rv;

    // this property changes multiple times
    // on account deletion or when the user changes their
    // default account.  ResetCurrent() will set
    // mInboxURI to null, so we use that
    // to prevent us from attempting to remove
    // something from the registry that has already been removed
    if (!mInboxURI.IsEmpty() && !mEmail.IsEmpty()) {
      rv = RemoveCurrentFromRegistry();
      NS_ENSURE_SUCCESS(rv,rv);
    }

    // reset so we'll go get the new default server next time
    rv = ResetCurrent();
    NS_ENSURE_SUCCESS(rv,rv);

    rv = UpdateUnreadCount();
    NS_ENSURE_SUCCESS(rv,rv);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsMessengerWinIntegration::OnItemEvent(nsIMsgFolder *, nsIAtom *)
{
  return NS_OK;
}

NS_IMETHODIMP
nsMessengerWinIntegration::OnItemIntPropertyChanged(nsIMsgFolder *aItem, nsIAtom *aProperty, int64_t aOldValue, int64_t aNewValue)
{
  // if we got new mail show a icon in the system tray
  if (mBiffStateAtom == aProperty && mFoldersWithNewMail)
  {
    nsCOMPtr<nsIWeakReference> weakFolder = do_GetWeakReference(aItem);
    uint32_t indexInNewArray;
    nsresult rv = mFoldersWithNewMail->IndexOf(0, weakFolder, &indexInNewArray);
    bool folderFound = NS_SUCCEEDED(rv);

    if (!mBiffIconInitialized)
      InitializeBiffStatusIcon();

    if (aNewValue == nsIMsgFolder::nsMsgBiffState_NewMail)
    {
      // if the icon is not already visible, only show a system tray icon iff
      // we are performing biff (as opposed to the user getting new mail)
      if (!mBiffIconVisible)
      {
        bool performingBiff = false;
        nsCOMPtr<nsIMsgIncomingServer> server;
        aItem->GetServer(getter_AddRefs(server));
        if (server)
          server->GetPerformingBiff(&performingBiff);
        if (!performingBiff)
          return NS_OK; // kick out right now...
      }
      if (!folderFound)
        mFoldersWithNewMail->InsertElementAt(weakFolder, 0, false);
      // now regenerate the tooltip
      FillToolTipInfo();
    }
    else if (aNewValue == nsIMsgFolder::nsMsgBiffState_NoMail)
    {
      // we are always going to remove the icon whenever we get our first no mail
      // notification.

      // avoid a race condition where we are told to remove the icon before we've actually
      // added it to the system tray. This happens when the user reads a new message before
      // the animated alert has gone away.
      if (mAlertInProgress)
        mSuppressBiffIcon = true;

      if (folderFound)
        mFoldersWithNewMail->RemoveElementAt(indexInNewArray);
      if (mBiffIconVisible)
      {
        mBiffIconVisible = false;
        GenericShellNotify(NIM_DELETE);
      }
    }
  } // if the biff property changed

  if (aProperty == mTotalUnreadMessagesAtom) {
    nsCString itemURI;
    nsresult rv;
    rv = aItem->GetURI(itemURI);
    NS_ENSURE_SUCCESS(rv,rv);

    if (mInboxURI.Equals(itemURI))
      mCurrentUnreadCount = aNewValue;

    // If the timer isn't running yet, then we immediately update the
    // registry and then start a one-shot timer. If the Unread counter
    // has toggled zero / nonzero, we also update immediately.
    // Otherwise, if the timer is running, defer the update. This means
    // that all counter updates that occur within the timer interval are
    // batched into a single registry update, to avoid hitting the
    // registry too frequently. We also do a final update on shutdown,
    // regardless of the timer.
    if (!mUnreadTimerActive ||
         (!mCurrentUnreadCount && mLastUnreadCountWrittenToRegistry) ||
         (mCurrentUnreadCount && mLastUnreadCountWrittenToRegistry < 1)) {
      rv = UpdateUnreadCount();
      NS_ENSURE_SUCCESS(rv,rv);
      // If timer wasn't running, start it.
      if (!mUnreadTimerActive)
        rv = SetupUnreadCountUpdateTimer();
    }
  }
  return NS_OK;
}

void
nsMessengerWinIntegration::OnUnreadCountUpdateTimer(nsITimer *timer, void *osIntegration)
{
  nsMessengerWinIntegration *winIntegration = (nsMessengerWinIntegration*)osIntegration;

  winIntegration->mUnreadTimerActive = false;
  nsresult rv = winIntegration->UpdateUnreadCount();
  NS_ASSERTION(NS_SUCCEEDED(rv), "updating unread count failed");
}

nsresult
nsMessengerWinIntegration::RemoveCurrentFromRegistry()
{
  // If Windows XP, open the registry and get rid of old account registry entries
  // If there is a email prefix, get it and use it to build the registry key.
  // Otherwise, just the email address will be the registry key.
  nsAutoString currentUnreadMailCountKey;
  if (!mEmailPrefix.IsEmpty()) {
    currentUnreadMailCountKey.Assign(mEmailPrefix);
    currentUnreadMailCountKey.Append(NS_ConvertASCIItoUTF16(mEmail));
  }
  else
    CopyASCIItoUTF16(mEmail, currentUnreadMailCountKey);

  WCHAR registryUnreadMailCountKey[_MAX_PATH] = {0};
  // Enumerate through registry entries to delete the key matching
  // currentUnreadMailCountKey
  int index = 0;
  while (SUCCEEDED(SHEnumerateUnreadMailAccountsW(HKEY_CURRENT_USER,
                                                  index,
                                                  registryUnreadMailCountKey,
                                                  sizeof(registryUnreadMailCountKey))))
  {
    if (wcscmp(registryUnreadMailCountKey, currentUnreadMailCountKey.get())==0) {
      nsAutoString deleteKey;
      deleteKey.Assign(NS_LITERAL_STRING(UNREADMAILNODEKEY).get());
      deleteKey.Append(currentUnreadMailCountKey.get());

      if (!deleteKey.IsEmpty()) {
        // delete this key and berak out of the loop
        RegDeleteKey(HKEY_CURRENT_USER,
                     NS_ConvertUTF16toUTF8(deleteKey).get());
        break;
      }
      else {
        index++;
      }
    }
    else {
      index++;
    }
  }
  return NS_OK;
}

nsresult
nsMessengerWinIntegration::UpdateRegistryWithCurrent()
{
  if (mInboxURI.IsEmpty() || mEmail.IsEmpty())
    return NS_OK;

  // only update the registry if the count has changed
  // and if the unread count is valid
  if ((mCurrentUnreadCount < 0) || (mCurrentUnreadCount == mLastUnreadCountWrittenToRegistry))
    return NS_OK;

  // commandliner has to be built in the form of statement
  // which can be open the mailer app to the default user account
  // For given profile 'foo', commandliner will be built as
  // ""<absolute path to application>" -p foo -mail" where absolute
  // path to application is extracted from mAppName
  nsAutoString commandLinerForAppLaunch;
  commandLinerForAppLaunch.Assign(NS_LITERAL_STRING(DOUBLE_QUOTE));
  commandLinerForAppLaunch.Append(mAppName);
  commandLinerForAppLaunch.Append(NS_LITERAL_STRING(DOUBLE_QUOTE));
  commandLinerForAppLaunch.Append(NS_LITERAL_STRING(PROFILE_COMMANDLINE_ARG));
  commandLinerForAppLaunch.Append(NS_LITERAL_STRING(DOUBLE_QUOTE));
  commandLinerForAppLaunch.Append(mProfilePath);
  commandLinerForAppLaunch.Append(NS_LITERAL_STRING(DOUBLE_QUOTE));
  commandLinerForAppLaunch.Append(NS_LITERAL_STRING(MAIL_COMMANDLINE_ARG));

  if (!commandLinerForAppLaunch.IsEmpty())
  {
    nsAutoString pBuffer;

    if (!mEmailPrefix.IsEmpty()) {
      pBuffer.Assign(mEmailPrefix);
      pBuffer.Append(NS_ConvertASCIItoUTF16(mEmail));
    }
    else
      CopyASCIItoUTF16(mEmail, pBuffer);

    // Write the info into the registry
    HRESULT hr = SHSetUnreadMailCountW(pBuffer.get(),
                                       mCurrentUnreadCount,
                                       commandLinerForAppLaunch.get());
  }

  // do this last
  mLastUnreadCountWrittenToRegistry = mCurrentUnreadCount;

  return NS_OK;
}

nsresult
nsMessengerWinIntegration::SetupInbox()
{
  nsresult rv;

  // get default account
  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);
  if (!account) {
    // this can happen if we launch mail on a new profile
    // we don't have a default account yet
    mDefaultAccountMightHaveAnInbox = false;
    return NS_OK;
  }

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

  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
    mDefaultAccountMightHaveAnInbox = true;

    mEmailPrefix.Truncate();

    // Get user's email address
    nsCOMPtr<nsIMsgIdentity> identity;
    rv = account->GetDefaultIdentity(getter_AddRefs(identity));
    NS_ENSURE_SUCCESS(rv,rv);

    if (!identity)
      return NS_ERROR_FAILURE;

    rv = identity->GetEmail(mEmail);
    NS_ENSURE_SUCCESS(rv,rv);

    nsCOMPtr<nsIMsgFolder> rootMsgFolder;
    rv = server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
    NS_ENSURE_SUCCESS(rv,rv);

    if (!rootMsgFolder)
      return NS_ERROR_FAILURE;

    nsCOMPtr <nsIMsgFolder> inboxFolder;
    rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
                                      getter_AddRefs(inboxFolder));
    NS_ENSURE_TRUE(inboxFolder, NS_ERROR_FAILURE);

    rv = inboxFolder->GetURI(mInboxURI);
    NS_ENSURE_SUCCESS(rv,rv);

    rv = inboxFolder->GetNumUnread(false, &mCurrentUnreadCount);
    NS_ENSURE_SUCCESS(rv,rv);
  }
  else {
    // the default account is valid, but it's not something
    // that we expect to have an inbox.  (local folders, news accounts)
    // set this flag to avoid calling SetupInbox() every time
    // the timer goes off.
    mDefaultAccountMightHaveAnInbox = false;
  }

  return NS_OK;
}

nsresult
nsMessengerWinIntegration::UpdateUnreadCount()
{
  nsresult rv;

  if (mDefaultAccountMightHaveAnInbox && mInboxURI.IsEmpty()) {
    rv = SetupInbox();
    NS_ENSURE_SUCCESS(rv,rv);
  }

  return UpdateRegistryWithCurrent();
}

nsresult
nsMessengerWinIntegration::SetupUnreadCountUpdateTimer()
{
  mUnreadTimerActive = true;
  if (mUnreadCountUpdateTimer)
    mUnreadCountUpdateTimer->Cancel();
  else
    mUnreadCountUpdateTimer = do_CreateInstance("@mozilla.org/timer;1");

  mUnreadCountUpdateTimer->InitWithFuncCallback(OnUnreadCountUpdateTimer,
    (void *)this, UNREAD_UPDATE_INTERVAL, nsITimer::TYPE_ONE_SHOT);

  return NS_OK;
}
