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

Components.utils.import("resource://gre/modules/PluralForm.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource:///modules/mailServices.js");

var mailtolength = 7;

/**
 * Function to change the highlighted row back to the row that is currently
 * outline/dotted without loading the contents of either rows. This is
 * triggered when the context menu for a given row is hidden/closed
 * (onpopuphiding).
 * @param tree the tree element to restore selection for
 */
function RestoreSelectionWithoutContentLoad(tree)
{
  if (gRightMouseButtonSavedSelection) {
    let view = gRightMouseButtonSavedSelection.view;
    // restore the selection
    let transientSelection = gRightMouseButtonSavedSelection.transientSelection;
    let realSelection = gRightMouseButtonSavedSelection.realSelection;
    view.selection = realSelection;
    // replay any calls to adjustSelection, this handles suppression.
    transientSelection.replayAdjustSelectionLog(realSelection);
    // Avoid possible cycle leaks.
    gRightMouseButtonSavedSelection.view = null;
    gRightMouseButtonSavedSelection = null;

    if (tree)
      tree.treeBoxObject.invalidate();

    UpdateMailToolbar("RestoreSelectionWithoutContentLoad");
  }
}

/**
 * Function to clear out the global nsContextMenu, and in the case when we
 * were a threadpane context menu, restore the selection so that a right-click
 * on a non-selected row doesn't move the selection.
 * @param event the onpopuphiding event
 */
function mailContextOnPopupHiding(aEvent)
{
  // Don't do anything if it's a submenu's onpopuphiding that's just bubbling
  // up to the top.
  if (aEvent.target != aEvent.currentTarget)
    return;

  let wasInThreadPane = gContextMenu.inThreadPane;
  gContextMenu = null;
  if (wasInThreadPane)
    RestoreSelectionWithoutContentLoad(GetThreadTree());
}

function fillMailContextMenu(event)
{
  // If the popupshowing was for a submenu, we don't need to do anything.
  if (event.target != event.currentTarget)
    return true;

  // No menu on grouped header row currently, any command would be an implied
  // multiselect.
  if (gFolderDisplay.tree) {
    let row = gFolderDisplay.treeBox.getRowAt(event.clientX, event.clientY);
    if (gFolderDisplay.view.isGroupedByHeaderAtIndex(row)) {
      RestoreSelectionWithoutContentLoad(gFolderDisplay.tree);
      return false;
    }
  }

  goUpdateCommand('cmd_killThread');
  goUpdateCommand('cmd_killSubthread');
  goUpdateCommand('cmd_watchThread');

  goUpdateCommand('cmd_printpreview');
  goUpdateCommand('cmd_print');

  updateCheckedStateForIgnoreAndWatchThreadCmds();

  // Hide "Edit Draft Message" menus if we're not in a draft folder.
  updateHiddenStateForEditDraftMsgCmd();

  gContextMenu = new nsContextMenu(event.target, event.shiftKey);
  return gContextMenu.shouldDisplay;
}

/**
 * Set the message id to show as label in the context menu item designated
 * for that purpose.
 */
function FillMessageIdContextMenu(messageIdNode)
{
  var msgId = messageIdNode.getAttribute("messageid");
  document.getElementById("messageIdContext-messageIdTarget")
          .setAttribute("label", msgId);

  // We don't want to show "Open Message For ID" for the same message
  // we're viewing.
  var currentMsgId = "<" + gFolderDisplay.selectedMessage.messageId + ">";
  document.getElementById("messageIdContext-openMessageForMsgId")
          .hidden = (currentMsgId == msgId);

  // We don't want to show "Open Browser With Message-ID" for non-nntp messages.
  document.getElementById("messageIdContext-openBrowserWithMsgId")
          .hidden = !gFolderDisplay.selectedMessageIsNews;
}

function CopyMessageId(messageId)
{
   var clipboard = Components.classes["@mozilla.org/widget/clipboardhelper;1"]
                             .getService(Components.interfaces.nsIClipboardHelper);

   clipboard.copyString(messageId);
}

