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

Components.utils.import("resource:///modules/FlockOnlineFavsUtils.jsm");
Components.utils.import("resource:///modules/FlockDateFormatter.jsm");
Components.utils.import("resource:///modules/FlockPrefsUtils.jsm");

// flockIBookmark prototype for interaction with online service publishing
function BookmarkProto() {
  this.URL = "";
  this.name = "";
  this.description = "";
  this.tags = "";
  this.time;
  this.shared;
}

BookmarkProto.prototype = {
  getInterfaces: function BmkP_getInterfaces(aCount) {
    var interfaceList = [Ci.flockIBookmark, Ci.nsIClassInfo];
    aCount.value = interfaceList.length;
    return interfaceList;
  },
  QueryInterface: function BmkP_QueryInterface(aIID) {
    if (!aIID.equals(Ci.flockIBookmark)) {
      throw Components.results.NS_ERROR_NO_INTERFACE;
    }
    return this;
  },
  getHelperForLanguage: function BmkP_getHelperForLanguage(aCount) {
    return null;
  }
};

// Simple Enumerator prototype
function simpleEnumProto (aArray) {
  this.data = aArray;
}

simpleEnumProto.prototype = {
  hasMoreElements: function sEnum_hasMoreElements() {
    return this.data.length != 0;
  },
  getNext: function sEnum_getNext() {
    return this.data.shift();
  },
  getInterfaces: function sEnum_getInterfaces(aCount) {
    var interfaceList = [Ci.nsISimpleEnumerator, Ci.nsIClassInfo];
    aCount.value = interfaceList.length;
    return interfaceList;
  },
  QueryInterface: function sEnum_QueryInterface(aIID) {
    if (!aIID.equals(Ci.nsISimpleEnumerator)) {
      throw Components.results.NS_ERROR_NO_INTERFACE;
    }
    return this;
  },
  getHelperForLanguage: function (count) {
    return null;
  }
};

var FlockPlaces = {
  // XUL namespace
  XUL_NS: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",

  // Online Bookmark default prefs
  PREF_FAV_DO: "flock.favorites.do.",
  PREF_FAV_PRIVATE: "flock.favorites.private.",

  // Local Bookmark default prefs
  PREF_SHOW_FIELDS: "flock.favorites.show.",
  PREF_FAV_REMOVE_CONFIRM: "flock.favorites.remove.confirm",
  PREF_FAV_LOCAL: "flock.favorites.do.local",
  PREF_FAV_FOLDER: "flock.favorites.do.folder",
  PREF_SHOW_STAR_PANEL: "flock.favorites.starShowProperties",

  // Notification icon
  PUBLISH_NOTIFICATION_ICON: "chrome://browser/skin/Info.png",

  // Places annotations
  ANNO_PRIVACY: "flock/onlinePrivacy",
  ANNO_ONLINE_ACCOUNT: "flock/onlinebookmarks-account",
  ANNO_ACCOUNT: "flock/account_",
  ANNO_LM_SITEURI: "livemark/siteURI",
  FLOCK_UNFILED: "0000:flock:unfiled",
  PRIVATE: 0,
  PUBLIC: 1,
  FF_PANEL_PREFIX: "editBMPanel_",

  // String bundle
  BUNDLE_URI: "chrome://flock/locale/favorites/favorites.properties",
  DONT_SHOW_AGAIN: "flock.places.prompt.dontShowAgain",

  _itemId: null,
  _uri: null,
  _onlineFavExists: false,
  _defaults: {},
  _initComplete: false,
  _locationsSelected: 0,
  _svcManagerListenerAdded: false,
  // Node reference of the most recently dragged bookmark
  _dragNode: null,
  // Account URN of the dropped online account
  _dropUrnAnnotation: null,
  // Places Manager members
  _placesEditableFields: ["namePicker", "locationField", "tagsField",
                          "keywordField", "descriptionField"],
  _placesTxnEnabled: null,
  _placesTxnArray: [],
  _placesTxnBookmark: null,
  _placesTxnItemId: null,
  _placesTxnNeedsPublish: false,
  _placesTagsNeedRebuiltOnUndo: true,
  _placesTxnCount: 0,
  _placesStoredItemProperties: [],

  _metrics: Cc["@flock.com/metrics-service;1"]
            .getService(Ci.flockIMetricsService)
};

FlockPlaces.onlineRootId =
onlineFavoritesBackend.getOnlineRootId();

FlockPlaces.internalOnlineRootId =
onlineFavoritesBackend.getInternalOnlineRootId();

FlockPlaces.starCurrentPage =
function FlockPlaces_starCurrentPage(aParentId) {
  var starred = PlacesStarButton._starred;

  if (!this._defaults.accnts) {
    this.loadDefaultPublishingLocations();
  }
  if (!starred && !FlockPlaces.checkShowStarPref(false)) {
    FlockPlaces.showStarMessage(null);
    this._metrics.report("Favorites-NewFavorite", null);
  }
  PlacesCommandHook.bookmarkCurrentPage(starred, aParentId);
}

FlockPlaces.showStarMessage =
function FlockPlaces_showStarMessage(aItemId) {
  var acUtils = Components.classes["@flock.com/account-utils;1"]
                .getService(Components.interfaces.flockIAccountUtils);
  var prefsService = Components.classes["@mozilla.org/preferences-service;1"]
                     .getService(Components.interfaces.nsIPrefBranch);

  // Are any online bookmark service accounts configured?
  var accountsEnum = acUtils.getAccountsByInterface("flockIBookmarkWebService");

  var message;
  var links = [{
    label: flockGetString("favorites/favorites",
                          "flock.favs.overlay.favorite-added.openSidebar"),
    callback: function FlockPlaces_showStarMessage_links_callback() {
      toggleSidebarWithMetric("viewBookmarksSidebar",
                              "FavoritesSidebar-Open",
                              null,
                              "StarFavesAddDiscovery",
                              true);
    }
  }];

  if (accountsEnum.hasMoreElements()) {
    var shortnameStr = "";
    while (accountsEnum.hasMoreElements()) {
      var acct = accountsEnum.getNext()
                             .QueryInterface(Ci.flockIWebServiceAccount);
      var prefAccntSfx = this.getOnlineBkPrefSuffix(acct.getParam("serviceId"),
                                                    acct.getParam("accountId"));
      if (this._defaults.accnts[prefAccntSfx].do) {
        var temp = prefAccntSfx.split("--");
        var bmSvc = Components.classes[temp[0]]
                    .getService(Components.interfaces.flockIWebService);
        if (shortnameStr) {
          shortnameStr += ", ";
        }
        shortnameStr += bmSvc.title + "(" + temp[1] + ")";
      }
    }

    if (shortnameStr) {
      if (this._defaults.localCheckbox) {
        // Local favorite and online
        message = flockGetString("favorites/favorites",
                                 "flock.favs.overlay.favorite-added.local")
                + "  "
                + flockGetString("favorites/favorites",
                                 "flock.favs.overlay.favorite-added.alsoOnline",
                                 [shortnameStr]);
      } else {
        // Online only: "This page has been added to " + shortnameStr + "."
        message = flockGetString("favorites/favorites",
                                 "flock.favs.overlay.favorite-added.online",
                                 [shortnameStr]);
      }
    } else {
      // Local favorite, online account is configured but we didn't publish online
      message = flockGetString("favorites/favorites",
                               "flock.favs.overlay.favorite-added.local")
              + "  "
              + flockGetString("favorites/favorites",
                               "flock.favs.overlay.favorite-added.notOnline");
    }
  } else {
    // Local favorite, no account is configured
    message = flockGetString("favorites/favorites",
                             "flock.favs.overlay.favorite-added.local");
    links.push({
      label: flockGetString("favorites/favorites",
                            "flock.favs.overlay.favorite-added.getAccount"),
      callback: function FlockPlaces_showStarMessage_links2_callback() {
        toggleSidebarWithMetric("flock_AccountsSidebarBroadcaster",
                                "AccountSidebar-Open",
                                null,
                                "StarFavesAddDiscovery",
                                true);
        var obs = Components.classes["@mozilla.org/observer-service;1"]
                  .getService(Components.interfaces.nsIObserverService);
        setTimeout(function FlockPlaces_showStarMessage_links2_timeout() {
            obs.notifyObservers(null, "toggleAccountsArea", "bookmarks");
          },
          300);
      }
    });

  }
  message += " "
          + flockGetString("favorites/favorites",
                           "flock.favs.overlay.favorite-added.clickToEdit");
  var nBox = gBrowser.getNotificationBox();
  var buttons = [{
    label: flockGetString("favorites/favorites",
                          "flock.favs.overlay.favorite-added.options"),
    accessKey: flockGetString("favorites/favorites",
                              "flock.favs.overlay.favorite-added.options.accesskey"),
    callback: function FlockPlaces_showStarMessage_buttons_callback() {
      if (aItemId) {
        StarUI.showEditBookmarkPopup(aItemId, getBrowser(), "overlap");
      } else {
        PlacesCommandHook.bookmarkCurrentPage(true);
      }
    },
    forceopen: true
  }];
  nBox.appendUniqueNotification(message,
                                "favorite-added",
                                null,
                                nBox.FLOCK_PRIORITY_MEDIUM,
                                buttons,
                                links);
}

