// vim: ts=2 sw=2 expandtab cindent
//
// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2009
// http://flock.com
//
// This file may be used under the terms of the
// GNU General Public License Version 2 or later (the "GPL"),
// http://www.gnu.org/licenses/gpl.html
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
//
// END FLOCK GPL

/**************************************************************************
 * Imports
 **************************************************************************/

CU.import("resource:///modules/FlockWebmailUtils.jsm");
CU.import("resource:///modules/FlockPrefsUtils.jsm");

/**************************************************************************
 * Internal variables
 **************************************************************************/

var _cc = Components.classes;
var _ci = Components.interfaces;

// Account utilities service
var _acUtils = _cc["@flock.com/account-utils;1"]
               .getService(_ci.flockIAccountUtils);

// Preferences service
var _prefs = _cc["@mozilla.org/preferences-service;1"]
             .getService(_ci.nsIPrefBranch);

// Rich DND service
var _richDND = _cc["@flock.com/rich-dnd-service;1"]
               .getService(_ci.flockIRichDNDService);

// Metrics service
var _metrics = _cc["@flock.com/metrics-service;1"]
               .getService(_ci.flockIMetricsService);

// Webmail string bundle
var _sb = _cc["@mozilla.org/intl/stringbundle;1"]
          .getService(_ci.nsIStringBundleService)
          .createBundle("chrome://flock/locale/webmail/webmail.properties");

// Window mediator service
var _wm = _cc["@mozilla.org/appshell/window-mediator;1"]
          .getService(_ci.nsIWindowMediator);

// An array of available webmail services
var _svcs = [];

// An array of configured webmail accounts
var _accts = [];

// An array of unread message objects, in sync with _accts
var _unreads = [];

// An array of upgrade objects, in sync with _accts
var _upgrades = [];

// An array of credentials for the user if logged in
var _shareFlockCredentials = [];

// Share Flock URL
const SHARE_FLOCK_URL = "https://secure.flock.com/shareflockform/";

const MAX_DISPLAY_COUNT = 20;
const MIN_DISPLAY_COUNT = 1;
const DEFAULT_DISPLAY_COUNT = 4;

/**************************************************************************
 * Event Handlers
 **************************************************************************/

window.addEventListener("load", _populateShareForm, true);

/**************************************************************************
 * These variables are referenced in the UI
 **************************************************************************/
 
 // Handle drag-and-drop to the webmail button
var webmailDNDObserver = {
  canDrop: function wm_canDrop(aEvent, aDragSession) {
    // Cannot drop if dragged from outside browser
    return (aDragSession.sourceNode != null);
  },

  canHandleMultipleItems: false,

  onDragOver: function wm_onDragOver(aEvent, aFlavor, aSession) {
  },

  onDragEnter: function wm_onDragEnter(aEvent, aSession) {
    this._primaryAccount = FlockWebmailUtils.getPrimaryAccount();
  },

  onDragExit: function wmc_onDragExit(aEvent, aSession) {
    this._primaryAccount = null;
  },

  onDrop: function wm_onDrop(aEvent, aDropData, aSession) {
    aEvent.stopPropagation();

    // If there is no primary account, prompt the user to log in to a webmail
    // account first.
    if (!this._primaryAccount) {
      var win = _wm.getMostRecentWindow('navigator:browser');
      var prompt = _cc["@mozilla.org/embedcomp/prompt-service;1"]
                   .getService(_ci.nsIPromptService);
      if (prompt.confirm(win,
                         _sb.GetStringFromName("flock.webmail.dnd.alert.title"),
                         _sb.formatStringFromName("flock.webmail.dnd.alert.text",
                                                  ["Flock"],
                                                  1)))
      {
        win.toggleSidebarWithMetric("flock_AccountsSidebarBroadcaster",
                                    "AccountSidebar-Open",
                                    null,
                                    "WebmailNoAccounts",
                                    true);
        var timerCallback = {
          notify: function webmail_dnd_callback_notify() {
            var obs = _cc["@mozilla.org/observer-service;1"]
                      .getService(_ci.nsIObserverService);
            obs.notifyObservers(null, "toggleAccountsArea", "webmail");
          }
        };
        var timer = _cc["@mozilla.org/timer;1"].createInstance(_ci.nsITimer);
        timer.initWithCallback(timerCallback, 300, _ci.nsITimer.TYPE_ONE_SHOT);
      }
      return;
    }

    // Get transferable from session
    var transferable = _richDND.getRichSelection(aSession,
                                                 aDropData.flavour.contentType,
                                                 aDropData.data);
    // Don't continue if we can't get the transferable data
    if (!transferable) {
      return;
    }

    // Get the message from the transferable
    var message = _richDND.getMessageFromTransferable(transferable,
                                                      2,
                                                      ["text/x-moz-url",
                                                       "text/unicode"]);

    // Build mailto string
    var mailtoString = _buildMailtoString(message.subject, message.body);

    // Send message to primary account
    FlockWebmailUtils.composeMessage(this._primaryAccount, mailtoString);
  },

  getSupportedFlavours: function wm_getSupportedFlavours() {
    var flavors = new FlavourSet();
    flavors.appendFlavour("application/x-flock-rich-content");
    flavors.appendFlavour("text/x-moz-url");
    flavors.appendFlavour("text/unicode");
    flavors.appendFlavour("text/html");
    return flavors;
  },

  _primaryAccount: null

}

