// vim: ts=2 sw=2 expandtab cindent
//
// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2008
// 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

const CC = Components.classes;
const CI = Components.interfaces;

Components.utils.import("resource:///modules/webmailBackend.jsm");

const ACCOUNTUTILS_CID        = Components.ID("{d84bce30-4ce5-11db-b0de-0800200c9a66}");
const ACCOUNTUTILS_CONTRACTID = "@flock.com/account-utils;1";

const OBS = Components.classes["@mozilla.org/observer-service;1"]
                      .getService(Components.interfaces.nsIObserverService);


const PREF_CONFIRM_LOGOUT = "flock.accountsSidebar.confirmLogouLogin";
const ACCOUNT_PROPERTIES = "chrome://flock/locale/services/accountsSidebar.properties";
const PROP_STR_TITLE = "flock.services.accountsSidebar.logoutLogin.title";
const PROP_STR_DESC = "flock.services.accountsSidebar.logoutLogin.description";
const PROP_STR_DONT_SHOW = "flock.services.accountsSidebar.logoutLogin.dontShow";

// ===================================================
// ========== BEGIN flockAccountUtils class ==========
// ===================================================

const ACCOUNTUTILS_INTERFACES = [
  Components.interfaces.nsISupports,
  Components.interfaces.nsIObserver,
  Components.interfaces.flockIAccountUtils,
  Components.interfaces.flockIWebmailMailtoHandler,
];

function flockAccountUtils() {
  this._logger = Components.classes['@flock.com/logger;1'].createInstance(Components.interfaces.flockILogger);
  this._logger.init('flockAccountUtils');
  this._logger.info('Created Account Utility Object');

  this.mSetupHasRun = false;
  OBS.addObserver(this, "flock-data-ready", false);

  // mTempPasswords is an associative array of temporary passwords
  this.mTempPasswords = [];
}


// BEGIN nsISupports interface
flockAccountUtils.prototype.QueryInterface =
function flockAccountUtils_QueryInterface(aIID)
{
  var interfaces = ACCOUNTUTILS_INTERFACES;
  for (var i in interfaces) {
    if (aIID.equals(interfaces[i])) {
      return this;
    }
  }
  throw Components.results.NS_ERROR_NO_INTERFACE;
}
// END nsISupports interface


// BEGIN nsIObserver interface
flockAccountUtils.prototype.observe =
function flockAccountUtils_observe(aSubject, aTopic, aState)
{
  switch (aTopic) {
    case 'flock-data-ready':
      OBS.removeObserver(this, "flock-data-ready");
      this.setup();
      break;
  }
}
// END nsIObserver interface


// BEGIN flockIAccountUtils interface
flockAccountUtils.prototype.activatedAccountExists =
function flockAccountUtils_activatedAccountExists(aServiceURN)
{
  this._logger.info("{flockIAccountUtils}.activatedAccountExists('"+aServiceURN+"')");
  this.setup();
  var svc = this._coop.get(aServiceURN);
  if (!svc) {
    throw Components.results.NS_ERROR_UNEXPECTED;
  }
  var accounts = this._coop.Account.find({service: svc, isTransient: false});
  return (accounts.length > 0);
}

flockAccountUtils.prototype.clearTempPassword =
function flockAccountUtils_clearTempPassword(aKey)
{
  this._logger.info("{flockIAccountUtils}.clearTempPassword('"+aKey+"')");
  this.mTempPasswords[aKey] = undefined;
}

flockAccountUtils.prototype.ensureNoAuthenticatedAccountsBeforeLogin =
function flockAccountUtils_ensureNoAuthenticatedAccountsBeforeLogin(aAccountURN)
{
  this._logger.info("{flockIAccountUtils}." +
                    "ensureNoAuthenticatedAccountsBeforeLogin('" +
                    aAccountURN + "')");
  this.setup();

  var c_acct = this._coop.get(aAccountURN);
  if (!c_acct || c_acct.isAuthenticated) {
    // If there's no account or it's already logged in, we're done
    return false;
  }

  // Is there another account already logged in for this service?
  var service = CC[c_acct.serviceId].getService(CI.flockIWebService);
  var authenticatedAccount = service.getAuthenticatedAccount();
  if (!authenticatedAccount) {
    // No authenticated account, so we're done
    return true;
  }

  // An authenticated account exists, so it needs to be logged out.
  var doLogout = true;

  // Unless the user has opted out, confirm that they want to log out of the
  // currently authenticated account
  var prefs = CC["@mozilla.org/preferences-service;1"]
              .getService(CI.nsIPrefBranch);
  if (!prefs.getPrefType(PREF_CONFIRM_LOGOUT) ||
      prefs.getBoolPref(PREF_CONFIRM_LOGOUT))
  {
    var sb = CC["@mozilla.org/intl/stringbundle;1"]
             .getService(CI.nsIStringBundleService)
             .createBundle(ACCOUNT_PROPERTIES);
    var title = sb.formatStringFromName(PROP_STR_TITLE, [service.title], 1);
    var newAcctName = service.getAccount(aAccountURN).getParam("name");
    var oldAcctName = authenticatedAccount.getParam("name");
    var desc = sb.formatStringFromName(PROP_STR_DESC,
                                       [newAcctName, oldAcctName],
                                       2);
    var dontShow = sb.GetStringFromName(PROP_STR_DONT_SHOW);
    var checkResult = {value: true};
    // Do they want to log out?
    var win = CC["@mozilla.org/appshell/window-mediator;1"]
              .getService(CI.nsIWindowMediator)
              .getMostRecentWindow("navigator:browser");
    doLogout = CC["@mozilla.org/embedcomp/prompt-service;1"]
               .getService(CI.nsIPromptService)
               .confirmCheck(win.content, title, desc, dontShow, checkResult);
    if (!checkResult.value) {
      // They've opted out from future confirmations
      prefs.setBoolPref(PREF_CONFIRM_LOGOUT, false);
    }
  }

  if (doLogout) {
    authenticatedAccount.logout(null);
  }

  return doLogout;
}

