// 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/FlockStringBundleHelpers.jsm");
CU.import("resource:///modules/FlockSvcUtils.jsm");
CU.import("resource:///modules/FlockCryptoHash.jsm");
CU.import("resource:///modules/FlockPhotoUploader.jsm");
CU.import("resource:///modules/FlockMediaUtils.jsm");

const MODULE_NAME = "Photobucket";

const CLASS_NAME = "Flock Photobucket Service";
const CLASS_SHORT_NAME = "photobucket";
const CLASS_TITLE = "Photobucket";
const CLASS_ID = Components.ID("{d3b147b0-a321-11da-a746-0800200c9a66}");
const CONTRACT_ID = "@flock.com/?photo-api-photobucket;1";

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

const PHOTOBUCKET_FAVICON = "chrome://flock/content/services/photobucket/favicon.png";

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

const PHOTOBUCKET_API_VERSION = "1.0";
const PHOTOBUCKET_TOKEN_PREF = "flock.photo.photobucket.token";

const PHOTOBUCKET_PK = "e57335b0847da8b1105b6c8bc27d217a";
const PHOTOBUCKET_SCID = "149825788";
const PHOTOBUCKET_PROPERTIES = "chrome://flock/locale/services/photobucket.properties";

// Refresh the session token every 6 hours
const PHOTOBUCKET_SESSION_REFRESH_INTERVAL = 21600000;

// String defaults... may be updated at runtime through Web Detective.
var gStrings = {
  "domains": "photobucket.com",
  "userloginURL": "http://photobucket.com/login",
  "userprofileURL": "http://photobucket.com/images/search/%accountid%",
  "serviceloginURL": "http://photobucket.com/svc/servicelogin.php",
  "apiURL": "http://photobucket.com/svc/api.php"
};

/**************************************************************************
 * General Helper Functions
 **************************************************************************/

function loadSubScript(spec) {
  var loader = CC["@mozilla.org/moz/jssubscript-loader;1"]
               .getService(CI.mozIJSSubScriptLoader);
  var context = {};
  loader.loadSubScript(spec, context);
  return context;
}

var flockMediaItemFormatter = {
  canBuildTooltip: false,
  canBuildHTML: false,
  canBuildLargeHTML: false,
  canBuildBBCode: true,
  canBuildMiniPage: true,
  buildBBCode: function fmif_buildBBCode(aMediaItem) {
    var bbCode = "";
    if (aMediaItem.is_video) {
      bbCode = "[photob]" + aMediaItem.largeSizePhoto + "[photob]";
    } else {
      bbCode = "[url=" + aMediaItem.webPageUrl
             + "][img]" + aMediaItem.largeSizePhoto
             + "[/img][/url]";
    }
    return bbCode;
  },
  buildMiniPage: function fmif_buildMiniPage(aMediaItem) {
    var html = '<html><head><title>'
             + aMediaItem.title
             + ' (' + aMediaItem.username + ')</title></head>'
             + '<body><center><object width="425" height="350">'
             + '<param name="movie" value="'
             + aMediaItem.largeSizePhoto
             + '"/>'
             + '<embed src="'
             + aMediaItem.largeSizePhoto
             + '&autoplay=1" type="application/x-shockwave-flash"'
             + ' width="425" height="350"/></object></center></body></html>';
    return html;
  }
};


/**************************************************************************
 * Photobucket API
 **************************************************************************/

var gPhotobucketAPI;

function PhotobucketAPI(aSvc) {
  FlockSvcUtils.getLogger(this);
  this._logger.init("photobucketAPI");
  this._logger.info("Created Photobucket API Object");

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

  this._svc = aSvc;
  this._WebDetective = CC["@flock.com/web-detective;1"]
                      .getService(CI.flockIWebDetective);
  this._apiUrl = gStrings["apiURL"];
  this._authUser = {};
  this._authAccount = null
  this._sessionRefreshTimer = null;
}

PhotobucketAPI.prototype.setAuthAccount =
function pbApi_setAuthAccount(aCoopAccount) {
  this._authAccount = aCoopAccount;
};

PhotobucketAPI.prototype.getToken =
function pbApi_getToken(aListener) {
  var inst = this;
  var tokenListener = {
    onResult: function pbApi_getToken_onResult(aXML) {
      try {
        var resp = aXML.getElementsByTagName("response");
        if (resp[0].getAttribute("stat") == "ok") {
          //rval = resp[0].firstChild.nodeValue;
          aListener.onResult(aXML);
        } else {
          aListener.onError(null);  //TODO - figure out errors
          //rval = -1;
        }
      } catch (ex) {
        inst._logger.error(ex);
      }
    },

    onError: function pbApi_getToken_onError(aError) {
      inst._logger.error(aError.errorString);
      aListener.onError(aError);
    }
  };
  var params = {};
  this.call(tokenListener, "getservicetoken", params);
};

PhotobucketAPI.prototype.call =
function pbApi_call(aFlockListener, aMethod, aParams, aAuth)
{
  var inst = this;
  var url = this.buildUrl(this._apiUrl, aMethod, aParams, aAuth);
  this._logger.debug(url + " < call");
  this.req = CC["@mozilla.org/xmlextras/xmlhttprequest;1"]
             .createInstance(CI.nsIXMLHttpRequest)
             .QueryInterface(CI.nsIJSXMLHttpRequest);

  this.req.open("GET", url, true);
  var req = this.req;
  this.req.onreadystatechange = function pbApi_call_onreadystatechange(aEvt) {
    if (req.readyState == 4) {
      var status = req.status;
      inst._logger.debug("doCall[onreadystatechange] req.status = " + status);
      if (Math.floor(status/100) == 2) {
        // XXX .DUMP inst._logger.debug(req.responseText);
        inst._logger.debug("response text length: " + req.responseText.length);
        var rsp = req.responseXML.getElementsByTagName("response")[0];
        var stat = rsp.getAttribute("stat");
        if (stat != "ok") {
          aFlockListener.onError(inst.getError("SERVICE_ERROR",
                                               req.responseXML,
                                               null));
        } else {
          aFlockListener.onResult(req.responseXML);
        }
      } else {
        // HTTP errors (0 for connection lost)
        aFlockListener.onError(inst.getError("HTTP_ERROR", null, status));
      }
    }
  };
  this.req.send(null);
};

PhotobucketAPI.prototype.authenticatedCall =
function pbApi_authenticatedCall(aFlockListener, aMethod, aParams) {
  this._logger.debug("authenticatedCall " + aMethod);
  var key = this._authUser.sessionkey;
  if (!key && this._authAccount) {
    var inst = this;
    var authListener = {
      onSuccess: function authListener_onSuccess(aSubject, aTopic) {
        inst._logger.info(".authenticatedCall(): authListener: onSuccess()");
        inst.call(aFlockListener, aMethod, aParams, true);
      },
      onError: function authListener_onError(aFlockError) {
        inst._logger.info(".authenticatedCall(): authListener: onError()");
        var svc = CC[CONTRACT_ID].getService(CI.flockIWebService);
        inst._acUtils.markAllAccountsAsLoggedOut(svc);
        inst._authAccount = null; // credentials are no longer valid
        inst.call(aFlockListener, aMethod, aParams);
      }
    };
    this.doLogin(authListener);
  } else {
    this._logger.debug("+++++" + (key ? key : "trying unauth call-" + aMethod));
    this.call(aFlockListener, aMethod, aParams, key);
  }
};