/**************************************************************************
 * These functions are called from the UI
 **************************************************************************/

// Builds the menu and submenus, and attaches them to the toolbar button
function webmailBuildFlyout(aTarget, aPopupNode) {
  // If this is from one of our sub-menus, we need to bail
  if (aTarget != aPopupNode) {
    return;
  }

  // Remove old menu items
  while (aPopupNode.hasChildNodes()) {
    aPopupNode.removeChild(aPopupNode.lastChild);
  }

  // Clear accounts and unread messages arrays
  _svcs.length = 0;
  _accts.length = 0;
  _unreads.length = 0;
  _upgrades.length = 0;

  var topWebmail = FlockPrefsUtils.getCharPref("flock.accounts.top.webmail");
  // Get available webmail services
  var svcsEnum = _acUtils.getWebServicesByInterface("flockIMailWebService");
  while (svcsEnum.hasMoreElements()) {
    var svc = svcsEnum.getNext();
    var att = {name: svc.shortName, service: svc};
    if (svc.shortName == topWebmail){
      _svcs.unshift(att);
    } else {
      _svcs.push(att);
    }
  }

  // Get configured webmail accounts
  var acctsEnum = _acUtils.getAccountsByInterface("flockIMailWebService");

  // The menu configuration depends on the number of accounts, which isn't
  // directly available from the enum, so we need to push them into a global
  // array which we can access from the UI. Likewise for each account's JSONs
  // of unread messages and upgrade information, we convert to objects and save
  // in global arrays.
  var nsJSON = _cc["@mozilla.org/dom/json;1"].createInstance(_ci.nsIJSON);
  while (acctsEnum.hasMoreElements()) {
    var acct = acctsEnum.getNext()
                        .QueryInterface(_ci.flockIWebmailAccount);
    _accts.push(acct);
    _unreads.push(this._getUnreadMessages(acct, false));
    _upgrades.push(nsJSON.decode(acct.getUpgradeAccount()));
  }

  // Configure based on number of accounts
  if (_accts.length == 0) {
    _addNoAccounts(aPopupNode);
  } else if (_accts.length == 1) {
    _addOneAccount(aPopupNode, 0, false);
  } else {
    _addManyAccounts(aPopupNode);
  }
}

// Fills the webmail button tooltip with the appropriate information
function webmailSetTooltipText(aTarget, aWebmailButton) {
  // If this is from one of our menus, we need to bail
  if (aTarget != aWebmailButton) {
    return;
  }

  // Default tooltip if activated primary account is not found
  var tooltip = _sb.GetStringFromName("flock.webmail.button.tooltip.noAccount");

  // Only show unread message count for the primary account, and only if it is
  // logged in
  var primaryAcct = FlockWebmailUtils.getPrimaryAccount();
  if (primaryAcct) {
    if (primaryAcct.isAuthenticated()) {
      var unreadMessages = this._getUnreadMessages(primaryAcct, false)
      var unread = unreadMessages.unreadMessageCount;
      tooltip = _sb.formatStringFromName("flock.webmail.button.tooltip.inbox",
                                         [unread],
                                         1)
              + " - " + _getLastChecked(unreadMessages);
    } else if (primaryAcct.isSessionExpired()) {
      tooltip =
        _sb.GetStringFromName("flock.webmail.button.tooltip.sessionExpired");
    }
  }

  // Update tooltip text appropriately
  aWebmailButton.setAttribute("tooltiptext", tooltip);
}

