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

CU.import("resource:///modules/FlockXPCOMUtils.jsm");
FlockXPCOMUtils.debug = false;
CU.import("resource:///modules/FlockPrefsUtils.jsm");
CU.import("resource:///modules/FlockSvcUtils.jsm");
CU.import("resource:///modules/FlockXMLUtils.jsm");
CU.import("resource://gre/modules/JSON.jsm");
CU.import("resource:///modules/FlockCryptoHash.jsm");
CU.import("resource:///modules/FlockPhotoUploader.jsm");
CU.import("resource:///modules/FlockMediaUtils.jsm");
CU.import("resource:///modules/FlockStringBundleHelpers.jsm");

const MODULE_NAME = "TinyPic";
const API_CLASS_NAME = "Flock TinyPic API";
const API_CLASS_ID = Components.ID("{30a7dff8-e464-4813-979c-c2f9df7a1166}");
const API_CONTRACT_ID = "@flock.com/webservice/api/tinypic;1";
const CLASS_NAME = "Flock TinyPic Service";
const CLASS_SHORT_NAME = "tinypic";
const CLASS_TITLE = "TinyPic";
const CLASS_ID = Components.ID("{83ee28c3-146a-41fa-bb59-04103003cb05}");
const CONTRACT_ID = "@flock.com/webservice/tinypic;1";
const FLOCK_ERROR_CONTRACTID = "@flock.com/error;1";
const XMLHTTPREQUEST_CONTRACTID = "@mozilla.org/xmlextras/xmlhttprequest;1";
const HASH_PROPERTY_BAG_CONTRACTID = "@mozilla.org/hash-property-bag;1";
// From nsIXMLHttpRequest.idl
const XMLHTTPREQUEST_READYSTATE_COMPLETED = 4;
const NOTIFY_COOKIES_RESTORED = "sessionstore-cookies-restored";

const SERVICE_ENABLED_PREF = "flock.service.tinypic.enabled";

const TINYPIC_URL = "http://www.tinypic.com/";
const FAVICON = "chrome://flock/content/services/tinypic/favicon.png";

const FLOCK_PHOTO_ALBUM_CONTRACTID  = "@flock.com/photo-album;1";

const MAX_MEDIA_RESULTS = 50;
const MAX_ALBUM_RESULTS = 100;
const UPLOAD_MAX_FILE_SIZE = 5 * 1024 * 1024;
const UPLOAD_MAX_SPACE = 100 * 5 * 1024 * 1024;

var gApi = null;
var gTinyPicId = "";
var gTinyPicKey = "";
var gTinyPicApiUrl = "";

function _getMediaItem(aItem) {
  var media = CC["@flock.com/photo;1"].createInstance(CI.flockIMediaItem);
  media.init(CLASS_SHORT_NAME, flockMediaItemFormatter);
  media.thumbnail = aItem.thumbnail;
  media.midSizePhoto = aItem.thumbnail;
  media.largeSizePhoto = aItem.fullsize;
  media.id = (aItem.mediakey).toString();
  media.is_public = true;
  media.is_video = (aItem.name() == "video") ? true : false;
  media.has_miniView = media.is_video;
  media.webPageUrl = aItem.emailimcode;
  media.embedTag = aItem.htmlcode;
  if (media.is_video) {
    // Tinypic returns without the </embed> so we need to add it
    media.embedTag += "</embed>";
  }
  return media;
}

function _createEnum(aArray) {
  return {
    QueryInterface: function QueryInterface(aIid) {
      if (aIid.equals(CI.nsISimpleEnumerator)) {
        return this;
      }
      throw CR.NS_ERROR_NO_INTERFACE;
    },
    hasMoreElements: function hasMoreElements() {
      return (aArray.length > 0);
    },
    getNext: function getNext() {
      return aArray.shift();
    }
  };
}

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

  buildHTML: function fmif_buildHTML(aMediaItem) {
    if (aMediaItem.is_video) {
      return aMediaItem.embedTag;
    } else {
      return '<a href="' + aMediaItem.webPageUrl + '">'
             + '<img src="' + aMediaItem.thumbnail + '" border="0" />'
             + '</a>';
    }
  },

  buildLargeHTML: function fmif_buildLargeHTML(aMediaItem) {
    return '<a href="' + aMediaItem.webPageUrl + '">'
           + '<img src="' + aMediaItem.largeSizePhoto + '" border="0" />'
           + '</a>';

  },

  buildMiniPage: function fmif_buildMiniPage(aMediaItem) {
    return '<html><body onLoad="window.sizeToContent();"><center>'
            + aMediaItem.embedTag
            + '</center></body></html>';
  }
};

/*************************************************************************
 * Component: TinyPicAPI
 *************************************************************************/
function TinyPicAPI() {
  FlockSvcUtils.getLogger(this);
  this._logger.init("TinyPicAPI");

  this._WebDetective = CC["@flock.com/web-detective;1"]
                       .getService(CI.flockIWebDetective);

  // Convenience variable.
  this._wd = this._WebDetective;

  var tpkHash = this._wd.getString(CLASS_SHORT_NAME, "tpkHash", "");
  gTinyPicKey = FlockSvcUtils.scrambleString(tpkHash);
  gTinyPicId = this._wd.getString(CLASS_SHORT_NAME, "tpid", "");
  gTinyPicApiUrl = this._wd.getString(CLASS_SHORT_NAME, "apiUrl", "");

  this._logger.debug("constructor");
  var wsa = FlockSvcUtils.flockIWebServiceAPI;
  wsa.addDefaultMethod(this, "getRequestMethod");

  // Used for media search results
  this._moreVideos = true;
}

TinyPicAPI.prototype = new FlockXPCOMUtils.genericComponent(
  API_CLASS_NAME,
  API_CLASS_ID,
  API_CONTRACT_ID,
  TinyPicAPI,
  0,
  [
    CI.flockIAuthenticatedAPI,
    CI.flockITokenAPI,
    CI.flockIWebServiceAPI
  ]
);

/*************************************************************************
 * TinyPicAPI: flockIWebServiceAPI Implementation
 *************************************************************************/

/**
 * void call(in AString aApiMethod,
 *           in nsISupports aParams,
 *           in nsISupports aPostVars,
 *           in nsISupports aRequestMethod,
 *           in flockIListener aFlockListener);
 * @see flockIWebServiceAPI#call
 */