PhotobucketAPI.prototype.doLogin =
function pbApi_doLogin(aListener) {
  this._logger.debug(".doLogin('" + this._authAccount.id() + "')");
  var inst = this;
  var tokenListener = {
    onResult: function pbApi_doLogin_onResult(aXML) {
      inst._logger.debug(".doLogin('"
                         + inst._authAccount.id()
                         + "'): tokenListener: onResult()");
      // Time to authenticate...
      var params = {
        service_token: aXML.getElementsByTagName("service_token")[0]
                           .firstChild
                           .nodeValue
      };
      // persist the token
      inst._authAccount.authToken = params.service_token;

      // listener for the upcoming "getsession" api call
      var getSessListener = {
        onResult: function pbApi_doLogin_gsl_onResult(aXML) {
          inst._logger.debug(".doLogin('"
                             + inst._authAccount.id()
                             + "'): tokenListener:"
                             + "getSessListener: onResult()");
          var username = aXML.getElementsByTagName("username")[0]
                             .firstChild
                             .nodeValue;
          var sessionkey = aXML.getElementsByTagName("session_key")[0]
                               .firstChild
                               .nodeValue;
          inst._apiUrl = aXML.getElementsByTagName("url")[0]
                             .firstChild
                             .nodeValue;
          inst._authUser.id = username;
          inst._authUser.username = username;
          inst._authUser.fullname = username;
          inst._authUser.sessionkey = sessionkey;

          if (!inst._sessionRefreshTimer) {
            inst.startSessionRefreshTimer();
          }

          inst._authAccount.name = username;

          if (aListener) {
            aListener.onSuccess(inst._authAccount, "");
          }
        },

        onError: function pbApi_doLogin_gsl_onError(aFlockError) {
          inst._logger.error(".doLogin('"
                             + inst._authAccount.id()
                             + "'): tokenListener: getSessListener: onError()");
          inst._logger.error(">>>>>> " + aFlockError.errorString);
          if (aListener) {
            aListener.onError(aFlockError, null);
          }
        }
      };

      // If we have the user's un/pw, then we can do this silently...
      var pw = inst._acUtils.getPassword("urn:photobucket:service:"
                                        + inst._authAccount.accountId);
      if (pw) {
        inst._logger.debug(".doLogin('"
                           + inst._authAccount.id()
                           + "'): tokenListener: onResult(): found password");

        var postBody = "action=login"
                     + "&service_token=" + params.service_token
                     + "&sig=" + inst.buildSig("login", params)
                     + "&scid=" + PHOTOBUCKET_SCID
                     + "&method=login"
                     + "&callback_verify="
                     + "&username=" + inst._authAccount.accountId
                     + "&password=" + pw.password
                     + "&login=Login";
        var hr = CC["@mozilla.org/xmlextras/xmlhttprequest;1"]
                 .createInstance(CI.nsIXMLHttpRequest)
                 .QueryInterface(CI.nsIJSXMLHttpRequest);

        var onReadyStateFunc =
        function pbApi_doLogin_onReadyStateFunc(eEvt) {
          if (hr.readyState == 4) {
            inst._logger.debug(".doLogin('"
                               + inst._authAccount.id()
                               + "'): tokenListener:"
                               + "onResult(): hr.onreadystatechange()");
            var results = CC["@mozilla.org/hash-property-bag;1"]
                          .createInstance(CI.nsIWritablePropertyBag2);
            if (inst._WebDetective.detectNoDOM("photobucket",
                                               "apiauth",
                                               "",
                                               hr.responseText,
                                               results))
            {
              var hr2 = CC["@mozilla.org/xmlextras/xmlhttprequest;1"]
                        .createInstance(CI.nsIXMLHttpRequest)
                        .QueryInterface(CI.nsIJSXMLHttpRequest);

              hr2.onreadystatechange =
              function pbApi_doLogin_onreadystatechange(eEvt2) {
                if (hr2.readyState == 4) {
                  inst.call(getSessListener,
                            "getsession",
                            params);
                }
              };

              var postBody2 = "action=complete"
                            + "&authorized=yes"
                            + "&service_token=" + params.service_token
                            + "&sig=" + inst.buildSig("login", params)
                            + "&scid=" + PHOTOBUCKET_SCID
                            + "&method=login"
                            + "&callback_verify="
                            + "&user_id="
                            + results.getPropertyAsAString("user_id");

              hr2.mozBackgroundRequest = true;
              hr2.open("POST", gStrings["serviceloginURL"],true);
              hr2.setRequestHeader("Content-Type",
                                   "application/x-www-form-urlencoded");
              hr2.send(postBody2);
            } else {
              inst.call(getSessListener,
                        "getsession",
                        params);
            }
          }
        };

        hr.onreadystatechange = onReadyStateFunc;
        hr.mozBackgroundRequest = true;
        hr.open("POST", gStrings["serviceloginURL"],true);
        hr.setRequestHeader("Content-Type",
                            "application/x-www-form-urlencoded");
        hr.send(postBody);

      } else {
        inst._logger.debug(".doLogin('"
                           + inst._authAccount.id()
                           + "'): tokenListener: onResult(): NO password!");

        // No password stored, so we have to prompt the user
        var windowManager = CC["@mozilla.org/appshell/window-mediator;1"]
                            .getService(CI.nsIWindowMediator);
        var topWindow = windowManager.getMostRecentWindow(null);

        var loginUrl = inst.buildUrl(gStrings["serviceloginURL"],
                                     "login",
                                     params);
        var url = "chrome://flock/content/photo/photoLoginWindow.xul?"
                + "url=" + escape(loginUrl)
                + "&finalString=" + escape("You have granted");
        var options = "chrome,"
                    + "close,"
                    + "titlebar,"
                    + "resizable=yes,"
                    + "toolbar,"
                    + "dialog=no,"
                    + "scrollbars=yes,"
                    + "modal,"
                    + "centerscreen";
        topWindow.open(url, "_blank", options);
        // we are waiting till window closes

        inst.call(getSessListener, "getsession", params);
      }
    },

    onError: function pbApi_doLogin_onError(aErr) {
      inst._logger.error(".doLogin('"
                         + inst._authAccount.id()
                         + "'): tokenListener: onError()");
      aListener.onError(aErr);
    }
  };

  if (!this._authUser) {
    this._authUser = {};
  }
  this.getToken(tokenListener);
};