FlockPlaces.getBmSvc =
function FlockPlaces_getBmSvc() {
  if (!this._bmsvc) {
    this._bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]
                  .getService(Ci.nsINavBookmarksService);
  }
  return this._bmsvc;
};

FlockPlaces.getHistSvc =
function FlockPlaces_getHistSvc() {
  if (!this._histsvc) {
    this._histsvc = Cc["@mozilla.org/browser/nav-history-service;1"]
                 .getService(Ci.nsINavHistoryService);
  }
  return this._histsvc;
};

FlockPlaces.getAnnoSvc =
function FlockPlaces_getAnnoSvc() {
  if (!this._annosvc) {
    this._annosvc = Cc["@mozilla.org/browser/annotation-service;1"]
                    .getService(Ci.nsIAnnotationService);
  }
  return this._annosvc;
};

FlockPlaces.getAcUtils =
function FlockPlaces_getAcUtils() {
  if (!this._acUtils) {
    this._acUtils = Cc["@flock.com/account-utils;1"]
                    .getService(Ci.flockIAccountUtils);
  }
  return this._acUtils;
};

FlockPlaces.getLogger =
function FlockPlaces_getLogger() {
  if (!this._logger) {
    this._logger = Cc["@flock.com/logger;1"]
                   .createInstance(Ci.flockILogger);
    this._logger.init("FlockPlaces");
  }
  return this._logger;
};

FlockPlaces.getCoop =
function FlockPlaces_getCoop() {
  if (!this._coop) {
  // Coop Object
  this._coop = Cc['@flock.com/singleton;1']
              .getService(Ci.flockISingleton)
              .getSingleton("chrome://flock/content/common/load-faves-coop.js")
              .wrappedJSObject;
  }
  return this._coop;
};

FlockPlaces.getBundle =
function FlockPlaces_getBundle() {
  if (!this._bundle) {
    this._bundle = Cc["@mozilla.org/intl/stringbundle;1"]
                   .getService(Ci.nsIStringBundleService)
                   .createBundle(this.BUNDLE_URI);
  }
  return this._bundle;
};

FlockPlaces.getPrefs =
function FlockPlaces_getPrefs() {
  if (!this._prefs) {
    this._prefs = Cc["@mozilla.org/preferences-service;1"]
                  .getService(Ci.nsIPrefBranch);
  }
  return this._prefs;
};

FlockPlaces.getPromptSvc =
function FlockPlaces_getPromptSvc() {
  if (!this._promptSvc) {
    this._promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"]
                      .getService(Ci.nsIPromptService);
  }
  return this._promptSvc;
};

FlockPlaces.initPanel =
function FlockPlaces_initPanel(aItemId, aURI) {
  this._itemId = aItemId;
  this._uri = aURI;

  // Disable any places transaction actions
  this._placesTxnEnabled = false;

  // Initialize the overlay preferences
  this.initPrefs();

  // Check the local folder to see if the selection is a cached
  // copy of our online bookmark
  this.checkLocalFolder();

  // Populate the online services list
  this.populateOnlineFavs(aURI.spec);

  // Add a listener to the web service manager
  if (!this._svcManagerListenerAdded) {
    this.addWebSvcManagerListener();
  }

  if (!this._initComplete) {
    this.loadLocalizationStrings();

    // Load the default bookmark prefs
    this.loadDefaultPublishingLocations();

    // If the DOM has not yet rendered, the bookmarkAccount (binding)
    // is unavailable to examine its attributes (for default locations)
    var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    // Create an FlockPlaces pointer for use within the callback
    var inst = FlockPlaces;
    timer.initWithCallback({
      notify: function searchPref_ensureInit() {
        // Determine if our current location selection is a default
        inst.checkUIForDefaults();
        // Add an event listener for the menulist which is always updated;
        // the tree is only updated when expanded
        inst.getEl("editBMPanel_folderMenuList")
            .addEventListener("select", inst.checkUIForDefaults, false);
        inst.getEl("editBMPanel_enableLocalCheckbox")
            .addEventListener("click", inst.checkUIForDefaults, false);
      }
    }, 10, Ci.nsITimer.TYPE_ONE_SHOT);
    this._initComplete = true;
  } else {
    // Must check the loaded variables for a default configuration
    this.checkUIForDefaults();
  }
};

/**
 * When an account is created/removed, reinitialize the _default prefs
 * if the account in question is a flockIBookmarkAccount
 */
FlockPlaces.addWebSvcManagerListener =
function FlockPlaces_addWebSvcManagerListener() {
  var webSvcMgr = Cc["@flock.com/webservice-manager;1"]
                  .getService(Ci.flockIWebServiceManager);
  var inst = FlockPlaces;
  var wsmListener = {
    onAccountCreated:
    function wsmListener_onAccountCreated(aAccount, aDocument) {
      if (aAccount instanceof Ci.flockIBookmarkAccount) {
        inst.loadDefaultPublishingLocations();
      }
    },
    onAccountPasswordMissing:
    function wsmListener_onAccountPasswordMissing(aService, aDocument) {
      // Stub function, no-op
    },
    onAccountRemoved:
    function wsmListener_onAccountRemoved(aAccount) {
      if (aAccount instanceof Ci.flockIBookmarkAccount) {
        inst.loadDefaultPublishingLocations();
      }
    },
    onAccountAuthChange:
    function wsmListener_onAccountAuthChange(aAccount) {
      // Stub function, no-op
    }
  };
  webSvcMgr.addListener(wsmListener);
  this._svcManagerListenerAdded = true;
};

FlockPlaces.initPrefs =
function FlockPlaces_initPrefs() {
  var prefs = this.getPrefs();
  if (prefs.getPrefType(this.PREF_SHOW_STAR_PANEL)) {
    // Load the state of the show star panel preference checkbox
    var showStarPanel = this.getEl("showStarPanel");
    if (showStarPanel) {
      showStarPanel.checked = prefs.getBoolPref(this.PREF_SHOW_STAR_PANEL);
    }
  }
};

FlockPlaces.loadLocalizationStrings =
function FlockPlaces_loadLocalizationStrings() {
  var bundle = this.getBundle();
  this._strings = {};
  this._strings.useDefaultSingular =
    bundle.GetStringFromName("flock.favs.online.default.use");
  this._strings.useDefaultPlural =
    bundle.GetStringFromName("flock.favs.online.defaults.use");
  this._strings.currentDefaultSingular =
    bundle.GetStringFromName("flock.favs.online.default.selected");
  this._strings.currentDefaultPlural =
    bundle.GetStringFromName("flock.favs.online.defaults.selected");
};

FlockPlaces.checkShowStarPref =
function FlockPlaces_checkShowStarPref(aShowEditUI) {
  if (!this._defaults.localCheckbox) {
    // We cannot display the UI because online BMs are published with a delay
    // and will cause an exception
    return false;
  }
  // Don't override if the preference is false
  var prefs = this.getPrefs();
  if (prefs.getPrefType(this.PREF_SHOW_STAR_PANEL) &&
      prefs.getBoolPref(this.PREF_SHOW_STAR_PANEL))
  {
    return true;
  }
  return aShowEditUI;
};

FlockPlaces.toggleShowStarPref =
function FlockPlaces_toggleShowStarPref(aDOMNode) {
  this.getPrefs().setBoolPref(this.PREF_SHOW_STAR_PANEL, aDOMNode.checked);
};

FlockPlaces.toggleLocalFolder =
function FlockPlaces_toggleLocalFolder(aIsChecked) {
  var folderCheckbox = this.getEl("editBMPanel_enableLocalCheckbox");
  var folderMenulist = this.getEl("editBMPanel_folderMenuList");
  var folderExpander = this.getEl("editBMPanel_foldersExpander");
  if (aIsChecked) {
    folderCheckbox.checked = true;
    folderMenulist.setAttribute("disabled", "false");
    folderExpander.setAttribute("disabled", "false");
  } else {
    folderCheckbox.checked = false;
    folderMenulist.setAttribute("disabled", "true");
    folderExpander.setAttribute("disabled", "true");
    // Ensure the tree is collapsed and the expander is in a proper state
    this.getEl("editBMPanel_folderTree").collapsed = true;
    folderExpander.setAttribute("class", "expander-down");
  }
};