flockAccountUtils.prototype.ensureOnlyAuthenticatedAccount =
function flockAccountUtils_ensureOnlyAuthenticatedAccount(aAccountURN)
{
  this._logger.info("{flockIAccountUtils}.ensureOnlyAuthenticatedAccount('"+aAccountURN+"')");
  this.setup();
  var alreadyAuthenticated = false;
  var acctCoopObj = this._coop.get(aAccountURN);
  if (acctCoopObj) {
    var accounts = this._coop.Account.find({serviceId: acctCoopObj.serviceId});
    for (var i = 0; i < accounts.length; i++) {
      // Here I'm trying not to change the RDF unless I actually need to, in
      // order to avoid RDF change notifications from being fired
      // unnecessarily
      if (accounts[i].id() == aAccountURN) {
        if (!accounts[i].isAuthenticated) {
          accounts[i].isAuthenticated = true;
        } else {
          alreadyAuthenticated = true;
        }
      } else {
        if (accounts[i].isAuthenticated) {
          accounts[i].isAuthenticated = false;
        }
      }
    }
  }
  return alreadyAuthenticated;
}

flockAccountUtils.prototype.getAccountURNById =
function flockAccountUtils_getAccountURNById(aServiceURN, aAccountID)
{
  this._logger.info("{flockIAccountUtils}.getAccountURNById('"+aServiceURN+"', '"+aAccountID+"')");
  this.setup();
  var svc = this._coop.get(aServiceURN);
  if (!svc) {
    throw Components.results.NS_ERROR_UNEXPECTED;
  }
  var accounts = this._coop.Account.find({service: svc, accountId: aAccountID});
  this._logger.info(" - found "+accounts.length+" matching account (should be exactly 1)");
  if (accounts.length > 0) {
    this._logger.info(" - account urn = "+accounts[0].id());
    return accounts[0].id();
  }
  return null;
}

flockAccountUtils.prototype.getAccountURNByLogin =
function flockAccountUtils_getAccountURNByLogin(aServiceURN, aLogin)
{
  this._logger.info(".getAccountURNByLogin('" + aServiceURN + "', '"
                                              + aLogin + "')");
  // First see if it's in the temporary passwords
  for (var key in this.mTempPasswords) {
    if (key.length && key.indexOf(aServiceURN) != -1) {
      var p = this.mTempPasswords[key];
      if (p && p.user == aLogin) {
        var accountID = key.substring(aServiceURN.length + 1);
        if (accountID && accountID.length) {
          this._logger.debug(".getAccountURNByLogin() - found temporary "
                             + "password with account ID = " + accountID);
          return this.getAccountURNById(aServiceURN, accountID);
        }
      }
    }
  }
  // Next try the password manager
  var pm = Components.classes["@mozilla.org/passwordmanager;1"]
                     .getService(Components.interfaces.nsIPasswordManager);
  var en = pm.enumerator;
  while (en.hasMoreElements()) {
    var p = en.getNext();
    p.QueryInterface(Components.interfaces.nsIPassword);
    if (p.user == aLogin && (p.host.indexOf(aServiceURN)==0)) {
      var accountID = p.host.substring(aServiceURN.length+1);
      this._logger.debug(".getAccountURNByLogin() - found password manager "
                         + "entry with account ID = " + accountID);
      return this.getAccountURNById(aServiceURN, accountID);
    }
  }
  return null;
}

flockAccountUtils.prototype._getAccount =
function flockAccountUtils__getAccount(aAccountUrn)
{
  var c_acct = this._coop.get(aAccountUrn);
  var service = CC[c_acct.serviceId].getService(CI.flockIWebService);
  return service.getAccount(aAccountUrn);
}

flockAccountUtils.prototype.getAccountsForService =
function flockAccountUtils_getAccountsForService(aServiceContractID)
{
  this._logger.info("{flockIAccountUtils}.getAccountsForService('"+aServiceContractID+"')");
  this.setup();
  var accountsEnum = {
    arr : [],
    QueryInterface : function(iid) {
      if (!iid.equals(Components.interfaces.nsISupports) &&
          !iid.equals(Components.interfaces.nsISimpleEnumerator))
      {
        throw Components.results.NS_ERROR_NO_INTERFACE;
      }
      return this;
    },
    hasMoreElements : function() {
      return (this.arr.length > 0);
    },
    getNext : function() {
      return this.arr.pop();
    }
  };
  var accounts = this._coop.Account.find({serviceId: aServiceContractID});
  var svc = Components.classes[aServiceContractID]
                      .getService(Components.interfaces.flockIWebService);
  for (var i = accounts.length - 1; i >= 0; i--) {
    accountsEnum.arr.push(svc.getAccount(accounts[i].id()));
  }
  return accountsEnum;
}