PhotobucketAPI.prototype.startSessionRefreshTimer =
function pbApi_startSessionRefreshTimer() {
  // Keep the API authentication alive at the designated interval.
  this._sessionRefreshTimer = CC["@mozilla.org/timer;1"]
                              .createInstance(CI.nsITimer);
  this._sessionRefreshTimer.initWithCallback(this._svc,
                                             PHOTOBUCKET_SESSION_REFRESH_INTERVAL,
                                             CI.nsITimer.TYPE_REPEATING_PRECISE);
};

PhotobucketAPI.prototype.getError =
function pbApi_getError(aErrorType, aXML, aHTTPErrorCode) {
  var errorNode;
  var code;
  var serverErrorMessage;
  var error = CC[FLOCK_ERROR_CONTRACTID]
              .createInstance(CI.flockIError);

  this._logger.debug("getError aErrorType="
                     + aErrorType
                     + "; aHTTPErrorCode="
                     + aHTTPErrorCode);

  if (aErrorType == "HTTP_ERROR") {
    error.errorCode = aHTTPErrorCode;
    return error;
  } else if (aErrorType == "SERVICE_ERROR") {
    try {
      errorNode = aXML.getElementsByTagName("error")[0];
      code = errorNode.getAttribute("code");
      serverErrorMessage = errorNode.getAttribute("msg");
      error.serviceErrorCode = code;
      error.serviceErrorString = serverErrorMessage;
      this._logger.debug("Found error code = "
                         + code
                         + " and message = '"
                         + serverErrorMessage
                         + "'");
    } catch (ex) {
      code = "-1";
    }

    if (!code) {
      code = "-1";
    }

    switch (code) {
      case "-1":
        error.errorCode = error.PHOTOSERVICE_INVALID_RESPONSE;
        break;
      case "007":
      case "008":
        error.errorCode = error.PHOTOSERVICE_LOGIN_FAILED;
        break;
      case "012":
        error.errorCode = error.PHOTOSERVICE_SESSION_KEY_EXPIRED;
        break;
      case "101":
        error.errorCode = error.PHOTOSERVICE_EMPTY_ALBUMNAME;
        break;
      case "102":
        error.errorCode = error.PHOTOSERVICE_INVALID_USER;
        break;
      case "103":
        error.errorCode = error.PHOTOSERVICE_INVALID_SEARCH_QUERY;
        break;
      case "104":
        error.errorCode = error.PHOTOSERVICE_INVALID_SEARCH_QUERY;
        break;
      case "105":
        error.errorCode = error.PHOTOSERVICE_INVALID_UPLOAD_FILE;
        break;
      case "106":
        error.errorCode = error.PHOTOSERVICE_INVALID_SEARCH_QUERY;
        break;
      case "107":
        error.errorCode = error.PHOTOSERVICE_INVALID_ALBUM;
        break;
      case "108":
        error.errorCode = error.PHOTOSERVICE_PRIVATE_ALBUM;
        break;
      case "109":
        error.errorCode = error.PHOTOSERVICE_PHOTOS_IN_ALBUM_LIMIT_REACHED;
        break;
      case "110":
        error.errorCode = error.PHOTOSERVICE_NO_FILE_UPLOADED;
        break;
      case "111":
        error.errorCode = error.PHOTOSERVICE_ALBUM_IS_OVER_SIZE_LIMIT;
        break;
      case "112":
        error.errorCode = error.PHOTOSERVICE_INVALID_UPLOAD_FILE;
        break;
      case "113":
        error.errorCode = error.PHOTOSERVICE_DUPLICATE_FILENAME;
        break;
      case "115":
        error.errorCode = error.PHOTOSERVICE_INVALID_ALBUMNAME;
        break;
      case "116":
        error.errorCode = error.PHOTOSERVICE_DUPLICATE_ALBUMNAME;
        break;
      case "314":
        error.errorCode = error.PHOTOSERVICE_FILE_IS_OVER_SIZE_LIMIT;
        break;
      default:
        error.errorCode = error.PHOTOSERVICE_UNKNOWN_ERROR;
        if (serverErrorMessage && serverErrorMessage.length) {
          error.serviceErrorString = serverErrorMessage;
        }
        break;
    }
    return error;
  }
  return null;
};

PhotobucketAPI.prototype.buildUrl =
function pbApi_buildUrl(aUrl, aMethod, aParams, aAuth) {
  var qs = "";
  var strConcat = "";
  for (var p in aParams) {
    qs += "&" + escape(p) + "=" + escape(aParams[p]);
    strConcat += escape(aParams[p]);
  }
  var rval = aUrl
           + "?method=" + aMethod
           + "&version=" + PHOTOBUCKET_API_VERSION;
  if (aAuth) {
    rval += "&session_key=" + this._authUser.sessionkey;
  }
  rval += "&scid=" + PHOTOBUCKET_SCID
       + qs
       + "&sig=" + this.buildSig(aMethod, aParams, aAuth);
  this._logger.debug("this is your buildUrl: " + rval + "\n");
  return rval;
};

PhotobucketAPI.prototype.buildSig =
function pbApi_buildSig(aMethod, aParams, aAuth) {
  var qs = "";
  var strConcat = "";
  for (var p in aParams) {
    qs += "&" + escape(p) + "=" + escape(aParams[p]);
    if (p == "service_token") {
      strConcat += escape(aParams[p]);
    }
  }

  this._logger.debug(strConcat + "<<<<<<<<strconcat\n");
  var preHash = aMethod
                + PHOTOBUCKET_PK
                + PHOTOBUCKET_SCID
                + strConcat
                + (aAuth ? this._authUser.sessionkey : "");
  this._logger.debug("going to sig::: " + preHash);

  var converter = CC["@mozilla.org/intl/scriptableunicodeconverter"]
                  .createInstance(CI.nsIScriptableUnicodeConverter);
  converter.charset = "UTF-8";

  var inputStream = converter.convertToInputStream(preHash);
  return FlockCryptoHash.md5Stream(inputStream);
};


/**************************************************************************
 * Component: Flock Photobucket Account
 **************************************************************************/