FlockPlaces.checkLocalFolder =
function FlockPlaces_checkLocalFolder() {
  var toggleBoolean = true;
  var folderMenulist = this.getEl("editBMPanel_folderMenuList");
  if (folderMenulist.selectedItem && folderMenulist.selectedItem.folderId) {
    var folderId = folderMenulist.selectedItem.folderId;
    // Check if the bookmark currently selected is a cached
    // copy of the online bookmark it represents
    if (folderId == onlineFavoritesBackend.getInternalOnlineRootId()) {
      var localFolderId = this.getLocalFolderId();
      if (localFolderId == -1) {
        toggleBoolean = false;
        // Set the folder to unfiled
        gEditItemOverlay._initFolderMenuList(PlacesUtils.unfiledBookmarksFolderId);
      } else {
        // Set the folder to localFolderId
        gEditItemOverlay._initFolderMenuList(localFolderId);
      }
    }
  }
  // Toggle the local folder checked
  this.toggleLocalFolder(toggleBoolean);
};

FlockPlaces.getLocalFolderId =
function FlockPlaces_getLocalFolderId() {
  var bmkIds = this.getBmSvc().getBookmarkIdsForURI(this._uri, {});
  for (var i = 0; i < bmkIds.length; i++) {
    // Find the first folder which isn't a tag container
    // or the internal online root
    var bk = bmkIds[i];
    var parent = this.getBmSvc().getFolderIdForItem(bk);
    if (parent == PlacesUtils.unfiledBookmarksFolderId) {
      return parent;
    }
    var grandparent = this.getBmSvc().getFolderIdForItem(parent);
    if (grandparent != PlacesUtils.tagsFolderId &&
        parent != onlineFavoritesBackend.getInternalOnlineRootId() &&
        !this.getAnnoSvc().itemHasAnnotation(parent, this.ANNO_LM_FEEDURI))
    {
      return parent;
    }
  }
  return -1;
}

FlockPlaces.checkUIForDefaults =
function FlockPlaces_checkUIForDefaults(aEvent) {
  // This is also called from an flockIWebServiceManager listener
  // make sure we are using a FlockPlaces instance for method calls
  var inst = FlockPlaces;
  inst._locationsSelected = 0;

  // Return if this object has not been created (Places Manager)
  if (!inst._defaults) {
    return;
  }

  var localCheckboxIsDefault = false;
  var localCheckbox = inst.getEl("editBMPanel_enableLocalCheckbox");
  if (inst._defaults.localCheckbox == localCheckbox.checked) {
    localCheckboxIsDefault = true;
  }

  var localFolderIsDefault = false;
  var folderMenuList = inst.getEl("editBMPanel_folderMenuList");
  // Determine whether or not the selected tree node is the local default
  if (folderMenuList &&
      folderMenuList.selectedItem &&
      folderMenuList.selectedItem.folderId)
  {
    inst._locationsSelected++;
    var folderId = folderMenuList.selectedItem.folderId;
    if (inst._defaults.local && inst._defaults.local == folderId) {
      localFolderIsDefault = true;
    }
  }
  // Determine if the online bookmarks settings are marked as defaults
  var accnts = inst.getEl("onlineBookmarkAccnts").childNodes;
  var onlineIsDefault = true;
  for (var i = 0; i < accnts.length; i++) {
    var accountItem = accnts[i];
    if (accountItem.checked) {
      inst._locationsSelected++;
    }
    var prefAccntSfx = inst.getOnlineBkPrefSuffix(accountItem.serviceId,
                                                  accountItem.accountId);
    if (!inst._defaults.accnts || !inst._defaults.accnts[prefAccntSfx]) {
      inst.loadDefaultPublishingLocations();
    }
    if ((accountItem.checked != inst._defaults.accnts[prefAccntSfx].do) ||
        (accountItem.private != inst._defaults.accnts[prefAccntSfx].private))
    {
      onlineIsDefault = false;
      // Break out of the loop for efficiency
      break;
    }
  }
  inst.toggleDefaultLocationBtn((localFolderIsDefault &&
                                 localCheckboxIsDefault &&
                                 onlineIsDefault));
};

/**
 * Toggle the button state to disabled to indicate the default
 * location is selected
 */
FlockPlaces.toggleDefaultLocationBtn =
function gFlockPl_toggleDefaultLocationBtn(aIsDefaultLocation) {
  var defaultButton = this.getEl("defaultLocations");
  if (!defaultButton) {
    return;
  }

  // Update UI based on current state
  if (aIsDefaultLocation) {
    defaultButton.setAttribute("disabled", "true");
  } else {
    defaultButton.setAttribute("disabled", "false");
  }
  if (this._locationsSelected > 1) {
    defaultButton.setAttribute("label",
                               this._strings.useDefaultPlural);
  } else {
    defaultButton.setAttribute("label",
                               this._strings.useDefaultSingular);
  }
};

/**
 * FF3 defaults hide the location and description fields;
 * make sure the user hasn't set a pref to show them
 */
FlockPlaces.checkHiddenRows =
function FlockPlaces_checkHiddenRows(aArray) {
  var userPrefs = ["description", "location"];
  var prefs = this.getPrefs();
  for each (var p in userPrefs) {
    if (prefs.getPrefType(this.PREF_SHOW_FIELDS + p) &&
        prefs.getBoolPref(this.PREF_SHOW_FIELDS + p))
    {
      var prefKey = aArray.indexOf(p);
      if (prefKey != -1) {
        // Remove the pref from the rows to hide; show it
        aArray.splice(prefKey, 1);
      }
    }
  }
  return aArray;
};

FlockPlaces.checkRemoveBookmark =
function FlockPlaces_checkRemoveBookmark(aEvent) {
  var prefs = this.getPrefs();
  if (this._onlineFavExists &&
      (!prefs.getPrefType(this.PREF_FAV_REMOVE_CONFIRM) ||
       prefs.getBoolPref(this.PREF_FAV_REMOVE_CONFIRM)))
  {
    // Hide the popup to display the confirm dialog
    StarUI.panel.hidePopup();
    var promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"]
                    .getService(Ci.nsIPromptService);
    // Don't show defaulted to false
    var checkbox = {value: false};
    var title = flockGetString("favorites/favorites",
                               "flock.favs.remove.confirm.title");
    var description = flockGetString("favorites/favorites",
                                     "flock.favs.remove.confirm.description");
    var dontAsk = flockGetString("favorites/favorites",
                                 "flock.favs.remove.confirm.dontAskAgain");
    var result = promptSvc.confirmCheck(null,
                                        title,
                                        description,
                                        dontAsk,
                                        checkbox);
    prefs.setBoolPref(this.PREF_FAV_REMOVE_CONFIRM,
                      (checkbox.value != "true"));
    if (!result) {
      // Remove canceled, display the panel again
      StarUI._doShowEditBookmarkPanel(this._itemId,
                                      this.getEl("star-button"),
                                      "after_start");
      return;
    }
  }
  // Remove the bookmark
  StarUI._itemId = this._itemId;
  StarUI.removeBookmarkButtonCommand();
  this._metrics.report("Favorites-RemoveFavorite", null);
};

/**
 * When bookmarking a page, check to see whether or not it should be
 * saved in a default local folder, and/or online location(s)
 */
FlockPlaces.checkDefaultPublishingLocations =
function FlockPlaces_checkDefaultPublishingLocations(aUri,
                                                     aTitle,
                                                     aDescription)
{
  // Publish to any online accounts if default
  this.publishToOnlineDefaults(aUri, aTitle, aDescription);
  var prefs = this.getPrefs();
  // Check for a default local folder in the preferences
  if (prefs.getPrefType(this.PREF_FAV_LOCAL) &&
      prefs.getBoolPref(this.PREF_FAV_LOCAL))
  {
    if (prefs.getPrefType(this.PREF_FAV_FOLDER)) {
      var folderId = prefs.getCharPref(this.PREF_FAV_FOLDER);
      if (this.isLocalFolderValid(folderId)) {
        return folderId;
      }
    }
    return PlacesUtils.unfiledBookmarksFolderId;
  }
  return null;
};

FlockPlaces.publishToOnlineDefaults =
function FlockPlaces_publishToOnlineDefaults(aUri, aTitle, aDescription) {
  var prefs = this.getPrefs();
  var accountsEnum = this.getAcUtils()
                         .getAccountsByInterface("flockIBookmarkWebService");
  while (accountsEnum.hasMoreElements()) {
    var account = accountsEnum.getNext()
                              .QueryInterface(Ci.flockIWebServiceAccount);
    var accountId = account.getParam("accountId");
    var serviceId = account.getParam("serviceId");
    var prefAccntSfx = this.getOnlineBkPrefSuffix(serviceId, accountId);
    var publishToSvc = false;
    if (prefs.getPrefType(this.PREF_FAV_DO + prefAccntSfx)) {
      publishToSvc = prefs.getBoolPref(this.PREF_FAV_DO + prefAccntSfx);
    }
    // Should we publish to the service?
    if (publishToSvc) {
      var isPrivate = false;
      if (prefs.getPrefType(this.PREF_FAV_PRIVATE + prefAccntSfx)) {
        isPrivate = prefs.getBoolPref(this.PREF_FAV_PRIVATE + prefAccntSfx);
      }
      // Create new flockIBookmark
      var bookmark = new BookmarkProto();
      bookmark.URL = aUri.spec;
      bookmark.name = aTitle;
      bookmark.tags = "";
      bookmark.description = aDescription;
      bookmark.shared = (!isPrivate);
      bookmark.time = this.getPlacesTime();
      // Ready to publish, get the service
      var svc = Cc[serviceId].getService(Ci.flockIWebService);
      svc.QueryInterface(Ci.flockIBookmarkWebService);
      this.publishOnlineBookmark(svc, bookmark, accountId);
    }
  }
};

