// 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 CR = Components.results;
const CU = Components.utils;

const MODULE_NAME = "RSSMedia";
const RSSMEDIA_TITLE = "RSS Media Service";
const RSSMEDIA_FAVICON = "chrome://browser/skin/feeds/feedIcon16.png";
const CLASS_ID = Components.ID("{e83c52ca-40f9-401d-8d4f-cd244a357dcf}");
const CONTRACT_ID = "@flock.com/?photo-rss-media;1";
const SHORTNAME = "mediarss";
const TITLE = "RSS Media";
const MEDIABAR_PROPERTIES = "chrome://flock/locale/photo/mediabar.properties";

const MEDIARSS_NS = "http://search.yahoo.com/mrss/";
// This namespace is deprecated, but some feeds still use it...
const OLD_MEDIARSS_NS = "http://search.yahoo.com/mrss";

CU.import("resource:///modules/FlockXPCOMUtils.jsm");
CU.import("resource:///modules/FlockSvcUtils.jsm");
CU.import("resource:///modules/FlockPrefsUtils.jsm");

/*************************************************************************
 * Component: flockRSSMedia
 *************************************************************************/
function flockRSSMedia() {
  FlockSvcUtils.nsIObserver.addDefaultMethod(this, "observe");
  FlockSvcUtils.flockIWebService.addDefaultMethod(this, "getStringBundle");
  FlockSvcUtils.flockIWebService.addDefaultMethod(this, "isHidden");

  FlockSvcUtils.flockIMediaWebService.addDefaultMethod(this, "getChannel");
  FlockSvcUtils.flockIMediaWebService.addDefaultMethod(this, "enumerateChannels");
  FlockSvcUtils.flockIMediaWebService.addDefaultMethod(this, "getMediaItemFormatter");

  this._init();
}

/*************************************************************************
 * flockRSSMedia: XPCOM Component Creation
 *************************************************************************/

flockRSSMedia.prototype = new FlockXPCOMUtils.genericComponent(
  RSSMEDIA_TITLE,
  CLASS_ID,
  CONTRACT_ID,
  flockRSSMedia,
  CI.nsIClassInfo.SINGLETON,
  [
    CI.nsIObserver,
    CI.flockIWebService,
    CI.flockIMediaWebService
  ]
);

// FlockXPCOMUtils.genericModule() categories
flockRSSMedia.prototype._xpcom_categories = [
  { category: "flockMediaProvider", entry: SHORTNAME }
];


flockRSSMedia.prototype._init =
function svs__init() {
  FlockSvcUtils.getLogger(this);
  this._logger.debug(".init()");

  // Used by getChannel() and enumerateChannels(), defined in FlockSvcUtils:
  // channels for the MediaBar
  this._channels = {
  };

  this._ios = CC["@mozilla.org/network/io-service;1"]
              .getService(CI.nsIIOService);

  this._logger.debug("initialized");
}

flockRSSMedia.prototype.shortName = SHORTNAME;
flockRSSMedia.prototype.title = TITLE;
flockRSSMedia.prototype.icon = RSSMEDIA_FAVICON;
flockRSSMedia.prototype.count = 1;

/*************************************************************************
 * flockRSSMedia: Private Data and Functions
 *************************************************************************/

flockRSSMedia.prototype._getFeed =
function flockRSSMedia_getFeed(aURL, aFlockListener) {
  var inst = this;
  this._req = CC["@mozilla.org/xmlextras/xmlhttprequest;1"]
              .createInstance(CI.nsIXMLHttpRequest);
  this._req.onreadystatechange = function fRSSM_onreadystatechange (aEvt) {
    if (inst._req.readyState == 4) {
      switch (inst._req.status) {
        case 200: 
        case 201:
        case 205:
          aFlockListener.onSuccess(inst._req.responseXML, "success");
          break;
        case 0: // connection lost
        case 500: // Server internal error
          var err = CC["@flock.com/error;1"].createInstance(CI.flockIError);
          err.serviceErrorCode = inst._req.status;
          err.serviceErrorString = inst._req.responseText;
          aFlockListener.onError(err, null);
          break;
        default: // The server should not return any other code
          break;
      }
    }
  };
  this._req.open("GET", aURL, true);

  // Tinypic media RSS feeds are slow to have their modified timestamps updated,
  // even when they have new content.  In order to display fresh content in the
  // mediabar after files have been uploaded, we need to bypass the cache.
  // c.f. https://bugzilla.flock.com/show_bug.cgi?id=15457
  var wd = CC["@flock.com/web-detective;1"].getService(CI.flockIWebDetective);
  if (wd.testDomain(aURL, "tinypic.com")) {
    this._req.channel.loadFlags |=
      CI.nsIRequest.INHIBIT_CACHING | CI.nsIRequest.INHIBIT_PERSISTENT_CACHING;
  }

  this._req.overrideMimeType("text/xml");
  this._req.send(null);
}