function PhotobucketAccount() {
  FlockSvcUtils.getLogger(this).info(".init()");
  FlockSvcUtils.getACUtils(this);
  FlockSvcUtils.getCoop(this);

  if (!gPhotobucketAPI) {
    this._logger.error("ERROR; why did the Account get created first?");
  }
  this._api = gPhotobucketAPI;

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

/**************************************************************************
 * Flock Photobucket Account: XPCOM Component Creation
 **************************************************************************/

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

/**************************************************************************
 * Flock Photobucket Account: flockIWebServiceAccount Implementation
 **************************************************************************/

// readonly attribute AString urn;
// This gets set in the call to flockIWebService::getAccount()
PhotobucketAccount.prototype.urn = null;

// void login(in flockIListener aFlockListener);
PhotobucketAccount.prototype.login =
function pbAcct_login(aFlockListener) {
  this._logger.debug("{flockIWebServiceAccount}.login() urn=" + this.urn);
  this._api.setAuthAccount(this.coopObj);
  this._acUtils.ensureOnlyAuthenticatedAccount(this);

  if (aFlockListener) {
    aFlockListener.onSuccess(this, "login");
  }
};

// DEFAULT: void logout(in flockIListener aFlockListener);

// void keep();
PhotobucketAccount.prototype.keep =
function pbAcct_keep() {
  this.coopObj.isTransient = false;
  var key = this._api._svc.urn + ":" + this.coopObj.accountId;
  this._acUtils.makeTempPasswordPermanent(key);
};

/**************************************************************************
 * Flock Photobucket Account: flockIMediaAccount Implementation
 **************************************************************************/

/* No methods or properties on this interface! */

/**************************************************************************
 * Flock Photobucket Account: flockIMediaUploadAccount Implementation
 **************************************************************************/

/* No methods or properties on this interface! */


/**************************************************************************
 * Component: Flock Photobucket Service
 **************************************************************************/

// Constructor.
function PhotobucketService() {

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

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

  FlockSvcUtils.flockILoginWebService.addDefaultMethod(this, "loginURL");
  FlockSvcUtils.flockILoginWebService.addDefaultMethod(this, "tooltip");
  FlockSvcUtils.flockILoginWebService.addDefaultMethod(this, "getAccount");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "getAuthenticatedAccount");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "getAccountUrns");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "docRepresentsSuccessfulLogin");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "ownsDocument");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "ownsLoginForm");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "getSessionValue");

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

  FlockSvcUtils.flockIMediaEmbedWebService
               .addDefaultMethod(this, "getSharingContent");
  this._init();
}

/**************************************************************************
 * Flock Photobucket Service: XPCOM Component Creation
 **************************************************************************/

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

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

/**************************************************************************
 * Flock Photobucket Service: Private Data and Functions
 **************************************************************************/

PhotobucketService.prototype._init =
function pbSvc__init() {
  FlockSvcUtils.getLogger(this).info(".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("Pref " + SERVICE_ENABLED_PREF
                              + " set to FALSE... not initializing.");
    this.deleteCategories();
    return;
  }

  // getWD() also adds the "_acUtils" property.
  FlockSvcUtils.getWD(this);
  
  // Initialize member that specifies path for localized string bundle
  this._stringBundlePath = PHOTOBUCKET_PROPERTIES;

  this._accountClassCtor = PhotobucketAccount;

  // Update the strings from Web Detective.
  for (var s in gStrings) {
    gStrings[s] = this._WebDetective.getString("photobucket", s, gStrings[s]);
  }

  // This property is part of our public interface.
  this.domains = gStrings["domains"];
  var authenticatedUrn = FlockSvcUtils.initService(this, true);

  this._photoUploader = null;
  if (!gPhotobucketAPI) {
    // Note: Constructor expects gStrings to be up-to-date.
    gPhotobucketAPI = new PhotobucketAPI(this);
  }
  this._api = gPhotobucketAPI;

  this._obsSvc = CC["@mozilla.org/observer-service;1"]
                 .getService(CI.nsIObserverService);

  var bundle = this.getStringBundle();

  this._channels = {
    "special:recent": {
      title: bundle.GetStringFromName("flock.photobucket.title.recent"),
      supportsSearch: false
    }
  };

  // On browser restart if a Photobucket coop account is still marked as
  // authenticated we will also need to reauth the api.
  if (authenticatedUrn) {
    this._api.setAuthAccount(this._coop.Account.get(authenticatedUrn));
  }
};

PhotobucketService.prototype._deauthenticateAPI =
function pbSvc__deauthenticateAPI() {
  if (this._api._authUser.sessionkey) {
    this._api._authUser = {};
    this._api.setAuthAccount(null);
    // Notify that api auth has changed
    this._obsSvc.notifyObservers(null, "apiAuthChange", this.shortName);
  }

  this._photoUploader = null;
  if (this._api._sessionRefreshTimer) {
    this._api._sessionRefreshTimer.cancel();
    this._api._sessionRefreshTimer = null;
  }
};

PhotobucketService.prototype._handlePhotosResult =
function pbSvc__handlePhotosResult(aXML) {
  var inst = this;
  function createItem(media, is_video) {
    var uploadDate = media.getAttribute("uploaddate");
    var title = media.getAttribute("name");
    var username = media.getAttribute("username");
    var is_public = media.getAttribute("public");
    var page_url = "";
    var thumb_url = media.getElementsByTagName("thumb")[0].firstChild.nodeValue;
    var small_url = media.getElementsByTagName("url")[0].firstChild.nodeValue;
    // this is a photobucket api issue - need a fix -
    try {
      page_url = media.getElementsByTagName("browseurl")[0].firstChild.nodeValue;
      page_url = page_url.replace("&amp;", "&");
    } catch (ex) {
      page_url = small_url;
    }
    var newMediaItem = CC["@flock.com/photo;1"]
                       .createInstance(CI.flockIMediaItem);
    newMediaItem.init(inst.shortName, flockMediaItemFormatter);
    newMediaItem.webPageUrl = page_url;
    newMediaItem.thumbnail = thumb_url;
    newMediaItem.midSizePhoto = thumb_url;
    newMediaItem.largeSizePhoto = small_url;
    newMediaItem.title = title;
    newMediaItem.username = username;
    newMediaItem.userid = username;
    if (uploadDate) {
      newMediaItem.id = uploadDate;
      newMediaItem.uploadDate = parseInt(uploadDate) * 1000;
    } else {
      newMediaItem.id = username + ":" + title;
    }
    newMediaItem.is_video = is_video;
    newMediaItem.has_miniView = is_video;
    newMediaItem.is_public = (is_public == "1") ? "true" : "false";
    return newMediaItem;
  }

  var rval = [];
  try {
    var i;
    var videoList = aXML.getElementsByTagName("video");
    for (i = 0; i < videoList.length; i++) {
      rval.push(createItem(videoList[i], true));
    }

    var photoList = aXML.getElementsByTagName("photo");
    for (i = 0; i < photoList.length; i++) {
      rval.push(createItem(photoList[i], false));
    }
  } catch (ex) {
    inst._logger.error(ex);
  }

  /*
   * do not sort anymore since PB no longer passes back the uploadDate.
   * instead we just present the photos in the order PB gives them to
   * us (upload order)
   * see https://bugzilla.flock.com/show_bug.cgi?id=9084

  // sort the photos and videos together by date
  // the follow sorts based on ID which we are using the uploadDate to be the id
  // it is ugly -- and probably not as efficient as it could be --- but it
  // is only sorting 20 items at most so it is good enough for now - ja
  rval.sort(function(a,b) {
    return (b.id-a.id);
  });
  */

  return rval;
};