function GetMessageIdFromNode(messageIdNode, cleanMessageId)
{
  var messageId  = messageIdNode.getAttribute("messageid");

  // remove < and >
  if (cleanMessageId)
    messageId = messageId.substring(1, messageId.length - 1);

  return messageId;
}

/**
 * Take the message id from the messageIdNode and use the url defined in the
 * hidden pref "mailnews.messageid_browser.url" to open it in a browser window
 * (%mid is replaced by the message id).
 * @param messageId the message id to open
 */
function OpenBrowserWithMessageId(messageId)
{
  var browserURL = Services.prefs.getComplexValue("mailnews.messageid_browser.url",
                                                  Components.interfaces.nsIPrefLocalizedString).data;
  browserURL = browserURL.replace(/%mid/, messageId);
  try
  {
    messenger.launchExternalURL(browserURL);
  }
  catch (ex)
  {
    Components.utils.reportError("Failed to open message-id in browser; " +
                                 "browserURL=" + browserURL);
  }
}

/**
 * Take the message id from the messageIdNode, search for the corresponding
 * message in all folders starting with the current selected folder, then the
 * current account followed by the other accounts and open corresponding
 * message if found.
 * @param messageId the message id to open
 */
function OpenMessageForMessageId(messageId)
{
  let startServer = msgWindow.openFolder.server;

  window.setCursor("wait");

  // first search in current folder for message id
  let messageHeader = CheckForMessageIdInFolder(msgWindow.openFolder, messageId);

  // if message id not found in current folder search in all folders
  if (!messageHeader)
  {
    let allServers = MailServices.accounts.allServers;

    messageHeader = SearchForMessageIdInSubFolder(startServer.rootFolder, messageId);

    for (let i = 0; i < allServers.length && !messageHeader; i++)
    {
      let currentServer =
        allServers.queryElementAt(i, Components.interfaces.nsIMsgIncomingServer);
      if (currentServer && startServer != currentServer &&
          currentServer.canSearchMessages && !currentServer.isDeferredTo)
      {
        messageHeader = SearchForMessageIdInSubFolder(currentServer.rootFolder, messageId);
      }
    }
  }
  window.setCursor("auto");

  // if message id was found open corresponding message
  // else show error message
  if (messageHeader)
    OpenMessageByHeader(messageHeader, Services.prefs.getBoolPref("mailnews.messageid.openInNewWindow"));
  else
  {
    let messageIdStr = "<" + messageId + ">";
    let bundle = document.getElementById("bundle_messenger");
    let errorTitle = bundle.getString("errorOpenMessageForMessageIdTitle");
    let errorMessage = bundle.getFormattedString("errorOpenMessageForMessageIdMessage",
                                                 [messageIdStr]);

    Services.prompt.alert(window, errorTitle, errorMessage);
  }
}

function OpenMessageByHeader(messageHeader, openInNewWindow)
{
  var folder    = messageHeader.folder;
  var folderURI = folder.URI;

  if (openInNewWindow)
  {
    window.openDialog("chrome://messenger/content/messageWindow.xul",
                      "_blank", "all,chrome,dialog=no,status,toolbar",
                      messageHeader);
  }
  else
  {
    if (msgWindow.openFolder != folderURI)
      gFolderTreeView.selectFolder(folder);

    var tree = null;
    var wintype = document.documentElement.getAttribute('windowtype');
    if (wintype != "mail:messageWindow")
    {
      tree = GetThreadTree();
      tree.view.selection.clearSelection();
    }

    try
    {
      gDBView.selectMsgByKey(messageHeader.messageKey);
    }
    catch(e)
    { // message not in the thread pane
      try
      {
        goDoCommand("cmd_viewAllMsgs");
        gDBView.selectMsgByKey(messageHeader.messageKey);
      }
      catch(e)
      {
         dump("select messagekey " + messageHeader.messageKey +
              " failed in folder " + folder.URI);
      }
    }

    if (tree && tree.currentIndex != -1)
      tree.treeBoxObject.ensureRowIsVisible(tree.currentIndex);
  }
}

