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

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

const MEDIAMAGIC_HIDE_DELAY = 250;  // in milliseconds
const MEDIA_MIN_HEIGHT = 100;
const MEDIA_MIN_WIDTH = 270;

/**
 Flock mediaMagic object, single instance of the object for each browser
 */
var mediaMagic = {
  service: null,
  refUrl: "",
  html: "",
  eventType: "",
  mTimer: null,
  // XXX Once height becomes dynamically calculated, remove comment
  // from skin/classic/mediaStream.css.
  // Note that computed style height is "auto" while object is hidden,
  // and does not get set to actual value until visible (and XRE notices).
  mmHeight: 20,
  contentTop: 0,
  contentleft: 0,
  scrollTop: 0,
  scrollLeft: 0,
  tabHeight: 0,
  notificationsHeight: 0
}

/**
 These are some compoenents that the mediaMagic uses to operate.
 */
mediaMagic.mPhotoAPIManager = CC['@flock.com/photo-api-manager;1?']
                              .getService(CI.flockIPhotoAPIManager);
mediaMagic.mPrefSvc = CC['@mozilla.org/preferences-service;1']
                      .getService(CI.nsIPrefBranch);
mediaMagic._logger = CC['@flock.com/logger;1'].createInstance(CI.flockILogger);

// Return the top left coordinates of the content pane.
mediaMagic.getContentOffsets =
function mediaMagic_getContentOffsets() {
  mediaMagic.contentTop = document.getElementById("content").boxObject.y;
  mediaMagic.contentLeft = document.getElementById("content").boxObject.x;
  
  mediaMagic._logger.info(".getContentOffsets top: " + mediaMagic.contentTop
                             + " left: " + mediaMagic.contentLeft);
  return [mediaMagic.contentTop, mediaMagic.contentLeft];
}

// Return the tab height.
mediaMagic.getTabHeight =
function mediaMagic_getTabHeight() {
  mediaMagic.tabHeight = document.getAnonymousElementByAttribute(gBrowser,
                                                                 "class",
                                                                 "tabbrowser-strip")
                                 .boxObject.height;

  mediaMagic._logger.info(".getTabHeight: " + mediaMagic.tabHeight);
  return mediaMagic.tabHeight;
}

// Return the total notifications height.
mediaMagic.getNotificationsHeight =
function mediaMagic_getNotificationsHeight() {
  mediaMagic.notificationsHeight = 0;
  var notificationBox = gBrowser.getNotificationBox();
  if (notificationBox) {
    var notifications = notificationBox.allNotifications;
    for (var i = 0; i < (notifications.length); i++) {
      mediaMagic.notificationsHeight += notifications[i].boxObject.height;
      var marginTop = parseInt(notifications[i].style.marginTop);
      if(marginTop > 0) mediaMagic.notificationsHeight += marginTop;
    }
  }
  
  mediaMagic._logger.info(".getNotificationsHeight: "  
                            + mediaMagic.notificationsHeight
                            + " for " + gBrowser.currentURI.spec);
  return mediaMagic.notificationsHeight;
}

// Return the scroll offsets.
mediaMagic.getScrollOffsets =
function mediaMagic_getScrollOffsets() {
  if (!mediaMagic.targetObj) {
    return [0, 0];
  }
  
  var ownerDoc = mediaMagic.targetObj.ownerDocument;
  
  mediaMagic.scrollTop = ownerDoc.documentElement.scrollTop
                           || ownerDoc.body.scrollTop;
  mediaMagic.scrollLeft = ownerDoc.documentElement.scrollLeft
                            || ownerDoc.body.scrollLeft;

  mediaMagic._logger.info(".getScrollOffsets top: " + mediaMagic.scrollTop
                             + " left: " + mediaMagic.scrollLeft);
  return [mediaMagic.scrollTop, mediaMagic.scrollLeft];
}

// Return the URL.
mediaMagic.getDocumentURL =
function mediaMagic_getDocumentURL(aTarget) {
  var theURL = "";
  if (aTarget &&
      aTarget.ownerDocument &&
      aTarget.ownerDocument instanceof CI.nsIDOMHTMLDocument)
  {
    theURL = aTarget.ownerDocument.URL;
  }
  mediaMagic._logger.debug("Document URL is: '" + theURL + "'");
  return theURL;
};

/**
 * onclick event handler for the mediaStream overlay
 * this will do an action based on what the user selects:
 * - View stream
 *     (opens media bar with selected media stream)
 * - Blog (opens blog editor with content selected)
 * - E-mail (invokes email client and puts data pointing
 *     to selected content in email)
 */
