// 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 = "Searchvideo";
const SEARCHVIDEO_TITLE                  = "Truveo Web Service";
const SEARCHVIDEO_FAVICON                = "chrome://flock/content/services/searchvideo/favicon.png";
const CLASS_ID = Components.ID("{31def21e-02d6-4d9a-848c-2c1895e50626}");
const CONTRACT_ID = "@flock.com/?photo-api-searchvideo;1";
const APPID                              = "ee9eafa6a67d66238";
const SHORTNAME = "searchvideo";
const TITLE = "Truveo";
const SERVICE_ENABLED_PREF               = "flock.service.searchvideo.enabled";

const SEARCHVIDEO_STRING_BUNDLE = "chrome://flock/locale/services/searchvideo.properties";

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

var flockMediaItemFormatter = {
  canBuildTooltip: false,
  canBuildHTML: true,
  canBuildLargeHTML: true,
  canBuildBBCode: false,
  canBuildMiniPage: true,

  buildHTML: function fmif_buildHTML(aMediaItem) {
    const EMBED_TAG = "</embed>";
    var embedTagCopy = aMediaItem.embedTag;

    // Add a 'value' to the EMBED tag. The Truveo URL is incredibly long and
    // can look like raw HTML, so bury it in an anchor tag and use the title to
    // give a richer experience.
    // (cf. https://bugzilla.flock.com/show_bug.cgi?id=13274)
    var value = '<br><a href="' + aMediaItem.webPageUrl + '">'
              + aMediaItem.title + "</a>";

    // Since aMediaItem.embedTag is a string, we need to insert the value in the
    // appropriate position so as not to break the markup
    // TODO JVL: find RegExp implementation
    var embedTagAtEndIndex = embedTagCopy.length - EMBED_TAG.length;
    if (embedTagCopy.lastIndexOf(EMBED_TAG) == embedTagAtEndIndex) {
      // case 1: <embed></embed>
      embedTagCopy = embedTagCopy.slice(0, embedTagAtEndIndex);
      embedTagCopy += value;
      embedTagCopy += EMBED_TAG;
    } else if (embedTagCopy.lastIndexOf("/>") == (embedTagCopy.length - 2)) {
      // case 2: <embed/>
      embedTagCopy = embedTagCopy.slice(0, embedTagCopy.length - 2);
      embedTagCopy += ">";
      embedTagCopy += value;
      embedTagCopy += EMBED_TAG;
    } else if (embedTagCopy.lastIndexOf(">") == (embedTagCopy.length - 1)) {
      // case 3: <embed>
      embedTagCopy += value;
      embedTagCopy += EMBED_TAG;
    }
    return embedTagCopy;
  },

  buildLargeHTML: function fmif_buildLargeHTML(aMediaItem) {
    return this.buildHTML(aMediaItem);
  },

  buildMiniPage: function fmif_buildMiniPage(aMediaItem) {
    return aMediaItem.webPageUrl;
  }
};


/*************************************************************************
 * Component: searchvideoService
 *************************************************************************/
function searchvideoService() {
  FlockSvcUtils.nsIObserver.addDefaultMethod(this, "observe");

  FlockSvcUtils.flockIWebService.addDefaultMethod(this, "url");
  FlockSvcUtils.flockIWebService.addDefaultMethod(this, "getStringBundle");
  FlockSvcUtils.flockIWebService.addDefaultMethod(this, "isHidden");

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

  this._init();
}

/*************************************************************************
 * searchvideoService: XPCOM Component Creation
 *************************************************************************/

searchvideoService.prototype = new FlockXPCOMUtils.genericComponent(
  SEARCHVIDEO_TITLE,
  CLASS_ID,
  CONTRACT_ID,
  searchvideoService,
  Ci.nsIClassInfo.SINGLETON,
  [
    Ci.nsIObserver,
    Ci.flockIWebService,
    Ci.flockIMediaWebService
  ]
);

