// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2008
// http://flock.com
//
// This file may be used under the terms of the
// GNU General Public License Version 2 or later (the "GPL"),
// http://www.gnu.org/licenses/gpl.html
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
//
// END FLOCK GPL

const CC = Components.classes;
const CI = Components.interfaces;
const CR = Components.results;
const CU = Components.utils;

CU.import("resource:///modules/FlockXPCOMUtils.jsm");
FlockXPCOMUtils.debug = false;

CU.import("resource:///modules/FlockScheduler.jsm");
CU.import("resource:///modules/FlockSvcUtils.jsm");
CU.import("resource:///modules/FlockStringBundleHelpers.jsm");
CU.import("resource:///modules/ISO8601DateUtils.jsm");
CU.import("resource://gre/modules/JSON.jsm");

const MODULE_NAME = "Picasa";
const CLASS_NAME = "Flock Picasa Service";
const CLASS_SHORT_NAME = "picasa";
const CLASS_TITLE = "Picasa";
const CLASS_ID = Components.ID("{a01db128-4b4a-42a7-9645-95fbc2c03e9f}");
const CONTRACT_ID = "@flock.com/photo/picasa;1";
const FLOCK_ERROR_CONTRACTID = "@flock.com/error;1";
const FLOCK_PHOTO_ALBUM_CONTRACTID  = "@flock.com/photo-album;1";

const SERVICE_ENABLED_PREF = "flock.service.picasa.enabled";
const FAVICON = "chrome://flock/content/services/picasa/favicon.png";
const PICASA_URL = "http://picasaweb.google.com";
const PICASA_API_POST_URL = "http://picasaweb.google.com/data/feed/api/user/";
const SERVICE_PROPERTIES = "chrome://flock/locale/services/picasa.properties";
const XMLHTTPREQUEST_CONTRACTID = "@mozilla.org/xmlextras/xmlhttprequest;1";
const GPHOTO_NAMESPACE = "http://schemas.google.com/photos/2007";
const GDATA_ALBUM_SCHEMA = "http://schemas.google.com/photos/2007#album";
const GDATA_PHOTO_SCHEMA = "http://schemas.google.com/photos/2007#photo";
const MEDIA_NAMESPACE = "http://search.yahoo.com/mrss/";
const ATOM_NAMESPACE = "http://www.w3.org/2005/Atom";

const PICASA_API_LOGIN_URL = "https://www.google.com/accounts/ClientLogin";
// this is an arbitrary application id required by the Picasa api
// c.f. http://code.google.com/apis/accounts/AuthForInstalledApps.html
const PICASA_APPLICATION_ID = "flock-flockbrower-1.1";

const SERVICES_STRING_BUNDLE = "services/services";
const SERVICES_PHOTO_FEED_COMMENT_STRING = "flock.photo.feeds.comment.title";

var gPicasaApi = null;

// String defaults... may be updated later through Web Detective
 var gStrings = {
  "userprofile": "http://picasaweb.google.com/%accountid%",
  "commentsRSS": "http://picasaweb.google.com/data/feed/base/user/%accountid%?kind=comment&alt=rss&hl=en_US"
 };

function _getLoader() {
  return CC["@mozilla.org/moz/jssubscript-loader;1"]
         .getService(CI.mozIJSSubScriptLoader);
}

function loadLibraryFromSpec(aSpec) {
  _getLoader().loadSubScript(aSpec);
}

loadLibraryFromSpec("chrome://browser/content/flock/photo/photoAPI.js");

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

// Override the buildTooptip function in order to use the
// large img for the tooltip image.
var flockMediaItemFormatter = {
  canBuildTooltip: true,
  buildTooltip: function fmif_buildTooltip(aMediaItem) {
    default xml namespace =
      "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

    var xml =
      <vbox>
        <hbox>
          <image src={aMediaItem.largeSizePhoto} style="margin-bottom: 2px;" />
          <spacer flex="1" />
        </hbox>
        <hbox>
          <vbox>
            <image src={aMediaItem.icon} />
            <spacer flex="1" />
          </vbox>
          <vbox anonid="ttInfoContainer">
            <label anonid="ttTitleTxt">{aMediaItem.title}</label>
            <label anonid="ttUserTxt" class="user">{aMediaItem.username}</label>
          </vbox>
        </hbox>
      </vbox>;

    return xml;
  }
};

/*************************************************************************
 * Component: PicasaServiceAPI
 *************************************************************************/

function flockPicasaAPI() {
  this._acUtils = CC["@flock.com/account-utils;1"]
                 .getService(CI.flockIAccountUtils);
  FlockSvcUtils.getCoop(this);
  var obService = CC["@mozilla.org/observer-service;1"]
                  .getService(CI.nsIObserverService);
  this._WebDetective = CC["@flock.com/web-detective;1"]
                      .getService(CI.flockIWebDetective);
  this._logger = CC["@flock.com/logger;1"].createInstance(CI.flockILogger);
  this._logger.init("picasaAPI");

  var sbs = CC["@mozilla.org/intl/stringbundle;1"]
            .getService(CI.nsIStringBundleService);
  this._bundle = sbs.createBundle(SERVICE_PROPERTIES);

  this._authKey = null;
}

flockPicasaAPI.prototype.authenticate =
function picasaAPI_authenticate(aAcctUrn, aListener) {
  this._logger.debug("authenticate()");
  var api = this;
  var authListener = {
    onSuccess: function al_onSuccess() {
      api._logger.debug("al_onSuccess()");
      api.isLoggedIn = true;
      aListener.onSuccess();
    },
    onError: function al_onError(aErrorCode) {
      api._logger.debug("al_onError()");
      api.isLoggedIn = false;
      aListener.onError(aErrorCode);
    }
  };
  this._createAuthKey(aAcctUrn, authListener);
}

flockPicasaAPI.prototype.deauthenticate =
function picasaAPI_deauthenticate(aListener) {
  this._logger.debug("deauthenticate()");
  this._authKey = null;
  this.isLoggedIn = false;
}

flockPicasaAPI.prototype._createAuthKey =
function picasaAPI__createAuthKey(aAcctUrn, aListener) {
  this._logger.debug("_createAuthKey()");
  var c_acct = this._coop.get(aAcctUrn);

  var pw = this._acUtils.getPassword(this.svc.urn + ":" + c_acct.accountId);

  var params = {
    accountType: "HOSTED_OR_GOOGLE",
    service:     "lh2",
    source:      PICASA_APPLICATION_ID,
    Email:       c_acct.accountId,
    Passwd:      pw.password
  };

  var api = this;
  var dcListener = {
    onSuccess: function dcl_onSuccess(aResponseText) {
      api._logger.debug("dcl_onSuccess() - " + aResponseText + "\n");
      if (aResponseText.match(/Auth=(.+)/)) {
        api._logger.debug("setting authKey = " + RegExp.$1 + "\n");
        api._authKey = RegExp.$1;
        aListener.onSuccess();
      } else {
        aListener.onError(aError);
      }
    },
    onError: function dcl_onError(aError) {
      api._logger.debug("dcl_onError()");
      aListener.onError(aError);
    }
  };
  this._doLoginCall(dcListener, PICASA_API_LOGIN_URL, params);
}