TinyPicAPI.prototype.call =
function TinyPicAPI_call(aApiMethod,
                         aParams,
                         aPostVars,
                         aRequestMethod,
                         aFlockListener)
{
  this._logger.debug("call('" + aApiMethod + "', ...)");

  if (!aParams) {
    aParams = {};
  }

  // _getParamString is duplicated function from flockGoogleAuth, we should
  // consider moving function into flockSvcUtils and calling it from there
  var params = "&" + this._getParamString(aParams);
  var url = gTinyPicApiUrl
          + "?action=" + aApiMethod
          + "&responsetype=xml"
          + "&tpid=" + gTinyPicId
          + "&sig=" + FlockCryptoHash.md5(aApiMethod + gTinyPicId + gTinyPicKey)
          + params;

  this._doUrl(url, aFlockListener, false);
};

TinyPicAPI.prototype._doUrl =
function TinyPicAPI__doUrl(aUrl, aFlockListener, aIsRSS) {
  this._logger.debug("_doUrl() - " + aUrl);
  var api = this;

  this.req = CC[XMLHTTPREQUEST_CONTRACTID]
             .createInstance(CI.nsIXMLHttpRequest);
  this.req.open("GET", aUrl, true);
  this.req.setRequestHeader("Content-Type",
                            "application/x-www-form-urlencoded");
  this.req.onreadystatechange = function login_onready(aEvt) {
    if (api.req.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
      var serializer = CC["@mozilla.org/xmlextras/xmlserializer;1"]
                       .createInstance(CI.nsIDOMSerializer);
      var xmlStr =
        serializer.serializeToString(api.req.responseXML.documentElement);
      var xmlData = new XML(xmlStr);
      if (aIsRSS || xmlData.status == "OK") {
        aFlockListener.onSuccess(xmlData, arguments.callee.name);
      } else {
        aFlockListener.onError(api.getError(xmlData.errorcode), "call");
      }
    }
  };
  api.req.send(null);
};

/**
 * flockIError getError(in AString aErrorCode);
 * @see flockIWebServiceAPI#getError
 */
TinyPicAPI.prototype.getError =
function TinyPicAPI_getError(aErrorCode) {
  this._logger.debug(".getError('" + aErrorCode + "')");

  var error = CC[FLOCK_ERROR_CONTRACTID].createInstance(CI.flockIError);
  error.serviceErrorCode = aErrorCode;

  switch (parseInt(aErrorCode)) {
    case 8:
      error.serviceName = CLASS_TITLE;
      error.errorCode = CI.flockIError.PHOTOSERVICE_USER_NOT_LOGGED_IN;
      break;
    default:
      error.errorCode = CI.flockIError.PHOTOSERVICE_UNKNOWN_ERROR;
      break;
  }

  return error;
};

/**
 * flockIError getHttpError(in AString aHttpErrorCode);
 * @see flockIWebServiceAPI#getHttpError
 */
TinyPicAPI.prototype.getHttpError =
function TinyPicAPI_getHttpError(aHttpErrorCode) {
  this._logger.debug(".getHttpError('" + aHttpErrorCode + "')");

  var error = CC[FLOCK_ERROR_CONTRACTID].createInstance(CI.flockIError);
  error.errorCode = aHttpErrorCode;
  error.serviceErrorCode = aHttpErrorCode;
  error.serviceName = CLASS_TITLE;

  return error;
};


/*************************************************************************
 * TinyPicAPI: flockIAuthenticatedAPI Implementation
 *************************************************************************/

/**
 * void authenticate(in nsILoginInfo aCredentials,
 *                   in flockIListener aFlockListener);
 * @see flockIAuthenticatedAPI#authenticate
 */
TinyPicAPI.prototype.authenticate =
function TinyPicAPI_authenticate(aCredentials, aFlockListener) {
  this._logger.debug(".authenticate("
                     + ((aCredentials) ? aCredentials.username : "")
                     + ", ...)");

  var inst = this;
  var tokenListener = {
    onSuccess: function token_onSuccess(aXmlData, aTopic) {
      inst._logger.debug(arguments.callee.name);
      inst.token = aXmlData.result.shuk;
      inst._getUploadKey();
      aFlockListener.onSuccess(inst, "authenticated");
    },
    onError: function token_onError(aFlockError, aTopic) {
      inst._logger.debug(arguments.callee.name);
      aFlockListener.onError(aFlockError, arguments.callee.name);
    }
  };

  var params = {
    email: aCredentials.username,
    pass: aCredentials.password
  };

  this.call("userauth", params, null, null, tokenListener);
};

/**
 * void deauthenticate();
 * @see flockIAuthenticatedAPI#deauthenticate
 */
TinyPicAPI.prototype.deauthenticate =
function TinyPicAPI_deauthenticate() {
  this._logger.debug(".deauthenticate()");
  this.token = "";
};


/*************************************************************************
 * TinyPicAPI: flockITokenAPI Implementation
 *************************************************************************/

// readonly attribute AString token;
TinyPicAPI.prototype.token = "";

// read only attribute PRUint32
TinyPicAPI.prototype.expiresOn = "";


/*************************************************************************
 * TinyPicAPI: Private Data and Functions
 *************************************************************************/

TinyPicAPI.prototype._isApiLoggedIn =
function TinyPicAPI__isApiLoggedIn() {
  return this.token.length != 0;
};

TinyPicAPI.prototype._getParamString =
function TinyPicAPI__getParamString(aParams) {
  var rval = "";

  var count = 0;
  for (var p in aParams) {
    if (count++ != 0) {
      rval += "&";
    }
    rval += encodeURIComponent(p) + "=" + encodeURIComponent(aParams[p]);
  }

  return rval;
};