flockAccountUtils.prototype.getWebServicesByInterface =
function flockAccountUtils_getWebServicesByInterface(aWebServiceInterface)
{
  var servicesEnum = {
    _arr: [],
    QueryInterface: function se_QueryInterface(aIid) {
      if (aIid.equals(Components.interfaces.nsISupports) ||
          aIid.equals(Components.interfaces.nsISimpleEnumerator))
      {
        return this;
      }
      throw Components.results.NS_ERROR_NO_INTERFACE;
    },
    hasMoreElements: function se_hasMoreElements() {
      return (this._arr.length > 0);
    },
    getNext: function se_getNext() {
      return this._arr.pop();
    }
  };

  var catMgr = Components.classes["@mozilla.org/categorymanager;1"]
                         .getService(Components.interfaces.nsICategoryManager);
  var catsEnum = catMgr.enumerateCategory("flockWebService");
  while (catsEnum.hasMoreElements()) {
    var entry = catsEnum.getNext()
                        .QueryInterface(Components.interfaces.nsISupportsCString);
    var contractId = catMgr.getCategoryEntry("flockWebService", entry.data);
    var svc = Components.classes[contractId]
                        .getService(Components.interfaces.flockIWebService);
    if (svc instanceof Components.interfaces[aWebServiceInterface]) {
      servicesEnum._arr.push(svc);
    }
  }

  return servicesEnum;
}

flockAccountUtils.prototype.getAccountsByInterface =
function flockAccountUtils_getAccountsByInterface(aServiceInterface)
{
  var accountsEnum = {
    arr : [],
    QueryInterface : function(iid) {
      if (!iid.equals(Components.interfaces.nsISupports) &&
          !iid.equals(Components.interfaces.nsISimpleEnumerator))
      {
        throw Components.results.NS_ERROR_NO_INTERFACE;
      }
      return this;
    },
    hasMoreElements : function() {
      return (this.arr.length > 0);
    },
    getNext : function() {
      return this.arr.pop();
    }
  };

  var acctRoot = this._coop.get("http://flock.com/rdf#AccountsRoot");
  var c_accounts = acctRoot.children.enumerate();
  while (c_accounts.hasMoreElements()) {
    var c_account = c_accounts.getNext();
    if (!c_account) continue; // getNext() can return NULL when hasMoreElements() is TRUE.
    if (Components.classes[c_account.serviceId]) {
      var svc = Components.classes[c_account.serviceId]
                          .getService(Components.interfaces.flockIWebService);
      if (svc instanceof Components.interfaces[aServiceInterface]) {
        accountsEnum.arr.push(svc.getAccount(c_account.id()));
      }
    } else {
      this._logger.debug( "No service found for account '"+c_account.name +
                          "' with serviceId: "+c_account.serviceId );
    }
  }

  return accountsEnum;
}

flockAccountUtils.prototype.doAccountsExist =
function flockAccountUtils_doAccountsExist()
{
  var accounts = this.getAllAccounts();
  if (accounts.hasMoreElements()) {
    return true;
  } else {
    return false;
  }
}

flockAccountUtils.prototype.isTransient =
function flockAccountUtils_isTransient(aURN)
{
  return this._coop.get(aURN).isTransient;
}

flockAccountUtils.prototype.getElementByAttribute =
function flockAccountUtils_getElementByAttribute(aAncestorElement, aAttributeName, aAttributeValue)
{
  //this._logger.info("{flockIAccountUtils}.getElementByAttribute("+aAncestorElement+", '"+aAttributeName+"', '"+aAttributeValue+"')");
  for (var i = 0; i < aAncestorElement.childNodes.length; i++) {
    var childNode = aAncestorElement.childNodes.item(i);
    try {
      var childElem = childNode.QueryInterface(Components.interfaces.nsIDOMHTMLElement);
      if (childElem.getAttribute(aAttributeName) == aAttributeValue) {
        return childElem;
      }
      var recursiveResult = this.getElementByAttribute(childElem, aAttributeName, aAttributeValue);
      if (recursiveResult) {
        return recursiveResult;
      }
    } catch (ex) {
      // This just means that childNode is not an Element
    }
  }
  return null;
}

flockAccountUtils.prototype.getServiceIDForAccountURN =
function flockAccountUtils_getServiceIDForAccountURN(aAccountURN)
{
  var account = this._coop.get(aAccountURN);
  return account.serviceId;
}

flockAccountUtils.prototype.createAccount =
function flockAccountUtils_createAccount(aService,
                                         aAccountId,
                                         aUsername,
                                         aUrl,
                                         aIsTransient)
{
  aService.QueryInterface(Components.interfaces.nsIClassInfo);
  var coopService = this._coop.get(aService.urn);

  // Add the account
  var accountURN = "urn:flock:"
                 + aService.shortName
                 + ":account:"
                 + aAccountId;
  var coopAccount = this._coop.get(accountURN);
  if (!coopAccount) {
    var coopAccount = new this._coop.Account(accountURN, {
      name: aUsername,
      serviceId: aService.contractId,
      service: coopService,
      accountId: aAccountId,
      favicon: aService.icon,
      isTransient: aIsTransient,
      URL: aUrl ? aUrl : aService.url
    });
    this._coop.accounts_root.children.addOnce(coopAccount);
  } else {
    this._cleanupMigratedAccountParams(coopAccount);
  }
  if (aService instanceof CI.flockISocialWebService) {
    // Create the friends list
    var friendsListUrn = accountURN + ":friends";
    if (!this._coop.get(friendsListUrn)) {
      var friendsList = new this._coop.FriendsList(
        friendsListUrn,
        {
          account: coopAccount
        });
      coopAccount.friendsList = friendsList;
    }
  }
  return accountURN;
}