PhotobucketService.prototype._queryChannel =
function pbSvc__queryChannel(aFlockListener,
                             aQueryString,
                             aCount,
                             aPage,
                             aRequestId)
{
  var aQuery = new QueryHelper(aQueryString);
  var inst = this;

  var myListener = {
    onResult: function pbSvc__queryChannel_onResult(aXML) {
      var rval = inst._handlePhotosResult(aXML);
      var resultsEnum = {
        hasMoreElements: function pbslenum_hasMoreElements() {
          return (rval.length > 0);
        },
        getNext: function pbslenum_getNext() {
          return rval.shift();
        },
        QueryInterface: function pbslenum_QueryInterface(aIID) {
          if (aIID.equals(CI.nsISimpleEnumerator)) {
            return this;
          }
          throw Cr.NS_ERROR_NO_INTERFACE;
        }
      };
      aFlockListener.onSuccess(resultsEnum, aRequestId);
    },

    onError: function pbSvc__queryChannel_onError(aFlockError) {
      aFlockListener.onError(aFlockError, aRequestId);
    }
  };

  var params = {
    page: aPage,
    num: aCount
  };

  if (aQuery.getKey("special") == "recent") {
    this._api.call(myListener, "getrecentphotos", params);
  }

  if (aQuery.hasKey("search")) {
    // don't pass in encoded text for PB
    // if an illegal char is used, we will return no results
    var aText = aQuery.search;
    if (aText && aText.length) {
      params.query = aText.split(".")[0];
      this._api.call(myListener, "search", params);
    }
  }
};

PhotobucketService.prototype._handleAlbumsResult =
function pbSvc__handleAlbumsResult(aXML) {
  var rval = [];
  var albumList = aXML.getElementsByTagName("photoalbum");
  for (var i = 0; i < albumList.length; i++) {
    var album = albumList[i];
    var title = album.getAttribute("name");

    // the first album node has no name attr, so skip it
    if (!title) {
      continue;
    }

    var username = album.getAttribute("username");
    var id;
    // parse out the prefix /username/
    if (title.match(album.getAttribute("username")) && title != username) {
      id = title.substring(username.length + 1);
      var newAlbum = CC[FLOCK_PHOTO_ALBUM_CONTRACTID]
                     .createInstance(CI.flockIPhotoAlbum);
      newAlbum.id = id;
      newAlbum.title = id;
      rval.push(newAlbum);
    }
  }
  return rval;
};

PhotobucketService.prototype._getAuthUser =
function pbSvc__getAuthUser() {
  return this._api._authUser;
};

PhotobucketService.prototype._getSessionKey =
function pbSvc__getSessionKey(aListener) {
  var accountUrn = this.getAuthenticatedAccount();
  if (!accountUrn) {
    aListener.onError(null);
  }
  var token = this._coop.Account.get(accountUrn).authToken;
  this._logger.debug(">>>_getSessionKey called with the token " + token);

  var inst = this;
  var mySessionListener = {
    onResult: function pbSvc__getSessionKey_onResult(aXML) {
      var username = aXML.getElementsByTagName("username")[0]
                         .firstChild.nodeValue;
      var sessionkey = aXML.getElementsByTagName("session_key")[0]
                           .firstChild.nodeValue;
      inst._api._apiUrl = aXML.getElementsByTagName("url")[0]
                              .firstChild.nodeValue;
      inst._getAuthUser().id = username;
      inst._getAuthUser().username = username;
      inst._getAuthUser().fullname = username;
      inst._getAuthUser().sessionkey = sessionkey;
      aListener.onAuth();
    },

    onError: function pbSvc__getSessionKey_onError(aErr) {
      inst._logger.debug(">>>>>> " + aErr.errorString);
      aListener.onError(aErr);
    }
  };
  var params = {};
  params.service_token = token;
  this._api.call(mySessionListener, "getsession", params);
};

/**************************************************************************
 * Flock Photobucket Service: nsIObserver Implementation
 **************************************************************************/

PhotobucketService.prototype.observe =
function pbSvc_observe(aSubject, aTopic, aData) {
};


/**************************************************************************
 * Flock Photobucket Service: flockIWebService Implementation
 **************************************************************************/

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

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

// readonly attribute AString icon;
PhotobucketService.prototype.icon = PHOTOBUCKET_FAVICON;

// readonly attribute AString contractId;
// ALMOST duplicated by nsIClassInfo::contractID
// with different case: contractId != contractID
// FIXME: File a bug for this as IDL is case-insensitive.
PhotobucketService.prototype.contractId = CONTRACT_ID;

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

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

// flockIWebServiceAccount addAccount(in AString aAccountId,
//                                    in boolean aIsTransient,
//                                    in flockIListener aFlockListener);
PhotobucketService.prototype.addAccount =
function pbSvc_addAccount(aAccountId, aIsTransient, aFlockListener,
                          aUsername, aToken)
{
  this._logger.debug(".addAccount('" + aAccountId
                     + "', " + aIsTransient + ", aFlockListener)");

  var name = "";
  var token = "";
  var auth = false;

  if (aUsername) {
    name = aUsername;
    token = aToken;
    auth = true;
  } else if (aAccountId) {
    // Use the account id if possible, in case the user logged in with their
    // email address. (https://bugzilla.flock.com/show_bug.cgi?id=16411)
    name = aAccountId;
  } else {
    // Get the password associated with this account
    var key = this.urn + ":" + aAccountId;
    name = this._acUtils.getPassword(key).username;
  }

  var accountUrn = this._acUtils.createAccount(this,
                                               aAccountId,
                                               name,
                                               null,
                                               aIsTransient);

  // Instantiate account component
  var account = this.getAccount(accountUrn);
  account.setParam("service", this._coopObj);
  account.setParam("authToken", token);
  account.setParam("isAuthenticated", auth);

  // Perhaps we should do a checktoken and if valid go ahead and set
  // the account as logged in?

  if (aFlockListener) {
    aFlockListener.onSuccess(account, "addAccount");
  }
  return account;
};

// void removeAccount(in AString aUrn);
PhotobucketService.prototype.removeAccount =
function pbSvc_removeAccount(aURN) {
  this._acUtils.removeAccount(aURN);
};

// DEFAULT: flockIWebServiceAccount getAccount(in AString aUrn);
// DEFAULT: nsIStringEnumerator getAccountUrns();

// void logout();
PhotobucketService.prototype.logout =
function pbSvc_logout() {
  this._deauthenticateAPI();
  this._acUtils.markAllAccountsAsLoggedOut(this);
  this._acUtils.removeCookies(this._WebDetective
                                  .getSessionCookies("photobucket"));
};


/**************************************************************************
 * Flock Photobucket Service: flockILoginWebService Implementation
 **************************************************************************/

// readonly attribute AString domains;
// This gets set up during construction, from Web Detective.
PhotobucketService.prototype.domains = {};

// DEFAULT: boolean docRepresentsSuccessfulLogin(in nsIDOMHTMLDocument aDocument);