TinyPicAPI.prototype._doMediaSearch =
function TinyPicAPI__doMediaSearch(aQuery,
                                  aMaxResults,
                                  aMediaType,
                                  aOffset,
                                  aResultsListener)
{
  var inst = this;
  var videoArray = [];
  var imageArray = [];
  var params = {
    term: aQuery,
    offset: aOffset,
    numresults: aMaxResults
  };

  // If offset is 0, then we have a new search - look for videos
  if (aOffset == 0) {
    this._moreVideos = true;
  }

  function processMediaResults(aXmlData) {
    var outArray = [];
    if (parseInt(aXmlData.matches) > 0) {
      var items = aXmlData.results.*;
      for each (var item in items) {
        var media = _getMediaItem(item);
        outArray.push(media);
      }
    }

    return outArray;
  }

  var videoSearchListener = {
    onSuccess: function vsl_onSuccess(aSubject, aTopic) {
      inst._logger.debug("videoSearch success");
      videoArray = processMediaResults(aSubject);

      // Form mixed stream by alternating between image and video
      var finalArray = [];
      for (var i = 0; i < aMaxResults; i++) {
        if (imageArray[i]) {
          finalArray.push(imageArray[i]);
        }
        if (videoArray[i]) {
          finalArray.push(videoArray[i]);
        } else {
          // Mark that there are no more videos to retrieve
          inst._moreVideos = false;
        }
      }

      aResultsListener.onResult(finalArray);
    },
    onError: function vsl_onError(aFlockError, aTopic) {
      inst._logger.error("videoSearch failed");
      aResultsListener.onError(aFlockError, arguments.callee.name);
    }
  };

  var imageSearchListener = {
    onSuccess: function isl_onSuccess(aSubject, aTopic) {
      inst._logger.debug("imageSearch success");
      imageArray = processMediaResults(aSubject);

      // If there are more videos to retrieve, then make the API call.
      // Otherwise just return images.
      if (inst._moreVideos) {
        params.type = "video";
        inst.call("search", params, null, null, videoSearchListener);
      } else {
        aResultsListener.onResult(imageArray);
      }
    },
    onError: function isl_onError(aFlockError, aTopic) {
      inst._logger.error("imageSearch failed");
      aResultsListener.onError(aFlockError, arguments.callee.name);
    }
  };

  params.type = "image";
  this.call("search", params, null, null, imageSearchListener);
};

TinyPicAPI.prototype._loadUserMedia =
function TinyPicAPI__loadUserMedia(aParams, aResultsListener) {
  var inst = this;
  function processMediaResults(aXmlData) {
    var outArray = [];
    var items = aXmlData.results..media;
    for each (var media in items) {
      var mediaItem = media.*;
      if (mediaItem.length() == 0) {
        return outArray;
      }
      var arrayElem = _getMediaItem(mediaItem);
      outArray.push(arrayElem);
    }

    return outArray;
  }
  var mediaSearchListener = {
    onSuccess: function msl_onSuccess(aSubject, aTopic) {
      var results;
      inst._logger.debug("getResults success");
      results = processMediaResults(aSubject);
      aResultsListener.onResult(results);
    },
    onError: function msl_onError(aFlockError, aTopic) {
      inst._logger.error("getResults failed");
      aResultsListener.onError(aFlockError, "loadUserMedia");
    }
  };

  var params = aParams;
  params.shuk = this.token;

  var action;
  if (params.albumid) {
    action = "getuseralbum";
    this.call("getuseralbum", params, null, null, mediaSearchListener);
  } else {
    action = "getuserstuff";
  }
  this.call(action, params, null, null, mediaSearchListener);
};

TinyPicAPI.prototype._loadUserFavorites =
function TinyPicAPI__loadUserFavorites(aParams, aResultsListener) {
  var inst = this;
  function processMediaResults(aXmlData) {
    var outArray = [];
    var items = aXmlData.results..media;
    for each (var media in items) {
      var mediaItem = media.*;
      if (mediaItem.length() == 0) {
        return outArray;
      }
      var arrayElem = _getMediaItem(mediaItem);
      outArray.push(arrayElem);
    }

    return outArray;
  }

  var favoritesSearchListener = {
    onSuccess: function fsl_onSuccess(aSubject, aTopic) {
      var results;
      inst._logger.debug(arguments.callee.name);
      results = processMediaResults(aSubject);
      aResultsListener.onResult(results);
    },
    onError: function fsl_onError(aFlockError, aTopic) {
      inst._logger.error(arguments.callee.name);
      aResultsListener.onError(aFlockError, "loadUserFavorites");
    }
  };

  var params = aParams;
  params.shuk = this.token;

  this.call("getuserfavorites", params, null, null, favoritesSearchListener);
};

TinyPicAPI.prototype._doRSSMedia =
function TinyPicAPI__doRSSMedia(aUrl, aPage, aResultsListener) {
  this._logger.debug(arguments.callee.name);

  if (aPage > 1) {
    aResultsListener.onResult([]);
    return;
  }

  var inst = this;

  function processRSSMedia(aXmlData) {
    var outArray = [];
    var items = aXmlData.channel.item;
    for each (var item in items) {
      var media = CC["@flock.com/photo;1"].createInstance(CI.flockIMediaItem);
      media.init(CLASS_SHORT_NAME, flockMediaItemFormatter);
      var mediaNS = new Namespace("http://search.yahoo.com/mrss/");
      media.thumbnail = item.mediaNS::content.mediaNS::thumbnail.@url;
      media.midSizePhoto = item.mediaNS::content.mediaNS::thumbnail.@url;
      media.largeSizePhoto = item.mediaNS::content.@url;
      media.is_public = true;
      media.is_video = (item.mediaNS::content.@medium.toString() == "video")
                     ? true : false;
      media.has_miniView = media.is_video;
      media.webPageUrl = item.link;

      var tmp = media.webPageUrl.split("?")[1].split("&");
      var file = tmp[0].split("=")[1];
      var s = tmp[1].split("=")[1];
      var player = item.mediaNS::content.mediaNS::player.@url;
      media.id = file + "-" + s;

      media.embedTag = '<embed width="440" height="420" '
                     + 'type="application/x-shockwave-flash" '
                     + 'src="' + player + '?file=' + file + '&s=' + s + '"><br>'
                     + '<font size="1">'
                     + '<a href="' + media.webPageUrl + '">Original Video</a>'
                     + ' - More videos at <a href="http://tinypic.com">TinyPic'
                     + '</a></font></embed>';

      outArray.push(media);
    }

    return outArray;
  }

  var rssMediaListener = {
    onSuccess: function rml_onSuccess(aSubject, aTopic) {
      aResultsListener.onResult(processRSSMedia(aSubject));
    },
    onError: function rml_onError(aFlockError, aTopic) {
      inst._logger.error(arguments.callee.name);
      aResultsListener.onError(aFlockError, "doRSSMedia");
    }
  };

  this._doUrl(aUrl, rssMediaListener, true);
};

