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

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

CU.import("resource:///modules/FlockStringBundleHelpers.jsm");
CU.import("resource:///modules/FlockDateFormatter.jsm");
CU.import("resource:///modules/FlockMediaUtils.jsm");

var gProfiler = CC["@flock.com/profiler;1"].getService(CI.flockIProfiler);
var gMBProfEvt = gProfiler.profileEventStart("mediabar");

var $ = function(aId) {
  return document.getElementById(aId);
};

const FLNS = "http://flock.com/rdf#";
const RECENT_ROOT = "urn:media:recent";
const FAV_CHANNELS_ROOT = "urn:media:favorites";
const XUL_NAMESPACE = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

const PERSON_QUERY_START = "user:";
const PER_PAGE = 50;
const MAX_RECENT = 20; // The maximum length of the recent channels list
const DFT_MEDIA_SEARCH_PREF = "flock.media.defaultquery.service";
const CUR_MEDIA_SEARCH_PREF = "flock.media.search.currentEngine";
const DFLT_STREAM_IGNORE_SVC_PREF = "flock.mediabar.defaultMediaStreams.ignoredServicesList";
const DFLT_SEARCH_IGNORE_SVC_PREF = "flock.mediabar.searchBar.ignoredServicesList";
const MEDIA_ADD_FAV_DLG_PREF = "flock.media.addFavDlg.show";
const MEDIA_VIEW_ALL_CAPTION = "flock.media.view.all";

// delay time before loading previous stream after "Clear all" action
const CLEAR_NEW_LOAD_DELAY = 100;

const height_small = 144;
const height_small_meta = 147;
const height_large = 250;

var gPhotoDrawer = null;
var gMediaQueryEditUILoaded = false;
var gInitialActionMessage = null;
var gUpdateTimer = null;
var mediabar = null;

var gPhotoUploadSvc = CC["@flock.com/media/upload-service;1"]
                      .getService(CI.flockIPhotoUploadService);

var unseenItemsObs = {
  rdfChanged: function pdUnseenItemsObs_rdfChanged(ds, type,
                                                   source, predicate,
                                                   target, oldtarget)
  {
    // JMC - Just set a dirty flag and update the UI on a timer
    // Otherwise this will really hurt when changing a lot of streams.
    // XXX TODO: Fix this for multi window bugs
    try {
      if (gUpdateTimer) {
        clearTimeout(gUpdateTimer);
      }
      gUpdateTimer = setTimeout("gPhotoDrawer.updateFavoritesChannelUI()", 1);
    } catch(ex) {
      dump(ex);
    }
  }
};


// The photo drawer controller
var PhotoDrawer = function(aNode, aSlideybox) {
  this.mSlideybox = aSlideybox;
  this.mGroupbarBox = this.mSlideybox.groupbarBox;
  this.pref_service = CC["@mozilla.org/preferences-service;1"]
                      .getService(CI.nsIPrefBranch);
  this.acUtils = CC["@flock.com/account-utils;1"]
                 .getService(CI.flockIAccountUtils);
  this.metrics = CC["@flock.com/metrics-service;1"]
                 .getService(CI.flockIMetricsService);
  this.mediaObserverService = CC["@mozilla.org/observer-service;1"]
                              .getService(CI.nsIObserverService);
  this.logger = CC["@flock.com/logger;1"].createInstance(CI.flockILogger);
  this.logger.init("mediabar");
  this.mFinishedTasks = 0;

  this.init();
  this.loadInitialMedia();
  this.mediaObserver = {
    observe: function pdMediaObserver_observe(aSubject, aTopic, aData) {
      if (aTopic == "mediaFinished") {
        // There are 2 tasks we are relying on when we search: searchPerson()
        // and loadQuery().  MBL: Bug #10745 When we are browsing an album,
        // "search" will be undefined.
        if (gPhotoDrawer.isEmpty &&
            (gPhotoDrawer.mFinishedTasks == 2 || !gPhotoDrawer.mQuery.search))
        {
          var message = "";
          if (gPhotoDrawer.hasPerson) {
            message = flockGetString("photo/photo",
                                     "flock.media.personfound.nomedia.label");
          } else {
            message = flockGetString("photo/photo",
                                     "flock.media.nomedia.label");
          }
          gPhotoDrawer.showActionMessage(message);
        }
      }
    }
  };
  this.mediaObserverService
      .addObserver(this.mediaObserver, "mediaFinished", false);
  this.apiAuthObserver = {
    observe: function pdApiAuthObserver_observe(aSubject, aTopic, aData) {
      if (aTopic == "apiAuthChange") {
        // Mediabar has detected that a service api auth has changed. Refresh
        // the mediabar only if the current stream is from the same service. We
        // need to do this to show/hide private media depending on the api auth
        // state.
        var svcShortname = aData;
        if (svcShortname == gPhotoDrawer.mApi) {
          gPhotoDrawer.refreshView();
        }
      }
    }
  };
  this.mediaObserverService
      .addObserver(this.apiAuthObserver, "apiAuthChange", false);
};

PhotoDrawer.prototype.init =
function pdInit() {
  this.logger.debug("init");
  this.metrics.report("MediaBar-Opened", null);

  var inst = this;
  this.mFinishedTasks = 0;
  this.mDnDInProgress = false;
  // Status of the UI
  this.mFilter = "";
  this.mPage = 0;
  this.isEmpty = true;
  this.isPersonQuery = false;
  this.objSynch = false;
  this.mControlBoxList = [];
  this.hasPerson = false;
  this.mViewAllCaption = flockGetString("photo/mediabar",
                                        MEDIA_VIEW_ALL_CAPTION);

  // Load engines based on RSS feeds from the pref
  var rssSearchJSON = this.pref_service.getCharPref("flock.photo.rssSearch");
  var nsJSON = CC["@mozilla.org/dom/json;1"].createInstance(CI.nsIJSON);
  this.mRSSEngines = nsJSON.decode(rssSearchJSON)[0];
  this.lastRSSEngine = null;

  // persist which photo size was last selected
  if ($("mediabar").getAttribute("photoSizeLarge") == "true") {
    this.setPhotoSize("large");
  } else {
    this.setPhotoSize("small");
    if (this.getMode() == "consume") {
      top.FlockTopbar.setHeight(height_small);
    } else {
      top.FlockTopbar.setHeight(height_small_meta);
    }
  }

  // hide group bar in consume mode
  if (this.getMode() == "consume") {
    this.mGroupbarBox.setAttribute("hidden", "true");
  }

  // Information about the current channel
  this.mApi = null;
  this.mQuery = new QueryHelper();
  this.mName = "";
  this.mAlbumLabel = "";
  this.mAlreadyLoaded = false;
  this.mPerson = null;
  this.mChannel = null;
  this.mHasPrivateMedia = false;

  this.mRDFService = CC["@mozilla.org/rdf/rdf-service;1"]
                     .getService(CI.nsIRDFService);
  this.mFavsDS = this.mRDFService.GetDataSource("rdf:flock-favorites");
  this.mFavsDS.QueryInterface(CI.flockIRDFObservable);

  this.unseenItemsPredicate = this.mRDFService.GetResource(FLNS
                                                           + "hasUnseenItems");
  this.mFavsDS.addArcObserver(CI.flockIRDFObserver.TYPE_CHANGE, null,
                              this.unseenItemsPredicate, null,
                              unseenItemsObs);

  // Coop stuff
  this._coop = CC["@flock.com/singleton;1"]
               .getService(CI.flockISingleton)
               .getSingleton("chrome://flock/content/common/load-faves-coop.js")
               .wrappedJSObject;
  this.recentRoot = this._coop.get(RECENT_ROOT);
  if (!this.recentRoot) {
    this.recentRoot = new this._coop.Folder(RECENT_ROOT);
    this._coop.favorites_root.children.add(this.recentRoot);
  }
  this.favChannelsRoot = this._coop.get(FAV_CHANNELS_ROOT);
  if (!this.favChannelsRoot) {
    this.favChannelsRoot = new this._coop.Folder(FAV_CHANNELS_ROOT);
    this._coop.favorites_root.children.add(this.favChannelsRoot);
  }

  // UI stuff
  this.slideyBoxListener = {
    onDrop: function onDrop(aSelection, aHoverChild) {
    },
    onScrollLeft: function onScrollLeft() {
    },
    onScrollRight: function onScrollRight() {
      var pagesLeft = inst.mSlideybox.pagesLeft;
      if (pagesLeft < 3.3) {
        gPhotoDrawer.loadMore();
      }
    },
    onRemoveSelectionLeft: function onRemoveSelectionLeft() {
    },
    onRemoveSelection: function onRemoveSelection(aSelection) {
    }
  };

  this.mSlideybox.setListener(this.slideyBoxListener);

  // add the list of default channels to the popup and populate the search dropdown
  var channelsPopup = $("mediabar-channels-popup");
  var channelsSeparator = $("mediabar-featured-separator");

  this.mApiMgr = CC["@flock.com/photo-api-manager;1?"]
                 .getService(CI.flockIPhotoAPIManager);
  var svcs = this.mApiMgr.services;

  // Get the list of search and media streams items that will be ignored.
  var prefIgnoredMediaStreamsList = "";
  if (this.pref_service.getPrefType(DFLT_STREAM_IGNORE_SVC_PREF)) {
    prefIgnoredMediaStreamsList =
      this.pref_service.getCharPref(DFLT_STREAM_IGNORE_SVC_PREF);
  }

  var prefIgnoredSearchServicesList = "";
  if (this.pref_service.getPrefType(DFLT_SEARCH_IGNORE_SVC_PREF)) {
    prefIgnoredSearchServicesList =
      this.pref_service.getCharPref(DFLT_SEARCH_IGNORE_SVC_PREF);
  }
  var ignoreServices = prefIgnoredSearchServicesList.split(",");
  // We hardcode mediarss here because we don't want to let people remove it
  // from the ignore list; that would just break things.
  ignoreServices.push("mediarss");

  // Iterate through the list of services and populate:
  // 1) media streams list
  // 2) search bar list

  function populateSvcChannel(aSvc,
                              aChannel,
                              aParentChannelArray,
                              aDOMNode)
  {
    // Iterate through sub channels and append output
    if (aChannel.subChannels) {
      // Create menu for channel to hold it's children
      var menu = document.createElement("menu");
      menu.setAttribute("label", aChannel.title);
      aDOMNode.appendChild(menu);
      var menupopupsub = document.createElement("menupopup");
      menu.appendChild(menupopupsub);
      aParentChannelArray.push(aChannel);
      while (aChannel.subChannels.hasMoreElements()) {
        var subChannel = aChannel.subChannels.getNext();
        subChannel.QueryInterface(CI.flockIMediaChannel);
        // Recursively check for sub channels
        populateSvcChannel(aSvc,
                           subChannel,
                           aParentChannelArray,
                           menupopupsub);
      }
    } else {
      var channelmenuitem = document.createElement("menuitem");
      var title = aChannel.title;
      if (aParentChannelArray.length) { 
        for (var k = aParentChannelArray.length - 1; k >= 0; k--) {
          title = aParentChannelArray[k].title + " - " + title;
        }
      }
      channelmenuitem.setAttribute("label", aChannel.title);
      channelmenuitem.setAttribute("value", aChannel.id);
      channelmenuitem.setAttribute("oncommand",
                 "gPhotoDrawer.loadServiceChannel('" + aSvc.shortName + "', "
                                               + "'" + aChannel.id + "', "
                                               + "'" + escape(title) + "');");
      aDOMNode.appendChild(channelmenuitem);
    }
  }

  var searchmenuitem = null;

  // Add the Media RSS search engines.
  for (var key in this.mRSSEngines) {
    var engine = this.mRSSEngines[key];
    searchmenuitem = document.createElement("menuitem");
    searchmenuitem.setAttribute("label",
                                flockGetString("photo/mediabar",
                                               "flock.media.search.title",
                                               [engine.title]));
    searchmenuitem.setAttribute("image", engine.icon);
    searchmenuitem.setAttribute("class", "menuitem-iconic");
    searchmenuitem.setAttribute("engine", key);
    searchmenuitem.setAttribute("oncommand",
                                "gPhotoDrawer.searchRSS(this.getAttribute('engine'));"
                                + "gPhotoDrawer.searchbarFocus();");
    $("media-search-menupopup").appendChild(searchmenuitem);
  }

  while (svcs.hasMoreElements()) {
    var svc = svcs.getNext();
    // Add the service to the media streams list if it is not on the
    // ignore list (configured for branding).
    if (prefIgnoredMediaStreamsList.split(",").indexOf(svc.shortName) == -1) {
      var menu = document.createElement("menu");
      menu.setAttribute("label", svc.title);
      menu.setAttribute("image", svc.icon);
      menu.setAttribute("class", "menu-iconic");
      var menupopup = document.createElement("menupopup");
      menu.appendChild(menupopup);

      var channels = svc.enumerateChannels();
      var count = 0;
      if (channels && channels.hasMoreElements()) {
        while (channels.hasMoreElements()) {
          var channel = channels.getNext();
          channel.QueryInterface(CI.flockIMediaChannel);
          populateSvcChannel(svc, channel, [], menupopup);
          count++;
        }
      }
      if (count > 0) {
        channelsPopup.insertBefore(menu, channelsSeparator);
      }
    }

    // Add the service to the media bar search bar if it is not on the
    // ignore list (configured for branding).
    if (ignoreServices.indexOf(svc.shortName) == -1) {
      searchmenuitem = document.createElement("menuitem");
      searchmenuitem.setAttribute("label",
                                  flockGetString("photo/mediabar",
                                                 "flock.media.search.title",
                                                 [svc.title]));
      searchmenuitem.setAttribute("image", svc.icon);
      searchmenuitem.setAttribute("class", "menuitem-iconic");
      searchmenuitem.setAttribute("oncommand", "gPhotoDrawer.searchService('"
                                  + svc.shortName
                                  + "'); gPhotoDrawer.searchbarFocus();");
      $("media-search-menupopup").appendChild(searchmenuitem);
    }
  }

  var searchSvc = this.pref_service.getCharPref(DFT_MEDIA_SEARCH_PREF);
  if (this.pref_service.getPrefType(CUR_MEDIA_SEARCH_PREF)) {
    searchSvc = this.pref_service.getCharPref(CUR_MEDIA_SEARCH_PREF);
  }

  if (searchSvc.substring(0,8) == "mediarss") {
    this.searchRSS(searchSvc.split(":")[1]);
  } else {
    this.searchService(searchSvc);
  }

  this.updateFavoritesChannelUI();

  this.mediabarForwardButton = $("mediabar-stream-forward");
  this.mediabarBackButton = $("mediabar-stream-back");

  // If there are no media accounts configured, pop message bar.
  var mediaAccts = this.acUtils.getAccountsByInterface("flockIMediaWebService");
  if (!mediaAccts.hasMoreElements()) {
    var message = flockGetString("photo/photo",
                                 "flock.photo.account.discovery.label");
    var nBox = top.gBrowser.getNotificationBox();
    var links = [{
      label: flockGetString("photo/photo", "flock.photo.account.discovery.link"),
      callback: function pdNotieLink_callback() {
        toggleSidebarWithMetric("flock_AccountsSidebarBroadcaster",
                                "AccountSidebar-Open",
                                null,
                                "MediabarPhotoDiscoveryNoAccount",
                                true);
        var obs = CC["@mozilla.org/observer-service;1"]
                  .getService(CI.nsIObserverService);
        setTimeout(function asbar_timeout() {
          obs.notifyObservers(null, "toggleAccountsArea", "media,people");
        }, 300);
      }
    }];
    nBox.appendUniqueNotification(message,
                                  "account-needed", null,
                                  nBox.FLOCK_PRIORITY_LOW, null,
                                  links,
                                  "accounts");
  }
  gProfiler.profileEventEnd(gMBProfEvt, "");
};