flockAccountUtils.prototype._cleanupMigratedAccountParams =
function flockAccountUtils__cleanupMigratedAccountParams(aCoopAccount)
{
  aCoopAccount.removeParam("fbPokes");
  aCoopAccount.removeParam("fbFriendRequests");
  aCoopAccount.removeParam("fbGroupInvites");
  aCoopAccount.removeParam("fbEventInvites");
  aCoopAccount.removeParam("flickr_comments");
  aCoopAccount.removeParam("flickr_nsid");
  aCoopAccount.removeParam("flickr_id");
  aCoopAccount.removeParam("flickr_username");
  aCoopAccount.removeParam("flickr_fullname");
  aCoopAccount.removeParam("flickr_comments_timespan"); 
}
flockAccountUtils.prototype.addCustomParamsToAccount =
function flockAccountUtils_addCustomParamsToAccount(aList, aAccountUrn)
{
  this._logger.info(".addCustomParamsToAccount('" + aAccountUrn + "')");

  var c_acct = this._coop.get(aAccountUrn);
  if (!c_acct) {
    var msg = aAccountUrn + " does not exist";
    this._reportError("addCustomParamsToAccount", msg);
  }
  var accountParamListUrn = aAccountUrn + ":accountParam";
  if (!c_acct.accountParamList) {
    var accountParamList = new this._coop.AccountParamList(
      accountParamListUrn,
      {
        account: c_acct
      });
    c_acct.accountParamList = accountParamList;
  }
  var enumerate = aList.enumerator;
  var account = this._getAccount(aAccountUrn);
  while (enumerate.hasMoreElements()) {
    var el = enumerate.getNext();
    el.QueryInterface(CI.nsIProperty);
    // use the setCustomParam so not to add duplicates
    account.setCustomParam(el.name, el.value);
  }
}

flockAccountUtils.prototype.getAccountCustomParam =
function flockAccountUtils_getAccountCustomParam(aAccountUrn, aParamName)
{
  this._logger.info(".getAccountCustomParam('" + aAccountUrn + "')");
  var account = this._getAccount(aAccountUrn);
  return account.getCustomParam(aParamName);
}

flockAccountUtils.prototype.setAccountCustomParamValue =
function flockAccountUtils_setAccountCustomParamValue(aAccountUrn,
                                                      aParamName,
                                                      aValue)
{
  this._logger.info(".setAccountCustomParamValue('" + aAccountUrn + "','"
                                                    + aParamName + "','"
                                                    + aValue + "')");
  var account = this._getAccount(aAccountUrn);
  account.setCustomParam(aParamName, aValue);
}

flockAccountUtils.prototype.getAllAccountCustomParams =
function flockAccountUtils_getAllAccountCustomParams(aAccountUrn) {
  this._logger.info(".getAllAccountCustomParams('" + aAccountUrn + "')");
  var account = this._getAccount(aAccountUrn);
  return account.getAllCustomParams();
}


