// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2008
// 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 ENABLE_DEBUG = true;
function DEBUG(x) { if (ENABLE_DEBUG) debug("flockSearchvideoService: "+x+"\n"); }

const SEARCHVIDEO_TITLE                  = "Truveo Web Service";
const SEARCHVIDEO_FAVICON                = "chrome://flock/content/services/searchvideo/favicon.png";
const SEARCHVIDEO_CID                    = Components.ID("{31def21e-02d6-4d9a-848c-2c1895e50626}");
const SEARCHVIDEO_CONTRACTID             = "@flock.com/?photo-api-searchvideo;1";
const APPID                              = "1x1jhj64466mi12ia";
const CATEGORY_ENTRY_NAME                = "searchvideo";
const SERVICE_ENABLED_PREF               = "flock.service.searchvideo.enabled";

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

var gCompTK;
var alreadySearched = false;
function getCompTK() {
  if (!gCompTK) {
    gCompTK = Cc["@flock.com/singleton;1"].getService(Ci.flockISingleton)
                .getSingleton("chrome://browser/content/flock/services/common/load-compTK.js").wrappedJSObject;
  }
  return gCompTK;
}


function loadLibraryFromSpec(aSpec)
{
  var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
               .getService(Ci.mozIJSSubScriptLoader);
  loader.loadSubScript(aSpec);
}
loadLibraryFromSpec("chrome://browser/content/flock/photo/photoAPI.js");

function searchvideoPhoto() {}

searchvideoPhoto.prototype = {
  id: "",
  thumbnail: "",
  webPageUrl: "",
  midSizePhoto: "",
  largeSizePhoto: "",
  title: "",
  username: "",
  userid: "",
  embedTag: "",
  is_public: "true",
  is_video: "true",
  svcShortName: 'searchvideo',
  has_miniView: "true",
  buildTooltip: function( ) {
    // do we have to use document from the window to ceate elements? -- ja
    var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
    var win = wm.getMostRecentWindow('navigator:browser');
    if (!win) return null;

    var hbox = win.document.createElement('hbox');
    var img = win.document.createElement('image');
    img.setAttribute('src', this.icon );
    hbox.appendChild(img);
    var box = win.document.createElement('vbox');
    box.setAttribute('style', 'max-width: 300px');
    var title = win.document.createElement('label');
    title.setAttribute('value', this.title );
    title.setAttribute('crop', 'end');
    box.appendChild(title);
    var lbl = win.document.createElement('label');
    lbl.setAttribute('value', this.username );
    lbl.setAttribute('class', 'user');
    box.appendChild(lbl);
    hbox.appendChild(box)

    var vbox = win.document.createElement('vbox');
    var cbox = win.document.createElement('cbox');
    var largeImg = win.document.createElement('image');
    largeImg.setAttribute('src', this.midSizePhoto);
    largeImg.setAttribute('style', 'margin-bottom: 2px;');
    var spacer = win.document.createElement('spacer');
    spacer.setAttribute('flex', '1');
    cbox.appendChild(largeImg);
    cbox.appendChild(spacer);
    vbox.appendChild(cbox);
    vbox.appendChild(hbox);

    return vbox;
  },
  buildHTML: function ( ) {
    const EMBED_TAG = "</embed>";
    var embedTagCopy = this.embedTag;

    // add a 'value' to the EMBED tag
    // since this.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
    if (embedTagCopy.lastIndexOf(EMBED_TAG) == (embedTagCopy.length - EMBED_TAG.length)) {
      // case 1: <embed></embed>
      embedTagCopy = embedTagCopy.slice(0, embedTagCopy.length - EMBED_TAG.length);
      embedTagCopy += this.webPageUrl;
      embedTagCopy += EMBED_TAG;
    } else if (embedTagCopy.lastIndexOf("/>") == (embedTagCopy.length - 2)) {
      // case 2: <embed/>
      embedTagCopy = embedTagCopy.slice(0, embedTagCopy.length - 2);
      embedTagCopy += ">";
      embedTagCopy += this.webPageUrl;
      embedTagCopy += EMBED_TAG;
    } else if (embedTagCopy.lastIndexOf(">") == (embedTagCopy.length - 1)) {
      // case 3: <embed>
      embedTagCopy += this.webPageUrl;
      embedTagCopy += EMBED_TAG;
    }
    return embedTagCopy;
  },
  buildBBCode: function ( ) {
    return '[url=' + this.webPageUrl + '][img]'+ this.midSizePhoto +'[/img][/url]';
  },
  buildMiniPage: function ( ) {
    return this.webPageUrl;
  },
  QueryInterface: function(iid) {
    if (!iid.equals(Ci.nsISupports) &&
        !iid.equals(Ci.flockIPhoto)) {
      throw Cr.NS_ERROR_NO_INTERFACE;
    }
    return this;
  }
};