// AString getAccountIDFromDocument(in nsIDOMHTMLDocument aDocument);
PhotobucketService.prototype.getAccountIDFromDocument =
function pbSvc_getAccountIDFromDocument(aDocument) {
  this._logger.debug(".getAccountIDFromDocument(aDocument)");
  aDocument.QueryInterface(CI.nsIDOMHTMLDocument);
  var results = CC["@mozilla.org/hash-property-bag;1"]
                .createInstance(CI.nsIWritablePropertyBag2);
  if (this._WebDetective.detect("photobucket",
                                "accountinfo",
                                aDocument,
                                results))
  {
    return results.getPropertyAsAString("userid");
  } else {
    // We could not detect any account info on the login landing page.  This
    // may mean that it's the API authentication login landing page, which
    // doesn't have any account info.  If this is the case, then we should
    // have a temp password entry associated with the session cookie.
    // XXX Use Web Detective
    var sessionValue = this._acUtils.getCookie("http://photobucket.com",
                                              "PHPSESSID");
    var pw =
      this._acUtils.getTempPassword("photobucket:session:" + sessionValue);
    if (pw) {
      this._logger.debug("Got acctID from temp password in session cookie!");
      return pw.username;
    }
  }
  return null;
};

// flockILoginInfo getCredentialsFromForm(in nsIDOMHTMLFormElement aForm);
PhotobucketService.prototype.getCredentialsFromForm =
function pbSvc_getCredentialsFromForm(aForm) {
  var inst = this;
  var detectForm = function pbsgc_detectForm(aType, aResults) {
    return inst._WebDetective.detectForm("photobucket", aType, aForm, aResults);
  };

  var formType = "login";
  var results = FlockSvcUtils.newResults();
  if (!detectForm(formType, results)) {
    formType = "signup";
    results = FlockSvcUtils.newResults();
    if (!detectForm(formType, results)) {
      formType = "changepassword";
      results = FlockSvcUtils.newResults();
      if (!detectForm(formType, results)) {
        results = null;
      }
    }
  }

  if (results) {
    var pw = {
      QueryInterface: function pbsgcpw_QueryInterface(aIID) {
        if (!aIID.equals(CI.nsISupports) &&
            !aIID.equals(CI.nsILoginInfo) &&
            !aIID.equals(CI.flockILoginInfo))
        {
          throw CI.NS_ERROR_NO_INTERFACE;
        }
        return this;
      },
      username: results.getPropertyAsAString("username"),
      password: results.getPropertyAsAString("password"),
      hostname: null,
      formType: formType
    };

    // Doing a bit of a hack here...
    // Since the Photobucket login landing page doesn't always reveal which
    // account is logged in, we will need to look at the session cookie and
    // see if its the same as what it was when the user last logged in.  So
    // at this point we're just storing the account username as a temp
    // password entry associated with the session cookie token.
    // XXX Use Web Detective
    var sessionValue = this._acUtils.getCookie("http://photobucket.com",
                                              "PHPSESSID");
    this._acUtils.clearTempPassword("photobucket:session:" + sessionValue);
    this._acUtils.setTempPassword("photobucket:session:" + sessionValue,
                                  pw.username, "", formType);
    return pw;
  }
  return null;
};

// DEFAULT: boolean ownsDocument(in nsIDOMHTMLDocument aDocument);
// DEFAULT: boolean ownsLoginForm(in nsIDOMHTMLFormElement aForm);

/**
 * @see flockILoginWebService#updateAccountStatusFromDocument
 */
PhotobucketService.prototype.updateAccountStatusFromDocument =
function pbSvc_updateAccountStatusFromDocument(aDocument,
                                               aAccount,
                                               aFlockListener)
{
  if (aAccount) {
    // We're logged in to this account
    if (!aAccount.isAuthenticated()) {
      aAccount.login(aFlockListener);
      // Media bar needs to know that that authentication has changed
      this._obsSvc.notifyObservers(null, "apiAuthChange", this.shortName);
    }
  } else if (this._WebDetective
                 .detect("photobucket", "loggedout", aDocument, null))
  {
    // We're logged out (of all accounts)
    this._acUtils.markAllAccountsAsLoggedOut(this);
    this._deauthenticateAPI();
  }
};


/**************************************************************************
 * Flock Photobucket Service: flockIMediaWebService Implementation
 **************************************************************************/
// readonly attribute boolean supportsUsers;
PhotobucketService.prototype.supportsUsers = true;

// void decorateForMedia(in nsIDOMHTMLDocument aDocument
//                       in flockIWebServiceAccount aAccount);
PhotobucketService.prototype.decorateForMedia =
function pbSvc_decorateForMedia(aDocument, aAccount) {
  this._logger.debug(".decorateForMedia(aDocument)");
  var results = CC["@mozilla.org/hash-property-bag;1"]
                .createInstance(CI.nsIWritablePropertyBag2);
  if (this._WebDetective.detect("photobucket", "media", aDocument, results)) {
    var userid = results.getPropertyAsAString("userid");
    var mediaArr = [];

    if (!aDocument._flock_decorations) {
      aDocument._flock_decorations = {};
    }
    var media = {
      name: userid,
      query: "user:" + userid + "|username:" + userid,
      label: userid,
      favicon: this.icon,
      service: this.shortName
    };
    mediaArr.push(media);

    if (aDocument._flock_decorations.mediaArr) {
      aDocument._flock_decorations.mediaArr =
        aDocument._flock_decorations.mediaArr.concat(mediaArr);
    } else {
      aDocument._flock_decorations.mediaArr = mediaArr;
    }

    this._obsSvc.notifyObservers(aDocument, "media", "media:update");
  }
};

// void createAlbum(in flockIListener aFlockListener ...);
PhotobucketService.prototype.createAlbum =
function pbSvc_createAlbum(aFlockListener, aAccountId, aAlbumName) {
  this._logger.debug("createAlbum - aAlbumName = " + aAlbumName);

  var parentDir = "";
  var pathName = aAlbumName.split("/");
  var params = {};
  params.new_album_name = pathName.pop();

  for (var i = 0; i < pathName.length; i++) {
    parentDir += pathName[i] + "/";
  }

  if (parentDir.length) {
    params.parent_album_name = parentDir;
  }

  var svc = this;
  var albumCreationListener = {
    onResult: function pbSvc_createAlbum_onResult(aXML) {
      svc._logger.info(aAlbumName + " album successfully created");
      var newAlbum = CC[FLOCK_PHOTO_ALBUM_CONTRACTID]
                     .createInstance(CI.flockIPhotoAlbum);
      var newAlbumPath;
      if (params.parent_album_name) {
        newAlbumPath = params.parent_album_name + params.new_album_name;
      } else {
        newAlbumPath = params.new_album_name;
      }
      newAlbum.title = newAlbumPath;
      newAlbum.id = newAlbumPath;
      aFlockListener.onSuccess(newAlbum, "");
    },

    onError: function pbSvc_createAlbum_onError(aFlockError) {
      svc._logger.error("Error in album creation: "
                        + aFlockError.errorCode
                        + " Service: ["
                        + aFlockError.serviceErrorCode
                        + "] "
                        + aFlockError.serviceErrorString
                        );
      aFlockListener.onError(aFlockError, null);
    }
  };

  this._api.authenticatedCall(albumCreationListener, "createalbum", params);
};