// Clear "hot" icon when then webmail button is clicked
function webmailOnMouseDown(aTarget, aWebmailButton) {
  // If this is from one of our menus, we need to bail
  if (aTarget != aWebmailButton) {
    return;
  }

  if (aWebmailButton.getAttribute("hasNew")) {
    FlockWebmailUtils.getPrimaryAccount()
                     .setCustomParam("hasNewMessages", false);
    FlockWebmailUtils.lightIcon(false);
  }
}

// "Inbox" menu item
function webmailOpenInbox(aAcctIndex) {
  _openInNewTab(_accts[aAcctIndex].getInboxURL());
  _metrics.report("WebMail-InboxClick",
                  _getServiceFromAccountIndex(aAcctIndex).shortName);
}

// "More" menu item
function webmailMore(aAcctIndex) {
  _openInNewTab(_accts[aAcctIndex].getInboxURL());
  _metrics.report("WebMail-MoreClick",
                  _getServiceFromAccountIndex(aAcctIndex).shortName);
}

// "Compose new message" menu item
function webmailCompose(aAcctIndex) {
  // Build mailto string
  var mailtoString = _buildMailtoString("", "");

  // Open compose page
  FlockWebmailUtils.composeMessage(_accts[aAcctIndex], mailtoString);
  _metrics.report("WebMail-ComposeClick",
                   _getServiceFromAccountIndex(aAcctIndex).shortName);
}

// "Share current page" menu item
function webmailSharePage(aAcctIndex) {
  var subject = "";
  var body = ""; 

  // Get current page
  var win = _wm.getMostRecentWindow("navigator:browser");
  if (win) {
    subject = win.gBrowser.contentTitle;
    body = win.gBrowser.currentURI.spec;
  }

  // Build mailto string
  var mailtoString = _buildMailtoString(subject, body);

  // Open compose page
  FlockWebmailUtils.composeMessage(_accts[aAcctIndex], mailtoString);
  _metrics.report("WebMail-ShareClick",
                  _getServiceFromAccountIndex(aAcctIndex).shortName);
}

// "Check now" menu item
function webmailCheckNow(aAcctIndex) {
  _unreads[aAcctIndex] = this._getUnreadMessages(_accts[aAcctIndex], true);
  _metrics.report("WebMail-CheckNowClick",
                  _getServiceFromAccountIndex(aAcctIndex).shortName);
}

// Unread message menu item
function webmailOpenMessage(aAcctIndex, aMessageIndex) {
  var url = _unreads[aAcctIndex].messages[aMessageIndex].url;
  _openInNewTab(url);
  _accts[aAcctIndex].refreshUnreadMessageList();
  _metrics.report("WebMail-MessageClick",
                  _getServiceFromAccountIndex(aAcctIndex).shortName);
}

// Log in account menu item
function webmailLogInAccount(aAcctIndex) {
  if (_acUtils.ensureNoAuthenticatedAccountsBeforeLogin(_accts[aAcctIndex]))
  {
    webmailLogInService(_getServiceFromAccountIndex(aAcctIndex).shortName);
  }
}

// Log in service menu item
function webmailLogInService(aServiceName) {
  var svc = _getService(aServiceName);
  if (svc) {
    // Tell the Web Service Manager to automatically keep any accounts
    // created for this service during the next while...
    _cc["@flock.com/webservice-manager;1"]
      .getService(_ci.flockIWebServiceManager)
      .autoKeepAccountForService(svc.urn);
    _openInNewTab(svc.loginURL);
    _metrics.report("WebMail-AccountActivateClick",
                    svc.shortName);
  }
}