// search for message by message id in given folder and its subfolders
// return message header if message was found
function SearchForMessageIdInSubFolder(folder, messageId)
{
  var messageHeader;
  var subFolders = folder.subFolders;

  // search in folder
  if (!folder.isServer)
    messageHeader = CheckForMessageIdInFolder(folder, messageId);

  // search subfolders recursively
  while (subFolders.hasMoreElements() && !messageHeader)
  {
    // search in current folder
    var currentFolder =
      subFolders.getNext().QueryInterface(Components.interfaces.nsIMsgFolder);
    messageHeader = CheckForMessageIdInFolder(currentFolder, messageId);

    // search in its subfolder
    if (!messageHeader && currentFolder.hasSubFolders)
      messageHeader = SearchForMessageIdInSubFolder(currentFolder, messageId);
  }

  return messageHeader;
}

/**
 * Check folder for corresponding message to given message id.
 * @return the message header if message was found
 */
function CheckForMessageIdInFolder(folder, messageId)
{
  var messageDatabase = folder.msgDatabase;
  var messageHeader;
  try
  {
    messageHeader = messageDatabase.getMsgHdrForMessageID(messageId);
  }
  catch (ex)
  {
    Components.utils.reportError("Failed to find message-id in folder; " +
                                 "messageId=" + messageId);
  }

  const nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
  if (!MailServices.mailSession.IsFolderOpenInWindow(folder) &&
      !(folder.flags & (nsMsgFolderFlags.Trash | nsMsgFolderFlags.Inbox)))
  {
    folder.msgDatabase = null;
  }

  return messageHeader;
}

function folderPaneOnPopupHiding()
{
  RestoreSelectionWithoutContentLoad(document.getElementById("folderTree"));
}