flockPicasaAPI.prototype._doLoginCall =
function picasaAPI__doLoginCall(aListener, aUrl, aParams) {
  this._logger.debug("_doLoginCall()");
  var api = this;

  if (!aParams) {
    aParams = {};
  }
  var paramStr = this._getParamString(aParams);

  this.req = CC[XMLHTTPREQUEST_CONTRACTID].
             createInstance(CI.nsIXMLHttpRequest);
  this.req.QueryInterface(CI.nsIJSXMLHttpRequest);
  this._logger.debug("sending the url - " + aUrl + "\n");
  this._logger.debug("with params - " + paramStr + "\n");
  this.req.open("POST", aUrl, true);
  this.req.setRequestHeader("Content-Type",
                            "application/x-www-form-urlencoded");
  var req = this.req;
  this.req.onreadystatechange = function (aEvt) {
    if (req.readyState == 4) {
      try {
        api._logger.debug("._doLoginCall() Status = " + req.status);
        api._logger.debug("._doLoginCall() response = \n" + req.responseText);
        var responseText = req.responseText;
        if(req.status/100 == 2) {
          try {
            if (responseText.match(/Error=(.+)/)) {
              api._logger.debug(".doLoginCall - api returned an error");
              aListener.onError(api._getError(RegExp.$1));
            } else {
              aListener.onSuccess(responseText);
            }
          } catch (ex) {
            // error parsing xml
            api._logger.error("._doLoginCall() Parse error = " + ex);
            aListener.onError(api.getError(null));
          }
        } else {
          //  http errors
          api._logger.debug("._doLoginCall() HTTP Error - " + req.status);
          if (responseText.match(/Error=(.+)/)) {
            api._logger.debug(".doLoginCall - api returned a login error");
            aListener.onError(api._getError(RegExp.$1));
          } else {
            aListener.onError(api._getHTTPError(req.status));
          }
        }
      } catch (ex) {
        // XMLHTTPERROR (connection lost)
        api._logger.debug(".doLoginCall() " + ex);
        aListener.onError(api._getHTTPError("9001"));
      }
    }
  }
  this.req.send(paramStr);
}

flockPicasaAPI.prototype.doCall =
function picasaAPI_doCall(aListener, aUrl, aParams) {
  this._logger.debug("doCall()");
  var api = this;

  if (!aParams) {
    aParams = {};
  }
  var paramStr = this._getParamString(aParams);
  var url = aUrl + "?" + paramStr;
  this._logger.debug(".doCall() sending the url - " + url + "\n");

  this.req = CC[XMLHTTPREQUEST_CONTRACTID].
             createInstance(CI.nsIXMLHttpRequest);
  this.req.QueryInterface(CI.nsIJSXMLHttpRequest);
  this.req.open("GET", url, true);

  // Add auth header if api is logged in
  if (this._authKey) {
    this.req.setRequestHeader("Authorization",
                              "GoogleLogin auth=" + this._authKey);
  }
  var req = this.req;
  this.req.onreadystatechange = function (aEvt) {
    if (req.readyState == 4) {
      try {
        api._logger.debug(".doCall() Status = " + req.status);
        api._logger.debug(".doCall() response = \n" + req.responseText);
        var responseText = req.responseText;
        if(req.status/100 == 2) {
          try {
            if (req.responseXML) {
              aListener.onResult(req.responseXML);
            } else {
              api._logger.debug(".doCall - api returned no XML\n");
              aListener.onError(api._getError(null));
            }
          } catch (ex) {
            // error parsing xml
            api._logger.error(".doCall() Parse error = " + ex);
            aListener.onError(api._getError(null));
          }
        } else {
          //  http errors
          api._logger.debug(".doCall() HTTP Error - " + req.status);
          if (responseText.match(/Error=(.+)/)) {
            api._logger.debug(".doCall - api returned a login error\n");
            aListener.onError(api._getError(RegExp.$1));
          } else {
            aListener.onError(api._getHTTPError(req.status));
          }
        }
      } catch (ex) {
        // XMLHTTPERROR (connection lost)
        api._logger.debug(".doCall() " + ex);
        aListener.onError(api._getHTTPError("9001"));
      }
    }
  }
  this.req.send(null);
}

flockPicasaAPI.prototype._doXMLCall =
function picasaAPI__doXMLCall(aListener, aUrl, aParams, aXml, aMethod) {
  this._logger.debug("_doXMLCall()");
  var api = this;

  // If no method provided, use POST
  var method = (aMethod) ? aMethod : "POST";

  if (!aParams) {
    aParams = {};
  }
  this._logger.debug("._doXMLCall() sending the url - " + aUrl + "\n");

  this.req = CC[XMLHTTPREQUEST_CONTRACTID].
             createInstance(CI.nsIXMLHttpRequest);
  this.req.QueryInterface(CI.nsIJSXMLHttpRequest);
  this.req.open(method, aUrl, true);

  if (aParams && aParams.contentType) {
    this.req.setRequestHeader("Content-Type", aParams.contentType);
  }
  // Add auth header if api is logged in
  if (this._authKey) {
    this.req.setRequestHeader("Authorization", "GoogleLogin auth="+this._authKey);
  }

  var req = this.req;
  this.req.onreadystatechange = function (aEvt) {
    if (req.readyState == 4) {
      try {
        api._logger.debug("._doXMLCall() Status = " + req.status);
        api._logger.debug("._doXMLCall() response = \n" + req.responseText);
        var responseText = req.responseText;
        // 201 status = created
        if(req.status/100 == 2 || req.status == 201) {
          try {
            if (req.responseXML) {
              aListener.onResult(req.responseXML);
            } else {
              api._logger.debug("._doXMLCall - api returned no XML\n");
              aListener.onError(api._getError(null));
            }
          } catch (ex) {
            // Error parsing xml
            api._logger.error("._doXMLCall() Parse error = " + ex);
            aListener.onError(api._getError(null));
          }
        } else {
          //  Http errors
          api._logger.debug("._doXMLCall() HTTP Error - " + req.status);
          if (responseText.match(/Error=(.+)/)) {
            api._logger.debug("._doXMLCall - api returned a login error\n");
            aListener.onError(api._getError(RegExp.$1));
          } else {
            aListener.onError(api._getHTTPError(req.status));
          }
        }
      } catch (ex) {
        // XMLHTTPERROR (connection lost)
        api._logger.debug("._doXMLCall() " + ex);
        aListener.onError(api._getHTTPError("9001"));
      }
    }
  }
  this.req.send(aXml.toString());
}

flockPicasaAPI.prototype._doUpload =
function picasaAPI___doUpload(aListener, aUpload, aFilename, aParams) {
  this._logger.debug("picasaAPI___doUpload(aListener, '" +
                                           aFilename + "','"+
                                           aParams + "','"+
                                           aUpload +"')");
  var api = this;
  var listener = {
    onResult: function listener_onResult(aXml) {
      var xmlString = CC["@mozilla.org/xmlextras/xmlserializer;1"]
                      .getService(CI.nsIDOMSerializer)
                      .serializeToString(aXml);
      api._logger.debug("Uploaded content xml is: " + xmlString);

      // Due to a bug in Picasa api, we have to attach the photo tags with
      // a separate api call.
      // c.f. https://bugzilla.flock.com/show_bug.cgi?id=11342
      if (aUpload.tags) {
        var tagUrl;
        var links = aXml.getElementsByTagName("link");
        for (var j = 0; j < links.length; j++) {
          var link = links[j];
          if (link.getAttribute("rel") == "edit") {
            tagUrl = link.getAttribute("href");
            break;
          }
        }
        api.svc._lastXML = aXml;
        api._doPhotoTag(aListener, tagUrl, aUpload);
      } else {
        aListener.onUploadComplete(aUpload);

        photoNode = aXml.getElementsByTagName("entry")[0];
        var resultItem = api.svc._mediaItemFromEntryNode(photoNode, []);
        aListener.onUploadFinalized(aUpload, resultItem);
      }
    },
    onError: function listener_onError(aErrorCode) {
      aListener.onError(api._getError(aErrorCode));
    },
    onProgress: function listener_onProgress(aCurrentProgress) {
      aListener.onProgress(aCurrentProgress);
    }
  };

  var params = {};
  params.dataXml = aParams.dataXml; 
  params.boundaryString = "END_OF_PART";
  params.headers = [];
  params.headers["Content-Type"] = 'multipart/related; boundary="' +
                                    params.boundaryString + '"';
  if (api._authKey) {
    params.headers["Authorization"] = "GoogleLogin auth=" + api._authKey;
  }

  var endPointUrl = PICASA_API_POST_URL + aParams.accountId;
  var uploader = new PhotoUploader();

  // No album was selected. If default album "Flock Photos" exists upload to
  // it. Otherwise create an album named "Flock Photos" and upload to it.
  if (!aUpload.album) {
    const DEFAULT_ALBUM_NAME =
      this._bundle.GetStringFromName("flock.picasa.uploader.defaultAlbumName");

    var getAlbumListener = {
      onStart: function ga_al_onStart(aSubject, aTopic) {
      },
      onSuccess: function ga_al_onSuccess(aSubject, aTopic) {
        api._logger.debug("ga_al_onSuccess()");
        var defaultAlbumId;
        aSubject.QueryInterface(CI.nsISimpleEnumerator);
        while (aSubject.hasMoreElements()) {
          var album = aSubject.getNext();
          if (album.title == DEFAULT_ALBUM_NAME) {
            defaultAlbumId = album.id;
          }
        }

        if (defaultAlbumId) {
          // Found default Flock album
          endPointUrl += "/albumid/" + defaultAlbumId;
          uploader.setEndpoint(endPointUrl);
          uploader.uploadWithXml(listener, aFilename, params, aUpload);
        } else {
          // Create default Flock album
          var createAlbumListener = {
            onStart: function ca_al_onStart(aSubject, aTopic) {
            },
            onSuccess: function ca_al_onSuccess(aSubject, aTopic) {
              api._logger.debug("ca_al_onSuccess()");
              aSubject.QueryInterface(CI.flockIPhotoAlbum);
              endPointUrl += "/albumid/" + aSubject.id;
              uploader.setEndpoint(endPointUrl);
              uploader.uploadWithXml(listener, aFilename, params, aUpload);
            },
            onError: function ca_al_onError(aSubject, aTopic, aError) {
              api._logger.debug("ca_al_onError()");
              aListener.onError(aError);
            }
          };

          api.svc.createAlbum(createAlbumListener, DEFAULT_ALBUM_NAME);
        }
      },
      onError: function ga_al_onError(aSubject, aTopic, aError) {
        api._logger.debug("ga_al_onError()");
        aListener.onError(aError);
      }
    };

    this.svc.getAlbums(getAlbumListener, aParams.accountId);
  } else {
    endPointUrl += "/albumid/" + aUpload.album;
    uploader.setEndpoint(endPointUrl);
    uploader.uploadWithXml(listener, aFilename, params, aUpload);
  }
}