// void findByUsername(in flockIListener aFlockListener, in AString aUsername);
PhotobucketService.prototype.findByUsername =
function pbSvc_findByUsername(aFlockListener, aUsername) {
  // XXX ERWAN: why are we making a call to the server, if we don't
  // use the xml we get as result??
  var inst = this;
  var myListener = {
    onResult: function pbSvc_findByUsername_onResult(aXML) {
      //var user = aXML.getElementsByTagName("photo")[0];
      // We should be getting all the user's details from the response xml, but
      // the pbucket response has all pertinent info with the photo tag.  If
      // there are no photos, then we have no user info
      var newUserObj = {};
      newUserObj.accountId = aUsername;
      newUserObj.serviceId = inst.contractID;
      newUserObj.name = aUsername;
      newUserObj.totalMedia = -1; // See mediabar.js
      var wrappedUser = {
        wrappedJSObject: { newUserObj: newUserObj }
      };
      aFlockListener.onSuccess(wrappedUser, "findByUsername");
    },

    onError: function pbSvc_findByUsername_onError(aXML) {
      aFlockListener.onError(null, null);
    }
  };
  var params = {};
  params.username = aUsername;
  this._api.authenticatedCall(myListener, "getrecentusermedia", params);
};

// void getAccountStatus(in flockIListener aFlockListener);
PhotobucketService.prototype.getAccountStatus =
function pbSvc_getAccountStatus(aFlockListener) {
  var inst = this;
  var myListener = {
    onResult: function pbSvc_getAccountStatus_onResult(aXML) {
      try {
        var result = CC["@mozilla.org/hash-property-bag;1"]
                     .createInstance(CI.nsIWritablePropertyBag2);
        var username = aXML.getElementsByTagName("username")[0]
                           .firstChild
                           .nodeValue;
        var maxSpace = aXML.getElementsByTagName("megabytes_allowed")[0]
                           .firstChild
                           .nodeValue;
        var usedSpace = aXML.getElementsByTagName("megabytes_used")[0]
                            .firstChild
                            .nodeValue;
        var isPremium = aXML.getElementsByTagName("premium")[0]
                            .firstChild
                            .nodeValue;
        result.setPropertyAsAString("username", username);
        result.setPropertyAsAString("maxSpace", maxSpace);
        result.setPropertyAsAString("usedSpace", usedSpace);
        result.setPropertyAsAString("maxFileSize", "");
        result.setPropertyAsAString("usageUnits", "megabytes");
        result.setPropertyAsBool("isPremium", (isPremium == "1"));
        aFlockListener.onSuccess(result, "");
      } catch (ex) {
        inst._logger.error("getaccountstatus error :" + ex);
      }
    },

    onError: function pbSvc_getAccountStatus_onError(aError) {
      aFlockListener.onError(aError, null);
    }
  };
  var params = {};
  this._api.authenticatedCall(myListener, "getuserinfo", params);
};

// void getAlbums(in flockIListener aFlockListener, in AString aUsername);
PhotobucketService.prototype.getAlbums =
function pbSvc_getAlbums(aFlockListener, aUsername) {
  var inst = this;
  var myListener = {
    onResult: function pbSvc_getAlbums_onResult(aXML) {
      try {
        var rval = inst._handleAlbumsResult(aXML);
        var albumsEnum = {
          hasMoreElements: function pbslenum_hasMoreElements() {
            return (rval.length > 0);
          },
          getNext: function pbslenum_getNext() {
            return rval.shift();
          },
          QueryInterface: function pbslenum_QI(aIID) {
            if (aIID.equals(CI.nsISimpleEnumerator)) {
              return this;
            }
            throw Cr.NS_ERROR_NO_INTERFACE;
          }
        };
        aFlockListener.onSuccess(albumsEnum, "");
      } catch (ex) {
        inst._logger.error(ex);
        aFlockListener.onError(null, null);
      }
    },

    onError: function pbSvc_getAlbums_onError(aXML) {
      aFlockListener.onError(null, null);
    }
  };
  var params = {};
  params.recurse = "true";
  params.view = "flat";
  params.media = "none";
  if (aUsername) {
    params.username = aUsername;
  }
  this._api.authenticatedCall(myListener, "getuseralbum", params);
};

PhotobucketService.prototype.getAlbumsForUpload = 
function pbSvc_getAlbumsForUpload(aFlockListener, aUsername) {
  this.getAlbums(aFlockListener, aUsername);
};

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

// flockIMediaItemFormatter getMediaItemFormatter();
PhotobucketService.prototype.getMediaItemFormatter =
function pbSvc_getMediaItemFormatter() {
  return flockMediaItemFormatter;
};

// void migrateAccount(in AString aId, in AString aUsername);
PhotobucketService.prototype.migrateAccount =
function pbSvc_migrateAccount( aId, aUsername) {
  var token = "";
  try {
    token = flock_getCharPref(PHOTOBUCKET_TOKEN_PREF);
  } catch (ex) { }

  this.addAccount(aId, false, null, aUsername, token);
};

// void search(in flockIListener aFlockListener,
//             in AString aQuery,
//             in long aCount,
//             in long aPage);
PhotobucketService.prototype.search =
function pbSvc_search(aFlockListener, aQueryString, aCount, aPage, aRequestId) {
  var aQuery = new QueryHelper(aQueryString);
  if (!aQuery.user) {
    this._queryChannel(aFlockListener, aQueryString, aCount, aPage, aRequestId);
    return;
  }
  var aUserid = aQuery.user;

  var inst = this;

  var myListener = {
    onResult: function pbSvc_search_onResult(aXML) {
      var rval = inst._handlePhotosResult(aXML);
      var resultsEnum = {
        hasMoreElements: function pbslenum_hasMoreElements() {
          return (rval.length > 0);
        },
        getNext: function pbslenum_getNext() {
          return rval.shift();
        },
        QueryInterface: function pbslenum_QueryInterface(aIID) {
          if (aIID.equals(CI.nsISimpleEnumerator)) {
            return this;
          }
          throw Cr.NS_ERROR_NO_INTERFACE;
        }
      };
      aFlockListener.onSuccess(resultsEnum, aRequestId);
    },

    onError: function pbSvc_search_onError(aFlockError) {
      aFlockListener.onError(aFlockError, aRequestId);
    }
  };

  var params = {};

  if (aUserid) {
    params.username = aUserid;
    params.perpage = aCount;
  }

  if (aQuery.search) {
    params.query = aQuery.search.split(".")[0];
  }
  if (aPage) {
    params.page = aPage;
  }
  if (aQuery.album) {
    params.album_name = aQuery.album;
  }

  params.media = "all";

  if (params.query) {
    this._api.call(myListener, "search", params);
  } else if (params.username) {
    if (params.album_name) {
      this._api.authenticatedCall(myListener, "getuseralbumpaginated", params);
    } else {
      this._api.authenticatedCall(myListener, "getrecentusermedia", params);
    }
  }
};