// "Upgrade to ..." menu item
function webmailUpgrade(aAcctIndex) {
  _openInNewTab(_upgrades[aAcctIndex].url);
  _metrics.report("WebMail-UpgradeClick",
                  _getServiceFromAccountIndex(aAcctIndex).shortName);
}

// "Use as primary webmail account" menu item
function webmailUseAsPrimary(aAcctIndex) {
  // Set this account as the new primary
  _accts[aAcctIndex].setAsPrimary();
  _metrics.report("WebMail-SetPrimary",
                  _getServiceFromAccountIndex(aAcctIndex).shortName);
}

// "Webmail settings..." menu item
function webmailOpenSettings() {
  // Opens the Advanced pane and select the Sharing tab
  openAdvancedPreferences("sharingTab");
}

// "Tell a friend" menu item
function webmailTellAFriend(aAcctIndex) {
  _openInNewTab(brandingUtilities.buildWebsiteUrl("flockbar",
                                                  "webmail",
                                                  "inviteAFriend",
                                                  "none"));
  _metrics.report("WebMail-TellFriend",
                  _getServiceFromAccountIndex(aAcctIndex).shortName);
}

// "Logout" menu item
function webmailLogout(aAcctIndex) {
  _accts[aAcctIndex].logout(null);
  _metrics.report("WebMail-Logout",
                  _getServiceFromAccountIndex(aAcctIndex).shortName);
}

/**************************************************************************
 * These functions are called internally
 **************************************************************************/

// Builds the "no configured accounts" menu
function _addNoAccounts(aPopupNode) {
  // No webmail accounts
  _addItem(aPopupNode, "flock.webmail.flyout.noAccounts", "header");

  // List available webmail services
  for each (var svc in _svcs) {
    var menuitem = _addItem(aPopupNode,
                            "flock.webmail.flyout.activateAccount",
                            "menuitem-iconic",
                            [svc.service.title],
                            1,
                            "webmailLogInService",
                            svc.name);
    menuitem.setAttribute("image", svc.service.icon);
  }

  // Separator
  _addSeparator(aPopupNode);

  // Settings...
  _addSettingsItem(aPopupNode);
}

// Builds a menu for the given account. If aMany is true, then the
// "use as primary webmail account" item is included.
function _addOneAccount(aPopupNode, aAcctIndex, aMany) {
  if (_accts[aAcctIndex].isAuthenticated()) {
    _addAccountFlyout(aPopupNode, aAcctIndex, aMany);
  } else {
    _addLoginMenu(aPopupNode, aAcctIndex, aMany);
  }
}

// Builds a menu for each account in the accounts array.
function _addManyAccounts(aPopupNode) {
  // Add account submenus
  for (var i = 0; i < _accts.length; i++) {
    _addAccountMenu(aPopupNode, i);
  }

  // Separator
  _addSeparator(aPopupNode);

  // Settings...
  _addSettingsItem(aPopupNode);
}

// Builds a menu allowing the user to log in to the given webmail account.
function _addLoginMenu(aPopupNode, aAcctIndex, aMany) {
  // Show the "logged out" or "session expired" header only if there is just one
  // account in the menu. If there are many accounts, then this info is already
  // displayed in the menu.
  if (!aMany) {
    // Retrieve the account's service
    var service = _getServiceFromAccountIndex(aAcctIndex);

    // Logged out or session expired
    _addItem(aPopupNode,
             _accts[aAcctIndex].isSessionExpired()
               ? "flock.webmail.flyout.currentlyExpired"
               : "flock.webmail.flyout.currentlyLoggedOut",
             "header",
             [service.title],
             1);
  }

  // Log in
  _addItem(aPopupNode,
           "flock.webmail.flyout.login",
           "",
           [_accts[aAcctIndex].getEmailAddress()],
           1,
           "webmailLogInAccount",
           aAcctIndex);

  if (!aMany) {
    // Separator
    _addSeparator(aPopupNode);

    // Settings...
    _addSettingsItem(aPopupNode);
  }
}