// FlockXPCOMUtils.genericModule() categories
searchvideoService.prototype._xpcom_categories = [
  { category: "flockWebService", entry: SHORTNAME },
  // Do not set entry to SHORTNAME ("searchvideo") since this messes up the
  // alpha-sort of the service names in the mediabar.
  { category: "flockMediaProvider", entry: "truveo" }
];


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

  // Determine whether this service has been disabled, and unregister if so.
  var prefService = Cc["@mozilla.org/preferences-service;1"]
                   .getService(Ci.nsIPrefBranch);
  if (prefService.getPrefType(SERVICE_ENABLED_PREF) &&
     !prefService.getBoolPref(SERVICE_ENABLED_PREF))
  {
    this._logger.info(SERVICE_ENABLED_PREF + " is FALSE! Not initializing.");
    this.deleteCategories();
    return;
  }

  // Initialize member that specifies path for localized string bundle
  this._stringBundlePath = SEARCHVIDEO_STRING_BUNDLE;

  var bundle = this.getStringBundle();

  // Used by getChannel() and enumerateChannels(), defined in FlockSvcUtils:
  // channels for the MediaBar
  this._channels = {
    "special:all": {
      title: bundle.GetStringFromName("flock.searchvideo.title.all"),
      supportsSearch: true
    },
    "special:pop": {
      title: bundle.GetStringFromName("flock.searchvideo.title.pop"),
      supportsSearch: true
    },
    "special:abc": {
      title: bundle.GetStringFromName("flock.searchvideo.title.abc"),
      supportsSearch: true
    },
    "special:myspace": {
      title: bundle.GetStringFromName("flock.searchvideo.title.myspace"),
      supportsSearch: true
    },
    "special:disney": {
      title: bundle.GetStringFromName("flock.searchvideo.title.disney"),
      supportsSearch: true
    },
    "special:bbcnews": {
      title: bundle.GetStringFromName("flock.searchvideo.title.bbcnews"),
      supportsSearch: true
    },
    "special:dailymotion": {
      title: bundle.GetStringFromName("flock.searchvideo.title.dailymotion"),
      supportsSearch: true
    }
  };

  FlockSvcUtils.getCoop(this);

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

searchvideoService.prototype.shortName = SHORTNAME;
searchvideoService.prototype.title = TITLE;
searchvideoService.prototype.icon = SEARCHVIDEO_FAVICON;
searchvideoService.prototype.contractId = CONTRACT_ID;
searchvideoService.urn = "urn:" + SHORTNAME + ":service";

searchvideoService.prototype.count = 1;

// flockIMediaWebService.getMediaItemFormatter()
searchvideoService.prototype.getMediaItemFormatter =
function searchvideoService_getMediaItemFormatter() {
  return flockMediaItemFormatter;
}

searchvideoService.prototype.handlePhotosResult =
function (aXML, aUserid) {
  var rval = new Array();
  var response = aXML.getElementsByTagName("VideoSet")[0];
  var videoList = aXML.getElementsByTagName("Video");
  for (var i = 0; i < videoList.length; i++) {
    var video = videoList[i];

    var newMediaItem = Cc["@flock.com/photo;1"]
                       .createInstance(Ci.flockIMediaItem);
    newMediaItem.init(this.shortName, flockMediaItemFormatter);
    newMediaItem.id = video.getElementsByTagName("id")[0].textContent;
    newMediaItem.title = video.getElementsByTagName("title")[0].textContent;
    newMediaItem.webPageUrl = video.getElementsByTagName("videoUrl")[0]
                                   .textContent;
    newMediaItem.thumbnail = video.getElementsByTagName("thumbnailUrl")[0]
                                  .textContent;
    var largeThumbnail = video.getElementsByTagName("thumbnailUrlLarge");
    if (largeThumbnail.length > 0) {
      newMediaItem.midSizePhoto = largeThumbnail[0].textContent;
      newMediaItem.largeSizePhoto = largeThumbnail[0].textContent;
    }
    else {
      newMediaItem.midSizePhoto = newMediaItem.thumbnail;
      newMediaItem.largeSizePhoto = newMediaItem.thumbnail;
    }
    newMediaItem.username = video.getElementsByTagName("channel")[0]
                                 .textContent;
    newMediaItem.userid = video.getElementsByTagName("channelUrl")[0]
                               .textContent;
    var dateProduced = video.getElementsByTagName("dateProduced");
    if (dateProduced.length > 0) {
      newMediaItem.uploadDate = new Date(dateProduced[0].textContent).getTime();
    } else {
      newMediaItem.uploadDate = new Date(video.getElementsByTagName("dateFound")[0]
                                              .textContent).getTime();
    }

    newMediaItem.is_public = true;
    newMediaItem.is_video = true;
    newMediaItem.has_miniView = "true";
    newMediaItem.embedTag = video.getElementsByTagName("videoResultEmbedTag")[0]
                                 .textContent;
    rval.push(newMediaItem);
  }
  return rval;
}