PhotoDrawer.prototype.uninit =
function pdUninit() {
  this.metrics.report("MediaBar-Close", null);

  this.clear();

  // var unseenItemsPredicate = this.mRDFService.GetResource(FLNS + "hasUnseenItems");
  if (gUpdateTimer) {
    clearTimeout(gUpdateTimer);
  }
  gUpdateTimer = null;
  this.mFavsDS.removeArcObserver(CI.flockIRDFObserver.TYPE_CHANGE, null,
                                 this.unseenItemsPredicate, null,
                                 unseenItemsObs);
  this.mediaObserverService.removeObserver(this.mediaObserver,
                                           "mediaFinished");
  this.mediaObserverService.removeObserver(this.apiAuthObserver,
                                           "apiAuthChange");
  // unseenItemsObs.rdfChanged = null;
  // unseenItemsObs = null;
};

PhotoDrawer.prototype.loadServiceChannel = 
function pdLoadServiceChannel(aSvcShortName, aValue, aLabel) {
  this.mFilter = "";
  this.loadQuery(aSvcShortName, aValue, unescape(aLabel));
  this.emptySearchbox();
  this.metrics.report("MediaBar-SelectChannel", [{service: aSvcShortName,
                                                  channel: aValue}]);
}

PhotoDrawer.prototype.loadInitialMedia =
function pdLoadInitialMedia() {
  // If there is new content, set initial media to be Preview New stream
  // then reset it to null (so it doesn't get loaded next time)
  var initialMedia = top.mediabar.initialMedia;
  top.mediabar.initialMedia = null;

  if (!initialMedia && this.mApiMgr.hasNewMedia) {
    initialMedia = {
      query: {
        service: "preview",
        query: "preview",
        name: flockGetString("photo/mediabar", "flock.media.search.prev.new")
      }
    };
  }

  if (initialMedia && initialMedia.query) {
    this.loadQuery(initialMedia.query.service,
                   initialMedia.query.query,
                   initialMedia.query.name);
    if (this.recentRoot.children.enumerate().hasMoreElements()) {
      this.disableMediabarBackButton(false);
      this.disableMediabarForwardButton(true);
    }
    return;
  }

  if (initialMedia && initialMedia.person) {
    this.loadPhotoPerson(initialMedia.person.urn, initialMedia.person.svc);
    if (this.recentRoot.children.enumerate().hasMoreElements()) {
      this.disableMediabarBackButton(false);
      this.disableMediabarForwardButton(true);
    }
    return;
  }

  var nonPreviewStreamFound = false;
  var theEnum = this.recentRoot.children.enumerate();
  // Look for the first non-preview stream in recent history to load.
  while (theEnum.hasMoreElements()) {
    var recentItem = theEnum.getNext();
    if (recentItem.service != "preview") {
      nonPreviewStreamFound = true;
      this.loadFromId(recentItem.id());
      break;
    }
  }

  // If there are no non-preview streams in recent history OR if recent history
  // is empty, load default media stream.
  if (!nonPreviewStreamFound) {
    var prefs = CC["@mozilla.org/preferences-service;1"]
                .getService(CI.nsIPrefService)
                .getBranch("flock.media.defaultquery.");
    var service = prefs.getCharPref("service");
    var query = prefs.getCharPref("query");
    var label = prefs.getCharPref("label");
    this.loadQuery(service, query, label);
  }
  this.onSearchboxBlur();
  this.disableMediabarBackButton(!theEnum.hasMoreElements());
  this.disableMediabarForwardButton(true);
};

PhotoDrawer.prototype.__defineGetter__("mediaQueryId",
  function PhotoDrawer_mediaQueryId() {
    return FAV_CHANNELS_ROOT + ":" + this.mApi + ":" + this.mQuery.stringVal;
  }
);

PhotoDrawer.prototype.searchPerson =
function pdSearchPerson(aSvcShortname, aQueryString) {
  var svc = CC["@flock.com/photo-api-manager;1?"]
            .getService(CI.flockIPhotoAPIManager)
            .getAPIFromShortname(aSvcShortname);
  if (svc instanceof CI.flockIMediaWebService) {
    var str = aQueryString;
    if (!str && !$("mediabar-search-textbox").hasAttribute("empty")) {
      str = $("mediabar-search-textbox").value;
    }
    if (!str || str == "") {
      return;
    }
    var inst = this;
    var findUserListener = {
      onSuccess: function pd_fbu_onSuccess(aSubject, aTopic) {
        var foundUser = aSubject.wrappedJSObject.newUserObj;
        inst.hasPerson = true;
        var query = new QueryHelper();
        query.user = foundUser.accountId;
        query.username = str;
        var name = str;
        inst.clearSnowman();
        inst.showSnowman(foundUser, aSvcShortname, query, name);
        inst.mFinishedTasks++;
        inst.mediaObserverService.notifyObservers(null, "mediaFinished", null);
      },
      onError: function pd_fbu_onError(aFlockError, aTopic) {
       inst.mFinishedTasks++;
       inst.mediaObserverService.notifyObservers(null, "mediaFinished", null);
      }
    };
    this.logger.debug("Asking " + svc.shortName + " for user '" + str + "'");
    svc.findByUsername(findUserListener, str);
  } else {
    this.logger.debug(aSvcShortname
                      + " does not implement flockIMediaWebService"
                      + " - skipping searchPerson()");
    this.mFinishedTasks++;
    this.mediaObserverService.notifyObservers(null, "mediaFinished", null);
  }
};