mediaMagic.onclick =
function mediaMagic_onclick(event) {
    mediaMagic._logger.info(".onclick");
    if (!mediaMagic.service) {
      return;
    }

    var mediaBarQueryListener = {
      onSuccess:function (aResults, aTopic) {
        // aResults is a bag of (query, title)
        if (mediabar.lock() && mediaMagic.service) {
          aResults = aResults.QueryInterface(CI.nsIPropertyBag2);
          var query = aResults.getPropertyAsAString("query");
          var title = aResults.getPropertyAsAString("title");
          mediabar.loadQuery(mediaMagic.service.shortName, query, title);

          // Pop message bar if not fav'd
          var _coop = CC["@flock.com/singleton;1"]
                      .getService(CI.flockISingleton)
                      .getSingleton("chrome://flock/content/common/load-faves-coop.js")
                      .wrappedJSObject;

          var favItem = _coop.MediaQuery.find({query: query, isPollable: true});
          if (favItem.length == 0) {
            var msg = flockGetString("favorites/favorites",
                                     "flock.favs.media.discovery");
            var nBox = mediaMagic.getNotificationBox();
            nBox.appendUniqueNotification(msg,
                                          "media-discovery-info",
                                          "chrome://flock/skin/favorites/media-icon.png",
                                          nBox.FLOCK_PRIORITY_LOW,
                                          null,
                                          null);
          }

          mediabar.unlock();
        }
        mediaMagic.forceHide();
      },
      onError:function(aFlockError, aTopic) {
        mediaMagic.hide();
        mediaMagic._logger.debug(aTopic);
      }
    }

    mediaMagic._logger.debug("Event Type == " + event);
    var metrics = CC["@flock.com/metrics-service;1"]
                  .getService(CI.flockIMetricsService);
    if (event == 'stream') {
      metrics.report("FlockMagic-ViewStream", mediaMagic.eventType);
      metrics.report("MediaBar-Open", "FlockMagic");
      mediaMagic.service.getMediaQueryFromURL(mediaMagic.refUrl,
                                              mediaBarQueryListener);
    }
    
    mediaMagic._logger.debug("HTML is [" + mediaMagic.html + "]");
    
    if (event == 'blog') {
      metrics.report("FlockMagic-BlogMedia", mediaMagic.eventType);
      metrics.report("BlogDialog-Open", "FlockMagic");
      // Bug 11803: remove any style attribute because that may cause
      // weird layout on the blog page
      var html = this.html.replace(/style="[^"]*"/gi, "");
      flock_blog.service.openEditor(null, html, null);
      mediaMagic.hide();
    }
    
    if (event == 'email') {
      metrics.report("FlockMagic-EmailMedia", mediaMagic.eventType);
      var mSubject = "";
      var mBody;
      if (mediaMagic.eventType == "image") {
        var result = CC["@mozilla.org/hash-property-bag;1"]
                     .createInstance(CI.nsIWritablePropertyBag2);
        if (mediaMagic.mPhotoAPIManager
                      .getSharingContent(mediaMagic.targetObj, result))
        {
          mBody = result.getPropertyAsAString("url");
          mSubject = result.getPropertyAsAString("title");
        } else {
          mBody = this.refUrl;
          if (mediaMagic.targetObj.title) {
            mSubject = mediaMagic.targetObj.title;
          } else if (mediaMagic.targetObj.alt) {
            mSubject = mediaMagic.targetObj.alt;
          } else {
            mSubject = document.getElementById("content").contentTitle;
          }
        }
      } else if (mediaMagic.eventType == "video"
                 && mediaMagic.refUrl.match(/youtube.com/)) {
        // Build up a YT friendly URL
        var videoID = mediaMagic.service.getVideoIDFromUrl(mediaMagic.refUrl);
        mBody = mediaMagic.refUrl.replace(/youtube.com\/v.+/, "youtube.com/watch?v=") + videoID;
        mSubject = document.getElementById("content").contentTitle;
      } else {
        mBody = mediaMagic.getDocumentURL(mediaMagic.targetObj);
        mSubject = document.getElementById("content").contentTitle;
      }
      if (mSubject) {
        mBody += "\n" + mSubject;
      }
      var breadcrumb = CC["@flock.com/rich-dnd-service;1"]
                       .getService(CI.flockIRichDNDService)
                       .getBreadcrumb("plain");
      mBody += breadcrumb;

      var url = "mailto:?subject=" + encodeURIComponent(mSubject)
              + "&body=" + encodeURIComponent(mBody);
      window.openUILink(url);
      mediaMagic.hide();
    }
}