flockAccountUtils.prototype.removeAccount =
function flockAccountUtils_removeAccount(aAccountUrn)
{
  this._logger.info("{flockIAccountUtils}.removeAccount('"+aAccountUrn+"')");
  var c_acct = this._coop.get(aAccountUrn);

  // Remove associated passwords
  if (c_acct.accountId && c_acct.serviceId && Components.classes[c_acct.serviceId]) {
    var svc = Components.classes[c_acct.serviceId]
                        .getService(Components.interfaces.flockIWebService);
    if (svc) {
      // Clear the internal password entry that some services use
      var internalPWhost = svc.urn+":"+c_acct.accountId;
      this.clearTempPassword(internalPWhost);
      this.removeAllPasswordsForHost(internalPWhost);
      // Clear the user-created password entry for this account, if we can
      // positively identify one
      if (svc instanceof Components.interfaces.flockIManageableWebService) {
        svc.QueryInterface(Components.interfaces.flockIManageableWebService);
        var pm = Components.classes["@mozilla.org/passwordmanager;1"]
                           .getService(Components.interfaces.nsIPasswordManager);
        var en = pm.enumerator;
        var domains = this._coop.get(svc.urn).domains.split(",");
        while (en.hasMoreElements()) {
          var p = en.getNext();
          p.QueryInterface(Components.interfaces.nsIPassword);
          for (var i = 0; i < domains.length; i++) {
            var domain = domains[i];
            var index = p.host.indexOf("."+domain);
            if ( (p.host == "http://"+domain) || (p.host == "https://"+domain) ||
                ((index != -1) && (index + domain.length + 1 == p.host.length)) )
            {
              // The host for this password matches the service domain
              try {
                if (p.user == c_acct.username || p.user == c_acct.accountId) {
                  // And the username matches the account, so remove this entry
                  pm.removeUser(p.host, p.user);
                }
              } catch (ex) {
                this._logger.debug("ERROR removing password {host: "+p.host+" user: "+c_acct.username+"}");
              }
            }
          }
        }
      }
    }
  }

  // Remove all of this Account's children which are Streams or Blogs
  var acctChildren = c_acct.children.enumerate();
  while (acctChildren.hasMoreElements()) {
    var acctChild = acctChildren.getNext();
    if (acctChild.isInstanceOf(this._coop.Stream)) {
      this._logger.info("    removing account Stream");
      var stream = acctChild;
      // Remove all Stream items
      var streamChildren = stream.children.enumerate();
      while (streamChildren.hasMoreElements()) {
        var streamChild = streamChildren.getNext();
        var parents = streamChild.getParents();
        for (var j = 0; j < parents.length; j++) {
          parents[j].children.remove(streamChild);
        }
        streamChild.destroy();
      }
      // Remove Stream from all parents
      var parents = stream.getParents();
      for (var k = 0; k < parents.length; k++) {
        parents[k].children.remove(stream);
      }
      stream.destroy();
    }
    else if (acctChild.isInstanceOf(this._coop.Blog)) {
      this._logger.info("    removing account Blog");
      var parents = acctChild.getParents();
      for (var k = 0; k < parents.length; k++) {
        parents[k].children.remove(acctChild);
      }
      acctChild.destroy();
    }
  }
  
  var friendsList = c_acct.friendsList;
  if (friendsList) {
    if (friendsList.children) {
      var friendsEnum = friendsList.children.enumerate();
      // Remove friends
      while (friendsEnum.hasMoreElements()) {
        var identity = friendsEnum.getNext();
        friendsList.children.remove(identity);
        this._logger.debug("Friend " + identity.accountId
                                     + " has been deleted from RDF");
        identity.destroy();
      }
    }
    // Remove friends list
    friendsList.destroy();
  }

  var accountParamList = c_acct.accountParamList;
  if (accountParamList) {
    if (accountParamList.children) {
      var accountParamEnum = accountParamList.children.getAllParams();
      // Remove friends
      var enumerate = accountParamEnum.enumerator;
      while (enumerate.hasMoreElements()) {
        var el = enumerate.getNext();
        el.QueryInterface(CI.nsIProperty);
        this._logger.info("removeAccount coop " + el.name);
        accountParamList.children.removeKey(el.name);
      }
    }
    // Remove friends list
    accountParamList.destroy();
  }

  // Remove this Account from all of its parents
  var parents = c_acct.getParents();
  this._logger.info("  account "+aAccountUrn+" has "+parents.length+" parents");
  for (var i = 0; i < parents.length; i++) {
    this._logger.info("   removing from parent "+i);
    parents[i].children.remove(c_acct);
  }
  this._logger.info("  destroying account "+aAccountUrn);
  c_acct.destroy();
}

flockAccountUtils.prototype.removeAllAccounts =
function flockAccountUtils_removeAllAccounts()
{
  var c_accounts = this.getAllAccounts();
  while (c_accounts.hasMoreElements()) {
    var c_acct = c_accounts.getNext();
    var svc = Components.classes[c_acct.serviceId]
                        .getService(Components.interfaces.flockIWebService);
    var acct = svc.getAccount(c_acct.id());
    acct.logout(null);
    svc.removeAccount(c_acct.id());
  }
}

flockAccountUtils.prototype.getCookie =
function flockAccountUtils_getCookie(aHost, aCookieName)
{
  var ios = Components.classes["@mozilla.org/network/io-service;1"]
                      .getService(Components.interfaces.nsIIOService);
  var cookieSvc = Components.classes["@mozilla.org/cookieService;1"]
                            .getService(Components.interfaces.nsICookieService);
  var uri = ios.newURI(aHost, null, null);
  var cookieString = cookieSvc.getCookieString(uri, null);
  if (!cookieString) { return null; }
  var index = cookieString.indexOf(aCookieName + "=");  
  if (index == -1) { return null; }
  index = cookieString.indexOf("=", index) + 1;  
  var endstr = cookieString.indexOf(";", index);  
  if (endstr == -1) {
    endstr = cookieString.length;  
  }
  var cookieValue = unescape(cookieString.substring(index, endstr));
  this._logger.info("{flockIAccountUtils}.getCookie('"+aHost+"', '"+aCookieName+"'): value = "+cookieValue);
  return cookieValue;
}

flockAccountUtils.prototype.getCookieValue =
function flockAccountUtils_getCookieValue(aHost, aName, aPath)
{
  this._logger.info(".getCookieValue('" + aHost + "', '"
                                        + aName + "', '"
                                        + aPath + "')");
  var cookieMgr = CC["@mozilla.org/cookiemanager;1"]
                  .getService(CI.nsICookieManager);
  var cookies = cookieMgr.enumerator;
  while (cookies.hasMoreElements()) {
    var cookie = cookies.getNext()
                        .QueryInterface(CI.nsICookie);
    if ((cookie.host == aHost) &&
        (cookie.name == aName) &&
        (cookie.path == aPath))
    {
      return cookie.value;
    }
  }
  return null;
}

flockAccountUtils.prototype.getActiveBookmarkAccount =
function flockAccountUtils_getActiveBookmarkAccount()
{
  this._logger.info("{flockIAccountUtils}.getActiveBookmarkAccountURU()");
  var accounts = this._coop.Account.find({isTransient: false, isAuthenticated: true});
  for (var i = 0; i < accounts.length; i++) {
    try {
      var svn = Components.classes[accounts[i].serviceId]
                          .getService(Components.interfaces.flockIBookmarkWebService);
    } catch (ex) {
      continue;
    }
    svn.QueryInterface(Components.interfaces.flockIWebService);
    var account = svn.getAccount(accounts[i].id());
    account.QueryInterface(Components.interfaces.flockIBookmarkWebServiceAccount);
    return account;
  }
  return null;
}