searchvideoService.prototype.queryChannel =
function searchvideoService_queryChannel(aFlockListener,
                                         aQueryString,
                                         aCount,
                                         aPage,
                                         aRequestId)
{
  var aQuery = new QueryHelper(aQueryString);
  var inst=this;
  var myListener = {
    onResult: function (aXML) {
      var rval = inst.handlePhotosResult(aXML);
      var resultsEnum = {
        hasMoreElements: function resultsEnum_hasMoreElements() {
          return (rval.length>0);
        },
        getNext: function resultsEnum_getNext() {
          return rval.shift();
        },
        QueryInterface: function resultsEnum_QueryInterface(aIID) {
          if (aIID.equals(Ci.nsISimpleEnumerator)) {
            return this;
          }
          throw Cr.NS_ERROR_NO_INTERFACE;
        }
      }
      aFlockListener.onSuccess(resultsEnum, aRequestId);
    },
    onError: function (aFlockError) {
      aFlockListener.onError(aFlockError, aRequestId);
    }
  }

  var safeSearch = FlockPrefsUtils.getBoolPref("flock.photo.safeSearch");
  var params = {
    showAdult: (safeSearch === false) ? 1 : 0,
    results: (aCount ? aCount : 50),
    start: (aPage ? aPage : 1)
  };

  switch (aQuery.special) {
    case "recent": params.query = "sort:mostRecent "; break;
    case "pop": params.query = "sort:mostPopular "; break;
    // Even though "election" is no longer a default mediastream, we
    // leave it in case a user has starred this stream previously.
    case "election": params.query = "sort:mostPopularNow category:News " +
                                    "OR category:Government 08 election " +
                                    "OR president OR debate OR primary " +
                                    "OR candidates OR vote"; break;
    case "poptoday": params.query = "sort:mostPopularToday "; break;
    case "popweek": params.query = "sort:mostPopularThisWeek "; break;
    case "popmonth": params.query = "sort:mostPopularThisMonth "; break;
    case "abc": params.query = "sort:mostPopular channel:ABC "; break;
    case "myspace": params.query = "sort:mostPopular channel:MYSPACE "; break;
    case "disney": params.query = "sort:mostPopular channel:Disney "; break;
    case "bbcnews": params.query = "sort:mostPopular channel:\"BBC News\""; break;
    case "dailymotion": params.query = "sort:vrank channel:\"Dailymotion\""; break;
    default: params.query = ""; // Truveo/All mediastream
  }

  if (aQuery.search)
       params.query += aQuery.search;

  this.call(myListener, "truveo.videos.getVideos", params);
}

searchvideoService.prototype.findByUsername =
function searchvideoService_findByUsername(aFlockListener, aUsername) {
  aFlockListener.onError(null, null);
}

searchvideoService.prototype.search =
function searchvideoService_search(aFlockListener,
                                   aQueryString,
                                   aCount,
                                   aPage,
                                   aRequestId)
{
  //this.count = this.count+aCount;
  // Searchvideo only supports channel queries for now
  if(aPage == 1) this.count = 1;
  this.queryChannel(aFlockListener,
                    aQueryString,
                    aCount,
                    (aPage ? this.count : 1),
                    aRequestId);
  this.count = aCount + this.count;
}

searchvideoService.prototype.supportsSearch =
function searchvideoService_supportsSearch( aQueryString ) {
  var aQuery = new QueryHelper(aQueryString);
  
  if (aQuery.special) {
    var channel = this._channels["special:" + aQuery.special];
    if (channel) {
      return channel.supportsSearch;
    }
  }

  if (aQuery.search) return false;
  if (aQuery.user) return true;
  return false;
}