/**
 * Push the currently viewed bookmark if properly configured to do so
 */
FlockPlaces.quitEditMode =
function FlockPlaces_quitEditMode() {
  // Complete any pending transactions
  this.doPlacesManagerTxn();
  var url = gBrowser.currentURI.spec;
  // Post to online locations
  var onlineBookmarkAccnts = this.getEl("onlineBookmarkAccnts");
  var onlineAccntsFound = false;
  var accnts = onlineBookmarkAccnts.childNodes;
  for (var i = 0; i < accnts.length; i++) {
    var accountItem = accnts[i];
    if (accountItem && accountItem.checked) {
      onlineAccntsFound = true;
      var svc = Cc[accountItem.serviceId]
                .getService(Ci.flockIBookmarkWebService);
      const FF_PREFIX = "editBMPanel_";
      // Create new flockIBookmark
      var bookmark = new BookmarkProto();
      bookmark.URL = this.getNormalizedURL(url);
      bookmark.name = this.getEl(FF_PREFIX + "namePicker").value;
      var tagField = this.getEl(FF_PREFIX + "tagsField");
      bookmark.tags = this.formatTagField(tagField.value);
      bookmark.description = this.getEl(FF_PREFIX + "descriptionField").value;
      bookmark.time = this.getPlacesTime();
      bookmark.shared = (!accountItem.getPrivate());
      // Publish the bookmark to the online service
      this.publishOnlineBookmark(svc,
                                 bookmark,
                                 accountItem.accountId);
    }
  }
  // If our local folder checkbox isn't checked, don't save to the local folder
  var isLocalCheckbox = this.getEl("editBMPanel_enableLocalCheckbox").checked;
  StarUI.panel.hidePopup();
  if (!isLocalCheckbox) {
    if (!onlineAccntsFound) {
      // Don't show defaulted to false
      var checkbox = {value: false};
      var title = flockGetString("favorites/favorites",
                                 "flock.favs.publish.noneSelected.title");
      var description = flockGetString("favorites/favorites",
                                       "flock.favs.publish.noneSelected.description");
      this.getPromptSvc().alert(null, title, description);
      PlacesCommandHook.bookmarkCurrentPage(true);
    } else {
      StarUI._itemId = this._itemId;
      StarUI.removeBookmarkButtonCommand();
    }
  } else if (this.getLocalFolderId() == -1){
      this.getBmSvc().insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
                                     PlacesUtils._uri(url),
                                     CI.nsINavBookmarksService.DEFAULT_INDEX,
                                     this.getBmSvc().getItemTitle(this._itemId));
  }
};

/**
 * Load the saved prefs into locally scoped variables for further use
 */
FlockPlaces.loadDefaultPublishingLocations =
function FlockPlaces_loadDefaultPublishingLocations() {
  // Reset the object
  this._defaults = {};

  // Load the state of the local checkbox
  var fav_local = FlockPrefsUtils.getBoolPref(this.PREF_FAV_LOCAL);
  this._defaults.localCheckbox = fav_local;

  // Load the current local folder in the prefs
  var folderId = FlockPrefsUtils.getCharPref(this.PREF_FAV_FOLDER);
  if (this.isLocalFolderValid(folderId)) {
    this._defaults.local = folderId;
  } else {
    // If no local folder, we are on the FF3 default (Unfiled Bookmarks)
    this._defaults.local = PlacesUtils.unfiledBookmarksFolderId;
  }
  // Enumerate through all available flockIBookmarkWebService accounts
  // and load the stored preference into scope
  this._defaults.accnts = [];
  var accountsEnum = this.getAcUtils()
                         .getAccountsByInterface("flockIBookmarkWebService");
  while (accountsEnum.hasMoreElements()) {
    var accnt = accountsEnum.getNext()
                            .QueryInterface(Ci.flockIWebServiceAccount);
    var prefSfx = this.getOnlineBkPrefSuffix(accnt.getParam("serviceId"),
                                             accnt.getParam("accountId"));
    // Create a new object to hold the account pref defaults
    var pref = {}
    pref.do = FlockPrefsUtils.getBoolPref(this.PREF_FAV_DO + prefSfx) == true;
    pref.private = FlockPrefsUtils.getBoolPref(this.PREF_FAV_PRIVATE + prefSfx) == true;
    this._defaults.accnts[prefSfx] = pref;
  }
};

/**
 * Saves the prefs and redefines locally scoped variables for continued use
 */
FlockPlaces.saveDefaultPublishingLocations =
function FlockPlaces_saveDefaultPublishingLocations() {
  // Make sure a default location is selected before saving a series of blanks
  var prefs = this.getPrefs();

  // Save the local checkbox state
  var isLocalChecked = this.getEl("editBMPanel_enableLocalCheckbox").checked;
  prefs.setBoolPref(this.PREF_FAV_LOCAL, isLocalChecked);
  this._defaults.localCheckbox = isLocalChecked;

  // Save the current local folder in the prefs and update the defaults
  var currentFolderId = PlacesUtils.bookmarks.getFolderIdForItem(this._itemId);
  if (currentFolderId == onlineFavoritesBackend.getInternalOnlineRootId()) {
    currentFolderId = PlacesUtils.unfiledBookmarksFolderId;
  }
  prefs.setCharPref(this.PREF_FAV_FOLDER, currentFolderId);
  this._defaults.local = currentFolderId;

  // Save the current state of the online bookmarks
  var accnts = this.getEl("onlineBookmarkAccnts").childNodes;
  var doChecked;
  var privateChecked;
  for (var i = 0; i < accnts.length; i++) {
    var accountItem = accnts[i];
    var prefAccntSfx = this.getOnlineBkPrefSuffix(accountItem.serviceId,
                                                  accountItem.accountId);
    this.getLogger().info("_saveDefaultPrefs() for: " + prefAccntSfx);
    doChecked = accountItem.checked;
    privateChecked = accountItem.private;
    // Save to preferences
    prefs.setBoolPref(this.PREF_FAV_DO + prefAccntSfx, doChecked);
    prefs.setBoolPref(this.PREF_FAV_PRIVATE + prefAccntSfx, privateChecked);
    // Update defaults
    this._defaults.accnts[prefAccntSfx].do = doChecked;
    this._defaults.accnts[prefAccntSfx].private = privateChecked;
  }
  // Disable button state to indicate the defaults are currently selected
  this.toggleDefaultLocationBtn(true);
};

/**
 * Queries the new FF3 nsINavHistoryService to determine if the folderId
 * in question has been previously saved
 * @param aFolderId - Folder Id of the hopeful SQL entry
 */
FlockPlaces.isLocalFolderValid =
function gFlockPl_isLocalFolderValid(aFolderId) {
  if (aFolderId) {
    // Ensure the stored folder is in fact valid
    var history = this.getHistSvc();
    var options = history.getNewQueryOptions();
    var query = history.getNewQuery();
    query.setFolders([aFolderId], 1);
    try {
      var queryResult = history.executeQuery(query, options);
      if (queryResult instanceof Ci.nsINavHistoryResult) {
        return true;
      }
    } catch (ex) {
      // The folder id is invalid
    }
  }
  return false;
};

/**
 * Populates a list of current online bookmark accounts and whether
 * or not the current URL has been bookmarked with its respective settings
 * @param aUrl - URL of the page currently being visited
 */