TinyPicAPI.prototype._getAlbums =
function TinyPicAPI__getAlbums(aNumResults, aFlockListener) {
  this._logger.debug(arguments.callee.name);

  var inst = this;
  var rval = [];
  var totalAlbums;

  function handleAlbum(aXmlData) {
    var albums = [];
    if (parseInt(aXmlData.matches) > 0) {
      var items = aXmlData.results.album;
      for each (var album in items) {
        var newAlbum = CC[FLOCK_PHOTO_ALBUM_CONTRACTID]
                       .createInstance(CI.flockIPhotoAlbum);
        newAlbum.id = album.albumid;
        newAlbum.title = album.name;
        albums.push(newAlbum);
      }
    }
    return albums;
  }

  var getAlbumsListener = {
    onSuccess: function ga_onSuccess(aXML, aTopic) {
      inst._logger.debug(arguments.callee.name);
      var page = aXML.page;
      totalAlbums = aXML.numresults;
      var numPages = Math.ceil(totalAlbums / MAX_ALBUM_RESULTS);

      rval = rval.concat(handleAlbum(aXML));
      if (rval.length >= totalAlbums) {
        aFlockListener.onSuccess(_createEnum(rval), "getAlbums");
      } else {
        params.page = parseInt(page) + 1;
        inst.call("getuseralbumlist", params, null, null, getAlbumsListener);
      }
    },
    onError: function ga_onError(aFlockError, aTopic) {
      inst._logger.debug(arguments.callee.name);
      aFlockListener.onError(aFlockError, "getAlbums");
    }
  };

  var params = {
    shuk: this.token,
    numresults: MAX_ALBUM_RESULTS,
    page: 1
  };
  this.call("getuseralbumlist", params, null, null, getAlbumsListener);
};

TinyPicAPI.prototype._getUploadKey =
function TinyPicAPI__getUploadKey() {
  this._logger.debug(arguments.callee.name);

  var inst = this;
  var uploadKeyListener = {
    onSuccess: function uploadKey_onSuccess(aSubject, aTopic) {
      inst._logger.debug(arguments.callee.name);
      gApi._uploadKey = aSubject.result.uploadkey;
    },
    onError: function uploadKey_onError(aFlockError, aTopic) {
      inst._logger.debug(arguments.callee.name);
    }
  };

  this.call("getuploadkey", null, null, null, uploadKeyListener);
};


/*************************************************************************
 * Component: TinyPicService
 *************************************************************************/
function TinyPicService() {
  FlockSvcUtils.getLogger(this);
  this._logger.init("TinyPicService");

  // Determine whether this service has been disabled, and unregister if so.
  if (FlockPrefsUtils.getBoolPref(SERVICE_ENABLED_PREF) === false) {
    this._logger.info(SERVICE_ENABLED_PREF + " is FALSE! Not initializing.");
    this.deleteCategories();
    return;
  }

  var profiler = CC["@flock.com/profiler;1"].getService(CI.flockIProfiler);
  var evtID = profiler.profileEventStart(CLASS_SHORT_NAME + "-init");

  this._obs = CC["@mozilla.org/observer-service;1"]
              .getService(CI.nsIObserverService);
  this._obs.addObserver(this, "xpcom-shutdown", false);
  this._obs.addObserver(this, NOTIFY_COOKIES_RESTORED, false);

  this._accountClassCtor = TinyPicAccount;

  // Convenience variable.
  this._wd = FlockSvcUtils.getWD(this);
  FlockSvcUtils.initService(this, true);

  // Initialize API
  gApi = new TinyPicAPI();

  profiler.profileEventEnd(evtID, "");

  var ws = FlockSvcUtils.flockIWebService;
  ws.addDefaultMethod(this, "getStringBundle");
  ws.addDefaultMethod(this, "isHidden");

  FlockSvcUtils.flockIAuthenticateNewAccount
               .addDefaultMethod(this, "authenticateNewAccount");

  var lws = FlockSvcUtils.flockILoginWebService;
  lws.addDefaultMethod(this, "loginURL");
  lws.addDefaultMethod(this, "tooltip");
  lws.addDefaultMethod(this, "docRepresentsSuccessfulLogin");
  lws.addDefaultMethod(this, "getAccount");
  lws.addDefaultMethod(this, "getAccountUrns");
  lws.addDefaultMethod(this, "getAuthenticatedAccount");
  lws.addDefaultMethod(this, "getCredentialsFromForm");
  lws.addDefaultMethod(this, "getSessionValue");
  lws.addDefaultMethod(this, "ownsDocument");
  lws.addDefaultMethod(this, "ownsLoginForm");
  lws.addDefaultMethod(this, "removeAccount");

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

  var muws = FlockSvcUtils.flockIMediaUploadWebService;
  muws.addDefaultMethod(this, "getAlbumsForUpload");

  var mews = FlockSvcUtils.flockIMediaEmbedWebService;
  mews.addDefaultMethod(this, "getSharingContent");

  var rcdh = FlockSvcUtils.flockIRichContentDropHandler;
  rcdh.addDefaultMethod(this, "_handleTextareaDrop");

  var bundle = this.getStringBundle();
  var photosUrl = this._wd.getString(CLASS_SHORT_NAME, "featuredPhotosUrl", "");
  var videosUrl = this._wd.getString(CLASS_SHORT_NAME, "featuredVideosUrl", "");
  this._channels = {
    "special:Featured_Photo": {
      query: "special:" + encodeURIComponent(photosUrl),
      title: bundle.GetStringFromName("flock.tinypic.title.featured.photos"),
      supportsSearch: false
    },
    "special:Featured_Video": {
      query: "special:" + encodeURIComponent(videosUrl),
      title: bundle.GetStringFromName("flock.tinypic.title.featured.videos"),
      supportsSearch: false
    }
  };
}

TinyPicService.prototype = new FlockXPCOMUtils.genericComponent(
  CLASS_NAME,
  CLASS_ID,
  CONTRACT_ID,
  TinyPicService,
  CI.nsIClassInfo.SINGLETON,
  [
    CI.nsIObserver,
    CI.flockIPollingService,
    CI.flockIWebService,
    CI.flockILoginWebService,
    CI.flockIAuthenticateNewAccount,
    CI.flockIMediaWebService,
    CI.flockIMediaUploadWebService,
    CI.flockIMediaEmbedWebService,
    CI.flockIRichContentDropHandler
  ]
);

// FlockXPCOMUtils.genericModule() categories
TinyPicService.prototype._xpcom_categories = [
  { category: "wsm-startup" },
  { category: "flockWebService", entry: CLASS_SHORT_NAME },
  { category: "flockMediaProvider", entry: CLASS_SHORT_NAME },
  { category: "flockRichContentHandler", entry: CLASS_SHORT_NAME }
];


/**************************************************************************
 * TinyPicService: nsIObserver Implementation
 **************************************************************************/