PhotoDrawer.prototype.showSnowman =
function pdShowSnowman(aPerson, aSvcShortname, aQuery, aUserName) {
  var SNOWMAN_ICON = "chrome://flock/skin/services/common/no_avatar.png";
  if (aPerson.avatar) {
    SNOWMAN_ICON = aPerson.avatar;
  }
  var snowmanLabel = document.createElement("label");
  snowmanLabel.setAttribute("value",
                            flockGetString("photo/mediabar",
                                           "flock.media.search.person.found"));
  var snowman = document.createElement("image");
  snowman.setAttribute("src", SNOWMAN_ICON);
  snowman.setAttribute("style", "cursor: pointer;");
  snowman.setAttribute("query", aQuery.stringVal);
  snowman.setAttribute("username", aUserName);
  snowman.setAttribute("onclick", "gPhotoDrawer.loadQuery('"
                                  + aSvcShortname
                                  + "', this.getAttribute('query'),"
                                  + " this.getAttribute('username'));"
                                  + "gPhotoDrawer.metrics"
                                  + ".report('MediaBar-UserClick', '"
                                  + aSvcShortname + "');");
  snowman.setAttribute("tooltiptext",
                       flockGetString("photo/mediabar",
                                      "flock.media.search.person.view",
                                      [aPerson.name]));
  var label = document.createElement("label");
  label.setAttribute("value", aPerson.name);
  label.setAttribute("crop", "end");
  label.setAttribute("style",
                     "font-weight: bold; max-width: 75px; cursor: pointer;");
  label.setAttribute("query", aQuery.stringVal);
  label.setAttribute("username", aUserName);
  label.setAttribute("onclick", "gPhotoDrawer.loadQuery('"
                                + aSvcShortname
                                + "', this.getAttribute('query'),"
                                + " this.getAttribute('username'));");
  label.setAttribute("tooltiptext",
                      flockGetString("photo/mediabar",
                                     "flock.media.search.person.view",
                                     [aPerson.name]));
  $("snowman").appendChild(snowmanLabel);
  $("snowman").appendChild(snowman);
  $("snowman").appendChild(label);
  // Display user photo count; see Flock bug 7105.
  if (aPerson.totalMedia > -1) {
    var photoCountLabel = document.createElement("label");
    photoCountLabel.setAttribute("value",
                                 flockGetString("photo/mediabar",
                                                "flock.media.search.person.number",
                                                 [aPerson.totalMedia]));
    $("snowman").appendChild(photoCountLabel);
  }
  $("snowman").setAttribute("empty", "false");
};

PhotoDrawer.prototype.clearSnowman =
function pdClearSnowman() {
  $("snowman").setAttribute("empty", "true");
  while ($("snowman").lastChild) {
    $("snowman").removeChild($("snowman").lastChild);
  }
};

PhotoDrawer.prototype.close =
function pdClose() {
  top.FlockTopbar.hide();
  this.clear();
};

PhotoDrawer.prototype.move =
function pdMove(aIgnoreLocation) {
  var location = top.FlockTopbar.move(aIgnoreLocation);
  $("mediabar").setAttribute("location", location);
};

// Recursively removes children from a parent in the DOM
PhotoDrawer.prototype.recursiveClear =
function pdRecursiveClear(aParent) {
  if (aParent) {
    while (aParent.hasChildNodes()) {
      var child = aParent.lastChild;
      if (child) {
        this.recursiveClear(child);
        aParent.removeChild(child);
      }
    }
  }
};

PhotoDrawer.prototype.clear =
function pdClear() {
  this.mLastSearchRequestId = null;
  // Reclaim some system memory by removing DOM nodes from the mediabar.
  this.recursiveClear(this.mSlideybox);
  this.recursiveClear(this.mGroupbarBox);

  this.mFinishedTasks = 0;
  this.mPage = 0;
  this.isEmpty = true;
  this.hasPerson = false;
  this.mHasPrivateMedia = false;
  this.mAlreadyLoaded = false;
  this.mControlBoxList = [];
  this.mSlideybox.scrollHome();
  this.clearSnowman();
};

PhotoDrawer.prototype.refreshView =
function pdRefreshView() {
  this.addRecent();
  this.loadFirst();
  if (this.mQuery.search && ! this.mFilter) {
    this.searchPerson(this.mApi, this.mQuery.search);
  }
};

PhotoDrawer.prototype.createGroupBar =
function pdCreateGroupBar(aSecondPhoto, aFirstPhoto) {
  var newBar = document.createElement("hbox");
  newBar.id = "mediabar-groupbar-" + aFirstPhoto.id;
  newBar.setAttribute("hidden", true);

  this.mGroupbarBox.appendChild(newBar);
  aFirstPhoto.setAttribute("groupbar", newBar.id);
  if (aSecondPhoto) {
    aSecondPhoto.setAttribute("groupbar", newBar.id);
  }
  newBar.mSize = this.mSize;
  newBar.setAttribute("align", "center");
  newBar.setAttribute("class", "groupbar");
  newBar.setAttribute("photowidth", "-2");

  var newLabel = document.createElement("label");
  newLabel.setAttribute("flex", "1");

  if (this.groupMethod(aSecondPhoto, aFirstPhoto) == "user") {
    newLabel.setAttribute("value", aFirstPhoto.username);
    newLabel.setAttribute("class", "authorLink");
    newLabel.setAttribute("querystring", "user:"
                          + aFirstPhoto.userid
                          + "|username:"
                          + aFirstPhoto.username);
    newLabel.setAttribute("username", aFirstPhoto.username);
    newLabel.setAttribute("onclick",
                          "gPhotoDrawer.loadQuery('"
                                                  + aFirstPhoto.svc
                                                  +"', "
                                                  + "this.getAttribute('querystring'),"
                                                  + "this.getAttribute('username')), "
                                                  + "gPhotoDrawer.popMessageBar(this.getAttribute('querystring'));");
  } else {
    var dateVal = new Date(Number(aFirstPhoto.mPhoto.uploadDate));
    newLabel.setAttribute("value",
                          FlockDateFormatter.getShortDateTimeString(dateVal));
  }
  newBar.appendChild(newLabel);
  newBar.photoCount = (aSecondPhoto) ? 2 : 1;
  newBar.setupBar = function pdCreateGroupBar_setupBar() {
    this.removeAttribute("hidden");
  };
  newBar.changeSize = function pdCreateGroupBar_changeSize(sizeDiff) {
    var newWidth = Number(this.getAttribute("photowidth")) + sizeDiff;
    this.setAttribute("photowidth", newWidth);
    if (newWidth < 0) {
      newWidth = 0;
    }
    this.setAttribute("width", newWidth);
    this.setAttribute("style", "width: " + this.getAttribute("width") + "px;");
  };

  this.processPhotoSize(aFirstPhoto);
  if (aSecondPhoto) {
    this.processPhotoSize(aSecondPhoto);
  }
};

PhotoDrawer.prototype.extendGroupBar =
function pdExtendGroupBar(aGroupBar, aPhoto) {
  aPhoto.setAttribute("groupbar", aGroupBar.id);
  aGroupBar.photoCount = aGroupBar.photoCount + 1;
  this.processPhotoSize(aPhoto);
};

PhotoDrawer.prototype.groupMethod =
function pdGroupMethod(aSecondPhoto, aFirstPhoto) {
  if (!this.isPersonQuery &&
      (!aSecondPhoto || aSecondPhoto.userid == aFirstPhoto.userid))
  {
    return "user";
  }

  // Photos are uploaded within x minutes of each other...
  if (!aSecondPhoto ||
      (Number(aFirstPhoto.mPhoto.uploadDate) -
       Number(aSecondPhoto.mPhoto.uploadDate)) < 300000)
  {
    return "date";
  }
  return;
};

PhotoDrawer.prototype.shouldGroup =
function pdShouldGroup(aSecondPhoto, aFirstPhoto) {
  if (this.groupMethod(aSecondPhoto, aFirstPhoto)) {
    return true;
  }
  return false;
};

PhotoDrawer.prototype.groupBarAround =
function pdGroupBarAround(aSecondPhoto, aFirstPhoto) {
  if (aFirstPhoto.getAttribute("groupbar")) {
    this.extendGroupBar($(aFirstPhoto.getAttribute("groupbar")), aSecondPhoto);
  } else {
    this.createGroupBar(aSecondPhoto, aFirstPhoto);
  }
};

PhotoDrawer.prototype.appendMediaItem =
function pdAppendMediaItem(aMediaItem) {
  var newPhoto = document.createElement("photo");
  newPhoto.setAttribute("photoControlBoxOpen", "false");
  this.mSlideybox.appendChild(newPhoto);
  if (!this.mAlreadyLoaded){
    newPhoto.setAttribute("transparency", "full");
    // Setup a process which ensures that the photo is not left invisible in
    // the event that the fade effect does not occur (should the onload
    // handler for the photo not get called). This sometimes happens when
    // the image set for the photo cannot be loaded.
    var forceVisibleThreadId =
      setTimeout(function() {newPhoto.setAttribute("transparency",
                                                   "none");}, 1500);
    newPhoto.setAttribute("forceVisibleThreadId", forceVisibleThreadId);
    newPhoto.onload = function photo_onload(aEvent) {
      gPhotoDrawer.fadeInPhoto(this);

      // No need to force visibility as the fade has taken care of that.
      var forceVisibleThreadId = this.getAttribute("forceVisibleThreadId");
      clearTimeout(forceVisibleThreadId);
      this.removeAttribute("forceVisibleThreadId");

      this.onload = null;
      aEvent.stopPropagation();
    };
  }

  newPhoto.photo = aMediaItem;
  // Calling this before setting the media item created a leak. Weird!
  newPhoto.size = this.mSize;
  newPhoto.favicon = this.currentIcon();

  var prevPhoto = newPhoto.previousSibling;
  if (prevPhoto && this.shouldGroup(newPhoto, prevPhoto)) {
    this.groupBarAround(newPhoto, prevPhoto);
  } else if (prevPhoto && !(prevPhoto.getAttribute("groupbar"))) {
    this.createGroupBar(null, prevPhoto);
  }

  if (newPhoto.is_public == "false") {
    this.mHasPrivateMedia = true;
  }
  newPhoto.tooltipId = "mediabar-tooltip";
  if (this.mQuery.hasKey("user")) {
    newPhoto.is_single = "true";
  } else {
    // JMC - Evil special case for the preview view.
    if (this.mApi == "preview") {
      newPhoto.svc = aMediaItem.svcShortName;
    } else {
      newPhoto.svc = this.mApi; // this is a string
    }
  }
  this.isEmpty = false;
};

PhotoDrawer.prototype.fadeInPhoto =
function pdFadeInPhoto(aPhoto) {
  this.processPhotoSize(aPhoto);
  gDomFx.fadeIn(aPhoto, null);
};

PhotoDrawer.prototype.loadFirst =
function pdLoadFirst(aForce) {
  this.clear();
  this.setupSearch();
  this.updateCurrentStreamLabels();
  this.loadMore();
};