function _decodeWindowsCharacters(aStr) {
  if (!aStr) {
    return "";
  }
  var decodeAndConvert =
    function decodeAndConvert() {
      var val = atob(RegExp.$2);
      var ScriptableUnicodeConverter =
        Components.Constructor("@mozilla.org/intl/scriptableunicodeconverter",
                               "nsIScriptableUnicodeConverter");
      var converter = new ScriptableUnicodeConverter();
      converter.charset = RegExp.$1.toLowerCase();
      return converter.ConvertToUnicode(val) + converter.Finish();
    };
  return aStr.replace(/=\?(WINDOWS-\d+)\?B\?(.+?)=\?=/ig, decodeAndConvert);
}

function _decodeUTF16Characters(aStr) {
  if (!aStr) {
    return "";
  }
  var replaceFunc = function _webmailDecodeUTF16() {
                      return String.fromCharCode(RegExp.$1);
                    };
  return aStr.replace(/&#(\d+);/g, replaceFunc);
}

// Builds a menu allowing the user to access this webmail account. If
// aMany is true, then the "use as primary webmail account" item is
// included.
function _addAccountFlyout(aPopupNode, aAcctIndex, aMany) {
  var unreadMessageCount = _unreads[aAcctIndex].unreadMessageCount;

  // Inbox: if one account is being displayed, then show email address;
  // otherwise, for multipe accounts, just show "inbox"
  var inboxTitle = aMany ? _sb.GetStringFromName("flock.webmail.flyout.inbox")
                         : _accts[aAcctIndex].getEmailAddress();
  var menuitem = _addItem(aPopupNode,
                          "flock.webmail.flyout.unread",
                          aMany ? "header" : "menuitem-iconic header",
                          [inboxTitle, unreadMessageCount],
                          2,
                          "webmailOpenInbox",
                          aAcctIndex);
  // Extra handling for single account menu
  if (!aMany) {
    // Make sure unreads aren't cropped
    menuitem.setAttribute("crop", "center");
    // Add service icon
    menuitem.setAttribute("image",
                          _getServiceFromAccountIndex(aAcctIndex).icon);
  }

  // Are there unread messages?
  if (unreadMessageCount > 0) {
    // Always display a reasonable number of unread messages
    var displayedMessageCount = DEFAULT_DISPLAY_COUNT;
    if (_prefs.getPrefType("flock.webmail.displayUnreadMessageCount")) {
      displayedMessageCount =
          _prefs.getIntPref("flock.webmail.displayUnreadMessageCount");
      if (displayedMessageCount < MIN_DISPLAY_COUNT ||
          displayedMessageCount > MAX_DISPLAY_COUNT)
      {
        displayedMessageCount = DEFAULT_DISPLAY_COUNT;
        _prefs.setIntPref("flock.webmail.displayUnreadMessageCount",
                          displayedMessageCount);
      }
    }

    // Unread message list
    var i = 0;
    while ((i < displayedMessageCount) &&
           (i < _unreads[aAcctIndex].messages.length))
    {
      // Show unread message summary
      var msg = _unreads[aAcctIndex].messages[i];
      var subj = msg.subject;
      if (_accts[aAcctIndex].getCustomParam("isUTF16Encoded")) {
        subj = _decodeUTF16Characters(subj);
      }
      subj = _decodeWindowsCharacters(subj);
      _addItem(aPopupNode,
               "flock.webmail.flyout.messageSummary",
               "message",
               [msg.from, subj],
               2,
               "webmailOpenMessage",
               aAcctIndex,
               i);
      i++;
    }

    // Are there more unread messages than the ones we've displayed?
    if (unreadMessageCount > displayedMessageCount) {
      // More...
      _addItem(aPopupNode,
               "flock.webmail.flyout.more",
               "more",
               null,
               0,
               "webmailMore",
               aAcctIndex);
    }
  }

  // Separator
  _addSeparator(aPopupNode);

  // Check now
  var when = _getLastChecked(_unreads[aAcctIndex]);
  _addItem(aPopupNode,
           "flock.webmail.flyout.checkNow",
           "",
           [when],
           1,
           "webmailCheckNow",
           aAcctIndex);

  // Compose new message
  _addItem(aPopupNode,
           "flock.webmail.flyout.composeNew",
           "",
           null,
           0,
           "webmailCompose",
           aAcctIndex);

  // Share page
  // Only share the page if it is consumable by others
  var url = _wm.getMostRecentWindow("navigator:browser")
               .gBrowser.currentURI;
  var sharePage = null;
  if (url.schemeIs("http") || url.schemeIs("https") || url.schemeIs("ftp")) {
    sharePage = "webmailSharePage";
  }
  _addItem(aPopupNode,
           "flock.webmail.flyout.sharePage",
           "",
           null,
           0,
           sharePage,
           aAcctIndex);

  // Tell a friend
  _addItem(aPopupNode,
           "flock.webmail.flyout.tellAFriend",
           "",
           null,
           0,
           "webmailTellAFriend",
           aAcctIndex);

  // Can this account be upgraded?
  if (_upgrades[aAcctIndex].title) {
    // Separator
    _addSeparator(aPopupNode);

    // Upgrade to ...
    _addItem(aPopupNode,
             "flock.webmail.flyout.upgrade",
             "menuitem bold",
             [_upgrades[aAcctIndex].title],
             1,
             "webmailUpgrade",
             aAcctIndex);
  }

  // Separator
  _addSeparator(aPopupNode);

  // If there are multiple accounts configured, we'll want to include the
  // "Use as primary webmail account" checkbox item; otherwise, we need to
  // show the settings item.
  if (aMany) {
    // Use as primary
    menuitem = _addItem(aPopupNode,
                        "flock.webmail.flyout.useAsPrimary",
                        "",
                        null,
                        0,
                        "webmailUseAsPrimary",
                        aAcctIndex);
    menuitem.setAttribute("type", "checkbox");
    menuitem.setAttribute("checked", _accts[aAcctIndex].isPrimary());
  } else {
    // Settings...
    _addSettingsItem(aPopupNode);
  }

  // Add logout
  _addItem(aPopupNode,
           "flock.webmail.flyout.logout",
           "",
           null,
           0,
           "webmailLogout",
           aAcctIndex);
}

// Builds a menu which contains a submenu to the webmail account flyout
function _addAccountMenu(aPopupNode, aAcctIndex) {
  // Make new popup element
  var menupopup = document.createElement("menupopup");

  // Add account to popup element
  _addOneAccount(menupopup, aAcctIndex, true);

  // Make new menu
  var menu = document.createElement("menu");
  var label = _accts[aAcctIndex].getEmailAddress();
  if (!_accts[aAcctIndex].isAuthenticated()) {
    // Flag if the account is logged out or has expired
    label += " - "
          + _sb.GetStringFromName(_accts[aAcctIndex].isSessionExpired()
                                  ? "flock.webmail.flyout.sessionExpired"
                                  : "flock.webmail.flyout.loggedOut");
  }

  menu.setAttribute("label", label);
  if (_accts[aAcctIndex].isPrimary()) {
    menu.setAttribute("class", "menu-iconic bold");
  } else {
    menu.setAttribute("class", "menu-iconic");
  }
  menu.setAttribute("image", _getServiceFromAccountIndex(aAcctIndex).icon);

  // Add popup to menu
  menu.appendChild(menupopup);

  // Add menu to main popup
  aPopupNode.appendChild(menu);
}

function _addSettingsItem(aPopupNode) {
  _addItem(aPopupNode,
           "flock.webmail.flyout.webmailSettings",
           "settings",
           null,
           0,
           "webmailOpenSettings");
}

function _addSeparator(aPopupNode) {
  aPopupNode.appendChild(document.createElement("menuseparator"));
}

// Adds a menu item to the given menu. The menu item's label is retrieved from
// the webmail.properties file, using the given aName property name. If aFormat
// and aSize are provided, they are used to format the label. If there is no
// associated property, then aName is used as the label. If aFunc is present, it
// is the function called when the item is clicked. If it requires arguments,
// they will be in aArg1 and aArg2. If aFunc is null, then the item is disabled.
// aClass is the css class attribute.
function _addItem(aMenu, aName, aClass, aFormat, aSize, aFunc, aArg1, aArg2) {
  function onPropertyNameError() {
    var logger = _cc["@flock.com/logger;1"].createInstance(_ci.flockILogger);
    logger.init("webmailOverlay");
    logger.error("_addItem ERROR: unknown property name: " + aName);
  }
  var menuitem = document.createElement("menuitem");
  var label = "";
  if (aFormat && aSize) {
    try {
      label = _sb.formatStringFromName(aName, aFormat, aSize);
    } catch (ex) {
      onPropertyNameError();
    }
  } else {
    try {
      label = _sb.GetStringFromName(aName);
    } catch (ex) {
      onPropertyNameError();
    }
  }
  if (!label) {
    // Unknown property name so use the name as a label instead
    label = aName;
  }
  menuitem.setAttribute("label", label);
  menuitem.setAttribute("crop", "end");
  if (aClass) {
    menuitem.setAttribute("class", aClass);
  }
  if (aFunc) {
    // Must use != null here because we want to accept zero values
    var paramList = ((aArg1 != null) ? "'" + aArg1 + "'" : "")
                  + ((aArg2 != null) ? ", '" + aArg2 + "'" : "");
    menuitem.setAttribute("oncommand", aFunc + "(" + paramList + ");");
  } else {
    menuitem.setAttribute("disabled", "true");
  }
  aMenu.appendChild(menuitem);
  return menuitem;
}

// Opens the given URL in a new tab on the current browser window.
function _openInNewTab(aUrl) {
  var win = _wm.getMostRecentWindow("navigator:browser");
  if (win) {
    win.openUILinkIn(aUrl, "tab");
  }
}

// Return the service from the list of services given the name
// Returns null if it cannot find the matching service from the list
function _getService(aServiceName) {
  for each (var svc in _svcs) {
    if (svc.name == aServiceName) {
      return svc.service;
    }
  }
  return null;
}

// Retrieve the service associated with the given account object
function _getServiceFromAccount(aAccount) {
  if (!aAccount) {
    return null;
  }

  // TODO XXX: We need to retrieve the account's service in this manner for
  //           now to avoid major structural changes.  We will need to
  //           do some major refactoring of this file in the very near future.
  var name = aAccount.urn;
  name = name.match(/urn:flock:(.+):account:/)[1];
  return _getService(name);
}

// Retrieve the service associated with the given account index
function _getServiceFromAccountIndex(aAcctIndex) {
  return _getServiceFromAccount(_accts[aAcctIndex]);
}

// If the given account returns an invalid unread messages object,
// return an empty unread messages object
function _getUnreadMessages(aAccount, aCheckNow) {
  var unreadMessages = aAccount.getUnreadMessages(aCheckNow);
  try {
    var nsJSON = _cc["@mozilla.org/dom/json;1"].createInstance(_ci.nsIJSON);
    unreadMessages = nsJSON.decode(unreadMessages);
  } catch (ex) {
    unreadMessages = {
      lastCheckedDate: Date.now(),
      unreadMessageCount: 0,
      messages: []
    };
  }
  return unreadMessages;
}

function _buildMailtoString(aSubject, aBody) {
  // Add breadcrumb if required
  var subject = aSubject;
  var body = aBody + _richDND.getBreadcrumb("plain");

  // Build mailto string
  return "subject=" + encodeURIComponent(subject) +
         "&body=" + encodeURIComponent(body);
}

function _getLastChecked(aUnreadMessages) {
  var when = "";

  // Get milliseconds since last check
  var lastChecked = Date.now() - aUnreadMessages.lastCheckedDate;

  // Vary text depending on units and length of time
  if (lastChecked < 2000) {
    // One second
    when = _sb.GetStringFromName("flock.webmail.flyout.checkNow.oneSecond");
  } else if (lastChecked < 60000) {
    // Many seconds
    when = _sb.formatStringFromName("flock.webmail.flyout.checkNow.manySeconds",
                                    [parseInt(lastChecked / 1000)],
                                    1);
  } else if (lastChecked < 120000) {
    // One minute
    when = _sb.GetStringFromName("flock.webmail.flyout.checkNow.oneMinute");
  } else {
    // Many minutes
    when = _sb.formatStringFromName("flock.webmail.flyout.checkNow.manyMinutes",
                                    [parseInt(lastChecked / 60000)],
                                    1);
  }
  return when;
}

var _serviceStateChangeHandler = {
  handleEvent: function handleEventStateChange(aEvent) {
    if (aEvent && aEvent.target) {
      var keyVal = aEvent.target.value;
      // Populate the fields with data
      if (_shareFlockCredentials[keyVal] &&
          _shareFlockCredentials[keyVal].user)
      {
        var win = _wm.getMostRecentWindow("navigator:browser");
        if (win) {
          var doc = win.getBrowser().contentDocument;
          doc.getElementById("aUsername").value =
                                         _shareFlockCredentials[keyVal].user;
          doc.getElementById("aPassword").value =
                                         _shareFlockCredentials[keyVal].pass;
        }
      }
    }
  }
};

function _computeCompareURI(aBrowser, aIoService) {
  // If the URI in question contains a query string, remove
  // these appended variables for a direct comparison
  var urlCompare = aBrowser.currentURI.spec;
  if (urlCompare.lastIndexOf("?") == -1) {
    return aBrowser.currentURI;
  }
  urlCompare = urlCompare.substring(0, urlCompare.lastIndexOf("?"));
  return aIoService.newURI(urlCompare, null, null);
}

function _populateShareForm() {
  // Initialize webmail button after window has loaded
  if (_acUtils.primaryAccountIsHot()) {
    document.getElementById("webmail-button").setAttribute("hasNew", "true");
  }
  var win = _wm.getMostRecentWindow("navigator:browser");
  if (win) {
    var browser = win.getBrowser();
    var doc = browser.contentDocument;
    // If we arrive on the Share Flock page, let's see if we can
    // prepopulate the user credentials for ease of use.
    var ioService = _cc["@mozilla.org/network/io-service;1"]
                    .getService(_ci.nsIIOService);
    var url = ioService.newURI(SHARE_FLOCK_URL, null, null);
    var urlBrowser = _computeCompareURI(browser, ioService);
    if (url.equals(urlBrowser)) {
      // Ensure the services array has been initialized
      if (_svcs.length == 0) {
        var svcsEnum =
            _acUtils.getWebServicesByInterface("flockIMailWebService");
        while (svcsEnum.hasMoreElements()) {
          var svc = svcsEnum.getNext();
          _svcs.push({name: svc.shortName, service: svc});
        }
      }
      for each (var svc in _svcs) {
        // Determine if the services are logged in or not
        var authedUrn = svc.service.getAuthenticatedAccount();
        if (authedUrn) {
          var acctId = svc.service.getAccount(authedUrn).getParam("accountId");
          var pwdObj = _acUtils.getPassword(svc.service.urn + ":" + acctId);
          if (!_shareFlockCredentials[svc.name]) {
            _shareFlockCredentials[svc.name] = {};
          }
          _shareFlockCredentials[svc.name].user = pwdObj.username;
          _shareFlockCredentials[svc.name].pass = pwdObj.password;
         }
      }
      // This element's value is update whenever a service button is clicked
      var shareService = doc.getElementById("aShareService");
      if (shareService) {
        shareService.addEventListener("DOMAttrModified",
                                      _serviceStateChangeHandler,
                                      false);
        browser.contentWindow.addEventListener("unload",
                                               _removeShareFormObservers,
                                               true);
      }
    }
  }
}

function _removeShareFormObservers(aEvent) {
  aEvent.stopPropagation();
  // Remove user credentials from memory
  _shareFlockCredentials = [];
  var win = _wm.getMostRecentWindow("navigator:browser");
  if (win) {
    try {
      var browser = win.getBrowser();
      var doc = browser.contentDocument;
      var ioService = _cc["@mozilla.org/network/io-service;1"]
                      .getService(_ci.nsIIOService);
      var url = ioService.newURI(SHARE_FLOCK_URL, null, null);
      var urlBrowser = _computeCompareURI(browser, ioService);
      if (url.equals(urlBrowser)) {
        var shareService = doc.getElementById("aShareService");
        if (shareService) {
          shareService.removeEventListener("DOMAttrModified",
                                           _serviceStateChangeHandler,
                                           false);
        }
      }
    } catch (ex) {
      var logger = _cc["@flock.com/logger;1"].createInstance(_ci.flockILogger);
      logger.init("_removeShareFormObservers exception: " + ex.message);
    }
  }
}