FlockPlaces.populateOnlineFavs =
function gFlockPl_populateOnlineFavs(aUrl) {
  var onlineBookmarkAccnts = this.getEl("onlineBookmarkAccnts");
  // Clean up existing DOM
  while (onlineBookmarkAccnts.childNodes.length > 0) {
    onlineBookmarkAccnts.removeChild(onlineBookmarkAccnts.lastChild);
  }
  var onlineAccntsFound = false;
  this._onlineFavExists = false;
  // Enumerate through all available flockIBookmarkWebService accounts
  var accountsEnum = this.getAcUtils()
                         .getAccountsByInterface("flockIBookmarkWebService");
  while (accountsEnum.hasMoreElements()) {
    var account = accountsEnum.getNext()
                              .QueryInterface(Ci.flockIWebServiceAccount);
    var coopAccnt = this.getCoop().get(account.urn);
    // Account has been remembered
    onlineAccntsFound = true;
    var serviceId = coopAccnt.serviceId;
    var accountId = coopAccnt.accountId;
    var svc = Cc[serviceId].getService(Ci.flockIWebService)
                           .QueryInterface(Ci.flockIBookmarkWebService);
    // Create the bookmarksAccount binding
    var accountItem = document.createElementNS(this.XUL_NS,
                                               "bookmarksAccount");
    // Does the online favorite exist for this url?
    var onlineFavExists = svc.exists(accountId, aUrl);
    var onlineFavPrivate = svc.isPrivate(accountId, aUrl);
    accountItem.setAttribute("checked", onlineFavExists);
    // The private checkbox will xbl:inherit the disabled attribute
    accountItem.setAttribute("disabled", (!onlineFavExists));
    accountItem.setAttribute("private", onlineFavPrivate);
    accountItem.setAttribute("value", coopAccnt.name);
    accountItem.setAttribute("icon", coopAccnt.favicon);
    accountItem.serviceId = serviceId;
    accountItem.accountId = accountId;
    accountItem.id = accountId + "-" + serviceId;
    if (onlineFavExists) {
      this._onlineFavExists = true;
      // Get the Places Item associated with this online bookmark
      var guid = this.getPlacesGUID(svc, accountId, aUrl);
      var localId = this.getBmSvc().getItemIdForGUID(guid);
      if (localId != -1) {
        // Determine the last published date if available
        var itemLastModified = this.getBmSvc().getItemLastModified(localId);
        // The returned time is in microseconds, convert to milliseconds
        var publishDate = new Date(itemLastModified / 1000);
        accountItem.setAttribute("publishDate",
                                 this.formatPublishDate(publishDate));
        var isBookmarkPrivate = this.getPrivacyAnnotation(localId);
        accountItem.setAttribute("private", isBookmarkPrivate);
        accountItem.setAttribute("privateStatus", isBookmarkPrivate);
      }
    }
    // Append the new online bookmark account
    onlineBookmarkAccnts.appendChild(accountItem);
  }
  var onlineBookmarkDeck = this.getEl("onlineBookmarkDeck");
  var localCheckbox = this.getEl("editBMPanel_enableLocalCheckbox");
  if (onlineAccntsFound) {
    // Display the online bookmark accounts
    onlineBookmarkDeck.selectedIndex = 0;
    localCheckbox.setAttribute("hidden", "false");
  } else {
    // Display the online bookmark discoverability verbiage
    onlineBookmarkDeck.selectedIndex = 1;
    localCheckbox.setAttribute("hidden", "true");
  }
};

FlockPlaces.publishOnlineBookmark =
function FlockPlaces_publishOnlineBookmark(aSvc,
                                        aBookmark,
                                        aAccountId)
{
  // Ensure we are within scope, as this can be silently called for
  // default publishing of the online bookmarks
  var inst = FlockPlaces;
  // Publish Listener
  var publishListener = {
    onSuccess: function publishListener_onSuccess(aSubject, aTopic) {
      inst.getLogger().info("publishListener_onSuccess('" + aSubject + "', '"
                                                          + aTopic + "')");
    },
    onError: function publishListener_onError(aSubject, aTopic, aError) {
      inst.getLogger().error("publishListener_onError('" + aSubject + "', '"
                                                         + aTopic + "', '"
                                                         + aError + "')");
      var strBundleProperty = "flock.favs.overlay.favorite-publication-error";
      var message = inst.getBundle()
                        .formatStringFromName(strBundleProperty,
                                              [inst._uri.spec, aSvc.shortName],
                                              2);
      var nBox = inst.getNotificationBox();
      if (nBox) {
        nBox.appendUniqueNotification(message,
                                      "favorite-publication-error",
                                      inst.PUBLISH_NOTIFICATION_ICON,
                                      nBox.FLOCK_PRIORITY_HIGH,
                                      null);
        return;
      }
      // Secondary method of notification if nBox is null
      var title = inst.getBundle()
                      .GetStringFromName("flock.favs.overlay.publish.error");
      inst.getPromptSvc().alert(null, title, message);
    }
  };
  // Publish the bookmark(s) to the online service
  this.getLogger().info("publishOnlineBookmark() to: "
                        + aSvc.shortName + " : " + aAccountId);
  if (aBookmark instanceof Object) {
    aSvc.publish(publishListener, aAccountId, aBookmark);
  } else if (aBookmark instanceof Array) {
    aSvc.publishList(publishListener,
                     aAccountId,
                     new simpleEnumProto(aBookmark));
  }
};

// Helper Functions

/**
 * When publishing from the Favorites Sidebar, or from the Places manager
 * gBrowser is undefined, let's grab the most recent browser this way
 */
FlockPlaces.getNotificationBox =
function getNotificationBox_getNotificationBox() {
  var wm = Cc["@mozilla.org/appshell/window-mediator;1"]
           .getService(Ci.nsIWindowMediator);
  var win = wm.getMostRecentWindow("navigator:browser");
  if (win) {
    return win.getBrowser().getNotificationBox();
  }
  return null;
};

/**
 * Return a string of how the prefs are stored for online bookmark defaults
 * @param aServiceId - ServiceId of the current online bookmark service
 * @param aAccountId - AccountId of the current user of service aServiceId
 */
FlockPlaces.getOnlineBkPrefSuffix =
function FlockPlaces_getOnlineBkPrefSuffix(aServiceId, aAccountId) {
  return aServiceId + "--" + aAccountId;
};

FlockPlaces.getPlacesGUID =
function FlockPlaces_getPlacesGUID(aService, aAccountId, aUrl) {
  return aService.urn + ":" + aAccountId + ":" + aUrl;
};

FlockPlaces.getPlacesTime =
function FlockPlaces_getPlacesTime() {
  // Note: new Date.getTime() returns a value in milliseconds
  // while type PRTime is in microseconds, let's convert it
  return ((new Date()).getTime() * 1000);
};

/**
 * Check to see if the item is a local directory path, and if so, convert
 * to a file URL so that aggregation with rdf:files works
 * @param aUrl - URL of the page currently being visited
 */
FlockPlaces.getNormalizedURL =
function FlockPlaces_getNormalizedURL(aUrl) {
  try {
    const kLF = Cc["@mozilla.org/file/local;1"]
                .createInstance(Ci.nsILocalFile);
    kLF.initWithPath(aUrl);
    if (kLF.exists()) {
      var ioService = Cc["@mozilla.org/network/io-service;1"]
                      .getService(Ci.nsIIOService);
      var fileHandler = ioService.getProtocolHandler("file")
                                 .QueryInterface(Ci.nsIFileProtocolHandler);
      aUrl = fileHandler.getURLSpecFromFile(kLF);
    }
  } catch (ex) {
    // no-op
  }
  return aUrl;
};

/**
 * Return a string of space delimited string of tags
 * @param aString - a comma delimited string of tags
 */
FlockPlaces.formatTagField =
function FlockPlaces_formatTagField(aString) {
  return (aString) ? aString.split(/[\s,]/).join(" ") : "";
};

/**
 * Return a formatted date
 * @param aDate - a new Date() value
 */
FlockPlaces.formatPublishDate =
function FlockPlaces_formatPublishDate(aDate) {
  if (aDate) {
    // Note: getDateString() takes into account the user's locale
    // when formatting this date
    return FlockDateFormatter.getDateString(aDate);
  }
  return "";
};

FlockPlaces.getEl =
function FlockPlaces_getEl(aId) {
  return document.getElementById(aId);
};

// Places Manager

FlockPlaces.learnAboutOnlineBookmarks =
function FlockPlaces_learnAboutOnlineBookmarks(aEvent) {
  var panel = this.getEl("editBookmarkPanel");
  if (panel && panel.state != "closed") {
    // Hide the panel as the user has chosen to visit the learn about
    // online favorites page
    StarUI.panel.hidePopup();
  }

  var wm = Cc["@mozilla.org/appshell/window-mediator;1"]
           .getService(Ci.nsIWindowMediator);
  var recent = wm.getMostRecentWindow("navigator:browser");

  var url = onlineFavoritesBackend.learnAboutOnlineFavoritesURL();

  recent.FlockTabs.openURL(url, aEvent);
};

FlockPlaces.togglePlacesOnlinePublishing =
function FlockPlaces_togglePlacesOnlinePublishing(aIsHidden) {
  this.getEl("placesManagerOnlineBookmarks").setAttribute("hidden", aIsHidden);
};

FlockPlaces.initPlacesOnlineFavs =
function FlockPlaces_initPlacesOnlineFavs(aItemId, aSpec) {
  // First check to see if the user has unsaved changes
  FlockPlaces.checkForValidPlacesTxn();

  // Enable the places manager transaction
  this._placesTxnEnabled = true;

  // If no item id specified, let's return
  if (!aItemId) {
    this.togglePlacesOnlinePublishing(true);
    return;
  }

  var itemType = null;
  try {
    itemType = this.getBmSvc().getItemType(aItemId);
  } catch (ex) {
    // Unable to retrieve the item type
  }
  // Ensure the item is not a query
  var isQuery = PlacesUtils._uri(aSpec).schemeIs("place");
  // Only show the online accounts if the selected item is a bookmark
  if (itemType &&
      itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK &&
      !isQuery)
  {
    this.populateOnlineFavs(aSpec);
  }

  // Toggle the online publishing container hidden or visibile depending
  // on if the item in question is a Query or not
  this.togglePlacesOnlinePublishing(isQuery);
};