// flockIListener#onError
PhotoDrawer.prototype.onError =
function pdOnError(aFlockError, aTopic) {

  // Each search request is sent out with a request id (timestamp value)
  // Since we only care about the most recent request we can ignore
  // a response where the request id is older than the most recent one
  // mLastSearchRequestId represents the most recent request id
  if (aTopic != this.mLastSearchRequestId) {
    return;
  }
  // Allow other requests to be sent
  this.mLastSearchRequestId = null;

  document.getElementById("mediabar-statusbox").error(aFlockError.errorString);
  // If this is a "not logged in" error, send the user to the API's login page.
  if (aFlockError.errorCode == CI.flockIError.PHOTOSERVICE_USER_NOT_LOGGED_IN)
  {
    var svcCoopObj = this._coop.get("urn:" + this.mApi + ":service");
    var webSvc = CC[svcCoopObj.serviceId].getService(CI.flockIWebService);
    // Open in new tab and switch to it immediately.
    if (webSvc instanceof CI.flockILoginWebService) {
      openUILinkIn(webSvc.loginURL, "tab");
    } else {
      openUILinkIn(webSvc.url, "tab");
    }
  }
};

// flockIListener#onSuccess
PhotoDrawer.prototype.onSuccess =
function pdOnSuccess(aSubject, aTopic) {

  // Each search request is sent out with a request id (timestamp value)
  // Since we only care about the most recent request we can ignore
  // a response where the request id is older than the most recent one.
  // mLastSearchRequestId represents the most recent request id.
  if (aTopic != this.mLastSearchRequestId) {
    return;
  }
  // Allow other requests to be sent
  this.mLastSearchRequestId = null;

  if (!this.mAlreadyLoaded) {
    this.setupAlbumMenu();
  }

  document.getElementById("mediabar-statusbox").hidden = true;
  if (!this.mFilter) {
      this.onSearchboxBlur();
  }
  var mediaItem;

  function EnumWrapper(aEnum) {
    this.innerEnum = [];
    while (aEnum.hasMoreElements()) {
      this.innerEnum.push(aEnum.getNext());
    }
    this.enumIndex = 0;
  }
  EnumWrapper.prototype.rewind =
  function pdOnSuccess_ew_rewind() {
    this.enumIndex = 0;
  };
  EnumWrapper.prototype.hasMoreElements =
  function pdOnSuccess_ew_hasMoreElements() {
    return this.innerEnum.length > this.enumIndex;
  };
  EnumWrapper.prototype.getNext =
  function pdOnSuccess_ew_getNext() {
    return this.innerEnum[this.enumIndex++];
  };

  aSubject.QueryInterface(CI.nsISimpleEnumerator);
  var aEnum = new EnumWrapper(aSubject);
  if (aEnum.innerEnum.length == 0) {
    this.mPage = -1; // no more photos available
  }

  while (aEnum.hasMoreElements()) {
    mediaItem = aEnum.getNext().QueryInterface(CI.flockIMediaItem);
    this.appendMediaItem(mediaItem);
  }
  if (mediaItem &&
      $(mediaItem.id) &&
      $(mediaItem.id).getAttribute("groupbar"))
  {
    if ($(mediaItem.id).previousSibling &&
        $($(mediaItem.id).previousSibling.getAttribute("groupbar")))
    {
      $($(mediaItem.id).previousSibling.getAttribute("groupbar")).setupBar();
    }
  }

  // appendMediaItem nicely sets up the group bar for the next to last
  // item in aEnum, but not the last item.  This ensures that the last item
  // has a groupbar and that it is made visible.
  if (mediaItem && $(mediaItem.id)) {
    var prevPhoto = $(mediaItem.id).previousSibling;
    if (prevPhoto && this.shouldGroup($(mediaItem.id), prevPhoto)) {
      this.groupBarAround($(mediaItem.id), prevPhoto);
    } else if (!($(mediaItem.id).getAttribute("groupbar"))) {
      this.createGroupBar(null, $(mediaItem.id));
    }
    $($(mediaItem.id).getAttribute("groupbar")).setupBar();
  }

  if (this.isPersonQuery && this.isEmpty) {
    this.hasPerson = true;
    this.isEmpty = false;
    var svc = CC["@flock.com/photo-api-manager;1?"]
              .getService(CI.flockIPhotoAPIManager)
              .getAPIFromShortname(this.mApi);
    var message = flockGetString("photo/photo",
                                 "flock.media.nomedia.account.label",
                                 [svc.title]);
    this.showActionMessage(message);
    if (!this.bHistoryNav) {
      this.addRecent();
    }
    this.updateWatch();
    if (!$("mediabar").getAttribute("newMediaPreview")) {
      this.disableWatch(false);
    }
    this.mAlreadyLoaded = true;
  } else if (!this.mAlreadyLoaded) {
    if (!this.bHistoryNav) {
      this.addRecent();
    }
    this.updateWatch();
    if (!$("mediabar").getAttribute("newMediaPreview")) {
      this.disableWatch(false);
    }
    this.mAlreadyLoaded = true;
    // JMC - Only process photostream if it's the first page of results.
    aEnum.rewind();
    var photoAPIManager = CC["@flock.com/photo-api-manager;1?"]
                          .getService(CI.flockIPhotoAPIManager);
    var aStreamUrn = FAV_CHANNELS_ROOT + ":"
                   + this.mApi + ":"
                   + this.mQuery.canonicalVal;
    photoAPIManager.processPhotoStream(aStreamUrn, aEnum, false);
  }
  // MSD: Bug #14003 - sizes are not available on initial load
  // so groupbars need to be resized
  this.resizePhotos();
  this.mFinishedTasks++;
  if (this.activeSearchSVC && this.activeSearchSVC.shortName == "mediarss") {
    // MediaRSS don't check for person, so we need to increment again
    this.mFinishedTasks++;
  }
  this.mediaObserverService.notifyObservers(null, "mediaFinished", null);
};

PhotoDrawer.prototype.loadMore =
function pdLoadMore() {
  if (this.mLastSearchRequestId != null) {
    // Wait for the pending response.
    return;
  }
  var svc = CC["@flock.com/photo-api-manager;1?"]
            .getService(CI.flockIPhotoAPIManager)
            .getAPIFromShortname(this.mApi);
  if (!svc) {
    return;
  }
  var query = new QueryHelper(this.mQuery.stringVal);
  if (this.mFilter != "") {
    query.search = this.mFilter;
  }

  if (this.mPage == -1) {
    return; // no more photos available
  }
  this.mPage++;
  // Tag this request as the most recent one.
  this.mLastSearchRequestId = Date.now();
  svc.search(this,
             query.stringVal,
             PER_PAGE,
             this.mPage,
             this.mLastSearchRequestId);
};

PhotoDrawer.prototype.searchService =
function pdSearchService(aSvcShortname) {
  this.metrics.report("MediaBar-SearchEngineChange", aSvcShortname);
  var svc = CC["@flock.com/photo-api-manager;1?"]
            .getService(CI.flockIPhotoAPIManager)
            .getAPIFromShortname(aSvcShortname);
  this.activeSearchSVC = svc;
  if (!this.activeSearchSVC) {
    svc = { svcShortname: "filter",
            icon: "chrome://flock/skin/common/lookingGlass.png" };
  }

  this.pref_service.setCharPref(CUR_MEDIA_SEARCH_PREF, aSvcShortname);
  $("mediabar-search-button").setAttribute("style",
                                           "list-style-image: url("
                                           + svc.icon + ");");
};

PhotoDrawer.prototype.searchRSS =
function pdSearchRSS(aEngineId) {
  this.lastRSSEngine = this.mRSSEngines[aEngineId];
  this.metrics.report("MediaBar-SearchEngineChange", "rssmedia");
  this.activeSearchSVC = CC["@flock.com/?photo-rss-media;1"]
                         .getService(CI.flockIMediaWebService);

  this.pref_service.setCharPref(CUR_MEDIA_SEARCH_PREF, "mediarss:" + aEngineId);
  var style = "list-style-image: url(" + this.lastRSSEngine.icon + ");";
  $("mediabar-search-button").setAttribute("style", style);
};

PhotoDrawer.prototype.searchbarFocus =
function pdSearchbarFocus() {
  $("mediabar-search-textbox").focus();
  $("mediabar-search-textbox").select();
};

PhotoDrawer.prototype.performSearch =
function pdPerformSearch() {
  $("mediabar-search-textbox").blur();

  var str = $("mediabar-search-textbox").value;
  if ($("mediabar-search-textbox").hasAttribute("empty")) {
    str = "";
  }

  if (!this.activeSearchSVC) {
    if (this.supportsSearch) {
      this.addRecent();
      this.mFilter = str;
      this.mQuery.search = this.mFilter;
      //this.refreshView();
      this.loadFirst();
      return;
    } else {
      this.searchService(this.mApi);
    }
  }
  if (str == "") {
    return;
  }
  var svcShortname = this.activeSearchSVC
                   ? this.activeSearchSVC.shortName
                   : "filter";
  this.metrics.report("MediaBar-ServiceSearch", svcShortname);
  var query;
  if (svcShortname == "mediarss") {
    var url = this.lastRSSEngine.url.replace("%s", encodeURIComponent(str));
    query = "special:" + encodeURIComponent(url);
    this.loadQuery(svcShortname, query, '"' + str + '"');
  } else {
    query = new QueryHelper();
    query.search = str; // TODO XXX: perhaps we should strip this?
    var name = '"' + str + '"';
    var inst = this;
    setTimeout(function pdPerformSearch_timeoutfunc() {
                 inst.searchPerson(svcShortname, str);
               },
               300);
    this.loadQuery(svcShortname, query.stringVal, name);
  }
};

PhotoDrawer.prototype.keyup =
function pdKeyUp(aEvent) {
  switch (aEvent.keyCode) {
    case aEvent.DOM_VK_RETURN:
      this.performSearch();
      break;

    case aEvent.DOM_VK_DOWN:
      $("media-search-menupopup").showPopup($("media-search-menupopup"),
                                            -1, -1, "popup", "bottomleft",
                                            "topleft");
      break;
  }
};

PhotoDrawer.prototype.keydown =
function pdKeyDown(aEvent) {
  // User is entering text, update to proper state.
  this.onSearchboxFocus();
};

PhotoDrawer.prototype.loadAuthPerson =
function pdLoadAuthPerson(aService) {
  var accounts = this._coop.Account.find({ serviceId: aService.contractId });
  if (accounts.length > 0) {
    this.loadPhotoPerson(accounts[0], aService.shortName);
  }
};