flockAccountUtils.prototype.getFirstAuthenticatedAccountForService =
function flockAccountUtils_getFirstAuthenticatedAccountForService(aServiceContractID)
{
  this._logger.info("{flockIAccountUtils}.getFirstAuthenticatedAccountForService('"+aServiceContractID+"')");
  this.setup();
  var accounts = this._coop.Account.find({serviceId: aServiceContractID, isAuthenticated: true});
  if (accounts.length) {
    return accounts[0].id();
  }
  return null;
}

flockAccountUtils.prototype.getPassword =
function flockAccountUtils_getPassword(aKey)
{
  this._logger.info("{flockIAccountUtils}.getPassword('"+aKey+"')");
  var pw = this.getTempPassword(aKey);
  if (!pw) {
    pw = this.getFirstPasswordForHost(aKey);
  }
  return pw;
}

flockAccountUtils.prototype.getPasswordForAnyHost =
function flockAccountUtils_getPasswordForAnyHost(aDomain)
{
  this._logger.info("{flockIAccountUtils}.getPasswordForAnyHost('"+aDomain+"')");
  var pm = Components.classes["@mozilla.org/passwordmanager;1"]
                     .getService(Components.interfaces.nsIPasswordManager);
  var en = pm.enumerator;
  while (en.hasMoreElements()) {
    var p = en.getNext();
    p.QueryInterface(Components.interfaces.nsIPassword);
    this._logger.info(" comparing to host: "+p.host);
    if ((p.host == aDomain)
        || (p.host == "http://"+aDomain)
        || (p.host == "https://"+aDomain))
    {
      // found a password for this exact domain
      return p;
    }
    var index = p.host.indexOf("."+aDomain);
    if ((index != -1) && (index + aDomain.length + 1 == p.host.length)) {
      // matched a host in the specified domain
      return p;
    }
  }
  return null;
}

flockAccountUtils.prototype.getFirstPasswordForHost =
function flockAccountUtils_getFirstPasswordForHost(aHost)
{
  this._logger.info("{flockIAccountUtils}.getFirstPasswordForHost('"+aHost+"')");
  var pm = Components.classes["@mozilla.org/passwordmanager;1"]
                     .getService(Components.interfaces.nsIPasswordManager);
  var en = pm.enumerator;
  while (en.hasMoreElements()) {
    var p = en.getNext();
    p.QueryInterface(Components.interfaces.nsIPassword);
    if (p.host == aHost) {
      return p;
    }
  }
  return null;
}

flockAccountUtils.prototype.getTempPassword =
function flockAccountUtils_getTempPassword(aKey)
{
  this._logger.info("{flockIAccountUtils}.getTempPassword('"+aKey+"')");
  return this.mTempPasswords[aKey];
}

flockAccountUtils.prototype.makeTempPasswordPermanent =
function flockAccountUtile_makeTempPasswordPermanent(aKey)
{
  this._logger.info("{flockIAccountUtils}.makeTempPasswordPermanent('"+aKey+"')");
  var temp = this.getTempPassword(aKey);
  if (temp) {
    this.removeAllPasswordsForHost(aKey);
    this.setPassword(aKey, temp.user, temp.password);
    this.clearTempPassword(aKey);
    return this.getPassword(aKey);
  } else {
    this._logger.info(" - ERROR: temporary password does not exist!  Can't make it permanent...");
    // TODO: Maybe throw an exception here?
  }
  return null;
}

flockAccountUtils.prototype.markAccountAsLoggedOut =
function flockAccountUtils_markAccountAsLoggedOut(aAccountURN)
{
  this._logger.info("{flockIAccountUtils}.markAccountAsLoggedOut('"
                    + aServiceContractID
                    + "')");
  var account = this._coop.get(aAccountURN);
  if (account && (account instanceof this._coop.Account)) {
    account.isAuthenticated = false;
  }
}

flockAccountUtils.prototype.markAllAccountsAsLoggedOut =
function flockAccountUtils_markAllAccountsAsLoggedOut(aServiceContractID)
{
  this._logger.info("{flockIAccountUtils}.markAllAccountsAsLoggedOut('"+aServiceContractID+"')");
  this.setup();
  var accounts = this._coop.Account.find({serviceId: aServiceContractID});
  for (var i = 0; i < accounts.length; i++) {
    accounts[i].isAuthenticated = false;
  }
}

flockAccountUtils.prototype.setPassword =
function flockAccountUtils_setPassword(aHost, aUser, aPassword)
{
  this._logger.info("{flockIAccountUtils}.setPassword('"+aHost+"', '"+aUser+"', 'XXXXXX')");
  var pm = Components.classes["@mozilla.org/passwordmanager;1"]
                     .getService(Components.interfaces.nsIPasswordManager);
  try {
    pm.addUser(aHost, aUser, aPassword);
  } catch (ex) {
    this._logger.warn("addUser('"+aHost+"', '"+aUser+"') FAILED: "+ex);
  }
}