flockPicasaAPI.prototype._doPhotoTag =
function picasaAPI__doPhotoTag(aListener, aUrl, aUpload) {
  this._logger.debug("_doPhotoTag()");

  var api = this;

  // Strip out double quotes in tags
  var tags = aUpload.tags.replace(/"/g, "");

  default xml namespace = ATOM_NAMESPACE;
  var tagXml = <entry xmlns={ATOM_NAMESPACE} xmlns:media={MEDIA_NAMESPACE}>
                 <media:group>
                   <media:keywords>{tags}</media:keywords>
                 </media:group>
                 <category term={GDATA_PHOTO_SCHEMA}
                           scheme="http://schemas.google.com/g/2005#kind" />
               </entry>;

  var tagListener = {
    onResult: function tl_onResult(aXml) {
      api._logger.debug("tl_onResult()");
      aListener.onUploadComplete(aUpload);
      photoNode = api.svc._lastXML.getElementsByTagName("entry")[0];
      var resultItem = api.svc._mediaItemFromEntryNode(photoNode, []);

      aListener.onUploadFinalized(aUpload, resultItem);
    },
    onError: function listener_onError(aError) {
      api._logger.debug("tl_onError()");
      aListener.onError(aError);
    }
  };

  var params = {};
  params.contentType = "application/atom+xml";
  this._doXMLCall(tagListener, aUrl, params, tagXml, "PUT");
}

flockPicasaAPI.prototype._getParamString =
function picasaAPI__getParamString(aParams) {
  var rval = "";

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

    // The gdata api uses some params with "_". since JS does not allow "-"
    // in var names, we use "_" in the params obj and convert back to "-"
    // before passing to google
    // e.g. start-index, max-results
    if (p.indexOf("_") != -1) {
      rval += encodeURIComponent(p.replace("_", "-"))
           + "=" + encodeURIComponent(aParams[p]);
    } else {
      rval += encodeURIComponent(p) + "=" + encodeURIComponent(aParams[p]);
    }
  }

  return rval;
}

flockPicasaAPI.prototype._getError =
function picasaAPI__getError(aErrorCode) {
  var error = CC[FLOCK_ERROR_CONTRACTID].createInstance(CI.flockIError);

  this._logger.debug("._getError() Picasa error code: " + aErrorCode);
  switch (aErrorCode) {
    case "BadAuthentication":
    // The login request used a username or password that is not recognized.
      error.errorCode = CI.flockIError.PHOTOSERVICE_LOGIN_FAILED;
      break;
    case "20":
      error.errorCode = CI.flockIError
                          .PHOTOSERVICE_PHOTOS_IN_ALBUM_LIMIT_REACHED;
      break;
    default:
      error.errorCode = CI.flockIError.PHOTOSERVICE_UNKNOWN_ERROR;
  }

  return error;
}

flockPicasaAPI.prototype._getHTTPError =
function picasaAPI__getHTTPError(aHTTPErrorCode) {
  var error = CC[FLOCK_ERROR_CONTRACTID].createInstance(CI.flockIError);
  error.errorCode = aHTTPErrorCode;

  return error;
}

/*************************************************************************
 * Component: PicasaService
 *************************************************************************/
function PicasaService() {
  this._init();

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

  FlockSvcUtils.flockIWebService.addDefaultMethod(this, "getAccount");
  FlockSvcUtils.flockIWebService.addDefaultMethod(this, "isHidden");
  FlockSvcUtils.flockIWebService.addDefaultMethod(this, "getAuthenticatedAccount");
  FlockSvcUtils.flockIWebService.addDefaultMethod(this, "getAccounts");
  FlockSvcUtils.flockIWebService.addDefaultMethod(this, "removeAccount");
  FlockSvcUtils.flockIWebService.addDefaultMethod(this, "logout");
  FlockSvcUtils.flockIWebService.addDefaultMethod(this, "getStringBundle");

  FlockSvcUtils.flockIManageableWebService
               .addDefaultMethod(this, "docRepresentsSuccessfulLogin");
  FlockSvcUtils.flockIManageableWebService
               .addDefaultMethod(this, "getAccountIDFromDocument");
  FlockSvcUtils.flockIManageableWebService
               .addDefaultMethod(this, "getCredentialsFromForm");
  FlockSvcUtils.flockIManageableWebService
               .addDefaultMethod(this, "ownsDocument");
  FlockSvcUtils.flockIManageableWebService
               .addDefaultMethod(this, "ownsLoginForm");
  FlockSvcUtils.flockIManageableWebService
               .addDefaultMethod(this, "getSessionValue");

  FlockSvcUtils.flockIMediaWebService.addDefaultMethod(this, "getChannel");
  FlockSvcUtils.flockIMediaWebService.addDefaultMethod(this, "enumerateChannels");
  FlockSvcUtils.flockIMediaUploadWebService.addDefaultMethod(this, "getAlbumsForUpload");
}


/*************************************************************************
 * PicasaService: XPCOM Component Creation
 *************************************************************************/

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

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


/*************************************************************************
 * PicasaService: Private Data and Functions
 *************************************************************************/