/**
 * Everytime we move the mouse we save the current cordinates for future checks
 */
mediaMagic.imagerMove =
function mediaMagic_imagerMove(event) {
  mediaMagic.curMouseY = event.clientY + mediaMagic.scrollTop;
  mediaMagic.curMouseX = event.clientX + mediaMagic.scrollLeft;

  //need to adjust, just like when we show mediaMagic
  switch (event.target.id) {
    case "media-magic-outer-container":
    case "media-magic-inner-container":
    case "flockmagic-view-button":
    case "flockmagic-blog-button":
    case "flockmagic-mail-button":
    case "flockmagic-image":
    case "content":
      mediaMagic.curMouseY -= (mediaMagic.contentTop + mediaMagic.tabHeight
                                 + mediaMagic.notificationsHeight);
      mediaMagic.curMouseX -= mediaMagic.contentLeft;
    break;
  }
  
  // Don't bother checking if no object.
  if (!mediaMagic.targetObj) {
    return;
  }

  // If media-magic is already visible, don't reshow
  if (document.getElementById("media-magic").state == "open") {
    return;
  }

  // If mouse in object, show flock magic.
  if (mediaMagic.checkIfMouseInObject(mediaMagic.targetObj)) {
    mediaMagic.realShow();
  }
}

/**
  gets the desktop cordinates of the image or embed we are overlaying...me
  */
mediaMagic.getBoxPos =
function mediaMagic_getBoxPos(obj) {
  if (!obj) return [0,0,0,0];
  
  var top = 0, left = 0;
  var temp = obj;
  while (temp) {
    top += temp.offsetTop;
    left += temp.offsetLeft;
    temp = temp.offsetParent;
  }
  
  // Need to use parseInt here for embed objects as they are strings not ints
  var bottom = top + parseInt(obj.height);
  var right = left + parseInt(obj.width);
  
  return [top, left, bottom, right];
}

/**
 * This will set the show data prior to show being called.
 */
mediaMagic.show = 
function mediaMagic_show(aTargetObj, aService, aRefUrl, aHtml, aEventType) {
  mediaMagic._logger.info(".show");
  
  // Try to get the CSS style height value if it exists first. Otherwise, use
  // the HTML height attribute value.
  var objHeight = (aTargetObj.style.height) ? aTargetObj.style.height
                                            : aTargetObj.height;
  var objWidth = (aTargetObj.style.width) ? aTargetObj.style.width
                                          : aTargetObj.width;

  // Only allow sufficiently large images to display flock magic.
  if (objHeight < MEDIA_MIN_HEIGHT || objWidth < MEDIA_MIN_WIDTH) {
    return;
  }
  
  mediaMagic.targetObj = aTargetObj;
  mediaMagic.service = aService;
  mediaMagic.refUrl = aRefUrl;
  mediaMagic.html = aHtml;
  mediaMagic.eventType = aEventType;

  // Initialize positioning data.
  mediaMagic.getScrollOffsets();
  mediaMagic.getContentOffsets();
  mediaMagic.getNotificationsHeight();
  mediaMagic.getTabHeight();

  // Show flock magic.
  mediaMagic.realShow();
}

// aWhich must be one of: "left" "top" "right" "bottom"
mediaMagic.calcNetBorderSize =
function mediaMagic_calcNetBorderSize(aComputedStyle, aWhich) {
  var ebs = 0;
  // XXX - This assumes pixels!
  ebs += (parseInt(aComputedStyle
                   .getPropertyCSSValue("border-" + aWhich + "-width")
                   .cssText) || 0);
  ebs += (parseInt(aComputedStyle
                   .getPropertyCSSValue("margin-" + aWhich)
                   .cssText) || 0);
  ebs += (parseInt(aComputedStyle
                   .getPropertyCSSValue("padding-" + aWhich)
                   .cssText) || 0);
  //mediaMagic._logger.debug(".calcNetBorderSize: " + aWhich + "=" + ebs);
  return ebs;
}

/**
 this will show the mediaMagic overlay when the user mouses over
 an image or video.
 */