PhotoDrawer.prototype.loadPhotoPerson =
function pdLoadPhotoPerson(aPersonURN, aService) {
  this.mFilter = "";
  var identityTarget = this._coop.get(aPersonURN);
  var query = new QueryHelper();
  query.user = identityTarget.accountId;
  query.username = identityTarget.name;
  this.loadQuery(aService, query.stringVal, identityTarget.name);
};

PhotoDrawer.prototype.populateAccountsMenu =
function pdPopulateAccountsMenu() {
  var mediaAccts = this.acUtils.getAccountsByInterface("flockIMediaWebService");
  var acctsMenu = $("flock-media-accounts-dropdown");
  while (acctsMenu.hasChildNodes()) {
    acctsMenu.removeChild(acctsMenu.lastChild);
  }
  var menuitem;
  if (!mediaAccts.hasMoreElements()) {
    menuitem = document.createElement("menuitem");
    menuitem.setAttribute("disabled", "true");
    menuitem.setAttribute("label", flockGetString("photo/mediabar",
                                                  "flock.media.noaccounts"));
    acctsMenu.appendChild(menuitem);
  }
  while (mediaAccts.hasMoreElements()) {
    var acct = mediaAccts.getNext().QueryInterface(CI.flockIWebServiceAccount);
    var c_acct = this._coop.get(acct.urn);
    var svc = CC[c_acct.serviceId].getService(CI.flockIWebService);
    menuitem = document.createElement("menuitem");
    menuitem.setAttribute("class", "menuitem-iconic");
    menuitem.setAttribute("image", c_acct.favicon);
    menuitem.setAttribute("label", c_acct.name);
    menuitem.setAttribute("username", c_acct.name);
    menuitem.setAttribute("querystring", "user:" + c_acct.accountId
                          + "|username:" + c_acct.name);
    menuitem.setAttribute("oncommand", "gPhotoDrawer.mFilter = '';"
                          + " gPhotoDrawer.loadQuery('"
                          + svc.shortName
                          + "', this.getAttribute('querystring'),"
                          + " this.getAttribute('username'));"
                          + " gPhotoDrawer.emptySearchbox(); ");
    acctsMenu.appendChild(menuitem);
  }
};

PhotoDrawer.prototype.onSettingsMenu =
function pdOnSettingsMenu() {
  if ($("mediabar").getAttribute("showDraggers") == "true") {
    $("toggle-draggers").setAttribute("label",
                                      flockGetString("photo/mediabar",
                                                     "flock.media.draggers.hide"));
  } else {
    $("toggle-draggers").setAttribute("label",
                                      flockGetString("photo/mediabar",
                                                     "flock.media.draggers.show"));
  }

  if ($("mediabar").getAttribute("advControls") == "on") {
    $("toggle-advanced-controls").setAttribute("label",
                                               flockGetString("photo/mediabar",
                                                              "flock.media.controls.hide"));
  } else {
    $("toggle-advanced-controls").setAttribute("label",
                                               flockGetString("photo/mediabar",
                                                              "flock.media.controls.show"));
  }

  if ($("mediabar").getAttribute("location") == "top") {
    $("toggle-location").setAttribute("label",
                                      flockGetString("photo/mediabar",
                                                     "flock.media.controls.bottom"));
  } else {
    $("toggle-location").setAttribute("label",
                                      flockGetString("photo/mediabar",
                                                     "flock.media.controls.top"));
  }
};

PhotoDrawer.prototype.toggleDraggers =
function pdToggleDraggers() {
  if ($("mediabar").getAttribute("showDraggers") == "true") {
    $("mediabar").setAttribute("showDraggers", "false");
  } else {
    $("mediabar").setAttribute("showDraggers", "true");
  }
};

PhotoDrawer.prototype.toggleAdvControls =
function pdToggleAdvControls() {
  if ($("mediabar").getAttribute("advControls") == "on") {
    $("mediabar").setAttribute("advControls", "off");
  } else {
    $("mediabar").setAttribute("advControls", "on");
  }
};

PhotoDrawer.prototype.removeMediaQuery =
function PhotoDrawer_removeMediaQuery() {
  var id = this.mediaQueryId;
  var mediaQuery = this._coop.get(id);
  if (mediaQuery) {
    if (!this.removeFavorite(mediaQuery)) {
      return;
    }
  }
  // Update state of the media bar
  top.updateMediaButton();
  this.updateWatch();

  // Hide panel popup
  gMediaQueryEditUI._hidePanel();

  // Report removal
  this.metrics.report("MediaBar-removeMediaQuery", [{ service: this.mApi,
                                                      favorite: false }]);
};

PhotoDrawer.prototype.starMediaQuery =
function PhotoDrawer_StarMediaQuery(aAnchorElement, aPosition) {
  var id = this.mediaQueryId;
  var mediaQuery = this._coop.MediaQuery.exists(id);
  if (!mediaQuery) {
    this.addFavorite(id);
  } else {
    // If the stream is already starred, open the panel for options
    if (!gMediaQueryEditUILoaded) {
      var loadObserver = {
        _self: this,
        observe: function starMediaQuery_observe(aSubject, aTopic, aData) {
          gMediaQueryEditUI._showPanel(aAnchorElement, aPosition, true);
          gMediaQueryEditUILoaded = true;
        }
      };
      document.loadOverlay("chrome://flock/content/photo/editMediaFolderOverlay.xul",
                           loadObserver);
    } else {
      gMediaQueryEditUI._showPanel(aAnchorElement, aPosition, true);
    }
  }
  this.metrics.report("MediaBar-StarMediaQuery", [{ service: this.mApi,
                                                    favorite: true }]);
};

PhotoDrawer.prototype.toggleFavorite =
function PhotoDrawer_ToggleFavorite(aAnchorElement, aPosition) {
  var id = this.mediaQueryId;
  var mediaQuery = this._coop.MediaQuery.exists(id);
  if (!mediaQuery) {
    // Add Media Query
    this.addFavorite(id);
  } else {
    // Remove Media Query
    this.removeMediaQuery();
  }
};

PhotoDrawer.prototype.toggleSize =
function pdToggleSize(aElement) {
  var newSize;
  if ($("mediabar").getAttribute("photoSizeLarge") == "true") {
    newSize = "small";
  } else {
    newSize = "large";
  }
  this.setPhotoSize(newSize);
  this.metrics.report("MediaBar-Options-SwitchView",
                      {view: newSize,
                       source: aElement.id});
  this.resizePhotos();
};

PhotoDrawer.prototype.toggleMode =
function pdToggleMode(aElement) {
  var mode;
  if ($("mediabar").getAttribute("mode") == "consume") {
    this.interactMode();
    this.mGroupbarBox.removeAttribute("hidden");
    mode = "detailed";
  } else {
    this.consumeMode();
    this.mGroupbarBox.setAttribute("hidden", "true");
    mode = "simple";
  }
  this.metrics.report("Mediabar-Options-SwitchDisplaymode",
                      {mode: mode,
                       source: aElement.id});
};

PhotoDrawer.prototype.setPhotoSize =
function pdSetPhotoSize(aSize) {
  if (aSize == "large") {
    $("photo-drawer-size").setAttribute("checked", "true");
    this.mSize = "medium";
    top.FlockTopbar.setHeight(height_large);
    $("mediabar").setAttribute("photoSizeLarge", true);
  } else {
    $("photo-drawer-size").setAttribute("checked", "false");
    this.mSize = "thumb";
    if (this.getMode() == "consume") {
      top.FlockTopbar.setHeight(height_small);
    } else {
      top.FlockTopbar.setHeight(height_small_meta);
    }
    $("mediabar").removeAttribute("photoSizeLarge");
  }
};

PhotoDrawer.prototype.consumeMode =
function pdConsumeMode() {
  $("mediabar").setAttribute("mode", "consume");
  if ($("mediabar").getAttribute("photoSizeLarge") != "true") {
    top.FlockTopbar.setHeight(height_small);
  }
};

PhotoDrawer.prototype.interactMode =
function pdInteractMode() {
  $("mediabar").setAttribute("mode", "interact");
  if ($("mediabar").getAttribute("photoSizeLarge") != "true") {
    top.FlockTopbar.setHeight(height_small_meta);
  }
};

PhotoDrawer.prototype.getMode =
function pdGetMode() {
  return $("mediabar").getAttribute("mode");
};

PhotoDrawer.prototype.resizePhotos =
function pdResizePhotos() {
  var i;
  for (i = 0; i < this.mGroupbarBox.childNodes.length; i++) {
    this.mGroupbarBox.childNodes[i].setAttribute("width", 0);
    this.mGroupbarBox.childNodes[i].setAttribute("photowidth", "-2");
    this.mGroupbarBox.childNodes[i].setAttribute("hidden", true);
  }

  var photos = this.mSlideybox.getElementsByTagName("photo");
  for (i = 0; i < photos.length; i++) {
    photos[i].sizeProcessed = null;
    photos[i].resize(this.mSize);
  }
};

PhotoDrawer.prototype.processPhotoSize =
function pdProcessPhotoSize(aPhoto) {
  // If photo loads before groupbar exists, then bail and don't do this twice.
  if (aPhoto.sizeProcessed || aPhoto.boxObject.width == 0) {
    return;
  }

  // If the photo is part of a groupbar, and it's the first photo,
  // add its size, plus left AND right edges.
  var padding = (aPhoto.mSize == "medium") ? 4 : 4;
  if (aPhoto.getAttribute("groupbar")) {
    if (!$(aPhoto.getAttribute("groupbar"))) {
      return;
    }
    $(aPhoto.getAttribute("groupbar")).changeSize(aPhoto.boxObject.width
                                                  + padding);
  } else {
    return; // Don't process it if we don't have a groupbar yet!
  }

  // If it doesn't have the same groupbar setting as the last one, close off
  // the previous groupbar size.
  if (aPhoto.previousSibling &&
      aPhoto.previousSibling.getAttribute("groupbar") &&
      aPhoto.previousSibling.getAttribute("groupbar") !=
        aPhoto.getAttribute("groupbar"))
  {
      $(aPhoto.previousSibling.getAttribute("groupbar")).setupBar();
  }
  if (!aPhoto.nextSibling && aPhoto.getAttribute("groupbar")) {
    $(aPhoto.getAttribute("groupbar")).setupBar();
  }
  aPhoto.sizeProcessed = true;
};