// Member variables.
PicasaService.prototype._init =
function fps__init() {
  FlockSvcUtils.getLogger(this);
  this._logger.debug(".init()");

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

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

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

  this._accountClassCtor = PicasaAccount;

  FlockSvcUtils.getCoop(this);

  this._baseUrn = "urn:picasa";
  this._serviceUrn = this._baseUrn + ":service";

  if (this._coop.Service.exists(this._serviceUrn)) {
    this._c_svc = this._coop.get(this._serviceUrn);
  } else {
    this._c_svc = new this._coop.Service(this._serviceUrn, {
      name: CLASS_SHORT_NAME,
      desc: CLASS_TITLE
    });
  }
  this._c_svc.serviceId = CONTRACT_ID;
  this._c_svc.logoutOption = true;

  // Note that using FlockSvcUtils.getWD here adds the "_WebDetective"
  // property to the service.
  this._c_svc.domains = FlockSvcUtils.getWD(this)
                                     .getString(CLASS_SHORT_NAME,
                                                "domains",
                                                "google.com");
  this._c_svc.loginURL = FlockSvcUtils.getWD(this)
                                      .getString(CLASS_SHORT_NAME,
                                                 "userlogin",
                                                 "https://www.google.com/accounts/ServiceLogin?hl=en_US&continue=http%3A%2F%2Fpicasaweb.google.com%2F&service=lh2&passive=true");

  // Initialize API
  if(!gPicasaApi) {
    gPicasaApi = new flockPicasaAPI();
  }
  this.api = gPicasaApi;
  this.api.svc = this;

  // Load auth manager
  this._authMgr = CC["@flock.com/authmgr;1"]
                  .getService(CI.flockIAuthenticationManager);
  this._authMgr.joinNetwork(CONTRACT_ID, "google");

  // Update auth states
  try {
    if (!this._detectLoggedInCookies()) {
      this._acUtils.markAllAccountsAsLoggedOut(CONTRACT_ID);
    }
  } catch (ex) {
    this._logger.error("ERROR updating auth states: " + ex);
  }

  // On browser restart if a Picasa coop account is still marked as
  // authenticated we also need to reauth the api. We do this by calling
  // login() on the xpcom account obj.
  var inst = this;
  var reloginCallback = {
    notify: function reloginCallback_notify(aTimer) {
      inst._logger.debug("reloginCallback.notify()");
      var acctURN = inst._acUtils
                        .getFirstAuthenticatedAccountForService(CONTRACT_ID);

      if (acctURN) {
        var account = inst.getAccount(acctURN);
        if (account) {
          var reloginListener = {
            onSuccess: function reloginListener_onSuccess(aSubject, aTopic) {
              inst._logger.debug(".init(): reloginListener: onSuccess()");
            },
            onError: function reloginListener_onError(aError) {
              inst._logger.error(".init(): reloginListener: onError()");
            }
          };
          account.login(reloginListener);
        }
      }
    }
  };
  var reloginTimer = CC["@mozilla.org/timer;1"].createInstance(CI.nsITimer);
  // Wait 100ms before attempting to relogin api so Picasa service can finish
  // constructing itself.
  reloginTimer.initWithCallback(reloginCallback, 100, CI.nsITimer.TYPE_ONE_SHOT);

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

  profiler.profileEventEnd(evtID, "");
};

PicasaService.prototype._detectLoggedInCookies =
function fps___detectLoggedInCookies() {
  var cookies = this.api._WebDetective.getSessionCookies(CLASS_SHORT_NAME);
  // If we have any of the session cookies we are still logged in in google's
  // mind and therefore in our service as well.
  if (cookies) {
    return true;
  }  

  return false;
};

/*************************************************************************
 * PicasaService: flockIAuthenticateNewAccount Implementation
 *************************************************************************/

// DEFAULT: void authenticateNewAccount(in flockIListener aListener);

/*************************************************************************
 * PicasaService: flockIWebService Implementation
 *************************************************************************/

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

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

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

// readonly attribute AString url;
PicasaService.prototype.url = PICASA_URL;
PicasaService.prototype.redirUrl = PICASA_URL; // TODO: remove when we go live

// readonly attribute AString contractId;
PicasaService.prototype.contractId = CONTRACT_ID;

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

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

// readonly attribute long status;
PicasaService.prototype.status = CI.flockIWebService.STATUS_UNKNOWN;

/**
 * @see flockIWebService#addAccount
 */