function fillFolderPaneContextMenu(aEvent)
{
  let target = document.popupNode;
  // If a column header was clicked, show the column picker.
  if (target.localName == "treecol") {
    let treecols = target.parentNode;
    let nodeList = document.getAnonymousNodes(treecols);
    let treeColPicker = null;
    for (let i = 0; i < nodeList.length; i++) {
      if (nodeList.item(i).localName == "treecolpicker") {
        treeColPicker = nodeList.item(i);
        break;
      }
    }
    if (treeColPicker) {
      let popup = document.getAnonymousElementByAttribute(treeColPicker, "anonid", "popup");
      treeColPicker.buildPopup(popup);
      popup.openPopup(target, "after_start", 0, 0, true);
    }
    return false;
  }

  // Do not show menu if rows are selected.
  var bundle = document.getElementById("bundle_messenger");
  var folders = gFolderTreeView.getSelectedFolders();
  if (!folders.length)
    return false;

  var numSelected = folders.length;

  function checkIsVirtualFolder(folder) {
    const kVirtualFlag = Components.interfaces.nsMsgFolderFlags.Virtual;
    return folder.flags & kVirtualFlag;
  }
  var haveAnyVirtualFolders = folders.some(checkIsVirtualFolder);

  function checkIsServer(folder) {
    return folder.isServer;
  }
  var selectedServers = folders.filter(checkIsServer);

  let specialFolder;
  if (numSelected == 1)
    specialFolder = haveAnyVirtualFolders ? "Virtual" :
                                          getSpecialFolderString(folders[0]);

  function checkCanSubscribeToFolder(folder) {
    if (checkIsVirtualFolder(folder))
      return false;

    // All feed account folders, besides Trash, are subscribable.
    if (folder.server.type == "rss" &&
        !(folder.flags & nsMsgFolderFlags.Trash))
      return true;

    // We only want the subscribe item on the account nodes.
    if (!folder.isServer)
      return false;

    return folder.server.type == "nntp" ||
           folder.server.type == "imap";
  }
  var haveOnlySubscribableFolders = folders.every(checkCanSubscribeToFolder);

  function checkIsNewsgroup(folder) {
    return !folder.isServer && folder.server.type == "nntp" &&
           !folder.getFlag(nsMsgFolderFlags.Virtual);
  }
  var haveOnlyNewsgroups = folders.every(checkIsNewsgroup);

  function checkIsMailFolder(folder) {
    return !folder.isServer && folder.server.type != "nntp";
  }
  var haveOnlyMailFolders = folders.every(checkIsMailFolder);

  function checkCanGetMessages(folder) {
    const kTrashFlag = Components.interfaces.nsMsgFolderFlags.Trash;
    return (folder.isServer && (folder.server.type != "none")) ||
           checkIsNewsgroup(folder) ||
           ((folder.server.type == "rss") && !folder.isSpecialFolder(kTrashFlag, true) &&
             !checkIsVirtualFolder(folder));
  }
  var selectedFoldersThatCanGetMessages = folders.filter(checkCanGetMessages);

  // --- Set up folder properties / account settings menu item.
  if (numSelected != 1) {
    ShowMenuItem("folderPaneContext-settings", false);
    ShowMenuItem("folderPaneContext-properties", false);
  }
  else if (selectedServers.length != 1)
  {
    ShowMenuItem("folderPaneContext-settings", false);
    ShowMenuItem("folderPaneContext-properties", true);
  }
  else
  {
    ShowMenuItem("folderPaneContext-properties", false);
    ShowMenuItem("folderPaneContext-settings", true);
  }

  // --- Set up the get messages menu item.
  // Show if only servers, or it's only newsgroups/feeds. We could mix,
  // but it gets messy for situations where both server and a folder
  // on the server are selected.
  ShowMenuItem("folderPaneContext-getMessages",
               (selectedServers.length > 0 &&
                selectedFoldersThatCanGetMessages.length == numSelected &&
                selectedServers.length == selectedFoldersThatCanGetMessages.length) ||
               selectedFoldersThatCanGetMessages.length == numSelected);

  // --- Set up new sub/folder menu item.
  if (numSelected == 1) {
    let showNewFolderItem =
      ((folders[0].server.type != "nntp") && folders[0].canCreateSubfolders) ||
      (specialFolder == "Inbox");
    ShowMenuItem("folderPaneContext-new", showNewFolderItem);
    // XXX: Can't offline imap create folders nowadays?
    EnableMenuItem("folderPaneContext-new", folders[0].server.type != "imap" ||
                                            MailOfflineMgr.isOnline());
    if (showNewFolderItem)
    {
      if (folders[0].isServer || specialFolder == "Inbox")
        SetMenuItemLabel("folderPaneContext-new",
                         bundle.getString("newFolder"));
      else
        SetMenuItemLabel("folderPaneContext-new",
                         bundle.getString("newSubfolder"));
    }
  }
  else {
    ShowMenuItem("folderPaneContext-new", false);
  }

  // --- Set up rename menu item.
  if (numSelected == 1) {
    ShowMenuItem("folderPaneContext-rename",
                 !folders[0].isServer && folders[0].canRename &&
                 specialFolder == "none" || specialFolder == "Virtual" ||
                 (specialFolder == "Junk" && CanRenameDeleteJunkMail(folders[0].URI)));
    EnableMenuItem("folderPaneContext-rename",
                   !folders[0].isServer && folders[0].isCommandEnabled("cmd_renameFolder"));
  }
  else {
    ShowMenuItem("folderPaneContext-rename", false);
  }

  // --- Set up the delete folder menu item.
  function checkCanDeleteFolder(folder) {
    if (folder.isSpecialFolder(nsMsgFolderFlags.Junk, false))
      return CanRenameDeleteJunkMail(folder.URI);
    return folder.deletable;
  }
  var haveOnlyDeletableFolders = folders.every(checkCanDeleteFolder);
  ShowMenuItem("folderPaneContext-remove", haveOnlyDeletableFolders && numSelected == 1);

  function checkIsDeleteEnabled(folder) {
    return folder.isCommandEnabled("cmd_delete");
  }
  var haveOnlyDeleteEnabledFolders = folders.every(checkIsDeleteEnabled);
  EnableMenuItem("folderPaneContext-remove", haveOnlyDeleteEnabledFolders);

  // --- Set up the compact folder menu item.
  function checkCanCompactFolder(folder) {
    const kVirtualFlag = Components.interfaces.nsMsgFolderFlags.Virtual;
    return folder.canCompact && !(folder.flags & kVirtualFlag) &&
           folder.isCommandEnabled("cmd_compactFolder");
  }
  var haveOnlyCompactableFolders = folders.every(checkCanCompactFolder);
  ShowMenuItem("folderPaneContext-compact", haveOnlyCompactableFolders);

  function checkIsCompactEnabled(folder) {
    return folder.isCommandEnabled("cmd_compactFolder");
  }
  var haveOnlyCompactEnabledFolders = folders.every(checkIsCompactEnabled);
  EnableMenuItem("folderPaneContext-compact", haveOnlyCompactEnabledFolders);

  // --- Set up favorite folder menu item.
  ShowMenuItem("folderPaneContext-favoriteFolder",
               numSelected == 1 && !folders[0].isServer);
  if (numSelected == 1 && !folders[0].isServer)
  {
    const kFavoriteFlag = Components.interfaces.nsMsgFolderFlags.Favorite;
     // Adjust the checked state on the menu item.
    document.getElementById("folderPaneContext-favoriteFolder")
            .setAttribute("checked", folders[0].getFlag(kFavoriteFlag));
  }

  // --- Set up the empty trash menu item.
  ShowMenuItem("folderPaneContext-emptyTrash",
               numSelected == 1 && specialFolder == "Trash");

  // --- Set up the empty junk menu item.
  ShowMenuItem("folderPaneContext-emptyJunk",
               numSelected == 1 && specialFolder == "Junk");

  // --- Set up the send unsent messages menu item.
  ShowMenuItem("folderPaneContext-sendUnsentMessages",
               numSelected == 1 && specialFolder == "Outbox");
  EnableMenuItem("folderPaneContext-sendUnsentMessages",
                 IsSendUnsentMsgsEnabled(folders[0]));

  // --- Set up the subscribe menu item.
  ShowMenuItem("folderPaneContext-subscribe",
               numSelected == 1 && haveOnlySubscribableFolders);

  // --- Set up the unsubscribe menu item.
  ShowMenuItem("folderPaneContext-newsUnsubscribe", haveOnlyNewsgroups);

  // --- Set up the mark newsgroup/s read menu item.
  ShowMenuItem("folderPaneContext-markNewsgroupAllRead", haveOnlyNewsgroups);
  SetMenuItemLabel("folderPaneContext-markNewsgroupAllRead",
                   PluralForm.get(numSelected,
                                  bundle.getString("markNewsgroupRead")));

  // --- Set up the mark folder/s read menu item.
  ShowMenuItem("folderPaneContext-markMailFolderAllRead",
               haveOnlyMailFolders && !haveAnyVirtualFolders);
  SetMenuItemLabel("folderPaneContext-markMailFolderAllRead",
                   PluralForm.get(numSelected,
                                  bundle.getString("markFolderRead")));

  // Set up the search menu item.
  ShowMenuItem("folderPaneContext-searchMessages",
               numSelected == 1 && !haveAnyVirtualFolders);
  goUpdateCommand('cmd_search');

  // handle our separators
  function hideIfAppropriate(aID) {
    var separator = document.getElementById(aID);
    var sibling = separator.previousSibling;
    while (sibling) {
      if (sibling.getAttribute("hidden") != "true") {
        ShowMenuItem(aID, sibling.localName != "menuseparator" &&
                          hasAVisibleNextSibling(separator));
        return;
      }
      sibling = sibling.previousSibling;
    }
    ShowMenuItem(aID, false);
  }

  // Hide / Show our menu separators based on the menu items we are showing.
  ShowMenuItem("folderPaneContext-sep1", selectedServers.length == 0);
  hideIfAppropriate("folderPaneContext-sep1");
  hideIfAppropriate("folderPaneContext-sep2");
  hideIfAppropriate("folderPaneContext-sep3");

  return true;
}