searchvideoService.prototype.getError =
function searchvideoService_getError (aErrorType, aXML, aHTTPErrorCode) {
  var error = Components.classes["@flock.com/error;1"].createInstance(Ci.flockIError);
  if  (aErrorType == "HTTP_ERROR") {
    error.errorCode = aHTTPErrorCode;
  } else if (aErrorType == "SERVICE_ERROR") {
    var errorCode;
    var errorMessage;
    var serviceErrorMessage;
    try {
      errorCode = aXML.getElementsByTagName("Error")[0].getAttribute('Code');
      serviceErrorMessage = aXML.getElementsByTagName("error")[0].textContent;
    } catch (ex) {
      errorCode = "999" // in case the error xml is invalid
    }

    switch (errorCode) {
      case "1":  // API key missing (should never happen, but...)
      case "14": // API key invalid
      case "15": // API key over the daily query limit (I hope it will never happen)
        error.errorCode = error.PHOTOSERVICE_INVALID_API_KEY;
      break;

      case "2": // A method was not submitted with the request
      case "3": // The query parameter was not submitted with the request
      case "4": // The results parameter must be an integer between 1 and 50
      case "5": // The query parameter was not submitted with the request
      case "7": // The showAdult parameter must be 0 or 1
      case "8": // The tagresults parameter must be an integer between 1 and 50
      case "9": // The channelresults parameter must be an integer between 1 and 50
      case "10": // The categoryresults parameter must be an integer between 1 and 50
      case "11": // The userresults parameter must be an integer between 1 and 50
      case "13": // The method you submitted with the request was not valid
      case "21": // The showRelatedItems parameter must be 0 or 1
        error.errorCode = error.PHOTOSERVICE_INVALID_QUERY;
      break;

      case "12":
        error.errorCode = error.PHOTOSERVICE_UNAVAILABLE;
      break;

      case "999":
        error.errorCode = error.PHOTOSERVICE_UNKNOWN_ERROR;
      break;

      default:
        error.errorCode = error.PHOTOSERVICE_UNKNOWN_ERROR;
      break;
    }
  }
  error.serviceErrorCode = errorCode;
  error.serviceErrorString = serviceErrorMessage;
  this._logger.error(error.errorString);
  return error;
};


searchvideoService.prototype.call =
function searchvideoService_call(aListener, aMethod, aParams) {
  var inst = this;
  this._req = Cc['@mozilla.org/xmlextras/xmlhttprequest;1'].createInstance(Ci.nsIXMLHttpRequest);
  this._req.onreadystatechange = function (aEvt) {
    if(inst._req.readyState == 4) {
      var status = inst._req.status;
      var dom = inst._req.responseXML;
      var domResponse = dom ? dom.getElementsByTagName("Response")[0] : null;
      var error;
      if (Math.floor(status/100) == 2) {
        if (domResponse.firstChild.nodeName == "Error") {
          error = inst.getError("SERVICE_ERROR", dom, null);
        } else {
          aListener.onResult(domResponse);
          return;
        }
      } else if (Math.floor(status/100) == 4 &&
               domResponse && (domResponse.firstChild.nodeName == "Error")) {
        error = inst.getError("SERVICE_ERROR", dom, null);
      } else {
        // HTTP errors (0 for connection lost)
        error = inst.getError("HTTP_ERROR", null, status);
      }
      aListener.onError(error);
    }
  };
  var body = "appid="+APPID+"&method="+aMethod;
  for (var k in aParams) {
    var v = aParams[k];
    if (v == null || v == undefined)
      v = "";
    body += ("&"+k+"="+escape(v));
  }
  this._req.open("GET", "http://xml.truveo.com/apiv3?" + body, true);
  this._req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded", false);
  rval = this._req.send(null); 
}

// BEGIN flockIMediaWebService interface
// readonly attribute boolean supportsUsers;
searchvideoService.prototype.supportsUsers = false;

searchvideoService.prototype.decorateForMedia =
function searchvideoService_decorateForMedia(aDocument, aAccount) {
  this._logger.debug(".decorateForMedia(aDocument)");
  // Search Video doesn't support lighting up the media icon
}
// END flockIMediaWebService

// ========== END searchvideoService class ==========

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

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

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