/**
 * void observe(in nsISupports subject, in char* topic, in PRUnichar* data);
 * @see nsIObserver#observe
 */
TinyPicService.prototype.observe =
function TinyPicService_observe(aSubject, aTopic, aState) {
  this._logger.debug(".observe(..., '" + aTopic + "', '" + aState + "')");

  switch (aTopic) {
    case "xpcom-shutdown":
      this._obs.removeObserver(this, "xpcom-shutdown");
      this._obs.removeObserver(this, NOTIFY_COOKIES_RESTORED);
      break;

    // Usually we do this in the service constructor. However session
    // cookies are *restored* after tinypic service constructor so we must
    // wait until we know that session cookies have been restored.
    // c.f. https://bugzilla.flock.com/show_bug.cgi?id=15439
    case NOTIFY_COOKIES_RESTORED:
      if (this._wd.detectCookies(CLASS_SHORT_NAME, "loggedincookie", null)) {
        var svc = this;
        var reLoginListener = {
          onSuccess: function relogin_onSuccess(aSubject, aTopic) {
            svc._logger.debug(".reLoginListener.onSuccess()");
          },
          onError: function relogin_onError(aFlockError, aTopic) {
            svc._logger.error(".reLoginListener.onError()");
          }
        };

        var accountUrns = this.getAccountUrns(); 
        while (accountUrns.hasMore()) {
          var account = this.getAccount(accountUrns.getNext());
          // Relogin the last auth'ed account
          if (account.getCustomParam("isLastAuthAccount")) {
            account.login(reLoginListener);
          }
        }
      }
      break;
  }
};


/*************************************************************************
 * TinyPicService: flockIPollingService Implementation
 *************************************************************************/

/**
 * @see flockIPollingService#refresh
 */
TinyPicService.prototype.refresh =
function TinyPicService_refresh(aUrn, aPollingListener) {
  this._logger.debug(".refresh('" + aUrn + "', ...)");
  aPollingListener.onResult();
};


/*************************************************************************
 * TinyPicService: flockIWebService Implementation
 *************************************************************************/

// readonly attribute AString title;
TinyPicService.prototype.title = CLASS_TITLE;

// readonly attribute AString shortName;
TinyPicService.prototype.shortName = CLASS_SHORT_NAME;

// readonly attribute AString icon;
TinyPicService.prototype.icon = FAVICON;

// readonly attribute AString url;
TinyPicService.prototype.url = TINYPIC_URL;

// readonly attribute AString contractId;
// ALMOST duplicated by nsIClassInfo::contractID
// with different case: contractId != contractID
TinyPicService.prototype.contractId = CONTRACT_ID;

// readonly attribute AString urn;
TinyPicService.prototype.urn = "urn:" + CLASS_SHORT_NAME + ":service";

// DEFAULT: nsIStringBundle getStringBundle();
// DEFAULT: boolean isHidden();

/*************************************************************************
 * TinyPicService: flockILoginWebService Implementation
 *************************************************************************/

// readonly attribute AString domains;
TinyPicService.prototype.__defineGetter__("domains",
function TinyPicService_getdomains() {
  this._logger.debug("Getting attribute: domains");

  return this._wd.getString(CLASS_SHORT_NAME, "domains", "tinypic.com");
});

// readonly attribute boolean needPassword;
TinyPicService.prototype.needPassword = true;

// DEFAULT: readonly attribute AString loginURL;

/**
 * @see flockILoginWebService#addAccount
 */
TinyPicService.prototype.addAccount =
function TinyPicService_addAccount(aAccountId, aIsTransient, aListener) {
  this._logger.debug(".addAccount('" + aAccountId + "', "
                                     + aIsTransient + ", aListener)");

  if (!aAccountId) {
    if (aListener) {
      var error = CC[FLOCK_ERROR_CONTRACTID].createInstance(CI.flockIError);
      // XXX See bug 10749 - flockIError cleanup
      error.errorCode = 9999;
      error.errorString = "No Account ID provided";
      aListener.onError(this, arguments.callee.name, error);
    }
    return;
  }

  var pw = this._acUtils.getPassword(this.urn + ":" + aAccountId);
  var name = (pw) ? pw.username : aAccountId;
  var accountUrn = this._acUtils.createAccount(this,
                                               aAccountId,
                                               name,
                                               "FIXME",
                                               aIsTransient);

  // Instantiate account component
  var account = this.getAccount(accountUrn);
  account.setParam("service", this._coopObj);
  if (aListener) {
    aListener.onSuccess(account, "addAccount");
  }
  return account;
};

// DEFAULT: boolean docRepresentsSuccessfulLogin(in nsIDOMHTMLDocument ...

/**
 * AString getAccountIDFromDocument(in nsIDOMHTMLDocument aDocument);
 *
 * @see flockILoginWebService#getAccountIDFromDocument
 */
TinyPicService.prototype.getAccountIDFromDocument =
function TinyPicService_getAccountIDFromDocument(aDocument) {
  this._logger.debug("getAccountIDFromDocument()");
  // TinyPic does not display the logged in user's name in their document.
  // We will have to grab it via the lastlogin user
  var lastLoginUser =
    FlockPrefsUtils.getCharPref("flock.service.lastlogin." + this.urn);
  return lastLoginUser ? lastLoginUser : null;
};

// DEFAULT: flockIWebServiceAccount getAccount(in AString aAccountUrn);
// DEFAULT: nsIStringEnumerator getAccountUrns();
// DEFAULT: flockIWebServiceAccount getAuthenticatedAccount();
// DEFAULT: nsIPassword getCredentialsFromForm(in nsIDOMHTMLFormElement aForm);
// DEFAULT: AString getSessionValue();

/**
 * void logout();
 * @see flockILoginWebService#logout
 */
TinyPicService.prototype.logout =
function TinyPicService_logout() {
  this._logger.debug(".logout()");
  gApi.deauthenticate();
  this._acUtils.removeCookies(this._wd.getSessionCookies(CLASS_SHORT_NAME));
  this._acUtils.markAllAccountsAsLoggedOut(this);
};

// DEFAULT: boolean ownsDocument(in nsIDOMHTMLDocument aDocument);
// DEFAULT: boolean ownsLoginForm(in nsIDOMHTMLFormElement aForm);
// DEFAULT: void removeAccount(in AString aAccountUrn);

