// 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 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);

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

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

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.ensureOnlyAuthenticatedAccount =
function flockAccountUtils_ensureOnlyAuthenticatedAccount(aAccountURN)
{
  this._logger.info("{flockIAccountUtils}.ensureOnlyAuthenticatedAccount('"+aAccountURN+"')");
  this.setup();
  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 {
        if (accounts[i].isAuthenticated) {
          accounts[i].isAuthenticated = false;
        }
      }
    }
  }
}

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("{flockIAccountUtils}.getAccountURNByLogin('"+aServiceURN+"', '"+aLogin+"')");
  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);
      return this.getAccountURNById(aServiceURN, accountID);
    }
  }
  return null;
}

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, aUsername)
{
  aService.QueryInterface(Components.interfaces.nsIClassInfo);
  
  // Add the account
  var accountURN = aService.urn+":"+aUsername;
  var account = new this._coop.Account(accountURN, {
    name: aUsername,
    serviceId: aService.contractId,
    service: null, // FIXME
    accountId: aUsername,
    favicon: aService.icon,
    URL: aService.url,
  });
  this._coop.accounts_root.children.addOnce(account);
  // this.USER = blAccount.id();
  // var acct = this.getAccount(blAccount.id());

  // Add the notification stream
  var notificationStream = new this._coop.Stream(accountURN + ":notifications", {
    name: "Notification",
    isPollable: false,
    isIndexable: false,
    notify: true,
    serviceId: aService.contractId
  });
  account.children.addOnce(notificationStream);
  return accountURN;
}

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("http://flock.com/rdf#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("http://flock.com/rdf#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();
  }

  // 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.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.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");
  var psbBroadcaster = win.document.getElementById("flockPeopleSidebarBroadcaster");
  if (psbBroadcaster.hasAttribute("checked")) {
    return true;
  } else {
    return false;
  }
}
// END flockIAccountUtils 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 ==========