PhotoDrawer.prototype.doLoadQuery =
function pdDoLoadQuery(aService, aQuery, aName) {
  this.currentCoopStream = null;
  this.mApi = aService;
  if (aService == "preview") {
    $("mediabar").setAttribute("newMediaPreview", "true");
  } else {
    $("mediabar").removeAttribute("newMediaPreview");
  }
  this.mQuery = aQuery;
  if (this.mQuery.search && this.mQuery.user) {
    this.mFilter = this.mQuery.search;
  }
  this.mName = aName;
  var svc = CC["@flock.com/photo-api-manager;1?"]
            .getService(CI.flockIPhotoAPIManager)
            .getAPIFromShortname(aService);

  if (svc instanceof CI.flockISocialWebService) {
    $("mediabar").setAttribute("social", "true");
  } else {
    $("mediabar").removeAttribute("social");
  }
  this.disableWatch(true);
  this.loadFirst(true);

  // If it's pollable, clear the "new" flag.
  // How to get the higest seq out of the query?
  // I guess (ugly) we register a callback to appendMediaItem.
  var uri = FAV_CHANNELS_ROOT + ":" + aService + ":" + aQuery.canonicalVal;
  var item = this._coop.get(uri);
  if (item && item.isPollable) {
    // JMC - Bug #6733
    // If the unseenItems flag is getting cleared, the template will remove
    // the item from one rule, but won't rerun against the other run,
    // so we have to manually call rebuild.
    var rebuilding = false;
    if (item.hasUnseenItems) {
      rebuilding = true;
    }
    item.hasUnseenItems = false;
    item.markAllSeen();
  }
  this.currentCoopStream = item;
};

PhotoDrawer.prototype.clearAllNew =
function pdClearAllNew() {
  // JMC - Should this only clear all new that's reflected in the current view?
  // For now, it clears all new mediaqueries no matter what.
  var mediaQueries = this._coop.MediaQuery.find({ hasUnseenItems: true });
  for each (var urn in mediaQueries) {
    var mediaQuery = this._coop.MediaQuery.get(urn);
    mediaQuery.hasUnseenItems = false;
    mediaQuery.markAllSeen();
  }

  // DP - Bug #8082
  // if we are on the preview stream and user clears new, load
  // the most recent non-preview stream
  if (this.mApi == "preview") {
    initialMedia = null;
    var inst = this;
    setTimeout(function pdClearAllNew_timeoutfunc() {
                 inst.loadInitialMedia();
               },
               CLEAR_NEW_LOAD_DELAY);
  }
};

PhotoDrawer.prototype.popMessageBar =
function pdPopMessageBar(aQueryString) {
  // Find aQueryString in a fav'd MediaQuery
  var favItem = this._coop.MediaQuery
                          .find({ query: aQueryString, isPollable: true });
  if (favItem.length == 0) {
    var msg = flockGetString("favorites/favorites",
                             "flock.favs.media.discovery");
    var nBox = top.gBrowser.getNotificationBox();
    nBox.appendUniqueNotification(msg,
                                  "media-discovery-info",
                                  "chrome://flock/skin/favorites/media-icon.png",
                                  nBox.FLOCK_PRIORITY_LOW,
                                  null,
                                  null);
  }
};

PhotoDrawer.prototype.loadQuery =
function pdLoadQuery(aService, aQueryString, aName, aIsHistoryNav) {
  this.logger.debug("loadQuery(" + aService + ", "
                                 + aQueryString + ", "
                                 + aName + ", "
                                 + aIsHistoryNav
                                 + ")");
  this.metrics.report("MediaBar-LoadMediaStream", "" + aService);
  this.hideActionMessage();
  this.bHistoryNav = (aIsHistoryNav == true);
  this.mFilter = "";
  var aQuery = new QueryHelper(aQueryString);
  this.isPersonQuery = aQuery.hasKey("user") || aQuery.hasKey("favorites");

  if ($("mediabar-slideybox").leftmostChild) {
    this.mostRecentSeq = $("mediabar-slideybox").leftmostChild.photo.id;
  } else {
    this.mostRecentSeq = null;
  }

  // we are doing a setTimeout because of a focus issue that happens
  // see Flock bug 5827 --- ja
  var inst = this;
  setTimeout(function pdLoadQuery_timeoutfunc() {
               inst.doLoadQuery(aService, aQuery, aName);
             },
             0);
};

PhotoDrawer.prototype.loadFromId =
function pdLoadFromId(aId, aIsHistoryNav) {
  this.mFilter = "";
  this.emptySearchbox();
  var item = this._coop.get(unescape(aId));
  this.currentItem = item;
  // load it
  // JMC: Special case for special streams, don't use item.name for the name
  var queryName = item.name;
  var itemQuery = new QueryHelper(item.query);
  this.loadQuery(item.service, item.query, queryName, aIsHistoryNav);

  if (aIsHistoryNav) {
    var node = document.getElementById(aId);
    // Update the back/forward buttons.
    var backItem = (node &&
                    node.nextSibling &&
                    node.nextSibling.tagName == "menuitem");
    var forwardItem = (node &&
                       node.previousSibling &&
                       node.previousSibling.tagName == "menuitem");
    this.disableMediabarBackButton(!backItem);
    this.disableMediabarForwardButton(!forwardItem);
  }

  if (itemQuery.search) {
    this.searchPerson(item.service, itemQuery.search);
  }
};

/**
 * Navigation via the back and forward arrows.
 * @param aBackwards  boolean: true == go backwards (ex: back arrow clicked)
 */
PhotoDrawer.prototype.navigate =
function pdNavigate(aBackwards) {
  //var currentItem = this._coop.get(RECENT_ROOT + ":" + this.mApi + ":" +
  //                                 this.mQuery.stringVal);
  var kids = this._coop.get(RECENT_ROOT).children;
  var recentEnum;
  if (aBackwards) {
    // Yes, this is unfortunately both unintuitive and correct.
    recentEnum = kids.enumerate();
  } else {
    recentEnum = kids.enumerateBackwards();
  }
  var item;
  var itemId;
  var gotToCurrent = false;
  while (recentEnum.hasMoreElements()) {
    item = recentEnum.getNext();
    itemId = item.id();
    if (gotToCurrent &&
        (item.service != "preview" ||
         $("mediabar").getAttribute("hasUnseenMedia")))
    {
      this.loadFromId(itemId, true);
      return;
    }
    if (this.currentItem && itemId == this.currentItem.id()) {
      gotToCurrent = true;
    }
    if (!this.currentItem) {
      gotToCurrent = true;
    }
  }
};

/**
 * Helper for disableMediabarBackButton and disableMediabarForwardButton.
 * @param aElement  button to act upon
 * @param aState    true to disable, false to enable
 */
PhotoDrawer.prototype.disableMediabarNavButton =
function pdDisableMediabarNavButton(aButton, aState) {
  if (aState) {
    aButton.setAttribute("disabled", aState);
  } else {
    if (aButton.hasAttribute("disabled")) {
      aButton.removeAttribute("disabled");
    }
  }
};

/**
 * Disables or enables media bar back arrow button
 * @param aState  true to disable, false to enable
 */
PhotoDrawer.prototype.disableMediabarBackButton =
function pdDisableMediabarBackButton(aState) {
  this.disableMediabarNavButton(this.mediabarBackButton, aState);
};

/**
 * Disables or enables media bar forward arrow button
 * @param aState  true to disable, false to enable
 */
PhotoDrawer.prototype.disableMediabarForwardButton =
function pdDisableMediabarForwardButton(aState) {
  this.disableMediabarNavButton(this.mediabarForwardButton, aState);
};

PhotoDrawer.prototype.addRecent =
function pdAddRecent() {
  var svc = CC["@flock.com/photo-api-manager;1?"]
            .getService(CI.flockIPhotoAPIManager)
            .getAPIFromShortname(this.mApi);

  // XXX TODO: This is not very robust, and it doesn't allow favorite channels
  // to be recent channels without duplicating the mediaquery
  // Also, the split, below, looks fragile  - similar queries may clobber?
  var detailedQuery = new QueryHelper(this.mQuery.stringVal);
/*
  if (this.mostRecentSeq) {
    detailedQuery.leftmost = this.mostRecentSeq;
  }
*/

  // Don't double up at the top.
  if ($("mediabar-recent-channels-popup").childNodes[1] &&
      this._coop.get($("mediabar-recent-channels-popup")
          .childNodes[1].id).query == detailedQuery.stringVal)
  {
    return;
  }

  var item = new this._coop.MediaQuery({
    name: this.mSimpleLabel,
    query: detailedQuery.stringVal,
    service: this.mApi,
    favicon: svc.icon
  });
  if (this.recentRoot.children.enumerate().hasMoreElements()) {
    // We have history.  Enable "back".
    this.disableMediabarBackButton(false);
  }
  this.recentRoot.children.insertAt(item, 1);
  this.cleanRecentList();
  this.currentItem = item;
};

PhotoDrawer.prototype.addFavorite =
function pdAddFavorite(aId) {
  var photoAPI = CC["@flock.com/photo-api-manager;1?"]
                 .getService(CI.flockIPhotoAPIManager);
  var svc = photoAPI.getAPIFromShortname(this.mApi);

  var item = new this._coop.MediaQuery(aId, {
    name: this.mSimpleLabel,
    query: this.mQuery.canonicalVal,
    service: this.mApi,
    favicon: svc.getIconForQuery(this.mQuery.canonicalVal),
    isPollable: true,
    hasUnseenItems: false //,
    // latestSeq: this.latestSeq
  });

  //XXX TODO: Should this be addOnce?
  this.favChannelsRoot.children.add(item);
  // Call refresh manually to force a no-newness refresh
  this.updateWatch();
  var lister = {
    onResult: function pdAddFavorite_l_onResult(a, b) {},
    onError: function pdAddFavorite_l_onError(a) {}
  };
  photoAPI.refresh(aId, lister);

  if (!this.pref_service.getPrefType(MEDIA_ADD_FAV_DLG_PREF) ||
      (this.pref_service.getBoolPref(MEDIA_ADD_FAV_DLG_PREF) == true))
  {
    var url = "chrome://flock/content/photo/addFavoriteDlg.xul";
    window.openDialog(url, "_blank", "centerscreen,modal");
  }
};

PhotoDrawer.prototype.removeFavorite =
function pdRemoveFavorite(aMediaQuery) {
  var photoAPIManager = CC["@flock.com/photo-api-manager;1?"]
                        .getService(CI.flockIPhotoAPIManager);
  if (photoAPIManager.destroyPhotos(aMediaQuery.id())) {
    aMediaQuery.destroy();
    return true;
  }
  return false;
};

PhotoDrawer.prototype.showActionMessage =
function pdShowActionMessage(aMessage) {
  $("mediabar-bannermessage").message = aMessage;
  $("mediabar-bannermessage").show();
};

PhotoDrawer.prototype.hideActionMessage =
function pdHideActionMessage() {
  $("mediabar-bannermessage").hide();
};

PhotoDrawer.prototype.updateWatch =
function pdUpdateWatch() {
  var id = FAV_CHANNELS_ROOT + ":" + this.mApi + ":" + this.mQuery.canonicalVal;
  // XXX: JMC - I have changed the above to allow favoriting of searches
  // within a user. Does this make sense?
  $("mediabar-watch").setAttribute("checked", this._coop.get(id) != null);
};