mediaMagic.realShow =
function mediaMagic_realShow() {
  mediaMagic._logger.info(".realShow");

  // Only show if we've got an object.
  if (!mediaMagic.targetObj) {
    mediaMagic._logger.debug(".realShow: exiting - nothing to show");
    return;
  }

  // Walk parents to determine true offset into tabBrowser.
  var targetOffsetTop = 0, targetOffsetLeft = 0;
  var temp = mediaMagic.targetObj;
  while (temp) {
    targetOffsetLeft += temp.offsetLeft;
    targetOffsetTop += temp.offsetTop;
    temp = temp.offsetParent;
  }

  // Only show if the top left corner of the target is visible.
  if (targetOffsetLeft < mediaMagic.scrollLeft) {
    mediaMagic._logger.debug(".realShow: exiting - left not visible");
    return;
  }
  if (targetOffsetTop < mediaMagic.scrollTop) {
    mediaMagic._logger.debug(".realShow: exiting - top not visible");
    return;
  }

  // Get computed style for target and determine various dimensions.
  var view = mediaMagic.targetObj.ownerDocument.defaultView;
  var targetObjStyle = view.getComputedStyle(mediaMagic.targetObj, "");
  var borderLeft = mediaMagic.calcNetBorderSize(targetObjStyle, "left");
  var borderTop = mediaMagic.calcNetBorderSize(targetObjStyle, "top");
  var borderRight = mediaMagic.calcNetBorderSize(targetObjStyle, "right");

  // JMC - use getBoxObjectFor, much simpler and more accurate.
  var docBoxObj = document.defaultView.top.document
                          .getBoxObjectFor(document.defaultView.top.document
                                                   .documentElement);
  var boxObj = mediaMagic.targetObj.ownerDocument
                         .getBoxObjectFor(mediaMagic.targetObj);

  // Calculate final position of MediaMagic bar, and make first guess at width.
  var mmComputedLeft = parseInt(boxObj.screenX) 
                     - parseInt(docBoxObj.screenX ) 
                     + borderLeft;
  var mmComputedTop = parseInt(boxObj.screenY) 
                    - parseInt(docBoxObj.screenY) 
                    + borderTop;
  var mmComputedWidth = (parseInt(targetObjStyle.getPropertyCSSValue("width")
                                               .cssText) || 0);

  // Adjust width if target is wider than the space it is shown in.
  temp = mediaMagic.targetObj.parentNode;
  while (temp && !temp.nodeName.match(/#document/)) {
    //mediaMagic._logger.debug(".realShow: inspecting " + temp.nodeName);
    var parentObjStyle = view.getComputedStyle(temp, "");

    // Does this parent clip the target object?
    var overflow = parentObjStyle.getPropertyCSSValue("overflow");
    if (overflow && 
       (overflow.cssText == "hidden" || overflow.cssText == "overflow-x")) {
      var parentBorderLeft = mediaMagic.calcNetBorderSize(parentObjStyle, "left");
      var parentWidth = temp.offsetWidth - parentBorderLeft;
      if (mmComputedWidth > parentWidth - borderRight) {
        var oldWidth = mmComputedWidth;
        mmComputedWidth = parentWidth - borderRight;
        mediaMagic._logger.debug(".realShow: reducing width from "
                                 + oldWidth + " to " + mmComputedWidth);
      }
      mediaMagic._logger.debug(".realShow: Hit overflow==[hidden|overflow-x]");
      break;
    }
    temp = temp.parentNode;
  }

  // Adjust width for any scrollbar encroachment on display area.
  // Note: This code has to take into account the width of the sidebar, because
  // the Media Magic bar is placed using absolute offsets into the browser,
  // NOT the document.

  // ownerDocument.width only gets body width - scroll bars
  // ownerDocument.documentElement.clientWidth gets full width - scroll bars
  var docWidth = mediaMagic.targetObj.ownerDocument.documentElement.clientWidth;
  availableWidth = docWidth
                 - (mmComputedLeft
                 - (document.width - gBrowser.boxObject.width) // Sidebar width
                   );
  // srollbarWidth = gBrowser.boxObject.width - docWidth;
  if (docWidth < gBrowser.boxObject.width &&
      availableWidth < mmComputedWidth)
  {
    mmComputedWidth = availableWidth;
  }

  // Now position, size, and show the Media Magic bar.
  mediaMagic._logger.info(".realShow: mmComputedLeft=" + mmComputedLeft
                           + " mmComputedTop=" + mmComputedTop
                           + " mmComputedWidth=" + mmComputedWidth);
  document.getElementById("media-magic").style.width = mmComputedWidth + "px";
  document.getElementById("media-magic").style.clip = "rect(0 "
                                                    + mmComputedWidth
                                                    + "px "
                                                    + mediaMagic.mmHeight
                                                    + "px 0)";
  document.getElementById("media-magic").openPopup(null, "", mmComputedLeft,
                                                   mmComputedTop, false, false);
}

mediaMagic.mouseover =
function mediaMagic_mouseover() {
  if (mediaMagic.mTimer) {
    clearTimeout(mediaMagic.mTimer);
    mediaMagic.mTimer = null;
  }
}

mediaMagic.mouseout =
function mediaMagic_mouseout(event) {
  mediaMagic.hide();
}

mediaMagic.getNotificationBox =
function mediaMagic_getNotificationBox() {
  return gBrowser.getNotificationBox();
}

/*! This is the final and definite hide function, it will hide the overlay and
    reset all the variables unless asked not to by aKeepMediaInfo.
 */
mediaMagic.forceHide =
function mediaMagic_forceHide(aKeepMediaInfo) {
  mediaMagic._logger.info("forceHide");
  document.getElementById("media-magic").hidePopup();
  
  clearTimeout(mediaMagic.mTimer);
  
  // We may still be in the media dimensions
  if (aKeepMediaInfo) {
    return;
  }
  
  mediaMagic.service = null;
  mediaMagic.refUrl = "";
  mediaMagic.html = "";

  mediaMagic.mTimer = null;
  mediaMagic.targetObj = null;
}

/*! This hide is called from hide after a timeout, here we check if the
    mouse is really not in the objects boundries and if not we hide calling
    forceHide function.
    
    Returns true if object hidden.
 */
mediaMagic.realHide =
function mediaMagic_realHide() {
  if (!mediaMagic.targetObj) {
    mediaMagic._logger.debug("missing target object while calling realHide()");
    return false;
  }
  if (mediaMagic.checkIfMouseInObject(mediaMagic.targetObj)) {
    return false;
  }
  mediaMagic.forceHide();
  return true;
}  

/*! This first hide sets a timeout to call the realHide function
    we put the timeout so that we don't get conflicting calls which
    would cause flickering.
 */
mediaMagic.hide =
function mediaMagic_hide() {
  mediaMagic.mTimer = setTimeout(mediaMagic.realHide, MEDIAMAGIC_HIDE_DELAY);
}

/**
 * This will check that the mouse is currently in the bounds of the object
 * passed in.
 * @param checkObject object to check if the mouse is over.
 */
mediaMagic.checkIfMouseInObject =
function mediaMagic_checkIfMouseInObject(checkObject)
{
  var objPos = mediaMagic.getBoxPos(checkObject);
  
  // Find mouse pos...
  var mX = mediaMagic.curMouseX;
  var mY = mediaMagic.curMouseY;

  if (objPos[0] == 0 && objPos[1] == 0 && objPos[2] == 0 && objPos[3] == 0) {
    return false;
  }
  
  if (mY > objPos[0] && mY < objPos[2] &&
    mX > objPos[1] && mX < objPos[3]) {
    return true;
  }
  return false;
}

mediaMagic.imagerOut =
function mediaMagic_imagerOut(event) {
  // First we have to find out what the box cordinates of the image/embed object
  // are, then we translate those into cordinates that will work with the
  // event.clientX/clientY cordinates.
  if (event.target.nodeName == 'IMG' ||
      event.target.nodeName == 'EMBED' ||
      event.target.nodeName == 'HBOX') {
    
    mediaMagic.hide();
  }
}

mediaMagic._decorateImage =
function mediaMagic_decorateImage(aImageDOMNode) {
  mediaMagic._logger.debug("found an IMG, checking handlers...");
  var useUrl = aImageDOMNode.parentNode.href;
  var urlIsHref = false;

  // If we are not wrapped in an Anchor then check the image source
  if (aImageDOMNode.parentNode.nodeName != "A") {
    mediaMagic._logger.debug("Searching for proper url since we don't "
                           + "have an A tag...");
    var pattImage = FlockFlickrStrings.getAsRegExp("imageUrlPattern");
    var pattSpaceball = FlockFlickrStrings.getAsRegExp("spaceballUrlPattern");
    if (aImageDOMNode.src.match(pattImage) ||
        aImageDOMNode.src.match(pattSpaceball))
    {
      mediaMagic._logger.debug("This is a Flickr image, so we need to "
                             + "grab the current url...");
      useUrl = mediaMagic.getDocumentURL(aImageDOMNode);
      urlIsHref = true;
    } else {
      mediaMagic._logger.debug("Not a special image, so just use the "
                             + "image source...");
      useUrl = aImageDOMNode.src;
    }
  } else if (aImageDOMNode.parentNode.href.match(/^javascript:void\(0\)/)) {
    // This could be a photobucket image or some other one wrapped in a
    // javascript <a> tag
    mediaMagic._logger.debug("Using the image tag source...");
    useUrl = aImageDOMNode.src;
  } else {
    mediaMagic._logger.debug("Using the A tag source...");
  }
  mediaMagic._logger.debug("useUrl = [" + useUrl + "]");

  for each (var service in mediaMagic.mediaWebServices) {
    mediaMagic._logger.debug("checking service: " + service.title);
    var results = service.checkIsStreamUrl(useUrl);
    if (results) {
      var html = aImageDOMNode.parentNode.innerHTML;
      if (aImageDOMNode.parentNode.href) {
        // E4X can't be used here because of the "html" variable
        html = "<a href='" + aImageDOMNode.parentNode.href + "'>"
             + html
             + "</a>";
      } else if (urlIsHref) {
        // For Flickr, parent is not an A tag, but we know the href anyway.
        html = "<a href='" + useUrl + "'>"
             + html
             + "</a>";
      }
      mediaMagic.show(aImageDOMNode, service, useUrl, html, "image");
      break;
    }
  }
}


mediaMagic._getYoutubeEmbedSource =
function mediaMagic__getYoutubeEmbedSource(aDOMNode, aEmbedSrc) {
  var html;

  var re1 = /\/player2\.swf\?.*video_id=([^&]+)&/;
  var re2 = /video_id=(.+)&.*&/;
  var arr = [];

  // Set the src object initially.
  aEmbedSrc.src = aDOMNode.src;
  if (aDOMNode.src.match(re1)) {
    arr = re1.exec(aDOMNode.src);
  } else if (aDOMNode.getAttribute("flashvars").match(re2)) {
    arr = re2.exec(aDOMNode.getAttribute("flashvars"));
    // Override the src object.
    aEmbedSrc.src = "/player2.swf?video_id=" + arr[1];
  }
  // The "t" attribute expires after one hour and causes the video
  // to stop working (bug 11498)
  var embedUrl = "http://www.youtube.com/v/"
               + arr[1].replace(/t\=[a-zA-Z0-9_\-]+/, "");
  // * We don't want to use E4X here because it would produce
  //   unwanted line breaks (interpreted by the blog editor)
  // * We use single quotes to avoid having to escape double quotes
  html = '<object width="425" height="350">'
       + '<param name="movie" value="' + embedUrl + '"></param>'
       + '<param name="wmode" value="transparent"></param>'
       + '<embed src="' + embedUrl + '"'
             + ' type="application/x-shockwave-flash"'
             + ' wmode="transparent" width="425" height="350">'
       + '</embed></object>';

  return html;
}


mediaMagic._getPhotobucketEmbedSource =
function mediaMagic__getPhotobucketEmbedSource(aDOMNode) {
  var html;

  var re = /\/player\.swf\?.*file=([^&]+)/;
  var arr = re.exec(aDOMNode.src);
  html = '<object width="425" height="350">'
       + '<param name="movie" value="http://i166.photobucket.com/player.swf?file='
       + arr[1] + '"></param>'
       + '<param name="wmode" value="transparent"></param>'
       + '<embed src="http://i166.photobucket.com/player.swf?file=' + arr[1] + '"'
            + ' type="application/x-shockwave-flash"'
            + ' wmode="transparent"'
            + ' width="425"'
            + ' height="350">'
       + '</embed></object>';

  return html;
}


mediaMagic._decorateEmbed =
function mediaMagic__decorateEmbed(aEmbedDOMNode) {
  mediaMagic._logger.debug("found an EMBED for shockwave-flash, "
                         + "checking handlers...");
  var useUrl = mediaMagic.getDocumentURL(aEmbedDOMNode);

  for each (var service in mediaMagic.mediaWebServices) {
    mediaMagic._logger.debug("checking service: "
                             + service.title);
    var results = service.checkIsStreamUrl(aEmbedDOMNode.src);
    if (!results) {
      continue;
    }
    var html;

    // MBL: This is so we don't change the src object, thus creating a bug
    // like #13418
    var embedSource = aEmbedDOMNode.src;
    if (aEmbedDOMNode.src.match(/\/player\.swf\?file=(.+)/)) {
      html = mediaMagic._getPhotobucketEmbedSource(aEmbedDOMNode);
    } else if (aEmbedDOMNode.src.match(/\/player.*\.swf/) ||
               aEmbedDOMNode.src.match(/\/yt\/swf\/.*\.swf/))
    {
      var srcObject = {};
      srcObject.src = "";
      html = mediaMagic._getYoutubeEmbedSource(aEmbedDOMNode, srcObject);
      embedSource = srcObject.src;
    } else {
      html = "<embed ";
      for (var j in aEmbedDOMNode.attributes) {
        html += aEmbedDOMNode.attributes.item(j).nodeName + "='"
             + aEmbedDOMNode.attributes.item(j).nodeValue + "' ";
      }
      html += ">" + aEmbedDOMNode.innerHTML + "</embed>";
      var re1 = /(.+)&/;
      if (aEmbedDOMNode.src.match(re1)) {
        var arr = re1.exec(aEmbedDOMNode.src);
        embedSource = arr[1];
      }
    }
    var embeddedVideo = mediaMagic.bundle
        .GetStringFromName("flock.services.mediaMagic.embeddedVideo");
    html += "<p class=\"citation\"><cite cite=\"" + useUrl
         + "\"><a href=\"" + useUrl + "\">" + embeddedVideo
         + "</a></cite></p>";

    // For Picasa the src url containing the userid is found not on the EMBED
    // node, but instead on the previous sibling IMG node.
    var refUrl;
    if (aEmbedDOMNode.src.match(/\/googleplayer\.swf/)) {
      if (aEmbedDOMNode.previousSibling) {
        refUrl = aEmbedDOMNode.previousSibling.src;
      } else {
        // XXX: Probably not on Picasa then, could be plain Google video.
        // If we ever support media magic for Google video this needs to be
        // revisited.
        continue;
      }
    } else {
      refUrl = embedSource;
    }

    mediaMagic.show(aEmbedDOMNode, service, refUrl, html, 'video');
    break;
  }
}


/**
 * This is called when the user moves the mouse over a valid media object.
 *   @param event mouse over event that contains the target data.
 */
mediaMagic.imager =
function mediaMagic_imager(event) {
  // Ignore other objects.
  if (event.target.nodeName != "IMG" && event.target.nodeName != "EMBED") {
    return;
  }
  if (!event.relatedTarget) {
    mediaMagic._logger.debug("mediaMagic_imager: event.relatedTarget is NULL, "
                             + "must be wrong tab. returning.");
    return;
  }

  // We're on a good image, force hide the old flock magic.
  if (mediaMagic.targetObj != event.target) {
    mediaMagic.forceHide();
  }

  // If media-magic is already visible
  if (document.getElementById("media-magic").state == "open") {
    // Hide if mouse no longer in media. If we don't hide then ignore.
    if (!mediaMagic.realHide()) {
      return;
    }
  }

  if (event.target.nodeName == 'IMG') {
    mediaMagic._decorateImage(event.target);
  } else if (event.target.nodeName == "EMBED"
             && event.target.type == "application/x-shockwave-flash") {
    mediaMagic._decorateEmbed(event.target);
  }
}

/**
 * Array to hold query-able web services for media objects.
 */
mediaMagic.mediaWebServices = [];

/**
 * This generates a list of services that we can query about media objects.
 */
mediaMagic.getFlockWebServices =
function mediaMagic_getFlockWebServices() {
  var CATMAN = CC['@mozilla.org/categorymanager;1']
               .getService(CI.nsICategoryManager);
  var e = CATMAN.enumerateCategory('flockWebService');
  while (e.hasMoreElements()) {
    try {
      var entry = e.getNext().QueryInterface(CI.nsISupportsCString);
      if (!entry) { continue; }
      var contractID = CATMAN.getCategoryEntry('flockWebService', entry.data);
      var mediaService = CC[contractID].getService(CI.flockIWebService);
      // We really only worry about media services so check those
      //flockIPhotoAPI
      if (mediaService instanceof CI.flockIMediaEmbedWebService) {
        mediaMagic._logger.debug("Adding service: " + mediaService.title);
        mediaService.QueryInterface(CI.flockIMediaEmbedWebService);
        mediaMagic.mediaWebServices.push(mediaService);
      }
    } catch (e) {
      mediaMagic._logger.debug("getFlockWebServices: " + e);
    }
  }
}

// Reset scroll offsets when scrolling occurs.
mediaMagic.scroll =
function mediaMagic_scroll() {
  mediaMagic.getScrollOffsets();
  mediaMagic.forceHide(true);
}

// Reset content, scroll, and notification heights.
// Resize occurs for showing/hiding sidebar, notifications and when the
// user does it themselves.
mediaMagic.resize =
function mediaMagic_resize() {
  mediaMagic._logger.info(".resize");

  mediaMagic.getScrollOffsets();
  mediaMagic.getContentOffsets();
  mediaMagic.getNotificationsHeight();
  mediaMagic.forceHide(true);
}

// Reset tab height when a tab event occurs.
mediaMagic.tabEvent =
function mediaMagic_tabEvent() {
  mediaMagic._logger.info(".tabEvent");

  mediaMagic.getTabHeight();
  mediaMagic.forceHide();
}

mediaMagic.load =
function mediaMagic_load() {
  // Use the flock logger
  mediaMagic._logger.init('mediaMagic');

  var stringBundleService = CC["@mozilla.org/intl/stringbundle;1"]
                            .getService(CI.nsIStringBundleService);
  mediaMagic.bundle =
    stringBundleService.createBundle('chrome://flock/locale/services/mediaStream.properties');

  mediaMagic.getFlockWebServices();

  // Monitor for network access (mainly location changes)
  gBrowser.addProgressListener(mediaMagic.mWebProgressListener,
                               CI.nsIWebProgress.NOTIFY_STATE_DOCUMENT);

  // Monitor for mouse actions on the window so we can show/hide the overlay
  gBrowser.addEventListener("mouseover", mediaMagic.imager, false);
  gBrowser.addEventListener("mouseout", mediaMagic.imagerOut, false);
  gBrowser.addEventListener("mousemove", mediaMagic.imagerMove, false);

  // Monitor for high level actions so we can hide the overlay
  gBrowser.addEventListener("scroll", mediaMagic.scroll, false);
  gBrowser.addEventListener("resize", mediaMagic.resize, false);

  // Monitor tab actions so we can hide when we need to
  var container = gBrowser.tabContainer;
  if (container) {
    container.addEventListener("TabClose", mediaMagic.tabEvent, false);
    container.addEventListener("TabOpen", mediaMagic.tabEvent, false);
    container.addEventListener("TabSelect", mediaMagic.forceHide, false);
  }
}

mediaMagic.unload =
function mediaMagic_unload() {
  gBrowser.removeProgressListener(mediaMagic.mWebProgressListener);
  gBrowser.removeEventListener("mouseover", mediaMagic.imager, false);
  gBrowser.removeEventListener("mouseout", mediaMagic.imagerOut, false);
  gBrowser.removeEventListener("mousemove", mediaMagic.imagerMove, false);
  gBrowser.removeEventListener("scroll", mediaMagic.scroll, false);
  gBrowser.removeEventListener("resize", mediaMagic.resize, false);

  var container = gBrowser.tabContainer;
  if (container) {
    container.removeEventListener("TabClose", mediaMagic.tabEvent, false);
    container.removeEventListener("TabOpen", mediaMagic.tabEvent, false);
    container.removeEventListener("TabSelect", mediaMagic.forceHide, false);
  }
}

mediaMagic.mWebProgressListener = {
  onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus) { },
  onProgressChange : function(aWebProgress, aRequest, aCurSelfProgress,
                              aMaxSelfProgress, aCurTotalProgress,
                              aMaxTotalProgress) { },
  onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage) { },
  onSecurityChange : function(aWebProgress, aRequest, aState) { },
  onLocationChange : function(aWebProgress, aRequest, aLocation) {
    // We need to hide the mediaMagic box when we navigate away
    mediaMagic.forceHide();
  }
}

/**
 Global listeners for the mediaMagic system.
 Added an onload and onunload listener to setup our mediaMagic listeners.
 These do not need to be removed since they will die with the browser.

 Add the listeners if flock.magic.enabled = true.
 */
if (!mediaMagic.mPrefSvc.getPrefType("flock.magic.enabled") ||
     mediaMagic.mPrefSvc.getBoolPref("flock.magic.enabled"))
{
  // call unload to cleanup listeners
  top.addEventListener('unload', mediaMagic.unload, false);
  // call load to setup listeners and register the handlers
  top.addEventListener('load', mediaMagic.load, false);
}