/**
 * void updateAccountStatusFromDocument(in nsIDOMHTMLDocument aDocument,
 *                                      flockIWebServiceAccount aAccount,
 *                                      in flockIListener aAuthListener);
 * @see flockILoginWebService#updateAccountStatusFromDocument
 */
TinyPicService.prototype.updateAccountStatusFromDocument =
function TinyPicService_updateAccountStatusFromDocument(aDocument,
                                                        aAccount,
                                                        aAuthListener)
{
  if (aAccount) {
    // We know we're already logged in to this account
    if (!aAccount.isAuthenticated()) {
      aAccount.login(aAuthListener);
    }
  } else if (this._wd.detect(this.shortName, "loggedout", aDocument, null)) {
    this.logout();
  }
};


/*************************************************************************
 * TinyPicService: flockIAuthenticateNewAccount Implementation
 *************************************************************************/

// DEFAULT: void authenticateNewAccount();


/*************************************************************************
 * TinyPicService: flockIMediaWebService Implementation
 *************************************************************************/

/**
 * void decorateForMedia(in nsIDOMHTMLDocument aDocument,
 *                       in flockIWebServiceAccount aAccount);
 * @see flockIMediaWebService#decorateForMedia
 */
TinyPicService.prototype.decorateForMedia =
function TinyPicService_decorateForMedia(aDocument, aAccount) {
  this._logger.debug(".decorateForMedia('" + aDocument.URL + "')");

  var mediaArr = [];
  var inst = this;
  aDocument.QueryInterface(CI.nsIDOMHTMLDocument);
  var results = FlockSvcUtils.newResults();
  if (this._wd.detect(this.shortName, "media", aDocument, results)) {
    var tag = results.getPropertyAsAString("tag");

    var label = flockGetString("photo/mediabar",
                               "flock.media.simpleviewing.search",
                               [tag]);
    var media = {
      name: tag,
      query: "search:" + tag,
      label: label,
      favicon: inst.icon,
      service: inst.shortName
    };
    mediaArr.push(media);

  } else if(this._wd.detect(this.shortName, "favorites", aDocument, results)) {
    var accountId = aAccount.getParam("accountId");
    var bundle = this.getStringBundle();
    var label = bundle.formatStringFromName("flock.tinypic.favorites",
                                            [accountId],
                                            1);
    var media = {
      name: tag,
      query: "favorites:" + accountId,
      label: label,
      favicon: inst.icon,
      service: inst.shortName
    };
    mediaArr.push(media);
  } else if (this._wd.detect(this.shortName, "yourhome", aDocument, results) ||
             this._wd.detect(this.shortName, "yourstuff", aDocument, results))
  {
    var accountId = aAccount.getParam("accountId");
    var media = {
      name: accountId,
      query: "user:" + accountId + "|username:" + accountId,
      label: accountId,
      favicon: inst.icon,
      service: inst.shortName
    };
    mediaArr.push(media);
  }

  if (!aDocument._flock_decorations) {
    aDocument._flock_decorations = {};
  }

  if (aDocument._flock_decorations.mediaArr) {
    aDocument._flock_decorations.mediaArr =
    aDocument._flock_decorations.mediaArr.concat(mediaArr);
  } else {
    aDocument._flock_decorations.mediaArr = mediaArr;
  }
  this._obs.notifyObservers(aDocument, "media", "media:update");
};

/**
 * void findByUsername(in flockIListener aListener, in AString aUsername);
 * @see flockIMediaWebService#findByUsername
 */
TinyPicService.prototype.findByUsername =
function TinyPicService_findByUsername(aListener, aUsername) {
  this._logger.debug(".findByUsername(..., '" + aUsername + "')");
  aListener.onError(null, aUsername);
};

/**
 * void getAlbums(in flockIListener aFlockListener, in AString aUsername);
 * @see flockIMediaWebService#getAlbums
 */
TinyPicService.prototype.getAlbums =
function TinyPicService_getAlbums(aFlockListener, aUsername) {
  this._logger.debug(".getAlbums(..., '" + aUsername + "')");
   var account = this.getAuthenticatedAccount();
   if (account && !gApi._isApiLoggedIn()) {
     this._authenticationNeeded(this.getAccount(account),
                                "getAlbums",
                                aFlockListener,
                                aUsername);
     return;
   }
   gApi._getAlbums(MAX_MEDIA_RESULTS, aFlockListener);
};


// DEFAULT: flockIMediaChannel getChannel(in AString aChannelId);
// DEFAULT: nsISimpleEnumerator enumerateChannels();

/**
 * flockIMediaItemFormatter getMediaItemFormatter();
 * @see flockIMediaWebService#getMediaItemFormatter
 */
TinyPicService.prototype.getMediaItemFormatter =
function TinyPicService_getMediaItemFormatter() {
  return flockMediaItemFormatter;
};

/**
 * void migrateAccount(in AString aId, in AString aUsername);
 * @see flockIMediaWebService#migrateAccount
 */
TinyPicService.prototype.migrateAccount =
function TinyPicService_migrateAccount(aId, aUsername) {
  this._logger.debug(".migrateAccount('" + aId + "', '" + aUsername + "')");
};

/**
 * void search(in flockIListener aListener, in AString aQuery,
 *             in long aCount, in AString aRequestId);
 * @see flockIMediaWebService#search
 */