flockRSSMedia.prototype.findByUsername =
function flockRSSMedia_findByUsername(aFlockListener, aUsername) {
  aFlockListener.onError(null, null);
}

flockRSSMedia.prototype._makeAbsolute =
function flockRSSMedia__makeAbsolute(aBaseURI, aURL) {
  return aBaseURI.resolve(aURL);
}

flockRSSMedia.prototype._getMediaTags =
function flockRSSMedia__getMediaTags(aParent, aTagName) {
  // Try the correct NS
  var tags = aParent.getElementsByTagNameNS(MEDIARSS_NS, aTagName);
  if (tags && tags[0]) {
    return tags;
  }

  // Try the incorrect (but often used) namespace
  var tags = aParent.getElementsByTagNameNS(OLD_MEDIARSS_NS, aTagName);
  if (tags && tags[0]) {
    return tags;
  }

  // Give up!
  return null;
}

flockRSSMedia.prototype._getMediaTag =
function flockRSSMedia__getMediaTag(aParent, aTagName) {
  var tags = this._getMediaTags(aParent, aTagName);
  if (tags && tags[0]) {
    return tags[0];
  }

  return null;
}

flockRSSMedia.prototype._isVideo =
function flockRSSMedia__isVideo(aDOMNode) {
  // Look for <media:content> in the toplevel
  var contentArr = this._getMediaTags(aDOMNode, "content");
  if (contentArr) {
    for (var i = 0; i < contentArr.length; i++) {
      if (!contentArr[i]) {
        continue;
      }
      if (contentArr[i].getAttribute("medium") &&
          contentArr[i].getAttribute("medium") == "video")
      {
        return true;
      }
      if (contentArr[i].getAttribute("type") &&
          contentArr[i].getAttribute("type").match("video"))
      {
        return true;
      }
    }
  }

  // Look for <media:content> in <media:group>
  var group = this._getMediaTag(aDOMNode, "group");
  if (group) {
    return this._isVideo(group);
  }

  return false;
}

// Convert a MRSS DOM node to a flockIMediaItem instance
flockRSSMedia.prototype._parseItem =
function flockRSSMedia__parseItem(aXMLNode, aSafeSearch, aBaseURI) {
  if (aXMLNode.getElementsByTagName("adult")[0]) {
    var adult = aXMLNode.getElementsByTagName("adult")[0].firstChild.nodeValue;
    if (aSafeSearch === true && adult == "true") {
      return null;
    }
  }
  var title;
  if (aXMLNode.getElementsByTagName("title")[0].firstChild) {
    title = aXMLNode.getElementsByTagName("title")[0].firstChild.nodeValue;
  } else {
    var sbs = CC["@mozilla.org/intl/stringbundle;1"]
              .getService(CI.nsIStringBundleService);
    var bundleUrl = MEDIABAR_PROPERTIES;
    var bundle = sbs.createBundle(bundleUrl);
    title = bundle.GetStringFromName("flock.media.tooltip.untitled");
  }
  var thumb_url = null;
  var thumbTag = this._getMediaTag(aXMLNode, "thumbnail");
  if (thumbTag) {
    thumb_url = this._makeAbsolute(aBaseURI,
                                   thumbTag.getAttribute("url"));
  }
  var link = aXMLNode.getElementsByTagName("link")[0]
                     .firstChild
                     .nodeValue;
  link = this._makeAbsolute(aBaseURI, link);
  var id = link.split("/").pop();
  var is_video = this._isVideo(aXMLNode);
  var media_url = null;
  var content = this._getMediaTag(aXMLNode, "content");
  if (content &&
      content.getAttribute("medium") == "image" &&
      content.getAttribute("url"))
  {
    media_url = this._makeAbsolute(aBaseURI,
                                   content.getAttribute("url"));
  }
  if (!media_url && !is_video) {
    // For videos, "enclosure" contains the video (not an image)
    // so we don't want to ust it for thumbnail, midSizePhoto or largeSizePhoto
    var enclosure = aXMLNode.getElementsByTagName("enclosure");
    if (enclosure && enclosure[0]) {
      media_url = this._makeAbsolute(aBaseURI,
                                     enclosure[0].getAttribute("url"));
    }
  }
  if (!thumb_url && !media_url) {
    // We couldn't find any actual image here, let's skip it
    return null;
  }
  var newMediaItem = CC["@flock.com/photo;1"]
                     .createInstance(CI.flockIMediaItem);
  newMediaItem.init(this.shortName, null);
  newMediaItem.webPageUrl = link;
  newMediaItem.thumbnail = thumb_url ? thumb_url : media_url;
  newMediaItem.midSizePhoto = media_url ? media_url : thumb_url;
  newMediaItem.largeSizePhoto = media_url ? media_url : thumb_url;
  newMediaItem.is_public = true;
  newMediaItem.is_video = is_video;
  newMediaItem.title = title;
  newMediaItem.id = id;

  return newMediaItem;
}