searchvideoPhoto.prototype.__defineGetter__('metaData', function () {
  var metaData = Components.classes["@mozilla.org/hash-property-bag;1"].createInstance(Components.interfaces.nsIWritablePropertyBag2);
  metaData.setPropertyAsAString("title", this.title);  
  metaData.QueryInterface(Components.interfaces.nsIPropertyBag);
  
  // expose other metadata props here, then you can display them with custom xbls
  // which extend from the generic photo binding
  
  return metaData;
})
// ====================================================
// ========== BEGIN searchvideoService class ==========
// ====================================================

function searchvideoService() {
  this.url = "http://www.searchvideo.com";
  this._ctk = {
    interfaces: [
      "nsISupports",
      "nsIClassInfo",
      "nsISupportsCString",
      "nsIObserver",
      "flockIWebService",
      "flockIMediaWebService",
    ],
    shortName: "searchvideo",
    fullName: "Truveo",
    description: SEARCHVIDEO_TITLE,
    favicon: SEARCHVIDEO_FAVICON,
    CID: SEARCHVIDEO_CID,
    contractID: SEARCHVIDEO_CONTRACTID
  };

  var sbs = Cc["@mozilla.org/intl/stringbundle;1"]
            .getService(Ci.nsIStringBundleService);
  var bundle = sbs.createBundle(SEARCHVIDEO_STRING_BUNDLE);

  this._channels = {
    "special:all": {
      title: bundle.GetStringFromName("flock.searchvideo.title.all"),
      supportsSearch: true
    },
    "special:topfav": {
      title: bundle.GetStringFromName("flock.searchvideo.title.topfav"),
      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
    }
  };

  this.webDetective = Cc["@flock.com/account-utils;1"].getService(Ci.flockIAccountUtils).useWebDetective("searchvideo.xml");
  this.faves_coop = Cc["@flock.com/singleton;1"]
                    .getService(Ci.flockISingleton)
                    .getSingleton("chrome://flock/content/common/load-faves-coop.js")
                    .wrappedJSObject;
  this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  this._logger.init('searchvideo');

  this.prefService = Components.classes["@mozilla.org/preferences-service;1"]
                               .getService(Components.interfaces.nsIPrefBranch);
  if (this.prefService.getPrefType(SERVICE_ENABLED_PREF) &&
      !this.prefService.getBoolPref(SERVICE_ENABLED_PREF))
  {
    this._logger.debug("Pref "+SERVICE_ENABLED_PREF+" set to FALSE... not initializing.");
    var catMgr = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
    catMgr.deleteCategoryEntry("flockMediaProvider", CATEGORY_ENTRY_NAME, true);
    return; 
  }
}

searchvideoService.prototype.serviceName = "Truveo";
searchvideoService.prototype.shortName = "searchvideo";
searchvideoService.prototype.iconUrl = SEARCHVIDEO_FAVICON;
searchvideoService.prototype.count = 1;