// boolean supportsFeature(in AString aFeature);
PhotobucketService.prototype.supportsFeature =
function pbSvc_supportsFeature(aFeature) {
  var supports = {};
  supports.tags = false;
  supports.title = false;
  supports.contacts = false;
  supports.description = true;
  supports.privacy = false;
  supports.fileName = true;
  supports.albumCreation = true;
  return supports[aFeature] ? true : false;
};

// boolean supportsSearch(in AString aQuery);
PhotobucketService.prototype.supportsSearch =
function pbSvc_supportsSearch( aQueryString ) {

  var aQuery = new QueryHelper(aQueryString);

  if (aQuery.special) {
    var channel = this._channels["special:" + aQuery.special];
    if (channel) {
      return channel.supportsSearch;
    }
  }

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

// void upload(in flockIPhotoUploadAPIListener aUploadListener,
//             in flockIPhotoUpload aUpload,
//             in AString aFilename);
PhotobucketService.prototype.upload =
function pbSvc_upload(aUploadListener, aUpload, aFile) {
  var inst = this;
  var sigParams = {};
  var myParams = {};

  this._logger.debug("uploading to album " + aUpload.album);
  myParams.version = PHOTOBUCKET_API_VERSION;
  myParams.scid = PHOTOBUCKET_SCID;
  myParams.method = "upload";
  myParams.description = aUpload.description;
  var prefService = CC["@mozilla.org/preferences-service;1"]
                    .getService(CI.nsIPrefService);
  var prefBranch = prefService.getBranch("flock.photo.uploader.");
  if (prefBranch.getPrefType("breadcrumb.enabled") &&
      prefBranch.getBoolPref("breadcrumb.enabled"))
  {
    var bundle = this.getStringBundle();
    myParams.description += bundle
      .GetStringFromName("flock.photobucket.uploader.breadcrumb");
  }
  myParams.title = aUpload.title;
  myParams.album_name = aUpload.album;
  myParams.session_key = this._getAuthUser().sessionkey;
  myParams.sig = this._api.buildSig("upload", sigParams, true);

  sigParams.session_key = this._getAuthUser().sessionkey;

  this._photoUploader = new FlockPhotoUploader();
  this._photoUploader.setEndpoint(inst._api._apiUrl);
  this._photoUploader.photoParam = "uploadfile";

  var myListener = {
    onResult: function pbSvc_upload_onResult(aResponseText) {
      var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
                   .createInstance(Components.interfaces.nsIDOMParser);
      var xml = parser.parseFromString(aResponseText, "text/xml");
      var photo = xml.getElementsByTagName("photo")[0];
      if (photo) {
        inst._logger.debug("uploaded pic named " + photo.getAttribute("name"));
        var rval = inst._handlePhotosResult(xml);
        aUploadListener.onUploadComplete(aUpload);
        aUploadListener.onUploadFinalized(aUpload, rval[0]);
      } else {
        var error = inst._api.getError("SERVICE_ERROR", xml, null);
        aUploadListener.onError(error);
      }
    },

    onError: function pbSvc_upload_onError(aErrorCode) {
      var error;
      if (aErrorCode) {
        error = inst._api.getError("HTTP_ERROR", null, aErrorCode);
      } else {
        error = inst._api.getError("SERVICE_ERROR", null, null);
      }
      aUploadListener.onError(error);
    },

    onProgress: function pbSvc_upload_onProgress(aCurrentProgress) {
        aUploadListener.onProgress(aCurrentProgress);
    }
  };
  this._photoUploader.upload(myListener, aFile, myParams, aUpload);
};

/**************************************************************************
 * Flock Photobucket Service: flockIMediaEmbedWebService Implementation
 **************************************************************************/

// boolean checkIsStreamUrl(in AString aUrl);
PhotobucketService.prototype.checkIsStreamUrl =
function pbSvc_checkIsStreamUrl(aUrl) {
  // Videos:
  // /player.swf?file=http://vid166.photobucket.com/albums/u113/h671226/Smart_Dog.flv
  // Images:
  // http://i166.photobucket.com/albums/u105/such-a-craka/thfhg.jpg
  if (this._WebDetective.detectNoDOM("photobucket",
                                     "isStreamUrl",
                                     "",
                                     aUrl,
                                     null))
  {
    this._logger.debug("Checking if url is photobucket stream: YES: " + aUrl);
    return true;
  }
  this._logger.debug("Checking if url is photobucket stream: NO: " + aUrl);
  return false;
};

// void getMediaQueryFromURL(in AString aUrl, in flockIListener aFlockListener);
PhotobucketService.prototype.getMediaQueryFromURL =
function pbSvc_getMediaQueryFromURL(aUrl, aFlockListener) {
  var userName = null;
  var detectResults = CC["@mozilla.org/hash-property-bag;1"]
                      .createInstance(CI.nsIWritablePropertyBag2);
  if (this._WebDetective.detectNoDOM("photobucket",
                                     "person",
                                     "",
                                     aUrl,
                                     detectResults))
  {
    userName = detectResults.getPropertyAsAString("userid");
  }

  if (userName) {
    var results = CC["@mozilla.org/hash-property-bag;1"]
                  .createInstance(CI.nsIWritablePropertyBag2);
    results.setPropertyAsAString("query",
                                 "user:" + userName + "|username:" + userName);
    results.setPropertyAsAString("title", this.title + " user: " + userName);
    aFlockListener.onSuccess(results, "query");
  } else {
    aFlockListener.onError(null, "Unable to get user.");
  }
};

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

/**************************************************************************
 * Flock Photobucket Service: flockIAuthenticateNewAccount Implementation
 **************************************************************************/

// DEFAULT: void authenticateNewAccount();

/**************************************************************************
 * Flock Photobucket Service: flockIPollingService Implementation
 **************************************************************************/

// void refresh(in AString aUrn, in flockIPollingListener aPollingListener);
PhotobucketService.prototype.refresh =
function pbSvc_refresh(aURN, aPollingListener) {
  aPollingListener.onResult();
};


/**************************************************************************
 * Flock Photobucket Service: nsITimerCallback Implementation
 **************************************************************************/

// void notify(in nsITimer timer);
PhotobucketService.prototype.notify =
function pbSvc_notify(aTimer) {
  var inst = this;
  var sessionRefreshListener = {
    onAuth: function pbSvc_notify_onAuth() {
      inst._logger.debug("refreshed session key with the timer");
    },

    onError: function pbSvc_notify_onError(aError) {
      inst._logger.error("error refreshing session key on the timer");
    }
  };
  this._getSessionKey(sessionRefreshListener);
};

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

// Create array of components.
var gComponentsArray = [PhotobucketService];

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

/**************************************************************************
 * END XPCOM Support
 **************************************************************************/