PhotoDrawer.prototype.disableWatch =
function pdDisableWatch(aState) {
  $("mediabar-watch").setAttribute("disabled", aState);
};

PhotoDrawer.prototype.cleanRecentList =
function pdCleanRecentList() {
  var count = 0;
  var theEnum = this.recentRoot.children.enumerate();
  while (theEnum.hasMoreElements()) {
    theEnum.getNext();
    count++;
  }
  // Only one element max to remove
  if (count > MAX_RECENT) {
    var fat = count - MAX_RECENT;
    var enumBack = this.recentRoot.children.enumerateBackwards();
    for (var i = 0; i < fat; i++) {
      var item = enumBack.getNext();
      this.recentRoot.children.remove(item);
      item.destroy();
    }
  }
};

PhotoDrawer.prototype.emptySearchbox =
function pdEmptySearchBox() {
  $("mediabar-search-textbox").value = "";
  $("mediabar-search-textbox").setAttribute("empty", "true");
  // this.onSearchboxBlur();
  $("mediabar-search-textbox").blur();
};

PhotoDrawer.prototype.onSearchboxBlur =
function pdOnSearchboxBlur(aForce) {
  if ($("mediabar-search-textbox").value.length == 0 || aForce) {
    var mediaSearchApi = this.pref_service.getCharPref(CUR_MEDIA_SEARCH_PREF);
    var txt;
    if (mediaSearchApi == "filter") {
      /* It'd be way better to pull this string once and keep it. */
      txt = flockGetString("photo/mediabar", "flock.media.search.filter");
    } else if (mediaSearchApi.substring(0, 8) == "mediarss") {
      var rssEngine = mediaSearchApi.split(":")[1];
      txt = flockGetString("photo/mediabar",
                           "flock.media.search.title",
                           [this.mRSSEngines[rssEngine].title]);
    } else {
      var svc = CC["@flock.com/photo-api-manager;1?"]
                .getService(CI.flockIPhotoAPIManager)
                .getAPIFromShortname(mediaSearchApi);
      txt = flockGetString("photo/mediabar",
                           "flock.media.search.title",
                           [svc.title]);
    }
    $("mediabar-search-textbox").value = txt;
    $("mediabar-search-textbox").setAttribute("empty", true);
  }
};

PhotoDrawer.prototype.onSearchboxFocus =
function pdOnSearchboxFocus() {
  if ($("mediabar-search-textbox").hasAttribute("empty")) {
    $("mediabar-search-textbox").removeAttribute("empty");
    $("mediabar-search-textbox").value = "";
  }
};

/* This function will hopefully go away when we work in our solution to
 * display the # of new items.
 */
PhotoDrawer.prototype.updateFavoritesChannelUI =
function pdUpdateFavoritesChannelUI() {
  if (this.mApiMgr.hasNewMedia) {
    $("mediabar").setAttribute("hasUnseenMedia", true);
  } else {
    $("mediabar").removeAttribute("hasUnseenMedia");
    // JMC - don't leave preview new queries in recent history
    var mediaQueries = this._coop.MediaQuery.find({ service: "preview" });
    for each (var urn in mediaQueries) {
      this._coop.MediaQuery.get(urn).destroy();
    }
  }
};

PhotoDrawer.prototype.lock =
function pdLock() {
  if (this.objSynch) {
    // If already locked return false; could not lock.
    return false;
  }
  this.objSynch = true;
  return true;
};

PhotoDrawer.prototype.unlock =
function pdUnlock() {
  setTimeout(function pdUnlock_timeoutfunc() {
               gPhotoDrawer.objSynch = false;
             },
             500);
};

PhotoDrawer.prototype.onMediabarChannelsPopupShowing =
function pdOnMediabarChannelsPopupShowing() {

  // Initialize Favorite Media Query Streams
  var rootSeparator = document.getElementById("mediabar-mediaQuery-separator");
  FlockMedia.addMediaStreams(rootSeparator.parentNode,
                             FAV_CHANNELS_ROOT,
                             rootSeparator);

  var id = this.mediaQueryId;
  if (this._coop.get(id)) {
    $("favorite-this-stream-menuitem").setAttribute("label",
                                                    flockGetString("photo/mediabar",
                                                                   "flock.media.favs.remove"));
  } else {
    $("favorite-this-stream-menuitem").setAttribute("label",
                                                    flockGetString("photo/mediabar",
                                                                   "flock.media.favs.add"));
  }

  // Disable "Add this stream" menuitem if on Preview New
  if (this.mApi == "preview") {
    $("favorite-this-stream-menuitem").setAttribute("disabled", "true");
  } else {
    $("favorite-this-stream-menuitem").removeAttribute("disabled");
  }

  // Disable the "Preview New" and "Clear New" menuitems if empty.
  var favEnum = this.favChannelsRoot.children.enumerate();
  var allNewMedia = document.getElementById("allNewMedia");
  var clearNew = document.getElementById("clearNew");
  allNewMedia.setAttribute("hidden", "true");
  clearNew.setAttribute("hidden", "true");
  while (favEnum.hasMoreElements()) {
    var favitem = favEnum.getNext();
    if (favitem && favitem.hasUnseenItems) {
      allNewMedia.removeAttribute("hidden");
      clearNew.removeAttribute("hidden");
    }
  }
};

PhotoDrawer.prototype.setAlbumName =
function pdSetAlbumName(aAlbumId, aAlbumLabel) {
  this.mQuery.album = aAlbumId;
  this.mQuery.albumlabel = aAlbumLabel;
  this.mAlbumLabel = aAlbumLabel;
  this.updateCurrentStreamLabels();
};

PhotoDrawer.prototype.browseAlbum =
function pdBrowseAlbum(aEventTarget) {
  if (aEventTarget.target.localName != "menuitem") {
    return;
  }

  var albumId = aEventTarget.target.getAttribute("value");
  var albumLabel = "";
  if (!aEventTarget.target.hasAttribute("dontDelete")) {
    // Don't set album name for "View All Photos, because it affects starring
    // the whole stream (star goes away when selecting this menu item).
    albumLabel = aEventTarget.target.getAttribute("label");
  }
  this.clear();
  this.mFilter = "";
  this.mQuery.search = "";
  this.mQuery.special = "";
  this.emptySearchbox();
  this.setAlbumName(albumId, albumLabel);
  this.setupSearch();
  this.loadMore();
};

PhotoDrawer.prototype.setupSearch =
function pdSetupSearch() {
  var title = "";
  if (this.mApi == "mediarss") {
    this.supportsSearch = false;
  } else {
    var svc = CC["@flock.com/photo-api-manager;1?"]
              .getService(CI.flockIPhotoAPIManager)
              .getAPIFromShortname(this.mApi);
    if (svc) {
      this.supportsSearch = svc.supportsSearch(this.mQuery.stringVal);
      title = " " + svc.title;
    } else {
      this.supportsSearch = false;
    }
  }
  $("mediabar-statusbox").busy(flockGetString("photo/mediabar",
                                              "flock.media.search.searching")
                               + title);
  if (this.supportsSearch) {
    $("media-search-filter-menuitem").removeAttribute("disabled");
  } else {
    $("media-search-filter-menuitem").setAttribute("disabled", true);
    var mediaSearchApi = this.pref_service.getCharPref(CUR_MEDIA_SEARCH_PREF);
    if (mediaSearchApi == "filter") {
      $("media-search-filter-menuitem").nextSibling.doCommand();
    }
  }
};

PhotoDrawer.prototype.setupAlbumMenu =
function pdSetupAlbumMenu() {
  this.logger.info(".setupAlbumMenu()");
  var inst = this;
  var albumsMenu = {
    popup: $("current-stream-popup"),
    numAlbums: 0,

    init: function pdSetupAlbumMenu_init() {
      $("current-stream-menu").setAttribute("hidden", "true");

      var numItems = this.popup.childNodes.length;

      for (var i = 0; i < numItems; i++) {
        if (!this.popup.lastChild.hasAttribute("dontDelete")) {
          this.popup.removeChild(this.popup.lastChild);
        }
      }
    },

    setViewAllCaption: function pdSetupAlbumMenu_setTitle(aBundle, aDefault) {
      var viewAllCaption;
      if (aBundle) {
        try {
          viewAllCaption = aBundle.GetStringFromName(MEDIA_VIEW_ALL_CAPTION);
        } catch(ex) {
          // No override string necessary; unfortunately, this throws.
        }
      }
      if (!viewAllCaption || !viewAllCaption.length) {
        viewAllCaption = aDefault;
      }
      $("view-all-label").setAttribute("label", viewAllCaption);
    },

    refresh: function pdSetupAlbumMenu_refresh(albumEnumerator) {
      this.init();
      this.numAlbums = 0;
      while(albumEnumerator.hasMoreElements()) {
        $("current-stream-menu").removeAttribute("hidden");

        var album = albumEnumerator.getNext();
        album.QueryInterface(CI.flockIPhotoAlbum);
        var menuitem = document.createElement("menuitem");
        this.popup.appendChild(menuitem);
        if (this.album == album.id) {
          inst.setAlbumName(album.id, album.title);
        }
        menuitem.setAttribute("value", album.id);
        menuitem.setAttribute("label", album.title);
        this.numAlbums++;
      }
    }
  };

  var albumsGetterListener = {
    onSuccess: function pdSetupAlbumMenu_agl_onSuccess(aSubject, aTopic) {
      aSubject.QueryInterface(CI.nsISimpleEnumerator);
      albumsMenu.refresh(aSubject);
    },
    onError: function pdSetupAlbumMenu_agl_onError(aFlockError, aTopic) {
      var msg = aFlockError ? aFlockError.errorString
                            : ("getAlbums() not implemented by " + inst.mApi);
      inst.logger.error(msg);
    }
  };

  albumsMenu.init();

  var userId = this.mQuery.getKey("user");

  if (userId && userId.length) {
    var svc = CC["@flock.com/photo-api-manager;1?"]
              .getService(CI.flockIPhotoAPIManager)
              .getAPIFromShortname(this.mApi);
    svc.getAlbums(albumsGetterListener, userId);
    var bundle = svc.getStringBundle();
    albumsMenu.setViewAllCaption(bundle, this.mViewAllCaption);
  } else {
    albumsMenu.setViewAllCaption(null, this.mViewAllCaption);
  }
};

PhotoDrawer.prototype.onMediaTooltipShowing =
function pdOnMediaTooltipShowing() {
  // If DnD in progress don't show the rich photo tooltip.
  // See Flock bug 8559
  if (this.mDnDInProgress) {
    return false;
  }
};