FlockPlaces.initPlacesManagerTxn =
function FlockPlaces_initPlacesManagerTxn(aItemId, aUri, aTxnCount) {
  this._placesTxnArray = [];
  this._placesTxnBookmark = null;
  this._placesTxnItemId = aItemId;
  this._placesTxnCount = aTxnCount;
  this._placesTxnNeedsPublish = false;
  this._placesStoredItemProperties = [];
  this.toggleButtonDisabled("flockOverlayCancel", true);
  this.toggleButtonDisabled("flockOverlayDone", true);
};

FlockPlaces.placesDestroy =
function FlockPlaces_placesDestroy() {
  this.checkForValidPlacesTxn();
  this._placesTxnEnabled = false;
  for each (var key in this._placesEditableFields) {
    var elementId = this.FF_PANEL_PREFIX + key;
    var domNode = this.getEl(elementId);
    if (domNode) {
      domNode.removeEventListener("keyup", FlockPlaces.elementOnKeyUp, false);
    }
  }
};

FlockPlaces.toggleButtonDisabled =
function FlockPlaces_toggleButtonDisabled(aButtonId, aIsDisabled) {
  var button = this.getEl(aButtonId);
  if (button) {
    button.setAttribute("disabled", aIsDisabled);
  }
};

FlockPlaces.elementOnKeyUp =
function FlockPlaces_elementOnKeyUp() {
  var inst = FlockPlaces;
  for each (var key in inst._placesEditableFields) {
    var elementId = inst.FF_PANEL_PREFIX + key;
    var domNode = inst.getEl(elementId);
    if (domNode &&
        (inst._placesStoredItemProperties[elementId] != domNode.value))
    {
      inst.toggleButtonDisabled("flockOverlayDone", false);
      return;
    }
  }
  // We also need to check the online bookmark properties
  var accnts = inst.getEl("onlineBookmarkAccnts").childNodes;
  for (var i = 0; i < accnts.length; i++) {
    var accnt = accnts[i];
    if (accnt &&
        accnt.checked &&
        (inst._placesStoredItemProperties[accnt.id] == null ||
         inst._placesStoredItemProperties[accnt.id] != accnt.getPrivate()))
    {
      inst.toggleButtonDisabled("flockOverlayDone", false);
      return;
    }
  }
  inst.toggleButtonDisabled("flockOverlayDone", true);
};

FlockPlaces.loadItemProperties =
function FlockPlaces_loadItemProperties() {
  // Grab all bookmark properties from the FF overlay
  for each (var key in this._placesEditableFields) {
    var elementId = this.FF_PANEL_PREFIX + key;
    var domNode = this.getEl(elementId);
    if (domNode) {
      domNode.addEventListener("keyup", FlockPlaces.elementOnKeyUp, false);
      this._placesStoredItemProperties[elementId] = domNode.value;
    }
  }
  // We also need to include the online bookmark properties
  var accnts = this.getEl("onlineBookmarkAccnts").childNodes;
  for (var i = 0; i < accnts.length; i++) {
    var accnt = accnts[i];
    if (accnt && accnt.checked) {
      // Store the private setting here, as if this changes we will want
      // to toggle the save button to its proper state
      this._placesStoredItemProperties[accnt.id] = accnt.getPrivate();
    } else {
      // Store a null reference to indicate that the bookmark isn't published
      this._placesStoredItemProperties[accnt.id] = null;
    }
  }
};

FlockPlaces.resetCurrentPlacesTxn =
function FlockPlaces_resetCurrentPlacesTxn() {
  this._placesTxnArray = [];
  this._placesTxnBookmark = null;
};

FlockPlaces.decreaseTxnCount =
function FlockPlaces_decreaseTxnCount() {
  if (this._placesTxnCount == 0) {
    return;
  }
  this._placesTxnCount--;
  if (this._placesTxnCount == 0) {
    // Re-initialize the panel with data from the sql backend
    gEditItemOverlay.initPanel(gEditItemOverlay._itemId,
                               {hiddenRows: ["folderPicker"]});
  }
};

FlockPlaces.placesPushUpdatedFields =
function FlockPlaces_placesPushUpdatedFields(aNode) {
  // Create the bookmark object if null
  if (!this._placesTxnBookmark) {
    this._placesTxnBookmark = {};
  }
  for (var i = 0; i < aNode.length; i++) {
    this._placesTxnNeedsPublish = true;
    for (var key in aNode[i]) {
      switch (key) {
        case "name":
          this._placesTxnBookmark.name = aNode[i].name;
          break;
        case "description":
          this._placesTxnBookmark.description = aNode[i].description;
          break;
        case "URL":
          this._placesTxnBookmark.URL = aNode[i].URL;
          break;
        case "tags":
          this._placesTxnBookmark.tags = aNode[i].tags;
          break;
        case "shared":
          this._placesTxnBookmark.shared = aNode[i].shared;
          break;
      }
    }
  }
};

FlockPlaces.togglePlacesTxnShared =
function FlockPlaces_togglePlacesTxnShared(aIsPrivate) {
  if (!this._placesTxnEnabled) {
    return;
  }
  this.placesPushUpdatedFields({shared: (!aIsPrivate)});
  this.elementOnKeyUp();
};

FlockPlaces.togglePlacesTxn =
function FlockPlaces_togglePlacesTxn() {
  if (!this._placesTxnEnabled) {
    return;
  }
  if (!this._placesTxnBookmark) {
    this._placesTxnBookmark = {};
  }
  this.elementOnKeyUp();
};

FlockPlaces.placesPushTxn =
function FlockPlaces_placesPushTxn(aFieldKey, aTxn) {
  if (aFieldKey && aTxn) {
    // Keep an Array of key value pairs so that we override
    // any existing transactions of the same type (aFieldKey)
    this._placesTxnArray[aFieldKey] = aTxn;
  }
};

FlockPlaces.doPlacesManagerTxn =
function FlockPlaces_doPlacesManagerTxn(aButton) {
  var txns = [];
  // We must reconstruct the associative array for
  // aggregateTransactions() to work properly
  for (var i in this._placesTxnArray) {
    txns.push(this._placesTxnArray[i]);
  }
  if (txns.length > 0) {
    var ptm = PlacesUIUtils.ptm;
    // Do Local Places Transactions
    ptm.doTransaction(ptm.aggregateTransactions("FlockPlacesTxn", txns));
    this._placesTxnCount++;
    this.toggleButtonDisabled("flockOverlayCancel",
                              (this._placesTxnCount == 0));
    delete txns;
  }

  // If the changes are undone, the tag field doesn't revert to
  // reflect the backend changes, we will need to rebuild the list
  this._placesTagsNeedRebuiltOnUndo = (this._placesTxnArray.indexOf("tag") ||
                                       this._placesTxnArray.indexOf("untag"));

  if (this._placesTxnItemId && this._placesTxnBookmark) {
    // Publish any changes online if the user agrees to do so
    this.publishPlacesChangesOnline(this._placesTxnItemId,
                                    this._placesTxnBookmark,
                                    null);
  }

  // Reset the the transaction and get ready for a new one
  this.resetCurrentPlacesTxn();
  this.toggleButtonDisabled("flockOverlayDone", true);
};

FlockPlaces.undoPlacesTxn =
function FlockPlaces_undoPlacesTxn() {
  // If no transactions have occured return
  if (this._placesTxnCount == 0) {
    return;
  }

  // Undo the transaction
  PlacesUIUtils.ptm.undoTransaction();

  if (this._placesTagsNeedRebuiltOnUndo) {
    var tags = PlacesUtils.tagging
                          .getTagsForURI(gEditItemOverlay._uri, {}).join(", ");
    gEditItemOverlay._initTextField("tagsField", tags, false);
  }

  // Republish any undone changes online
  this.publishPlacesChangesOnline(gEditItemOverlay._itemId, {}, null);

  // Check for state change
  this.decreaseTxnCount();
  this.toggleButtonDisabled("flockOverlayCancel",
                            (this._placesTxnCount == 0));
};

FlockPlaces.publishPlacesChangesOnline =
function FlockPlaces_publishPlacesChangesOnline(aItemId, aBookmark, aNode) {
  // Get an updated version of our locally modified bookmark
  var newBookmark = this.createUpdatedBookmark(aItemId, aBookmark);
  var accnts = this.getEl("onlineBookmarkAccnts").childNodes;
  for (var i = 0; i < accnts.length; i++) {
    var accountItem = accnts[i];
    if (accountItem && accountItem.checked) {
      var svc = Cc[accountItem.serviceId]
                .getService(Ci.flockIBookmarkWebService);
      // Update bookmark object's shared setting
      newBookmark.shared = (!accountItem.getPrivate());

      // The user has explicitly checked the online account to republish
      // so there is no need to confirm the publish with the user
      this.publishOnlineBookmark(svc, newBookmark, accountItem.accountId);
    }
  }
};