flockAccountUtils.prototype.setTempPassword =
function flockAccountUtils_setTempPassword(aKey, aUser, aPassword, aFormType)
{
  this._logger.info("{flockIAccountUtils}.setTempPassword('"+aKey+"', '"+aUser+"', 'XXXXXXXX', '"+aFormType+"')");
  var pw = {
    QueryInterface: function(aIID) {
      if (!aIID.equals(Components.interfaces.nsISupports) &&
          !aIID.equals(Components.interfaces.nsIPassword) &&
          !aIID.equals(Components.interfaces.flockIPassword)) {
        throw Components.interfaces.NS_ERROR_NO_INTERFACE;
      }
      return this;
    },
    host: aKey,
    user: aUser,
    password: aPassword,
    formType: aFormType
  };
  this.mTempPasswords[aKey] = pw;
}

flockAccountUtils.prototype.removeAllPasswordsForHost =
function flockAccountUtils_removeAllPasswordsForHost(aHost)
{
  this._logger.info("{flockIAccountUtils}.removeAllPasswordsForHost('"+aHost+"')");
  var pm = Components.classes["@mozilla.org/passwordmanager;1"]
                     .getService(Components.interfaces.nsIPasswordManager);
  var en = pm.enumerator;
  while (en.hasMoreElements()) {
    var p = en.getNext()
              .QueryInterface(Components.interfaces.nsIPassword);
    if (p.host == aHost) {
      try {
        pm.removeUser(p.host, p.user);
      } catch (ex) {
        this._logger.debug( "ERROR - perhaps an attempt to remove a password "
                          + "that has already been removed... ?");
        this._logger.debug(" host: "+aHost);
      }
    }
  }
}

flockAccountUtils.prototype.raiseNotification =
function flockAccountUtils_raiseNotification(aStreamUrn, aNotificationUrn, aNotificationProps)
{
  this._logger.info("{flockIAccountUtils}.raiseNotification('"+aStreamUrn+"', '"+aNotificationUrn+"', aNotificationProps)");
  aNotificationProps.QueryInterface(Components.interfaces.nsIPropertyBag);
  var notification = new this._coop.Notification(aNotificationUrn);
  notification.datevalue = new Date();
  var props = aNotificationProps.enumerator;
  while (props.hasMoreElements()) {
    var prop = props.getNext();
    prop.QueryInterface(Components.interfaces.nsIProperty);
    notification[prop.name] = prop.value;
  }
  var notificationStream = this._coop.get(aStreamUrn);
  notificationStream.children.addOnce(notification);
  // OBS.notifyObservers(this, "new-stuff-notification", notification.id());
}

flockAccountUtils.prototype.extractPasswordFromHTMLForm =
function flockAccountUtils_extractPasswordFromHTMLForm(aForm)
{
  this._logger.info("{flockIAccountUtils}.extractPasswordFromHTMLForm(aForm)");
  aForm.QueryInterface(Components.interfaces.nsIDOMHTMLFormElement);
  var formElements = aForm.elements;
  var passElements = [];
  var username = "";
  var firstPasswordIndex = formElements.length;
  for (var i = 0; i < formElements.length; i++) {
    var formElement = formElements.item(i);
    if (formElement.type == "password") {
      passElements.push(formElement);
      if (firstPasswordIndex == formElements.length) {
        firstPasswordIndex = i;
      }
    }
  }
  if (firstPasswordIndex == formElements.length) {
    // We didn't find a password
    return null;
  }
  // Backwards to get closest text field
  for (var i = firstPasswordIndex - 1; i >= 0; i--) {
    var formElement = formElements.item(i);
    if (formElement.type == "text") {
      username = formElement.value;
      break;
    }
  }
  var pwObj = {
    host: "",
    password: passElements[0].value,
    user: username
  };
  return pwObj;
}

flockAccountUtils.prototype.decorateDocument =
function flockAccountUtils_decorateDocument(aDocument, aCategory, aName, aValue)
{
  this._logger.info("{flockIAccountUtils}.decorateDocument(aDocument, '"+aCategory+"', '"+aName+"', '"+aValue+"')");
  aDocument.QueryInterface(Components.interfaces.nsIDOMHTMLDocument);
  if (!aDocument._flock_decorations) {
    aDocument._flock_decorations = {};
  }
  var fd = aDocument._flock_decorations;
  if (!fd[aCategory]) {
    fd[aCategory] = {};
  }
  fd[aCategory][aName] = aValue;
}

flockAccountUtils.prototype.getDocumentDecoration =
function flockAccountUtils_getDocumentDecoration(aDocument, aCategory, aName)
{
  this._logger.info("{flockIAccountUtils}.getDocumentDecoration(aDocument, '"+aCategory+"', '"+aName+"')");
  aDocument.QueryInterface(Components.interfaces.nsIDOMHTMLDocument);
  try {
    var value = aDocument._flock_decorations[aCategory][aName];
    return value;
  } catch (ex) {
    return null;
  }
}