function ShowMenuItem(id, showItem)
{
  document.getElementById(id).hidden = !showItem;
}

function EnableMenuItem(id, enableItem)
{
  document.getElementById(id).disabled = !enableItem;
}

function SetMenuItemLabel(id, label)
{
  document.getElementById(id).setAttribute('label', label);
}

// helper function used by shouldShowSeparator
function hasAVisibleNextSibling(aNode)
{
  var sibling = aNode.nextSibling;
  while (sibling)
  {
    if (sibling.getAttribute("hidden") != "true"
        && sibling.localName != "menuseparator")
      return true;
    sibling = sibling.nextSibling;
  }
  return false;
}

function IsMenuItemShowing(menuID)
{
  var item = document.getElementById(menuID);
  if (item)
    return item.hidden != "true";
  return false;
}

// message pane context menu helper methods
function addEmail()
{
  var url = gContextMenu.linkURL;
  var addresses = getEmail(url);
  window.openDialog("chrome://messenger/content/addressbook/abNewCardDialog.xul",
                    "",
                     "chrome,resizable=no,titlebar,modal,centerscreen",
                    {primaryEmail: addresses});
}

function composeEmailTo()
{
  let fields = Components.classes["@mozilla.org/messengercompose/composefields;1"]
                         .createInstance(Components.interfaces.nsIMsgCompFields);
  let params = Components.classes["@mozilla.org/messengercompose/composeparams;1"]
                         .createInstance(Components.interfaces.nsIMsgComposeParams);
  fields.to = getEmail(gContextMenu.linkURL);
  params.type = Components.interfaces.nsIMsgCompType.New;
  params.format = Components.interfaces.nsIMsgCompFormat.Default;
  if (gFolderDisplay.displayedFolder) {
    params.identity = accountManager.getFirstIdentityForServer(
                        gFolderDisplay.displayedFolder.server);
  }
  params.composeFields = fields;
  MailServices.compose.OpenComposeWindowWithParams(null, params);
}