/*************************************************************************
 * flockRSSMedia: flockIMediaWebService
 *************************************************************************/

flockRSSMedia.prototype.search =
function flockRSSMedia_search(aFlockListener,
                              aQueryString,
                              aCount,
                              aPage,
                              aRequestId)
{
  this._logger.debug("Attempting to run search query: " + aQueryString);

  var url = decodeURIComponent(aQueryString.split(":")[1]);
  var baseURI = this._ios.newURI(url, null, null);

  var inst = this;
  if (aPage > 1 ) {
    // Implement a pseudo-pagination, because RSS doesn't support
    // pagination
    getDetails(aCount, aPage);
    return;
  }

  // Workaround for Yahoo!image: they advertise an adult feed even when
  // their "safeSearch" is on!
  var safeSearch = FlockPrefsUtils.getBoolPref("flock.photo.safeSearch");
  if (safeSearch !== false && url.match(/yahoo\.com/)) {
    url = url.replace(/\&adult_ok=1/, "");
  }

  function createEnum( array ) {
    return {
      QueryInterface: function cE_QueryInterface(iid) {
        if (iid.equals(Components.interfaces.nsISimpleEnumerator)) {
          return this;
        }
        throw Components.results.NS_ERROR_NO_INTERFACE;
      },
      hasMoreElements: function cE_hasMoreElements() {
        return (array.length > 0);
      },
      getNext: function cE_getNext() {
        return array.shift();
      }
    }
  }

  function getDetails(aCount, aPage) {
    var safeSearch = FlockPrefsUtils.getBoolPref("flock.photo.safeSearch");
    var sbs = CC["@mozilla.org/intl/stringbundle;1"]
              .getService(CI.nsIStringBundleService);
    var bundleUrl = MEDIABAR_PROPERTIES;
    var bundle = sbs.createBundle(bundleUrl);

    var mediaItems = [];

    var pageEnd = aCount * aPage;
    if (pageEnd > inst.mediaItemList.length) {
      pageEnd = inst.mediaItemList.length;
    }
    for (var i = aCount * (aPage - 1); i < pageEnd; i++) {
      try {
        var newMediaItem = inst._parseItem(inst.mediaItemList[i],
                                           safeSearch,
                                           baseURI);
        if (newMediaItem) {
          // _parseItem returns null if some attributes were missing
          mediaItems.push(newMediaItem);
        }
      } catch(ex) {
        // Some attributes were missing in this image, skip it
        inst._logger.error(ex);
      }
    }

    aFlockListener.onSuccess(createEnum(mediaItems), aRequestId);
  };

  var listener = {
    onSuccess: function listener_onSuccess(aXml, aStatus) {
      var channel = aXml.getElementsByTagName("channel")[0];
      if (!channel) {
        inst._logger.error("<channel> tag absent from the feed");
        var error = CC["@flock.com/error;1"].createInstance(CI.flockIError);
        error.errorCode = error.PHOTOSERVICE_INVALID_RESPONSE;
        aFlockListener.onError(error, aRequestId);
        return;
      }
      inst.mediaItemList = channel.getElementsByTagName("item");
      getDetails(aCount, 1);
    },
    onError: function listener_onError(aFlockError, aTopic) {
      aFlockListener.onError(aFlockError, aRequestId);
    }
  };

  this._getFeed(url, listener);
}

flockRSSMedia.prototype.supportsSearch =
function flockRSSMedia_supportsSearch( aQueryString ) {
  return false;
}


// readonly attribute boolean supportsUsers;
flockRSSMedia.prototype.supportsUsers = false;

flockRSSMedia.prototype.decorateForMedia =
function flockRSSMedia_decorateForMedia(aDocument, aAccount) {
  DEBUG("{flockIMediaWebService}.decorateForMedia(aDocument)");
  // Discovery not supported yet
}

flockRSSMedia.prototype.getIconForQuery =
function flockRSSMedia_getIconForQuery(aQuery, aListener) {
  // Most service just return their favicon here... But we want to get the
  // icon from the site that hosts the feed.

  var url = decodeURIComponent(aQuery.split(":")[1]);
  var ios = CC["@mozilla.org/network/io-service;1"]
            .getService(CI.nsIIOService);
  var uri = ios.newURI(url, null, null);

  return uri.resolve("/favicon.ico");
}

/*************************************************************************
 * XPCOM Support - Module Construction
 *************************************************************************/

// Create array of components.
var componentsArray = [flockRSSMedia];

// Generate a module for XPCOM to find.
var NSGetModule = FlockXPCOMUtils.generateNSGetModule(MODULE_NAME,
                                                      componentsArray);