flockAccountUtils.prototype.useWebDetective =
function flockAccountUtils_useWebDetective(aDetectionFileName)
{
  this._logger.info("{flockIAccountUtils}.useWebDetective('"+aDetectionFileName+"')");
  this.setup();
  var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
                         .getService(Components.interfaces.nsIProperties);
  var profDir = dirSvc.get("ProfD", Components.interfaces.nsIFile);
  var file = Components.classes["@mozilla.org/file/local;1"]
                       .createInstance(Components.interfaces.nsILocalFile);
  file.initWithPath(profDir.path);
  file.append("detect");
  var profDetectDir = file.clone();
  if (!profDetectDir.exists()) {
    profDetectDir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
  }
  file.append(aDetectionFileName);
  if (!file.exists()) {
    // The file doesn't exist in the profile, so check if there's one in the
    // res folder that we can copy over
    var resDir = dirSvc.get("ARes", Components.interfaces.nsIFile);
    var file2 = Components.classes["@mozilla.org/file/local;1"]
                          .createInstance(Components.interfaces.nsILocalFile);
    file2.initWithPath(resDir.path);
    file2.append("detect");
    file2.append(aDetectionFileName);
    if (!file2.exists()) {
      this._logger.info("File does not exist: "+aDetectionFileName);
    }
    file2.copyTo(profDetectDir, aDetectionFileName);
    file = profDetectDir.clone();
    file.append(aDetectionFileName);
  }
  var webDetective = Components.classes["@flock.com/web-detective;1"]
                               .getService(Components.interfaces.flockIWebDetective);
  webDetective.loadDetectFile(file);
  return webDetective;
}

flockAccountUtils.prototype.removeCookies =
function flockAccountUtils_removeCookies(aCookies)
{
  aCookies.QueryInterface(Components.interfaces.nsISimpleEnumerator);
  var cookieManager = Components.classes["@mozilla.org/cookiemanager;1"]
                                .getService(Components.interfaces.nsICookieManager);
  var deletedCount = 0;
  while (aCookies.hasMoreElements()) {
    var c = aCookies.getNext()
      .QueryInterface(Components.interfaces.nsICookie);
    cookieManager.remove(c.host, c.name, c.path, false);
    deletedCount++;
  }
  this._logger.info(".removeCookies() - Deleted "+deletedCount+" cookies");
}

flockAccountUtils.prototype.isPeopleSidebarOpen =
function flockAccountUtils_isPeopleSidebarOpen()
{
  var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                     .getService(Components.interfaces.nsIWindowMediator);
  var win = wm.getMostRecentWindow("navigator:browser");
  if (win) {
    var psb = win.document.getElementById("flockPeopleSidebarBroadcaster");
    return (psb.hasAttribute("checked"));
  }
  return false;
}
// END flockIAccountUtils interface


// BEGIN flockIWebmailMailtoHandler interface
flockAccountUtils.prototype.webmailMailtoURIHandler =
function flockAccountUtils_webmailMailtoURIHandler(aMailtoURI)
{
  // Check for webmail circumvention. When this is present in the mailto:
  // string we always want to use the default mailer rather than webmail.
  if (aMailtoURI.indexOf("&bypasswebmail=true") != -1) {
    return false;
  }

  // Is there a primary webmail account?
  var primaryAcct = webmailBackend.getPrimaryAccount();
  if (primaryAcct &&
      webmailBackend.composeMessage(primaryAcct, aMailtoURI))
  {
    CC["@flock.com/metrics-service;1"]
      .getService(CI.flockIMetricsService)
      .report("WebMail-MailTo", primaryAcct.getService().shortName);
    return true;
  }

  // Unsuccessful
  return false;
}


// END flockIWebmailMailtoHandler interface


// BEGIN helper functions
flockAccountUtils.prototype.setup =
function flockAccountUtils_setup()
{
  if (this.mSetupHasRun) {
    return;
  } else {
    this.mSetupHasRun = true;
    this._coop = Components.classes["@flock.com/singleton;1"]
                           .getService(Components.interfaces.flockISingleton)
                           .getSingleton("chrome://flock/content/common/load-faves-coop.js")
                           .wrappedJSObject;
  }
}

flockAccountUtils.prototype.getAllAccounts =
function flockAccountUtils_getAllAccounts()
{
  return this._coop.Account.all();
}
// END helper functions

// ========== END flockAccountUtils class ==========



// ================================================
// ========== BEGIN XPCOM Module support ==========
// ================================================

// BEGIN flockAccountUtilsModule object
var flockAccountUtilsModule = {};

flockAccountUtilsModule.registerSelf =
function flockAccountUtilsModule_registerSelf(compMgr, fileSpec, location, type)
{
  compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  compMgr.registerFactoryLocation( ACCOUNTUTILS_CID, 
                                   "Flock Account Utils JS Component",
                                   ACCOUNTUTILS_CONTRACTID, 
                                   fileSpec, 
                                   location,
                                   type );
}

flockAccountUtilsModule.getClassObject =
function flockAccountUtilsModule_getClassObject(compMgr, cid, iid)
{
  if (!cid.equals(ACCOUNTUTILS_CID)) {
    throw Components.results.NS_ERROR_NO_INTERFACE;
  }
  if (!iid.equals(Components.interfaces.nsIFactory)) {
    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  }
  return flockAccountUtilsFactory;
}

flockAccountUtilsModule.canUnload =
function flockAccountUtilsModule_canUnload(compMgr)
{
  return true;
}
// END flockAccountUtilsModule object


// BEGIN flockAccountUtilsFactory object
var flockAccountUtilsFactory = {};

flockAccountUtilsFactory.createInstance =
function flockAccountUtilsFactory_createInstance(outer, iid)
{
  if (outer != null) {
    throw Components.results.NS_ERROR_NO_AGGREGATION;
  }
  return (new flockAccountUtils()).QueryInterface(iid);
}
// END flockAccountUtilsFactory object


// NS module entry point
function NSGetModule(compMgr, fileSpec) {
  return flockAccountUtilsModule;
}

// ========== END XPCOM module support ==========