searchvideoService.prototype.getPhotoFromRDFNode = 
function (aRDFId)
{
  var newPhoto = new searchvideoPhoto();
  var coopPhoto = this.faves_coop.get(aRDFId);
  newPhoto.webPageUrl = coopPhoto.URL;
  newPhoto.thumbnail = coopPhoto.thumbnail;
  newPhoto.midSizePhoto = coopPhoto.midSizePhoto;
  newPhoto.largeSizePhoto = coopPhoto.largeSizePhoto;
  newPhoto.username = coopPhoto.username;
  newPhoto.userid = coopPhoto.userid;
  newPhoto.title = coopPhoto.name;
  newPhoto.id = coopPhoto.photoid;
  newPhoto.icon = coopPhoto.favicon;
  newPhoto.uploadDate = coopPhoto.datevalue;
  newPhoto.is_public = coopPhoto.is_public;
  newPhoto.is_video = coopPhoto.is_video;
  return newPhoto;
}

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 newPhoto = new searchvideoPhoto();
    newPhoto.id = video.getElementsByTagName("id")[0].textContent;
    newPhoto.title = video.getElementsByTagName("title")[0].textContent;
    newPhoto.webPageUrl = video.getElementsByTagName("videoUrl")[0].textContent;
    newPhoto.thumbnail = video.getElementsByTagName("thumbnailUrl")[0].textContent;
    var largeThumbnail = video.getElementsByTagName("thumbnailUrlLarge");
    if (largeThumbnail.length > 0) {
      newPhoto.midSizePhoto = largeThumbnail[0].textContent;
      newPhoto.largeSizePhoto = largeThumbnail[0].textContent;
    }
    else {
      newPhoto.midSizePhoto = newPhoto.thumbnail;
      newPhoto.largeSizePhoto = newPhoto.thumbnail;
    }
    newPhoto.username = video.getElementsByTagName("channel")[0].textContent;
    newPhoto.userid = video.getElementsByTagName("channelUrl")[0].textContent;
    var dateProduced = video.getElementsByTagName("dateProduced");
    if (dateProduced.length > 0)
      newPhoto.uploadDate = dateProduced[0].textContent;
    else
      newPhoto.uploadDate = video.getElementsByTagName("dateFound")[0].textContent;
    newPhoto.is_public = true;
    newPhoto.is_video = true;
    newPhoto.embedTag = video.getElementsByTagName("videoResultEmbedTag")[0].textContent;
    rval.push(newPhoto);
  }
  return rval;
}

searchvideoService.prototype.queryChannel =
function searchvideoService_queryChannel(aListener, aQueryString, aCount, aPage) {
  var aQuery = new queryHelper(aQueryString);
  var inst=this;
  var myListener = {
    onResult: function (aXML) {
      var rval = inst.handlePhotosResult(aXML);
      var enum_ = {
        hasMoreElements: function() {
          return (rval.length>0);
        },
        getNext: function() {
          return rval.shift();
        }
      }
      aListener.onSearchResult(enum_);
    },
    onError: function (aError) {
      aListener.onError(aError);
    }
  }

  var params = {
    results: (aCount ? aCount : 50),
    start: (aPage ? aPage : 1)
  };

  switch (aQuery.special) {
    case "recent": params.query = "sort:mostRecent "; break;
    case "topfav": params.query = "sort:topFavorites "; break;
    case "pop": params.query = "sort:mostPopular "; 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;
    default: params.query = "sort:vrank ";
  }

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

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

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

searchvideoService.prototype.search =
function searchvideoService_search(aListener, aQueryString, aCount, aPage) {
  
  //this.count = this.count+aCount;
  // Searchvideo only supports channel queries for now
  if(aPage == 1) this.count = 1;
  this.queryChannel(aListener, aQueryString, aCount, (aPage ? this.count : 1));
  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) {
      if (inst._req.status/100 == 2) {
        var dom = inst._req.responseXML;
        var domResponse = dom.getElementsByTagName("Response")[0];
        if (domResponse.firstChild.nodeName == "Error") {
          var error = inst.getError('SERVICE_ERROR', dom, null);
          aListener.onError(error);
        }
        else
          aListener.onResult(domResponse);
      }
      else if (inst._req.status/100 == 4) {
        var dom = inst._req.responseXML;
        var domResponse = dom.getElementsByTagName("Response")[0];
        if (domResponse && (domResponse.firstChild.nodeName == "Error")) {
          var error = inst.getError('SERVICE_ERROR', dom, null);
          aListener.onError(error);
        }
        else {
          // http errors
          aListener.onError(inst.getError("HTTP_ERROR", null, inst._req.status));
        }
      }
      else {
        // http errors
        aListener.onError(inst.getError("HTTP_ERROR", null, inst._req.status));
      }
    }
  };
  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));
  }
  dump("JMC: searchvideo querystring is " + body + "\n");
  this._req.open("GET", 'http://api.searchvideo.com/apiv3?'+body, true);
  this._req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded", false);
  rval = this._req.send(null); 
}