TinyPicService.prototype.search =
function TinyPicService_search(aFlockListener,
                               aQueryString,
                               aCount,
                               aPage,
                               aRequestId)
{
  this._logger.debug(".search(..., '" + aQueryString + "', '"
                     + aCount + "', '" + aPage + "', '" + aRequestId + "')");
  var query = new QueryHelper(aQueryString);

  // This ad-hoc listener is known as "aResultsListener" in functions to
  // which it is passed, to distinguish if from a flockIListener.
  var queryListener = {
    onResult: function queryListener_onResult(aResult) {
      var resultsEnum = {
        hasMoreElements: function resultsEnum_hasMoreElements() {
          return (aResult.length > 0);
        },
        getNext: function resultsEnum_getNext() {
          return aResult.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 queryListener_onError(aFlockError, aTopic) {
      // Replace the passed topic with the request id.
      aFlockListener.onError(aFlockError, aRequestId);
    }
  };
  if (query.search) {
    var offset = (aPage == 1) ? 0 : aCount * (aPage - 1);
    var mediatype = query.searchunique ? query.searchunique : "image";
    gApi._doMediaSearch(query.search,
                        MAX_MEDIA_RESULTS,
                        mediatype,
                        offset,
                        queryListener);
  } else if (query.user) {
    var acctUrn = this.getAuthenticatedAccount();
    if (acctUrn && this._coop.Account.get(acctUrn).accountId != query.user) {
      // We not looking for the logged in user's media stream
      queryListener.onResult({}, null);
    }
    var params = {
      albumid: query.album,
      numresults: MAX_MEDIA_RESULTS,
      page: aPage
    };
    this._doUserMediaQuery(params, queryListener);
  } else if (query.special) {
    var channel = this._channels["special:" + query.special];
    if (channel) {
      var tmp = channel.query.split(":");
      gApi._doRSSMedia(decodeURIComponent(tmp[1]), aPage, queryListener);
    }
  } else if (query.favorites) {
    var params = {
      numresults: MAX_MEDIA_RESULTS,
      page: aPage
    };
    this._doUserFavoritesQuery(params, queryListener);
  }
};

/**
 * boolean supportsSearch(in AString aQuery);
 * @see flockIMediaWebService#supportsSearch
 */
TinyPicService.prototype.supportsSearch =
function TinyPicService_supportsSearch(aQuery) {
  return false;
};

// DEFAULT: AString getIconForQuery(in AString aQuery);

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


/*************************************************************************
 * TinyPicService: flockIMediaUploadWebService Implementation
 *************************************************************************/

/**
 * void getAccountStatus(in flockIListener aFlockListener);
 *
 * @see flockIMediaUploadWebService#getAccountStatus
 */
TinyPicService.prototype.getAccountStatus =
function TinyPicService_getAccountStatus(aFlockListener) {
  this._logger.debug(".getAccountStatus(aListener)");
  var result = CC[HASH_PROPERTY_BAG_CONTRACTID]
               .createInstance(CI.nsIWritablePropertyBag2);

  // TODO: find actual TinyPic upload limits
  result.setPropertyAsAString("maxSpace", UPLOAD_MAX_SPACE);
  result.setPropertyAsAString("usedSpace", 0);
  result.setPropertyAsAString("maxFileSize", UPLOAD_MAX_FILE_SIZE);
  result.setPropertyAsAString("usageUnits", "bytes");
  result.setPropertyAsBool("isPremium", true); // Tinypic has no "pro" level.
  aFlockListener.onSuccess(result, "");
};

// DEFAULT: void getAlbumsForUpload(in flockIListener aFlockListener,
//                                  in AString aUsername);

/**
 * void createAlbum(in flockIListener aFlockListener, in AString aAccountId ..);
 * @see flockIMediaUploadWebService#createAlbum
 */
TinyPicService.prototype.createAlbum =
function TinyPicService_createAlbum(aFlockListener, aAccountId, aAlbumName) {
  this._logger.debug(".createAlbum(aListener, '" + aAlbumName + "')");

  var inst = this;

  // Set the date of the created album to now
  var now = new Date();
  var date = now.getFullYear() + "-"
           + (now.getMonth() + 1) + "-"
           + now.getDate();

  var params = {
    albumname: aAlbumName,
    date: date,
    shuk: gApi.token
  };

  var createAlbumListener = {
    onSuccess: function createAlbum_onSuccess(aSubject, aTopic) {
      inst._logger.debug(arguments.callee.name);
      var xmlData = aSubject;
      var album = xmlData.results.album.albuminfo;
      var newAlbum = CC[FLOCK_PHOTO_ALBUM_CONTRACTID]
                    .createInstance(CI.flockIPhotoAlbum);
      newAlbum.title = album.name;
      newAlbum.id = album.albumid;
      aFlockListener.onSuccess(newAlbum, "success");
    },
    onError: function createAlbum_onError(aFlockError, aTopic) {
      inst._logger.debug(".createAlbum() error: " + aFlockError.errorString);
      aFlockListener.onError(aFlockError, "createAlbum");
    }
  };

  gApi.call("adduseralbum", params, null, null, createAlbumListener);
};

/**
 * boolean supportsFeature(in AString aFeature);
 * @see flockIMediaUploadWebService#supportsFeature
 */
TinyPicService.prototype.supportsFeature =
function TinyPicService_supportsFeature(aFeature) {
  this._logger.debug(".supportsFeature('" + aFeature + "')");

  var supports = {
    albumCreation: true,
    contacts: false,
    filename: false,
    privacy: false,
    tags: true,
    notes: false,
    title: false
  };
  return supports[aFeature];
};

/**
 * void upload(in flockIPhotoUploadAPIListener aUploadListener,
 *             in flockIPhotoUpload aUpload,
 *             in AString aFilename);
 * @see flockIMediaUploadWebService#upload
 */
TinyPicService.prototype.upload =
function TinyPicService_upload(aUploadListener, aUpload, aFilename) {
  this._logger.debug(".upload(aUploadListener, aUpload, '" + aFilename + "')");

  var inst = this;

  var params = {
    action: "userupload",
    tpid: gTinyPicId,
    sig: FlockCryptoHash.md5("userupload" + gTinyPicId + gTinyPicKey),
    responsetype: "xml",
    shuk: gApi.token,
    upk: gApi._uploadKey,
    type: "image",
    tags: aUpload.tags
  };
  if (aUpload.album) {
    params.albumid = aUpload.album;
  }

  var uploadListener = {
    onResult: function upload_onSuccess(aXml) {
      inst._logger.debug(arguments.callee.name);

      // E4X doesn't support parsing XML declaration (<?xml version=...?>)
      // c.f. https://bugzilla.mozilla.org/show_bug.cgi?id=336551
      aXml = aXml.replace(/^<\?xml\s+version\s*=\s*(["'])[^\1]+\1[^?]*\?>/, "");
      var xmlData = new XML(aXml);

      if (xmlData.status != "OK") {
        var err = xmlData.errorcode;
        aUploadListener.onError(gApi.getError(err));
      } else {
        var image = xmlData.result.image;
        aUploadListener.onUploadComplete(aUpload);
        aUploadListener.onUploadFinalized(aUpload, _getMediaItem(image));
      }
    },
    onError: function upload_onError(aFlockError) {
      inst._logger.debug("uploadListener.onError(): " + aFlockError.errorString);
      aUploadListener.onError(aFlockError);
    },
    onProgress: function upload_onProgress(aCurrentProgress) {
      aUploadListener.onProgress(aCurrentProgress);
    }
  };

  var photoUploader = new FlockPhotoUploader();
  photoUploader.setEndpoint(gTinyPicApiUrl);
  photoUploader.photoParam = "uploadfile";
  photoUploader.upload(uploadListener, aFilename, params, aUpload);
};


/*************************************************************************
 * TinyPicService: flockIMediaEmbedWebService Implementation
 *************************************************************************/

TinyPicService.prototype.checkIsStreamUrl =
function TinyPicService_checkIsStreamUrl(aUrl) {
  if (this._wd.detectNoDOM(CLASS_SHORT_NAME, "isStreamUrl", "", aUrl, null)) {
    this._logger.debug("Checking if url is tinypic stream: YES: " + aUrl);
    return true;
  }
  this._logger.debug("Checking if url is tinypic stream: NO: " + aUrl);
  return false;
};

TinyPicService.prototype.getMediaQueryFromURL =
function TinyPicService_getMediaQueryFromURL(aUrl, aListener) {
  return false;
};

// DEFAULT: boolean getSharingContent(in nsIDOMHTMLElement aSrc,
//                                    in nsIWritablePropertyBag2 aProp);


/**************************************************************************
 * TinyPicService: flockIRichContentDropHandler Implementation
 **************************************************************************/

TinyPicService.prototype.handleDrop =
function TinyPicService_handleDrop(aFlavours, aTextarea) {
  this._logger.debug(".handleDrop()");
};


/*************************************************************************
 * TinyPicService: Private Data and Functions
 *************************************************************************/

TinyPicService.prototype._doUserMediaQuery =
function TinyPicService__doUserMediaQuery(aParams, aResultsListener)
{
  var account = this.getAuthenticatedAccount();
  if (account && !gApi._isApiLoggedIn()) {
    this._authenticationNeeded(this.getAccount(account),
                               "_doUserMediaQuery",
                               aParams,
                               aResultsListener);
    return;
  }
  gApi._loadUserMedia(aParams, aResultsListener);
};

TinyPicService.prototype._doUserFavoritesQuery =
function TinyPicService__doUserFavoritesQuery(aParams, aResultsListener) {
   var account = this.getAuthenticatedAccount();
   if (account && !gApi._isApiLoggedIn()) {
     this._authenticationNeeded(this.getAccount(account),
                                "_doUserFavoritesQuery",
                                aParams,
                                aResultsListener);
     return;
   }
  gApi._loadUserFavorites(aParams, aResultsListener);
};

TinyPicService.prototype._authenticationNeeded =
function TinyPicService__authenticationNeeded(aAccount, aMethod, aP1, aP2) {
  var svc = this;
  var listener = {
    onSuccess: function doCall_onSuccess(aSubject, aTopic) {
      svc[aMethod](aP1, aP2);
    },
    onError: function doCall_onError(aFlockError, aTopic) {
      svc._logger.error("._authNeeded() listener.onError()");
    }
  };
  aAccount.login(listener);
};


/*************************************************************************
 * Component: TinyPicAccount
 *************************************************************************/
function TinyPicAccount() {
  FlockSvcUtils.getLogger(this);
  this._logger.init("TinyPicAccount");

  FlockSvcUtils.getACUtils(this);
  FlockSvcUtils.getCoop(this);

  this._WebDetective = CC["@flock.com/web-detective;1"]
                       .getService(CI.flockIWebDetective);

  // Convenience variable.
  this._wd = this._WebDetective;

  var wsa = FlockSvcUtils.flockIWebServiceAccount;
  wsa.addDefaultMethod(this, "getAllCustomParams");
  wsa.addDefaultMethod(this, "getCustomParam");
  wsa.addDefaultMethod(this, "getParam");
  wsa.addDefaultMethod(this, "getService");
  wsa.addDefaultMethod(this, "isAuthenticated");
  wsa.addDefaultMethod(this, "logout");
  wsa.addDefaultMethod(this, "keep");
  wsa.addDefaultMethod(this, "setCustomParam");
  wsa.addDefaultMethod(this, "setParam");
}

// Use genericComponent() for the goodness it provides (QI, nsIClassInfo, etc),
// but do NOT add this component to the list of constructors passed
// to FlockXPCOMUtils.genericModule().
TinyPicAccount.prototype = new FlockXPCOMUtils.genericComponent(
  CLASS_NAME + " Account",
  "",
  "",
  TinyPicAccount,
  0,
  [
    CI.flockIWebServiceAccount,
    CI.flockIMediaAccount,
    CI.flockIMediaUploadAccount
  ]
);


/*************************************************************************
 * TinyPicAccount: flockIWebServiceAccount Implementation
 *************************************************************************/

// readonly attribute AString urn;
TinyPicAccount.prototype.urn = "";

// DEFAULT: flockILoginWebService getService();

/**
 * void login(in flockIListener aListener);
 * @see flockIWebServiceAccount#login
 */
TinyPicAccount.prototype.login =
function TinyPicAccount_login(aFlockListener) {
  this._logger.debug(".login()");

  var inst = this;
  var authListener = {
    onSuccess: function auth_onSuccess(aSubject, aTopic) {
      inst._logger.debug(arguments.callee.name);
      inst._acUtils.ensureOnlyAuthenticatedAccount(inst);

      // Reset the isLastAuthAccount param for all accounts
      var accountUrns = inst.getService().getAccountUrns(); 
      while (accountUrns.hasMore()) {
        var paramList = inst._coop.get(accountUrns.getNext()).accountParamList;
        if (paramList) {
          paramList.setCustomParam("isLastAuthAccount", false);
        }
      }
      // Mark isLastAuthAccount as true for this account
      inst.setCustomParam("isLastAuthAccount", true);

      if (aFlockListener) {
        aFlockListener.onSuccess(inst, "login");
      }
    },
    onError: function auth_onError(aFlockError, aTopic) {
      inst._logger.debug(arguments.callee.name);
      aFlockListener.onError(aFlockError, "login");
    }
  };

  var pwKey = this.getService().urn + ":" + this.getParam("accountId");
  var loginInfo = this._acUtils.getPassword(pwKey);

  gApi.authenticate(loginInfo, authListener);
};

// DEFAULT: void logout(in flockIListener aListener);
// DEFAULT: void keep();
// DEFAULT: boolean isAuthenticated();
// DEFAULT: nsIVariant getParam(in AString aParamName);
// DEFAULT: void setParam(in AString aParamName, in nsIVariant aValue);
// DEFAULT: nsIVariant getCustomParam(in AString aParamName);
// DEFAULT: nsIPropertyBag getAllCustomParams();
// DEFAULT: void setCustomParam(in AString aParamName, in nsIVariant aValue);


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

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

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