FlockPlaces.createUpdatedBookmark =
function FlockPlaces_createUpdatedBookmark(aItemId, aBookmarkObj) {
  // Create new flockIBookmark
  var bookmark = new BookmarkProto();

  // Url
  var newURL = aBookmarkObj.URL;
  var uri;
  if (newURL) {
    uri = PlacesUtils._uri(newURL);
    bookmark.URL = newURL;
  } else {
    uri = this.getBmSvc().getBookmarkURI(aItemId);
    bookmark.URL = uri.spec;
  }

  // Name
  var newName = aBookmarkObj.name;
  if (newName) {
    bookmark.name = newName;
  } else {
    bookmark.name = this.getBmSvc().getItemTitle(aItemId);
  }

  // Tags
  var newTags = aBookmarkObj.tags;
  if (newTags) {
    bookmark.tags = newTags;
  } else {
    bookmark.tags = PlacesUtils.tagging
                               .getTagsForURI(uri, {})
                               .join(" ");
  }

  // Description
  var newDescription = aBookmarkObj.description;
  if (newDescription) {
    bookmark.description = newDescription;
  } else {
    bookmark.description = PlacesUIUtils.getItemDescription(aItemId);
  }
  bookmark.time = this.getPlacesTime();
  bookmark.shared = (!this.getPrivacyAnnotation(aItemId));
  return bookmark;
};

FlockPlaces.setDragDropNode =
function FlockPlaces_setDragDropNode(aDragNode, aContainer) {
  this._dragNode = aDragNode;
  this._dropUrnAnnotation = this.getContainerQueryAnnotation(aContainer);
};

/**
 * Check to see whether the online bookmark is private or public
 */
FlockPlaces.getPrivacyAnnotation =
function FlockPlaces_getPrivacyAnnotation(aItemId) {
  try {
    var privateAnnotation = this.getAnnoSvc()
                                .getItemAnnotation(aItemId,
                                                   this.ANNO_PRIVACY);
    return (privateAnnotation == this.PRIVATE);
  } catch (ex) {
    // Annotation is unavailable, in this case we don't want to use
    // itemHasAnnotation as it creates an extra SQL query
  }
  return false;
};

FlockPlaces.getAccntSvcObjFromUrn =
function FlockPlaces_getAccntSvcObjFromUrn(aUrnAnnotation) {
  var coopAccnt = this.getCoop()
                      .get(aUrnAnnotation.split(this.ANNO_ACCOUNT)[1])
  var service = Cc[coopAccnt.serviceId]
                .getService(Ci.flockIWebService)
                .QueryInterface(Ci.flockIBookmarkWebService);
  return {svc: service, accountId: coopAccnt.accountId};
};

/**
 * Extract the annotation from the container in question
 */
FlockPlaces.getContainerQueryAnnotation =
function FlockPlaces_getQueryAnnotation(aContainer) {
  if (aContainer instanceof Ci.nsINavHistoryQueryResultNode) {
    var result = aContainer.getQueries({}, {});
    if (result &&
        result[0] &&
        result[0].annotation)
    {
      return result[0].annotation;
    }
  }
  return null;
};

/**
 * Determines if the node in question is a child of an online service node
 */
FlockPlaces.isNodeOnline =
function FlockPlaces_isNodeOnline(aNode) {
  var annotation = this.getContainerQueryAnnotation(aNode);
  if (annotation && annotation.indexOf(this.ANNO_ACCOUNT) != -1) {
    return true;
  }
  return false;
};

/**
 * There are 2 cases where we want to prevent deletion:
 *  - the node is an online tag
 *  - the node is an online unfiled bookmark
 */
FlockPlaces.isOnlineReadonly =
function FlockPlaces_isOnlineReadonly(aNode) {
  if (FlockPlaces.isNodeOnline(aNode)) {
    // The node is an online tag
    return true;
  }

  if (!FlockPlaces.isNodeOnline(aNode.parent)) {
    // The node is neither an online tag nor an online bookmark
    return false;
  }

  var itemURI = PlacesUtils._uri(aNode.uri);
  var tags = PlacesUtils.tagging.getTagsForURI(itemURI, {});

  return (tags.length == 0);
};

FlockPlaces.isItemOnlineAccount =
function FlockPlaces_isItemOnlineAccount(aParentItemId) {
  return (aParentItemId == this.onlineRootId);
};

FlockPlaces.getAccountUrnFromMasterTag =
function FlockPlaces_getAccountUrnFromMasterTag(aURI) {
  var history = this.getHistSvc();
  var accountsEnum = this.getAcUtils()
                         .getAccountsByInterface("flockIBookmarkWebService");
  while (accountsEnum.hasMoreElements()) {
    var account = accountsEnum.getNext()
                              .QueryInterface(Ci.flockIWebServiceAccount);
    // If the bookmark is accessed from the master tag list, we do not have a
    // parent/child relationship that would indicate that the bookmark item
    // is a child to an online account, let's create a query based on:
    // i) URI
    // ii) The internal online root id
    // iii) An online account urn
    var options = history.getNewQueryOptions();

    // Only interested in bookmarks
    options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
    var query = history.getNewQuery();

    // Contrain our search by the internal online root
    query.setFolders([this.internalOnlineRootId], 1);

    // Set the URI for the query
    query.uri = aURI;

    // Set the annotation
    query.annotation = this.ANNO_ACCOUNT + account.urn;
    try {
      var queryResult = history.executeQuery(query, options);
      if (queryResult instanceof Ci.nsINavHistoryResult) {
        var resultNode = queryResult.root;
        resultNode.containerOpen = true;
        if (resultNode.childCount > 0) {
          return query.annotation;
        }
      }
    } catch (ex) {
      // The query is invalid
      this.getLogger().info("getAccountUrnFromMasterTag - invalid query");
    }
  }
  return null;
};

FlockPlaces.onPlacesContextDelete =
function FlockPlaces_onPlacesContextDelete(aNode) {
  var bundle = this.getBundle();
  var contextDelete = this.getEl("cmd_delete");
  if (aNode &&
      aNode.parent &&
      PlacesUtils.nodeIsBookmark(aNode) &&
      PlacesUtils.nodeIsTagQuery(aNode.parent))
  {
    var tagName = aNode.parent.title ||
                  PlacesOrganizer._places.selectedNode.title;

    if (tagName != this.FLOCK_UNFILED) {
      contextDelete.setAttribute("label",
        bundle.formatStringFromName("flock.places.context.removeTag.label",
                                    [tagName],
                                    1));
      return;
    }
  }
  contextDelete.setAttribute("label",
    bundle.GetStringFromName("flock.places.context.deleteCmd.label"));
};

/**
 * When a user deletes a bookmark from any online Tag folder,
 * synchronize the removal with the online service it belongs to
 */
FlockPlaces.removeTagForNodes =
function FlockPlaces_removeTagForNodes(aNodeArray) {
  var arrayLength = aNodeArray.length;
  // Confirm with the user that they want to republish the changes
  var accntSvcObj = this.getAccntSvcObjFromUrn(aNodeArray[0].annotation);
  var svc = accntSvcObj.svc;
  var accountId = accntSvcObj.accountId;
  if ((arrayLength == 0) ||
      !this.confirmPublishWithUser(aNodeArray[0].node, svc, accountId))
  {
    return;
  }

  for (var i = 0; i < arrayLength; i++) {
    var childObj = aNodeArray[i];
    var node = childObj.node;
    var nodeId = node.itemId;
    var uri = PlacesUtils._uri(node.uri);

    // Create new flockIBookmark
    var bookmark = new BookmarkProto();
    bookmark.URL = uri.spec;
    bookmark.name = node.title;
    // Grab the existing tag(s)
    var tagSet = PlacesUtils.tagging.getTagsForURI(uri, {});
    // Remove the tag of the folder that was deleted; if the firefox txn
    // hasn't finnished this ensures the tag is removed from the list
    var removedTagIndex = tagSet.indexOf(childObj.tagName);
    if (removedTagIndex != -1) {
      tagSet.splice(removedTagIndex, 1);
    }
    bookmark.tags = tagSet.join(" ");
    bookmark.description = PlacesUIUtils.getItemDescription(nodeId);
    bookmark.time = this.getPlacesTime();
    bookmark.shared = (!this.getPrivacyAnnotation(nodeId));

    // Republish the bookmark to the online service
    this.publishOnlineBookmark(svc,
                               bookmark,
                               accountId);
  }
};

/**
 * Recursively iterate through a given folder's contents and create
 * an Array of bookmarks to be published
 */