// BEGIN flockIMediaWebService interface
searchvideoService.prototype.decorateForMedia =
function searchvideoService_decorateForMedia(aDocument) {
  DEBUG("{flockIMediaWebService}.decorateForMedia(aDocument)");
  var results = Cc["@mozilla.org/hash-property-bag;1"]
                          .createInstance(Ci.nsIWritablePropertyBag2);
  if (this.webDetective.detect("searchvideo", "person", aDocument, results)) {
    var mediaArr = new Array();
    var media = {
      name: results.getPropertyAsAString("username"),
      query: "user:" + results.getPropertyAsAString("userid"),
      label: results.getPropertyAsAString("username"),
      favicon: this.icon,
      service: this.shortName
    }
    mediaArr.push(media);
    if (!aDocument._flock_decorations) {
      aDocument._flock_decorations = new Object();
    }
    aDocument._flock_decorations.mediaArr = mediaArr;
    this.obs.notifyObservers(aDocument, "media", "media:update");
  }
}
// END flockIMediaWebService

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


// ================================================
// ========== BEGIN XPCOM Module support ==========
// ================================================

function createModule(aParams) {
  var Cc = Components.classes;
  var Ci = Components.interfaces;
  var Cr = Components.results;
  return {
    registerSelf: function (aCompMgr, aFileSpec, aLocation, aType) {
      aCompMgr.QueryInterface(Ci.nsIComponentRegistrar);
      aCompMgr.registerFactoryLocation( aParams.CID, aParams.componentName,
                                        aParams.contractID, aFileSpec,
                                        aLocation, aType );
      var catMgr = Cc["@mozilla.org/categorymanager;1"]
                     .getService(Ci.nsICategoryManager);
      catMgr.addCategoryEntry( "flock-startup", aParams.componentName,
                               "service,"+aParams.contractID, true, true ); 
      if (!aParams.categories) { aParams.categories = []; }
      for (var i = 0; i < aParams.categories.length; i++) {
        var cat = aParams.categories[i];
        catMgr.addCategoryEntry( cat.category, cat.entry,
                                 cat.value, true, true ); 
      }
    },
    getClassObject: function (aCompMgr, aCID, aIID) {
      if (!aCID.equals(aParams.CID)) { throw Cr.NS_ERROR_NO_INTERFACE; }
      if (!aIID.equals(Ci.nsIFactory)) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; }
      return { // Factory
        createInstance: function (aOuter, aIID) {
          if (aOuter != null) { throw Cr.NS_ERROR_NO_AGGREGATION; }
          var comp = new aParams.componentClass();
          if (aParams.implementationFunc) { aParams.implementationFunc(comp); }
          return comp.QueryInterface(aIID);
        }
      };
    },
    canUnload: function (aCompMgr) { return true; }
  };
}

// NS Module entrypoint
function NSGetModule(aCompMgr, aFileSpec) {
  return createModule({
    componentClass: searchvideoService,
    CID: SEARCHVIDEO_CID,
    contractID: SEARCHVIDEO_CONTRACTID,
    componentName: "Searchvideo JS Component",
    implementationFunc: function (aComp) { getCompTK().addAllInterfaces(aComp); },
    categories: [ // "flock-startup" is automagically added
      { category: "flockMediaProvider", entry: CATEGORY_ENTRY_NAME, value: SEARCHVIDEO_CONTRACTID }
    ]
  });
}

// ========== END XPCOM Module support ==========