PhotoDrawer.prototype.updateCurrentStreamLabels =
function pdUpdateCurrentStreamLabels() {
  this.logger.debug(".updateCurrentStreamLabels()");
  $("current-stream-label").setAttribute("value", this.mLabel);
  $("current-stream-menu").setAttribute("label", this.mLabel);
  $("current-stream-icon").setAttribute("src", this.currentIcon());
};

PhotoDrawer.prototype.currentIcon =
function PhotoDrawer_currentIcon() {
  if (this.mApi == "preview") {
    return "";
  }
  var svc = CC["@flock.com/photo-api-manager;1?"]
            .getService(CI.flockIPhotoAPIManager)
            .getAPIFromShortname(this.mApi);
  if (svc instanceof CI.flockIMediaWebService) {
    return svc.getIconForQuery(this.mQuery.canonicalVal);
  }
  return "";
}

PhotoDrawer.prototype.__defineGetter__("mLabel",
function pdGetter_mLabel() {
  var label = "";
  var suffix = "";
  if (this.mApi == "facebook") {
    suffix = ".facebook";
  }

  if (!this.mQuery) {
    return;
  }
  // ALBUM, USERNAME, OR SEARCHUNIQUE start the label...
  if (this.mQuery.album) {
    label = flockGetString("photo/mediabar", "flock.media.viewing.album" + suffix,
                           [this.mQuery.albumlabel, this.mQuery.username]);
  } else if (this.mQuery.favorites) {
    label = flockGetString("photo/mediabar", "flock.media.viewing.favorites" + suffix,
                             [this.mQuery.favorites]);
  } else if (this.mQuery.username) {
    label = flockGetString("photo/mediabar", "flock.media.viewing.user" + suffix,
                             [this.mQuery.username]);
  } else if (this.mQuery.searchunique) {
    label += flockGetString("photo/mediabar", "flock.media.viewing.searchunique" + suffix,
                           [this.mQuery.username]);
  }

  // ... And a search filter might finish it off
  if (this.mApi == "preview") {
    this.mName = flockGetString("photo/mediabar", "flock.media.search.prev.new");
  }
  if (this.mQuery.special || this.mApi == "preview") {
    label += flockGetString("photo/mediabar", "flock.media.viewing.special" + suffix,
                            [this.mName]);
  }
  if (this.mQuery.pool) {
    label += flockGetString("photo/mediabar", "flock.media.viewing.group" + suffix,
                             [this.mQuery.albumlabel]);
  }
  if (this.mQuery.search) {
    if (label) {
      label += " " + flockGetString("photo/mediabar", "flock.media.viewing.filteredby",
                                                  [this.mQuery.search]);
    } else {
      label = flockGetString("photo/mediabar", "flock.media.viewing.search",
                             [this.mQuery.search]);
    }
  }
  return label;
})

PhotoDrawer.prototype.__defineGetter__("mSimpleLabel",
function pdGetter_mSimpleLabel() {
  var label = "";

  if (!this.mQuery) {
    return;
  }
  // ALBUM, USERNAME, OR SEARCHUNIQUE start the label...
  if (this.mQuery.album) {
    label = flockGetString("photo/mediabar", "flock.media.simpleviewing.album",
                           [this.mQuery.albumlabel, this.mQuery.username]);
  } else if (this.mQuery.favorites) {
    label = flockGetString("photo/mediabar", "flock.media.simpleviewing.favorites",
                             [this.mQuery.favorites]);
  } else if (this.mQuery.username) {
    label = flockGetString("photo/mediabar", "flock.media.simpleviewing.user",
                             [this.mQuery.username]);
  } else if (this.mQuery.searchunique) {
    label = flockGetString("photo/mediabar", "flock.media.simpleviewing.searchunique",
                           [this.mQuery.username]);
  }

  // ... And a search filter might finish it off
  if (this.mApi == "preview") {
    this.mName = flockGetString("photo/mediabar", "flock.media.search.prev.new");
  }
  if (this.mQuery.special || this.mApi == "preview") {
    label = this.mName;
  }
  if (this.mQuery.pool) {
    label += flockGetString("photo/mediabar", "flock.media.simpleviewing.group",
                             [this.mQuery.albumlabel]);
  }
  if (this.mQuery.search) {
    if (label) {
      label += " " + flockGetString("photo/mediabar", "flock.media.simpleviewing.filteredby",
                                                  [this.mQuery.search]);
    } else {
      label = flockGetString("photo/mediabar", "flock.media.simpleviewing.search",
                             [this.mQuery.search]);
    }
  }
  return label;
})

function onMediaRefresh(aEvt) {
  if (gPhotoDrawer.lock()) {
    gPhotoDrawer.refreshView();
    gPhotoDrawer.unlock();
  }
}

function onLoad_photo(aEvt) {
  gPhotoDrawer = new PhotoDrawer($("mediabar"), $("mediabar-slideybox"));
  if (gInitialActionMessage) {
    gPhotoDrawer.showActionMessage(gInitialActionMessage);
    gInitialActionMessage = null;
  }
  $("mediabar-refresh").addEventListener("command", onMediaRefresh, false);
  gPhotoDrawer.move(true);

  // Notify that the Mediabar's DOM is ready for interaction
  gPhotoDrawer.mediaObserverService.notifyObservers(null,
                                                    "mediabarDOMReady",
                                                    null);
}

function onUnload_photo(aEvt) {
  gDomFx.uninit();
  if (gPhotoDrawer) {
    gPhotoDrawer.uninit();
  }
}

function onClick_photo(aEvt) {
  $("mediabar-bannermessage").hide();
}

function PhotoDropProcessor(aList, aHoverChild) {
  this.mList = aList;
  this.mHoverChild = aHoverChild;
}

PhotoDropProcessor.prototype.mList = null;

PhotoDropProcessor.prototype.start =
function pdpStart() {
  if (this.mHoverChild) {
    var newThing = CC["@flock.com/photo-upload;1"]
                   .createInstance(CI.flockIPhotoUpload);
    newThing.id = this.mHoverChild.getAttribute("id");
    this.mHoverChild = newThing;
  }
  gPhotoUploadSvc.addObserver(this);
  this.next();
};

PhotoDropProcessor.prototype.next =
function pdpNext() {
  if (this.mList.length == 0) {
    gPhotoUploadSvc.startImportQueue();
    return;
  }
  var url = this.mList.shift();
  if (url.split(":")[0] == "file") {
    gPhotoUploadSvc.addMediaToImportQueue(url);
    this.next();
  } else {
    // dnding from a remote source
    var URIFixup = CC["@mozilla.org/docshell/urifixup;1"]
                   .getService(CI.nsIURIFixup);
    var uri = URIFixup.createFixupURI(url, URIFixup.FIXUP_FLAG_NONE);

    var IOService = CC["@mozilla.org/network/io-service;1"]
                    .getService(CI.nsIIOService);
    var channel = IOService.newChannelFromURI(uri);

    var self = this;

    var streamListener = {
      processor: self,
      sinkFile: null,
      sinkBufferStream: null,

      onStartRequest: function pdpNext_onStartRequest(request, context) {
        var extension;
        switch (channel.contentType) {
          case "image/jpeg":
            extension = "jpg";
            break;
          case "image/png":
            extension = "png";
            break;
          case "image/gif":
            extension = "gif";
            break;
          default:
            extension = "jpg";
            break;
        }
        var sinkFileName = url.split("/").pop();

        this.sinkFile = CC["@mozilla.org/file/directory_service;1"]
                        .getService(CI.nsIProperties)
                        .get("ProfD", CI.nsIFile);
        this.sinkFile.append("flock_imagecache");
        if (!this.sinkFile.exists()) {
          this.sinkFile.createUnique(this.sinkFile.DIRECTORY_TYPE, 0700);
        }
        if (sinkFileName) {
          this.sinkFile.append(sinkFileName);
        }

        var sinkFileStream = CC["@mozilla.org/network/file-output-stream;1"]
                             .createInstance(CI.nsIFileOutputStream);
        sinkFileStream.init(this.sinkFile, 0x02 | 0x08 | 0x20, 0664, 0);
        this.sinkBufferStream = CC["@mozilla.org/network/buffered-output-stream;1"]
                                .createInstance(CI.nsIBufferedOutputStream);
        this.sinkBufferStream.init(sinkFileStream, 65536 * 4);
      },

      onStopRequest: function pdpNext_onStopRequest(request,
                                                    context,
                                                    statusCode)
      {
        var ios = CC["@mozilla.org/network/io-service;1"]
                  .getService(CI.nsIIOService);
        var fileHandler = ios.getProtocolHandler("file")
                             .QueryInterface(CI.nsIFileProtocolHandler);
        this.sinkBufferStream.close();
        var url = fileHandler.getURLSpecFromFile(this.sinkFile);
        gPhotoUploadSvc.addMediaToImportQueue(url);
        this.processor.next();
      },

      onDataAvailable: function pdpNext_onDataAvailable(request, context,
                                                        inputStream, offset,
                                                        count)
      {
        this.sinkBufferStream.writeFrom(inputStream, count);
      }
    };

    channel.asyncOpen(streamListener, null);
  }
};

PhotoDropProcessor.prototype.cancel =
function pdpCancel() {
  // Not implemented.
};

PhotoDropProcessor.prototype.onMediaBatchImportStart =
function pdpOnMediaBatchImportStart(aNumImport) {
  // Not implemented.
};

PhotoDropProcessor.prototype.onMediaBatchImportFinish =
function pdpOnMediaBatchImportFinish() {
  gPhotoUploadSvc.removeObserver(this);
};

PhotoDropProcessor.prototype.onMediaImportStart =
function pdpOnMediaImportStart(aFilename, aNum) {
  // Not implemented.
};

PhotoDropProcessor.prototype.onMediaImportFinish =
function pdpOnMediaImportFinish(aPhotoUpload) {
  gPhotoUploadSvc.addPhotoUpload(this.mHoverChild ? this.mHoverChild : null,
                                 aPhotoUpload);
};

PhotoDropProcessor.prototype.onMediaImportError =
function pdpOnMediaImportError(aFile) {
  var stringBundle = FlockMedia.getStringBundle();
  // XXX: Don't call alert() from the chrome!
  alert(stringBundle.GetStringFromName(
    "flock.photo.upload.error.invalid").replace("%1",aFile));
  this.next();
};

PhotoDropProcessor.prototype.onUploadAdd =
function pdpOnUploadAdd() {
  // Not implemented.
};

PhotoDropProcessor.prototype.onUploadRemove =
function pdpOnUploadRemove() {
  // Not implemented.
};

PhotoDropProcessor.prototype.onError =
function pdpOnError(aError) {
  // XXX: Don't call alert() from the chrome!
  alert(aError.errorString);
  this.next();
};

window.addEventListener("load", onLoad_photo, false);
window.addEventListener("unload", onUnload_photo, false);
window.addEventListener("click", onClick_photo, false);