FlockPlaces.getBookmarksFromFolder =
function FlockPlaces_getBookmarksFromFolder(aFolderId,
                                            aTagDroppedOn,
                                            aBookmarkArray,
                                            aBmSvc,
                                            aHistorySvc)
{
  var options = aHistorySvc.getNewQueryOptions();
  var query = aHistorySvc.getNewQuery();
  query.setFolders([aFolderId], 1);
  var queryResult = aHistorySvc.executeQuery(query, options);
  if (queryResult instanceof Ci.nsINavHistoryResult) {
    // Open the folder, and iterate over its contents.
    var folderNode = queryResult.root;
    folderNode.containerOpen = true;
    for (var i = 0; i < folderNode.childCount; ++i) {
      var childNode = folderNode.getChild(i);
      var childNodeId = childNode.itemId;
      switch (childNode.type) {
        case Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER:
          aBookmarkArray = this.getBookmarksFromFolder(childNodeId,
                                                       aTagDroppedOn,
                                                       aBookmarkArray,
                                                       aBmSvc,
                                                       aHistorySvc);
          break;
        case Ci.nsINavHistoryResultNode.RESULT_TYPE_URI:
          var uri = PlacesUtils._uri(childNode.uri);
          // Create new flockIBookmark
          var bookmark = new BookmarkProto();
          bookmark.URL = childNode.uri;
          bookmark.name = childNode.title;
          var tagSet = PlacesUtils.tagging.getTagsForURI(uri, {});
          tagSet.push(aTagDroppedOn);
          bookmark.tags = tagSet.join(" ");
          bookmark.description = PlacesUIUtils.getItemDescription(childNodeId);
          bookmark.time = this.getPlacesTime();
          bookmark.shared = (!this.getPrivacyAnnotation(childNodeId));
          aBookmarkArray.push(bookmark);
          break;
      }
    }
  }
  return aBookmarkArray;
};

/**
 * Drap and Drop publishing to online service from either the Places
 * Manager or the Favorites sidebar.
 */
FlockPlaces.dropPublishOnlineBookmark =
function FlockPlaces_dropPublishOnlineBookmark(aInsertionPoint,
                                               aDragObj,
                                               aDroppedOnAccount)
{
  var draggedBookmarkId = aDragObj.id;
  if (!this._dropUrnAnnotation || !draggedBookmarkId) {
    return;
  }

  var bmSvc = this.getBmSvc();
  var uri = PlacesUtils._uri(aDragObj.uri);
  var tagSet = PlacesUtils.tagging.getTagsForURI(uri, {});
  var tagDroppedOn = bmSvc.getItemTitle(aInsertionPoint.itemId);
  // We need to also ensure this tag has been added to our *online account*
  var accntSvcObj = this.getAccntSvcObjFromUrn(this._dropUrnAnnotation);
  var svc = accntSvcObj.svc;
  var accountId = accntSvcObj.accountId;
  if ((tagSet.indexOf(tagDroppedOn) != -1) &&
      svc.exists(accountId, aDragObj.uri))
  {
    // If bookmark already contains the tag prompt the user and return
    this.promptTagExists(aDragObj.title, tagDroppedOn);
    return;
  }

  // Confirm with the user that they want to publish to
  // the respective online service
  if (!this.confirmPublishWithUser(aDragObj, svc, accountId)) {
    return;
  }

  this._uri = uri;
  var bookmark;

  if (this._dragNode && PlacesUtils.nodeIsFolder(this._dragNode)) {
    // The Dragged node was a bookmark folder,
    // we need to traverse its contents
    var dragNodeId = PlacesUtils.getConcreteItemId(this._dragNode);
    bookmark = this.getBookmarksFromFolder(dragNodeId,
                                           tagDroppedOn,
                                           [],
                                           bmSvc,
                                           this.getHistSvc());
  } else {
    // The Dragged node was a bookmark item
    // Create new flockIBookmark
    bookmark = new BookmarkProto();
    bookmark.URL = this._uri.spec;
    bookmark.name = aDragObj.title;
    if (!aDroppedOnAccount) {
      // Add the dropped on tag if it isn't an online account
      tagSet.push(tagDroppedOn);
    }
    bookmark.tags = tagSet.join(" ");
    bookmark.description =
      PlacesUIUtils.getItemDescription(draggedBookmarkId);
    bookmark.time = this.getPlacesTime();
    bookmark.shared = (!this.getPrivacyAnnotation(draggedBookmarkId));
  }

  // Publish the bookmark(s) to the online service
  this.publishOnlineBookmark(svc,
                             bookmark,
                             accountId);
  if (aDroppedOnAccount) {
    // Should we resynch our account with the service?
    var urn = this._dropUrnAnnotation.split(this.ANNO_ACCOUNT)[1];
    var flockPollingListener = {
      onResult: function FP_flockPollingListener_onResult() {
        // Stub function, no op.
      },
      onError: function FP_flockPollingListener_onError() {
        // Stub function, no op.
      }
    }
    svc.refresh(urn, flockPollingListener);
  }
};

FlockPlaces.confirmPublishWithUser =
function FlockPlaces_confirmPublishWithUser(aNode, aSvc, aAccountId) {
  var shouldPublish = true;
  var dontShowAgainPref, title, label;
  var prefs = this.getPrefs();

  if (aNode && PlacesUtils.nodeIsFolder(aNode)) {
    dontShowAgainPref = "flock.favorites.doPublishFolder.DontShowAgain";
    title = "flock.places.prompt.publishFolder.title";
    label = "flock.places.prompt.publishFolder.label";
  } else {
    dontShowAgainPref = "flock.favorites.doPublish.DontShowAgain";
    // Determine if the favorite is already online
    // Prompt to a) publish or b) republish
    if (aNode && aSvc.exists(aAccountId, aNode.uri)) {
      title = "flock.places.prompt.republishBookmark.title";
      label = "flock.places.prompt.republishBookmark.label";
    } else {
      title = "flock.places.prompt.publishBookmark.title";
      label = "flock.places.prompt.publishBookmark.label";
    }
  }

  if (!prefs.getBoolPref(dontShowAgainPref)) {
    var checkResult = {};
    var bundle = this.getBundle();
    var svcTitle = aSvc.title;
    if (!this.getPromptSvc()
             .confirmCheck(window,
                           bundle.formatStringFromName(title,
                                                       [svcTitle],
                                                       1),
                           bundle.formatStringFromName(label,
                                                       [svcTitle, aAccountId],
                                                       2),
                           bundle.GetStringFromName(this.DONT_SHOW_AGAIN),
                           checkResult))
    {
      shouldPublish = false;
    }
    if (checkResult.value) {
      prefs.setBoolPref(dontShowAgainPref, true);
    }
  }
  return shouldPublish;
};

FlockPlaces.promptTagExists =
function FlockPlaces_promptTagExists(aBookmarkName, aTag) {
  var prefs = this.getPrefs();
  var dontShowAgainPref = "flock.favorites.tagExists.dontShowAgain";
  if (!prefs.getPrefType(dontShowAgainPref) ||
      !prefs.getBoolPref(dontShowAgainPref))
  {
    var checkResult = {};
    var bundle = this.getBundle();
    var tagBranch = "flock.places.prompt.tagExists.";
    this.getPromptSvc()
        .alertCheck(window,
                    bundle.GetStringFromName(tagBranch + "title"),
                    bundle.formatStringFromName(tagBranch + "description",
                                                [aBookmarkName, aTag],
                                                2),
                    bundle.GetStringFromName(this.DONT_SHOW_AGAIN),
                    checkResult);
    if (checkResult.value) {
      prefs.setBoolPref(dontShowAgainPref, true);
    }
  }
};

FlockPlaces.checkForValidPlacesTxn =
function FlockPlaces_checkForValidPlacesTxn() {
  // Check the associative array for properties
  var localChange = false;
  for (var i in this._placesTxnArray) {
    localChange = true;
    break;
  }
  var onlineChange = (this._placesTxnItemId && this._placesTxnBookmark);
  if (localChange || onlineChange) {
    this.promptUserForSave();
  }
  // Reset the the transaction
  this.resetCurrentPlacesTxn();
};

FlockPlaces.promptUserForSave =
function FlockPlaces_promptUserForSave() {
  var bundle = this.getBundle();
  var savePromptBranch = "flock.places.prompt.saveBookmark.";
  var promptSvc = this.getPromptSvc();
  var flags = (promptSvc.BUTTON_TITLE_SAVE * promptSvc.BUTTON_POS_0)
            + (promptSvc.BUTTON_TITLE_DONT_SAVE * promptSvc.BUTTON_POS_1)
            + (0 * promptSvc.BUTTON_POS_2);
  var result = promptSvc
               .confirmEx(window,
                          bundle.GetStringFromName(savePromptBranch + "title"),
                          bundle.GetStringFromName(savePromptBranch + "description"),
                          flags, null, null, null, null, {})

  // If the user clicks "Don't Save" the returned result is 1; when
  // the user clicks "Save" the value returned is 0
  if (result == 0) {
    this.doPlacesManagerTxn();
    return;
  }
};