// Extracts email address from url string
function getEmail (url)
{
  var qmark = url.indexOf( "?" );
  var addresses;

  if ( qmark > mailtolength )
      addresses = url.substring( mailtolength, qmark );
  else
     addresses = url.substr( mailtolength );
  // Let's try to unescape it using a character set
  try {
    var characterSet = gContextMenu.target.ownerDocument.characterSet;
    const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
                                 .getService(Components.interfaces.nsITextToSubURI);
    addresses = textToSubURI.unEscapeURIForUI(characterSet, addresses);
  }
  catch(ex) {
    // Do nothing.
  }
  return addresses;
}

function CopyMessageUrl()
{
  try {
    var hdr = gDBView.hdrForFirstSelectedMessage;
    var server = hdr.folder.server;

    // TODO let backend construct URL and return as attribute
    var url = (server.socketType == Components.interfaces.nsMsgSocketType.SSL) ?
              "snews://" : "news://";
    url += server.hostName + ":" + server.port + "/" + hdr.messageId;

    var clipboard = Components.classes["@mozilla.org/widget/clipboardhelper;1"]
                              .getService(Components.interfaces.nsIClipboardHelper);
    clipboard.copyString(url);
  }
  catch (ex) {
    dump("ex="+ex+"\n");
  }
}