PicasaService.prototype.addAccount =
function fps_addAccountById(aAccountId, aIsTransient, aListener)
{
  this._logger.info(".addAccount('" + aAccountId + "', "
                                        + aIsTransient + ")");

  var pw = this._acUtils.getPassword(this.urn + ":" + aAccountId);
  var name = (pw) ? pw.user : aAccountId;
  var url = gStrings["userprofile"].replace("%accountid%", aAccountId);
  var accountUrn = this._acUtils.createAccount(this,
                                               aAccountId,
                                               name,
                                               url,
                                               aIsTransient);
  var coopAccount = this._coop.get(accountUrn);

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

// DEFAULT: flockIWebServiceAccount getAccount(in AString aUrn);
// DEFAULT: flockIWebServiceAccount getAccounts();
// DEFAULT: flockIWebServiceAccount removeAccount(in AString aUrn);
// DEFAULT: flockIWebServiceAccount logout();

/*************************************************************************
 * PicasaService: flockIManageableWebService Implementation
 *************************************************************************/

// DEFAULT: boolean docRepresentsSuccessfulLogin(in nsIDOMHTMLDocument aDocument);
// DEFAULT: AString getAccountIDFromDocument(in nsIDOMHTMLDocument aDocument);
// DEFAULT: nsIPassword getCredentialsFromForm(in nsIDOMHTMLFormElement aForm);

// boolean ownsDocument(in nsIDOMHTMLDocument aDocument);
PicasaService.prototype.ownsDocument =
function fps_ownsDocument(aDocument)
{
  this._logger.debug(".ownsDocument()");
  this._logger.debug("domain: " + aDocument.domain);

  // We need to override the default ownsDocument in this case because
  // multiple services share the same login page (google.com in this case)
  return this._WebDetective.detect(this.shortName,
                                   "ownsDocument",
                                   aDocument,
                                   null);  
}

// DEFAULT: boolean ownsLoginForm(in nsIDOMHTMLFormElement aForm);

/**
 * void updateAccountStatusFromDocument(in nsIDOMHTMLDocument aDocument,
 *                                      in AString aAcctURN);
 *
 * @see flockIManageableWebService#updateAccountStatusFromDocument
 */
PicasaService.prototype.updateAccountStatusFromDocument =
function fps_updateAccountStatusFromDocument(aDocument, aAcctURN)
{
  this._logger.debug("updateAccountStatusFromDocument('" + aAcctURN + "')");
  if (aAcctURN) {
    var account = this.getAccount(aAcctURN);
    if (!account.isAuthenticated()) {
      var inst = this;
      var loginListener = {
        onSuccess: function () {
          inst._logger.debug(".updateAccountStatusFromDocument() loginListener: onSuccess()");
          inst._authMgr.reportCompleteLoginToNetwork(aAcctURN);
        },
        onError: function (aError) {
          inst._logger.error(".updateAccountStatusFromDocument() loginListener: onError()");
          inst._logger.error(aError ? (aError.errorString) : "No details");
          inst._coop.get(aAcctURN).isAuthenticated = false;
        }
      };
      account.login(loginListener);
    }
  } else if (this._WebDetective
                 .detect("picasa", "loggedout", aDocument, null))
  {
    // We're logged out (of all accounts)
    this._acUtils.markAllAccountsAsLoggedOut(CONTRACT_ID);
    // Also de-authenticate the API
    this.api.deauthenticate();
    this._authMgr.reportCompleteLogoutFromNetwork(this.contractId);
  }
}

/*************************************************************************
 * PicasaService: flockIMediaWebService Implementation
 *************************************************************************/
// readonly attribute boolean supportsUsers;
PicasaService.prototype.supportsUsers = true;

// void decorateForMedia (in nsIDOMHTMLDocument aDocument)
PicasaService.prototype.decorateForMedia =
function fps_decorateForMedia(aDocument) {
  this._logger.debug(".decorateForMedia()");
  var results = CC["@mozilla.org/hash-property-bag;1"]
                .createInstance(CI.nsIWritablePropertyBag2);

  var mediaArr = [];
  if (this._WebDetective.detect("picasa", "person", aDocument, results)) {
    this._logger.debug("detected person\n");
    var media = {
      name: results.getPropertyAsAString("username"),
      query: "user:" + results.getPropertyAsAString("userid")
                     + "|username:" + results.getPropertyAsAString("username"),
      label: results.getPropertyAsAString("username"),
      favicon: this.icon,
      service: this.shortName
    }
    mediaArr.push(media);
  } else if (this._WebDetective
                 .detect("picasa", "person_and_album", aDocument, results))
  {
    this._logger.debug("detected person_and_album\n");
    var media = {
      name: results.getPropertyAsAString("username"),
      query: "user:" + results.getPropertyAsAString("userid")
                     + "|username:" + results.getPropertyAsAString("username"),
      label: results.getPropertyAsAString("username"),
      favicon: this.icon,
      service: this.shortName
    }
    mediaArr.push(media);

    media = {
      name: results.getPropertyAsAString("albumid"),
      query: "user:" + results.getPropertyAsAString("userid")
                     + "|username:" + results.getPropertyAsAString("username")
                     + "|album:" + results.getPropertyAsAString("albumid")
                     + "|albumlabel:" + results.getPropertyAsAString("albumname"),
      label: results.getPropertyAsAString("albumname"),
      favicon: this.icon,
      service: this.shortName
    }
    mediaArr.push(media);
  }

  if (mediaArr.length > 0) {
    if (!aDocument._flock_decorations) {
      aDocument._flock_decorations = {};
    }
    aDocument._flock_decorations.mediaArr = mediaArr;
    this._obs.notifyObservers(aDocument, "media", "media:update");
  }
}

// BEGIN flockIMediaEmbedWebService interface
// boolean checkIsStreamUrl(in AString aUrl)
PicasaService.prototype.checkIsStreamUrl =
function fps_checkIsStreamUrl(aUrl) {
  this._logger.debug(".checkIsStreamUrl(" + aUrl + ")");
 if (this._WebDetective.detectNoDOM("picasa", "isStreamUrl", "", aUrl, null)) {
   this._logger.debug("Checking if url is picasa stream: YES: " + aUrl);
   return true;
 }

 this._logger.debug("Checking if url is picasa stream: NO: " + aUrl);
 return false;
}

// void getMediaQueryFromURL(in AString aUrl, in flockIListener aListener)
PicasaService.prototype.getMediaQueryFromURL =
function fps_getMediaQueryFromURL(aUrl, aListener) {
  this._logger.debug(".getMediaQueryFromURL()");

  var userId = null;
  var userName = null;
  var detectResults = CC["@mozilla.org/hash-property-bag;1"]
                      .createInstance(CI.nsIWritablePropertyBag2);
  if (this._WebDetective.detectNoDOM("picasa",
                                     "person",
                                     "",
                                     aUrl,
                                     detectResults))
  {
    userId = detectResults.getPropertyAsAString("userid");
  }

  if (userId) {
    var inst = this;
    var infoListener = {
      onResult: function il_onSuccess(aXml) {
        var feed = aXml.getElementsByTagName("feed")[0];
        userName =
          feed.getElementsByTagNameNS(GPHOTO_NAMESPACE, "nickname")[0]
              .firstChild.nodeValue;

        var results = CC["@mozilla.org/hash-property-bag;1"]
                      .createInstance(CI.nsIWritablePropertyBag2);
        results.setPropertyAsAString("query",
                                     "user:" + userId + "|username:" + userName);
        results.setPropertyAsAString("title", this.title + " user: " + userName);

        aListener.onSuccess(results, "query");
      },
      onError: function il_onError(aError) {
        aListener.onError(null, aError, null);
      }
    };

    var params = {
      kind: "album"
    };

    this.api.doCall(infoListener,
                     "http://picasaweb.google.com/data/feed/api/user/"
                       + userId,
                     params);
  } else {
    aListener.onError(null, "Unable to get user.", null);
  }
}

// boolean getSharingContent(in nsIDOMHTMLElement aSrc,
//                           in nsIWritablePropertyBag2 aProp);
PicasaService.prototype.getSharingContent =
function fps_getSharingContent(aSrc, aProp) {
  this._logger.debug("getSharingContent()");
  if (aSrc && aSrc instanceof CI.nsIDOMHTMLImageElement) {
    var results = FlockSvcUtils.newResults();
    if (this._WebDetective
            .detectNoDOM("picasa", "person", "", aSrc.src, results))
    {
      var url = aSrc.src;
      var title = (aSrc.title == "") ? aSrc.alt : aSrc.title;
      var doc = aSrc.ownerDocument;
      if (this.ownsDocument(doc)) {
        url = doc.URL;
        if (!title) {
          title = doc.title;
        }
      }
      aProp.setPropertyAsAString("url", url);
      aProp.setPropertyAsAString("title", title);
      return true;
    }
  }
  return false;
}
// END flockIMediaEmbedWebService interface

// void createAlbum()
PicasaService.prototype.createAlbum =
function fps_createAlbum(aListener, aAlbumName) {
  this._logger.debug("createAlbum('" + aAlbumName + "')");
  var acctUrn = this._acUtils
                     .getFirstAuthenticatedAccountForService(CONTRACT_ID);
  if (!acctUrn) {
    aListener.onError(null, null, this._createLoginError());
    return;
  }
  var coopAcct = this._coop.get(acctUrn);

  // Trim white space from front and end of string
  var newTitle = aAlbumName.replace(/^\s+|\s+$/g, "");
  if (newTitle) {
   default xml namespace = ATOM_NAMESPACE;
   var albumXml = <entry xmlns={ATOM_NAMESPACE}
                         xmlns:media={MEDIA_NAMESPACE}
                         xmlns:gphoto={GPHOTO_NAMESPACE}>
                    <title type="text">{newTitle}</title>
                    <summary type="text" />
                    <gphoto:location />
                    <gphoto:access>public</gphoto:access>
                    <gphoto:commentingEnabled>true</gphoto:commentingEnabled>
                    <category term={GDATA_ALBUM_SCHEMA}
                              scheme="http://schemas.google.com/g/2005#kind" />
                  </entry>;

    var myListener = {
      onResult: function listener_onResult(aXml) {
        var albumEntry = aXml.getElementsByTagName("entry")[0];
        var id = albumEntry.getElementsByTagNameNS(GPHOTO_NAMESPACE, "id")[0]
                           .firstChild.nodeValue;
        var title = albumEntry.getElementsByTagName("title")[0]
                              .firstChild.nodeValue;

        var newAlbum = CC[FLOCK_PHOTO_ALBUM_CONTRACTID]
                       .createInstance(CI.flockIPhotoAlbum);
        newAlbum.title = title;
        newAlbum.id = id;
        aListener.onSuccess(newAlbum, "");
      },
      onError: function listener_onError(aFlockError) {
        aListener.onError(null, null, aFlockError);
      }
    }
    var params = {};
    params.contentType = "application/atom+xml";
    this.api._doXMLCall(myListener,
                        PICASA_API_POST_URL + coopAcct.accountId,
                        params,
                        albumXml,
                        "POST");
  } else {
    var error = CC[FLOCK_ERROR_CONTRACTID].createInstance(CI.flockIError);
    error.errorCode = CI.flockIError.PHOTOSERVICE_EMPTY_ALBUMNAME;
    aListener.onError(null, null, error);
  }
}

// void getAlbums(in flockIListener aListener, in AString aUsername)
PicasaService.prototype.getAlbums =
function fps_getAlbums(aListener, aUsername) {
  this._logger.debug(".getAlbums(aListener, " + aUsername + ")");
  var inst = this;
  var albumListener = {
    onResult: function album_onSuccess(aXml) {
      inst._logger.debug("album_onSuccess()");
      var resultsArray = inst._handleAlbumResults(aXml);
      aListener.onSuccess(createEnum(resultsArray), "");
    },
    onError: function album_onError(aError) {
      inst._logger.debug("album_onError()");
      aListener.onError(null, null, null);
    }
  };

  var params = {
    kind: "album"
  };

  this.api.doCall(albumListener,
                   "http://picasaweb.google.com/data/feed/api/user/"
                     + aUsername,
                   params);
}

// DEFAULT: flockIMediaChannel getChannel(in AString AChannelId)
// DEFAULT: nsISimpleEnumerator enumerateChannels()
// DEFAULT: void getAlbumsForUpload(aListener, aUsername);

// flockIMediaItemFormatter getMediaItemFormatter()
PicasaService.prototype.getMediaItemFormatter =
function PicasaService_getMediaItemFormatter() {
  return flockMediaItemFormatter;
}

// void migrateAccount(in AString aId, in AString aUsername)
PicasaService.prototype.migrateAccount =
function fps_migrateAccount(aUrn, aUsername) {
  this._logger.debug(".migrateAccount()");
  throw CR.NS_ERROR_NOT_IMPLEMENTED;
}

// void search(in flockIListener aListener,
//             in AString aQuery,
//             in long aCount,
//             in long aPage);
PicasaService.prototype.search =
function fps_search(aListener, aQueryString, aCount, aPage, aRequestId) {
  this._logger.debug(".search() with query - " +aQueryString);
  var query = new queryHelper(aQueryString);

  // If the search string only contains whitespace, then return empty result
  var querySearch = query.getKey("search");
  if (querySearch && (querySearch.replace(/\s/g, "") == "")) {
    aListener.onSuccess(createEnum([]), aRequestId);
    return;
  }

  if (!aPage) {
    aPage = 1;
  }

  if (query.user) {
    this._searchUserPhotos(aListener, aQueryString, aCount, aPage, aRequestId);
    return;
  }

  var inst = this;

  var searchListener = {
    onResult: function sl_onResult(aXml) {
      inst._logger.debug("sl_onResult()");
      var resultsArray = inst._handlePhotoResults(aXml);
      aListener.onSuccess(createEnum(resultsArray), aRequestId);
    },
    onError: function sl_onError(aError) {
      inst._logger.debug("sl_onError()");
      aListener.onError(null, aRequestId, aError);
    }
  };

  var params = {
    kind: "photo",
    q: query.search,
    start_index: (aPage - 1) * aCount + 1,
    max_results: aCount
  };

  this.api.doCall(searchListener,
                   "http://picasaweb.google.com/data/feed/api/all",
                   params);
}

// boolean supportsFeature(in AString aFeature)
PicasaService.prototype.supportsFeature =
function fps_supportsFeature(aFeature) {
  this._logger.debug(".supportsFeature('" + aFeature + "')");
  var supports = {};
  supports.tags = true;
  supports.title = false;
  supports.fileName = true;
  supports.contacts = true;
  supports.privacy = false;
  supports.albumCreation = true;
  return (supports[aFeature] == true);
}

// boolean supportsSearch(in AString aQuery)
PicasaService.prototype.supportsSearch =
function fps_supportsSearch(aQuery) {
  this._logger.debug(".supportsSearch()");
  return false;
}

// void upload(in flockIPhotoUploadAPIListener aListener, in flockIPhotoUpload aUpload, in AString aFilename)
PicasaService.prototype.upload =
function fps_upload(aListener, aUpload, aFilename) {
  this._logger.debug("upload(aListener, '" + aUpload + "','"+ aFilename +"')");
  var acctUrn = this._acUtils
                     .getFirstAuthenticatedAccountForService(CONTRACT_ID);
  if (!acctUrn) {
    aListener.onError(this._createLoginError());
    return;
  }
  var coopAcct = this._coop.get(acctUrn);

  // Strip out double quotes in tags
  var tags = aUpload.tags.replace(/"/g, "");

  default xml namespace = ATOM_NAMESPACE;
  var photoXml = <entry xmlns={ATOM_NAMESPACE} xmlns:media={MEDIA_NAMESPACE}>
                   <title>{aUpload.fileName}</title>
                   <summary>{aUpload.description}</summary>
                   <media:group>
                     <media:keywords>{tags}</media:keywords>
                   </media:group>
                   <category term={GDATA_PHOTO_SCHEMA}
                             scheme="http://schemas.google.com/g/2005#kind" />
                 </entry>;

  var params = {};
  params.dataXml = photoXml.toString();
  params.accountId = coopAcct.accountId;
  this.api._doUpload(aListener, aUpload, aFilename, params);
}

// void findByUsername(in flockIListener aListener, in AString aUsername);
PicasaService.prototype.findByUsername =
function fps_findByUsername(aListener, aUsername) {
  // need to call this to trigger mediaObserver
  aListener.onError(null, null, null);
}

// void getAccountStatus(in flockIListener aListener)
PicasaService.prototype.getAccountStatus =
function fps_getAccountStatus(aListener) {
  this._logger.debug(".getAccountStatus()");
  var inst = this;
  var accountListener = {
    onResult:function accl_onResult(aXml) {
      inst._logger.info('we got a result for account status...');
      var result = CC["@mozilla.org/hash-property-bag;1"]
                   .createInstance(CI.nsIWritablePropertyBag2);
      var username = aXml.getElementsByTagNameNS(GPHOTO_NAMESPACE, "nickname")[0]
                         .firstChild.nodeValue;
      var usedSpace = aXml.getElementsByTagNameNS(GPHOTO_NAMESPACE, "quotacurrent")[0]
                          .firstChild.nodeValue;
      var maxSpace = aXml.getElementsByTagNameNS(GPHOTO_NAMESPACE, "quotalimit")[0]
                         .firstChild.nodeValue;
      result.setPropertyAsAString("username", username);
      result.setPropertyAsAString("maxSpace", maxSpace);
      result.setPropertyAsAString("usedSpace", usedSpace);
      result.setPropertyAsAString("maxFileSize", 20 * 1024 * 1024); // 20megs
      result.setPropertyAsAString("usageUnits", "bytes");
      result.setPropertyAsBool("isPremium", false);
      aListener.onSuccess(result, "");
    },
    onError: function accl_onError(aError) {
      aListener.onError(aError);
    }
  };

  var acctUrn = this._acUtils
                    .getFirstAuthenticatedAccountForService(CONTRACT_ID);
  var cAcct = this._coop.get(acctUrn);

  var params = {
    kind: "album"
  };

  this.api.doCall(accountListener,
                   "http://picasaweb.google.com/data/feed/api/user/"
                     + cAcct.accountId,
                   params);
}

PicasaService.prototype._searchUserPhotos =
function fps__searchUserPhotos(aListener,
                               aQueryString,
                               aCount,
                               aPage,
                               aRequestId)
{
  this._logger.debug("._searchUserPhotos() with query - " +aQueryString);
  var query = new queryHelper(aQueryString);
  var inst = this;
  var endpointUrl = "http://picasaweb.google.com/data/feed/api/user/"
                    + query.user;
  if (query.album) {
    endpointUrl += "/albumid/" + query.album;
  }

  var params = {
    kind: "photo",
    start_index: (aPage - 1) * aCount + 1,
    max_results: aCount
  };

  var albumsResult = null;
  var searchUserListener = {
    onResult: function sul_onResult(aXml) {
      inst._logger.debug("sul_onResult()");
      var albumsArray = [];
      if (albumsResult) {
        albumsResult.QueryInterface(CI.nsISimpleEnumerator);
        while(albumsResult.hasMoreElements()) {
          var album = albumsResult.getNext();
          albumsArray[album.id] = album.isPublic;
        }
      }
      var resultsArray = inst._handleUserPhotoResults(aXml, albumsArray);
      aListener.onSuccess(createEnum(resultsArray), aRequestId);
    },
    onError: function sul_onError(aError) {
      aListener.onError(null, aRequestId, aError);
    }
  };

  var getAlbumListener = {
    onStart: function sup_al_onStart(aSubject, aTopic) {
    },
    onSuccess: function sup_al_onSuccess(aSubject, aTopic) {
      inst._logger.debug("sup_al_onSuccess()");
      albumsResult = aSubject;
      inst.api.doCall(searchUserListener,
                       endpointUrl,
                       params);
    },
    onError: function sup_al_onError(aSubject, aTopic, aError) {
      inst._logger.debug("sup_al_onError()");
      if (!aError) {
        aError = inst.api._getError(null);
      }
      aListener.onError(null, aRequestId, aError);
    }
  };

  // Private photos are only displayed for the case when the user loads
  // their own user stream. If it is not their stream OR it is a search
  // based stream, private photos are not displayed.
  var acctUrn = this._acUtils
                    .getFirstAuthenticatedAccountForService(CONTRACT_ID);
  if(acctUrn == this._acUtils.getAccountURNById(this.urn, query.user)) {
    // Get photo privacy by querying the user's album privacy
    this.getAlbums(getAlbumListener, query.user);
  } else {
    // No need to check for photo privacy
    this.api.doCall(searchUserListener,
                     endpointUrl,
                     params);
  }
}

PicasaService.prototype._handlePhotoResults =
function fps__handlePhotoResults(aXml) {
  this._logger.debug(".handlePhotoResults()");
  var rval = [];
  var photoList = aXml.getElementsByTagName("entry");
  for (var i = 0; i < photoList.length; i++) {
    var photo = photoList[i];

    var id = photo.getElementsByTagNameNS(GPHOTO_NAMESPACE, "id")[0]
                  .firstChild.nodeValue;
    var title = photo.getElementsByTagName("title")[0]
                     .firstChild.nodeValue;
    var desc = "";
    if (photo.getElementsByTagName("summary")[0].firstChild) {
      desc = photo.getElementsByTagName("summary")[0]
                  .firstChild.nodeValue;
    }
    var ownerName = photo.getElementsByTagName("name")[0]
                         .firstChild.nodeValue;
    var owner = photo.getElementsByTagName("email")[0]
                     .firstChild.nodeValue;
    var pageUrl;
    var links = photo.getElementsByTagName("link");
    for(var j = 0; j < links.length; j++) {
      var link = links[j];
      if (link.getAttribute("rel") == "alternate") {
        pageUrl = link.getAttribute("href");
        break;
      }
    }

    var uploadDate = 0;
    var tsNode = photo.getElementsByTagNameNS(GPHOTO_NAMESPACE, "timestamp");
    if (photo.getElementsByTagName("published").length) {
      // Published date return in the format 2008-02-08T22:13:17.000Z
      var published = photo.getElementsByTagName("published")[0]
                           .firstChild.nodeValue;
      uploadDate = ISO8601DateUtils.parse(published).getTime();
    } else if (tsNode.length) {
      uploadDate = tsNode[0].firstChild.nodeValue;
    }

    var thumbs = photo.getElementsByTagNameNS(MEDIA_NAMESPACE, "thumbnail");
    var smallUrl = thumbs[0].getAttribute("url");
    var medUrl = thumbs[1].getAttribute("url");
    var largeUrl = thumbs[2].getAttribute("url");

    var newMediaItem = CC["@flock.com/photo;1"]
                       .createInstance(CI.flockIMediaItem);
    newMediaItem.init(this.shortName, flockMediaItemFormatter);
    newMediaItem.webPageUrl = pageUrl;
    newMediaItem.thumbnail = medUrl;
    newMediaItem.midSizePhoto = medUrl;
    newMediaItem.largeSizePhoto = largeUrl;
    newMediaItem.username = ownerName;
    newMediaItem.userid = owner;
    newMediaItem.title = (desc) ? desc : title;
    newMediaItem.id = id;
    newMediaItem.uploadDate = uploadDate;
    newMediaItem.is_public = true;
    newMediaItem.is_video = false;

    rval.push(newMediaItem);
  }

  return rval;
}

PicasaService.prototype._mediaItemFromEntryNode =
function _mediaItemFromEntryNode(photo, aAlbumsArray) {
  var id = photo.getElementsByTagNameNS(GPHOTO_NAMESPACE, "id")[0]
                .firstChild.nodeValue;
  var title = photo.getElementsByTagName("title")[0]
                   .firstChild.nodeValue;
  var desc = "";
  if (photo.getElementsByTagName("summary")[0].firstChild) {
    desc = photo.getElementsByTagName("summary")[0]
                .firstChild.nodeValue;
  }
  var pageUrl;
  var links = photo.getElementsByTagName("link");
  for (var j = 0; j < links.length; j++) {
    var link = links[j];
    if (link.getAttribute("rel") == "alternate") {
      pageUrl = link.getAttribute("href");
      break;
    }
  }
  var uploadDate =
    photo.getElementsByTagNameNS(GPHOTO_NAMESPACE, "timestamp")[0]
         .firstChild.nodeValue;
  var albumId =
    photo.getElementsByTagNameNS(GPHOTO_NAMESPACE, "albumid")[0]
         .firstChild.nodeValue;

  var thumbs = photo.getElementsByTagNameNS(MEDIA_NAMESPACE, "thumbnail");
  var smallUrl = thumbs[0].getAttribute("url");
  var medUrl = thumbs[1].getAttribute("url");
  var largeUrl = thumbs[2].getAttribute("url");

  var newMediaItem = CC["@flock.com/photo;1"]
                     .createInstance(CI.flockIMediaItem);
  newMediaItem.init(this.shortName, flockMediaItemFormatter);
  newMediaItem.webPageUrl = pageUrl;
  newMediaItem.thumbnail = medUrl;
  newMediaItem.midSizePhoto = medUrl;
  newMediaItem.largeSizePhoto = largeUrl;
  newMediaItem.title = (desc) ? desc : title;
  newMediaItem.id = id;
  newMediaItem.uploadDate = uploadDate;
  newMediaItem.is_public = (aAlbumsArray && aAlbumsArray[albumId] === undefined)
                           ? true
                           : aAlbumsArray[albumId];
  newMediaItem.is_video = false;
  return newMediaItem;
}

PicasaService.prototype._handleUserPhotoResults =
function fps__handleUserPhotoResults(aXml, aAlbumsArray) {
  this._logger.debug(".handleUserPhotoResults()");
  var rval = [];

  var ownerName = aXml.getElementsByTagName("name")[0]
                      .firstChild.nodeValue;
  var owner = aXml.getElementsByTagNameNS(GPHOTO_NAMESPACE, "user")[0]
                   .firstChild.nodeValue;
  var category = aXml.getElementsByTagName("category")[0]
                     .getAttribute("term");
  var icon;
  if (category != GDATA_ALBUM_SCHEMA) {
  icon = aXml.getElementsByTagName("icon")[0]
             .firstChild.nodeValue;
  }

  var photoList = aXml.getElementsByTagName("entry");
  for (var i = 0; i < photoList.length; i++) {
    var photo = photoList[i];
    var newMediaItem = this._mediaItemFromEntryNode(photo, aAlbumsArray);
    newMediaItem.username = ownerName;
    newMediaItem.userid = owner;
    newMediaItem.icon = icon;
    rval.push(newMediaItem);
  }
  return rval;
}

PicasaService.prototype._handleAlbumResults =
function fps__handleAlbumResults(aXml) {
  this._logger.debug(".handleAlbumsResults()");
  var rval = [];
  var photosetList = aXml.getElementsByTagName("entry");
  for (var i = 0; i < photosetList.length; i++) {
    var photoset = photosetList[i];
    var id = photoset.getElementsByTagNameNS(GPHOTO_NAMESPACE, "id")[0]
                     .firstChild.nodeValue;
    var title = photoset.getElementsByTagName("title")[0]
                        .firstChild.nodeValue;
    var rights = null;
    try {
      rights = photoset.getElementsByTagName("rights")[0]
                        .firstChild.nodeValue;
    } catch (ex) {
      this._logger.error("API seems to be missing rights nodes.");
    }

    var newAlbum = Components.classes[FLOCK_PHOTO_ALBUM_CONTRACTID]
                             .createInstance(CI.flockIPhotoAlbum);
    newAlbum.id = id;
    newAlbum.title = title;
    newAlbum.isPublic = (rights == "public") ? true : false;

    rval.push(newAlbum);
  }
  return rval;
}

PicasaService.prototype._createLoginError =
function fps__createLoginError() {
  this._logger.debug("._createLoginError()");
  var error = CC[FLOCK_ERROR_CONTRACTID].createInstance(CI.flockIError);
  error.errorCode = CI.flockIError.PHOTOSERVICE_USER_NOT_LOGGED_IN;
  return error;
}

/*************************************************************************
 * PicasaService: flockIPollingService Implementation
 *************************************************************************/

// void refresh(in AString aUrn, in flockIPollingListener aListener)
PicasaService.prototype.refresh =
function fps_refresh(aUrn, aListener) {
  this._logger.debug(".refresh(" + aUrn + ")");
  var refreshItem = this._coop.get(aUrn);

  if (refreshItem instanceof this._coop.Account) {
    this._logger.debug("refreshing an Account");
    if (this.api.isLoggedIn) {
      this._refreshAccount(aUrn, aListener);
    } else {
      // If the user is not logged in, return a success without
      // refreshing anything
      aListener.onResult();
    }
  } else {
    this._logger.error("can't refresh " + aUrn + " (unsupported type)");
    aListener.onError(null);
  }
};

PicasaService.prototype._refreshAccount =
function fps__refreshAccount(aUrn, aListener) {
  this._logger.debug("._refreshAccount(" + aUrn + ")");
  var coopAcct = this._coop.get(aUrn);
  var inst = this;
  var infoListener = {
    onResult: function album_onSuccess(aXml) {

      var feed = aXml.getElementsByTagName("feed")[0];
      var userName =
        feed.getElementsByTagNameNS(GPHOTO_NAMESPACE, "nickname")[0]
            .firstChild.nodeValue;
      var avatar =
        feed.getElementsByTagNameNS(GPHOTO_NAMESPACE, "thumbnail")[0]
            .firstChild.nodeValue;

      coopAcct.name = userName;
      coopAcct.avatar = avatar;

      aListener.onResult();
    },
    onError: function album_onError(aError) {
      aListener.onError(aError);
    }
  };

  var params = {
    kind: "album"
  };

  this.api.doCall(infoListener,
                   "http://picasaweb.google.com/data/feed/api/user/"
                     + coopAcct.accountId,
                   params);
}

/**************************************************************************
 * PicasaService: nsIObserver Implementation
 **************************************************************************/

// void observe(in nsISupports aSubject , in AString aTopic, in AString aData)
PicasaService.prototype.observe =
function fps_observe(aSubject, aTopic, aData) {
  switch (aTopic) {
    case "xpcom-shutdown":
      this._obs.removeObserver(this, "xpcom-shutdown");
      break;
  }
};


/*************************************************************************
 * Component: PicasaAccount
 *************************************************************************/

function PicasaAccount() {
  FlockSvcUtils.getLogger(this);
  this._logger.init("picasaAccount");

  FlockSvcUtils.getACUtils(this);
  FlockSvcUtils.getCoop(this);
  // XXX: I should be able to use FlockSvcUtils.getWD() here, but it can
  //      only be called by the service.
  this._WebDetective = CC["@flock.com/web-detective;1"]
                       .getService(CI.flockIWebDetective);

  FlockSvcUtils.flockIWebServiceAccount.addDefaultMethod(this, "deactivate");
  FlockSvcUtils.flockIWebServiceAccount.addDefaultMethod(this, "getService");
  FlockSvcUtils.flockIWebServiceAccount.addDefaultMethod(this, "isAuthenticated");
  FlockSvcUtils.flockIWebServiceAccount.addDefaultMethod(this, "getParam");
  FlockSvcUtils.flockIWebServiceAccount.addDefaultMethod(this, "setParam");
  FlockSvcUtils.flockIWebServiceAccount.addDefaultMethod(this, "getCustomParam");
  FlockSvcUtils.flockIWebServiceAccount.addDefaultMethod(this, "getAllCustomParams");
  FlockSvcUtils.flockIWebServiceAccount.addDefaultMethod(this, "setCustomParam");

  var sbs = CC["@mozilla.org/intl/stringbundle;1"]
            .getService(CI.nsIStringBundleService);
  this._bundle = sbs.createBundle(SERVICE_PROPERTIES);

  this.api = gPicasaApi;
}

/*************************************************************************
 * PicasaAccount: XPCOM Component Creation
 *************************************************************************/

// 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().
PicasaAccount.prototype = new FlockXPCOMUtils.genericComponent(
  CLASS_NAME + " Account",
  "",
  "",
  PicasaAccount,
  0,
  [
    CI.flockIWebServiceAccount,
    CI.flockIMediaUploadAccount,
    CI.flockIMediaWebServiceAccount
  ]
  );

/*************************************************************************
 * PicasaAccount: flockIWebServiceAccount Implementation
 *************************************************************************/

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

// void activate(in flockIListener aListener)
PicasaAccount.prototype.activate =
function fpa_activate(aListener) {
  this._logger.debug(".activate()");
  var c_acct = this._coop.get(this.urn);
  c_acct.isPollable = true;
  if (aListener) {
    aListener.onSuccess(this, "activate");
  }
}

// DEFAULT: void deactivate(in flockIListener aListener);

// void login(in flockIListener aListener);
PicasaAccount.prototype.login =
function fpa_login(aListener) {
  this._logger.debug(".login()");
  var inst = this;
  var myListener = {
    onSuccess: function listener_onSuccess() {
      inst._logger.debug("login_onSuccess()");
      inst._acUtils.ensureOnlyAuthenticatedAccount(inst.urn);
      // force refresh on login
      var pollerSvc = CC["@flock.com/poller-service;1"]
                      .getService(CI.flockIPollerService);
      pollerSvc.forceRefresh(inst.urn);
      if (aListener) {
        aListener.onSuccess(inst, "login");
      }
    },
    onError: function listener_onError(aError) {
      if (aListener) {
        aListener.onError(inst, "login", aError);
      }
    }
  }

  this.api.deauthenticate();
  this._acUtils.markAllAccountsAsLoggedOut(CONTRACT_ID);
  this.api.authenticate(this.urn, myListener);
}

// void logout(in flockIListener aListener);
PicasaAccount.prototype.logout =
function fpa_logout(aListener) {
  this._logger.debug(".logout()");
  var cAcct = this._coop.get(this.urn);
  if (cAcct.isAuthenticated) {
    cAcct.isAuthenticated = false;
    CC[cAcct.serviceId].getService(CI.flockIWebService).logout();
  }
  this.api.deauthenticate();
  this._acUtils.markAllAccountsAsLoggedOut(CONTRACT_ID);
  if (aListener) {
    aListener.onSuccess(this, "logout");
  }
}

// void keep();
PicasaAccount.prototype.keep =
function fpa_keep() {
  this._logger.debug(".keep()");
  var cAcct = this._coop.get(this.urn);
  cAcct.isTransient = false;
  var urn = this.urn.replace("account:", "service:").replace("flock:", "");
  this._acUtils.makeTempPasswordPermanent(urn);

  // Subscribe to the comments activity RSS feed for this account
  var feedMgr = CC["@flock.com/feed-manager;1"]
                .getService(CI.flockIFeedManager);
  var inst = this;
  var feedListener = {
    onGetFeedComplete: function keep_feed_complete(aFeed) {
      inst._logger.debug("keep_feed_complete()");
      feedMgr.getFeedContext("news").getRoot().subscribeFeed(aFeed);
      CC["@flock.com/metrics-service;1"]
        .getService(CI.flockIMetricsService)
        .report("FeedsSidebar-AddFeed",  "PicasaAccountKeep");

      var coop = FlockSvcUtils.getCoop(this);

      var title = flockGetString(SERVICES_STRING_BUNDLE,
                                 SERVICES_PHOTO_FEED_COMMENT_STRING);
      var username = coop.get(inst.urn).name;

      var feedTitle = title.replace("%s", username);

      coop.get(aFeed.id()).name = feedTitle;
    },
    onError: function keep_feed_error(aError) {
      inst._logger.debug("There was a problem subscribing to the recent "
                         + "activity feed for Picasa account "
                         + cAcct.accountId);
    }
  };
  try {
    var feedURI = CC["@mozilla.org/network/standard-url;1"]
                  .createInstance(CI.nsIURI);
    feedURI.spec = gStrings["commentsRSS"]
                   .replace("%accountid%", cAcct.accountId);
    feedMgr.getFeed(feedURI, feedListener);
  } catch (ex) {
    this._logger.debug("There was an error subscribing to the recent "
                       + "activity feed for Picasa account "
                       + cAcct.accountId);
  }
}

// DEFAULT: boolean isAuthenticated();

/*************************************************************************
 * PicasaAccount Private Data and Functions
 *************************************************************************/


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

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

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