// 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 CU = Components.utils;

CU.import("resource:///modules/FlockCryptoHash.jsm");
CU.import("resource:///modules/FlockPhotoUploader.jsm");

const XMLHTTPREQUEST_READYSTATE_COMPLETED = 4;
const FLICKR_CONTRACTID = "@flock.com/?photo-api-flickr;1";
const FLOCK_PHOTO_ALBUM_CONTRACTID  = "@flock.com/photo-album;1";

var EXPORTED_SYMBOLS = ["FlickrAPI"];

// String defaults... may be updated later through Web Detective
var gStrings = {
  "photopage": "http://www.flickr.com/photos/%accountid%/%photoid%/",
  "staticimage": "http://static.flickr.com/%server%/%photoid%_%secret%%size%.jpg",
  "buddyicon": "http://www.flickr.com/images/buddyicon.jpg",
  "staticbuddyicon": "http://static.flickr.com/%iconserver%/buddyicons/%owner%.jpg",
  "nofriendavataricon": "http://static.flickr.com/0/buddyicons/%owner%.jpg",
  "apikey": "92c2a562f0e9c2ed8dfe78f42a7734c7",
  "api_secret": "17b26c20558cf979",
  "endpoint": "http://www.flickr.com/services/rest/",
  "uploadendpoint": "http://www.flickr.com/services/upload/",
  "authendpoint": "http://www.flickr.com/services/auth/"
};

function FlickrAPI() {
  this._logger = CC["@flock.com/logger;1"].createInstance(CI.flockILogger);
  this._logger.init("flickrAPI");
  this._logger.info("Created Flickr API Object");
  this.acUtils = CC["@flock.com/account-utils;1"]
                 .getService(CI.flockIAccountUtils);
  this.webDetective = CC["@flock.com/web-detective;1"]
                      .getService(CI.flockIWebDetective);
  for (var s in gStrings) {
    gStrings[s] = this.webDetective.getString("flickr", s, gStrings[s]);
  }
  this.faves_coop = Components.classes["@flock.com/singleton;1"]
                              .getService(Components.interfaces.flockISingleton)
                              .getSingleton("chrome://flock/content/common/load-faves-coop.js")
                              .wrappedJSObject;

  this.authAccount = null;
  this.need2CreateAlbum = false;
  this.api_key = gStrings["apikey"];
  this.api_secret = gStrings["api_secret"];
  this.endpoint = gStrings["endpoint"];
  this.upload_endpoint = gStrings["uploadendpoint"];
  this.auth_endpoint = gStrings["authendpoint"];
  this.fakeAlbums = {};
  this.realishAlbums = {};

  this.req = null;
  this.frob = null;
  this.lastToken = new Date();
  this.authUser = null;
  this.filesToDelete = [];
  this.setEndpoint = function(aEndpoint) {
    this.endpoint = aEndpoint;
  };

  this.mAddingAlbums = false;
  this.mPhotos2Album = [];
  this.mCheckTicketsInProcess = false;
  this.mPendingTickets = {};
}

FlickrAPI.prototype.reset =
function FlickrAPI_reset() {
  this.hasCreateAlbum = null;
};

/*************************************************************************
 * Authentication
 *************************************************************************/

FlickrAPI.prototype.getAuthUser =
function FlickrAPI_getAuthUser() {
  return this.authUser;
};

FlickrAPI.prototype.setAuthAccount =
function flickrAPI_setAuthAccount(authAccountUrn) {
  this.authAccount = authAccountUrn;
};

FlickrAPI.prototype._getAccount =
function flickrAPI__getAccount(authAccountUrn) {
  var service = CC[FLICKR_CONTRACTID].getService(CI.flockIWebService);
  return service.getAccount(authAccountUrn);
};

FlickrAPI.prototype._getNewToken =
function flickrAPI__getNewToken(aListener, aFrob) {
  this._logger.info("entering _getNewToken");
  var acctCoopObj = this.faves_coop.get(this.authAccount);
  var flickrUser = new FlickrUser();
  var inst = this;
  var tokenListener = {
    onResult: function tokenListener_onResult(aXML) {
      flickrUser.token = aXML.getElementsByTagName("token")[0].firstChild.nodeValue;
      var user = aXML.getElementsByTagName("user")[0];
      flickrUser.nsid = user.getAttribute("nsid");
      flickrUser.id = user.getAttribute("nsid");
      flickrUser.username = user.getAttribute("username");
      flickrUser.fullname = user.getAttribute("fullname");
      inst.authUser = flickrUser;

      acctCoopObj.authToken = flickrUser.token;
      var account = inst._getAccount(acctCoopObj.id());
      account.setCustomParam("flickr_username", flickrUser.username);
      account.setCustomParam("flickr_fullname", flickrUser.fullname);
      account.setCustomParam("flickr_nsid", flickrUser.fullname);

      inst._logger.info("got token");
      this.lastToken = new Date();
      aListener.onSuccess();
    },
    onError: function tokenListener_onError(aError) {
      inst._logger.info("error getting token");
      var svc = CC[FLICKR_CONTRACTID].getService(CI.flockIWebService);
      inst.acUtils.markAllAccountsAsLoggedOut(svc);
      this.frob = null;
      if (!aError.errorCode) {
        aError.errorCode = 1002;
      }
      aListener.onError(aError);
    }
  };

  var frob = aFrob;
  if (!frob) {
    frob = this.frob;
  }
  var params = {
    frob: frob
  };
  this.call(tokenListener, "flickr.auth.getToken", params);
};


FlickrAPI.prototype.logout =
function flickrAPI_logout() {
  this.authUser = null;
  this.authAccount = null;
  this.frob = null;
  this.refreshMediabarIfHasPrivate();
};


FlickrAPI.prototype.login =
function flickrAPI_login(aAccountURN, aListener) {
  this._logger.info("api.login('" + aAccountURN + "')");
  this.authAccount = aAccountURN;
  this.frob = null;
  var api = this;

  // Get new frob
  this._logger.info("api.login('" + aAccountURN + "'): need new token");
  var frobListener = {
    onResult: function FL_onResult(aXML) {
      try {
        var frobNode = aXML.getElementsByTagName("frob")[0];
        var frob = frobNode.firstChild.nodeValue;
        var authUrl = api.getAuthUrl(frob, "write");
        api.frob = frob;

        var hr = CC["@mozilla.org/xmlextras/xmlhttprequest;1"]
                 .createInstance(CI.nsIXMLHttpRequest);

        hr.onreadystatechange = function login_onReadyStateFunc(eEvt) {
          if (hr.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {

            var authPageListener = {
              onSuccess: function authPage_onSuccess(aSubject, aTopic) {
                api._logger.info("Successfully authorized!");
                api._getNewToken(aListener, frob);
              },
              onError: function authPage_onError(aError, aTopic) {
                api._logger.error(aError);
                aListener.onError(aError);
              }
            };

            var data = {
              api_key: api.api_key,
              frob: frob
            };

            api._handleAuthPage(authPageListener, hr.responseText, data);
          }
        };

        hr.mozBackgroundRequest = true;
        hr.overrideMimeType("text/txt");
        hr.open("GET", authUrl, true);
        hr.send(null);
      } catch (ex) {
        api._logger.info("caught this one in login: " + ex);
      }
    },
    onError: function FL_onError(aError) {
      api._logger.debug("There was an error getting the frob.");
      aListener.onError(aError);
    }
  };
  this.call(frobListener, "flickr.auth.getFrob", null);
};

FlickrAPI.prototype._handleAuthPage =
function FlockAPI__handleAuthPage(aListener, aResponseText, aData) {
  this._logger.debug("_handleAuthPage(...)");
  var api = this;
  var wd = api.webDetective;

  // For convenience
  function detect(aType, aResults) {
    return wd.detectNoDOM("flickr", aType, "", aResponseText, aResults);
  }

  // If we're done, bail now
  if (detect("apiAuthComplete")) {
    aListener.onSuccess(null, "apiAuthComplete");
    return;
  }

  // Helper to read data from the current page
  function assimilateResults(aResults) {
    var fields = aResults.getPropertyAsAString("fields");
    fields = fields.split(",");
    for each (let f in fields) {
      aData[f] = aResults.getPropertyAsAString(f);
    }
  }

  // Helper to determine details of the next request we need to make
  function getNextRequestDetails() {
    function getString(aName) {
      var s = wd.getString("flickr", aData.next + "_" + aName, "null");
      return (s == "null") ? null : s;
    }
    var next = {
      overrideMimeType: getString("overrideMimeType"),
      url: getString("url"),
      post: getString("post")
    };
    for each (let sub in getString("replacements").split(",")) {
      next.post = next.post.replace("%"+sub+"%", aData[sub]);
    }
    return next;
  }

  var results = CC["@mozilla.org/hash-property-bag;1"]
                .createInstance(CI.nsIWritablePropertyBag2);
  if (detect("apiAuthIncomplete", results)) {
    assimilateResults(results);
    var next = getNextRequestDetails();

    var xhr = CC["@mozilla.org/xmlextras/xmlhttprequest;1"]
              .createInstance(CI.nsIXMLHttpRequest);
    xhr.mozBackgroundRequest = true;
    if (next.overrideMimeType) {
      xhr.overrideMimeType(next.overrideMimeType);
    }

    xhr.onreadystatechange = function _handleAuthPage_ORSC(aEvent) {
      if (xhr.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
        api._handleAuthPage(aListener, xhr.responseText, aData);
      }
    };

    if (next.post) {
      xhr.open("POST", next.url);
      xhr.setRequestHeader("Content-Type",
                           "application/x-www-form-urlencoded");
      xhr.send(next.post);
    } else {
      xhr.open("GET", next.url, true);
      xhr.send(null);
    }
    return;
  }

  aListener.onError({
    errorCode: CI.flockIError.PHOTOSERVICE_LOGIN_FAILED,
    errorString: "Authentication error.",
    serviceName: "Flickr"
  }, "");
};

/*************************************************************************
 * Media consumption
 *************************************************************************/

FlickrAPI.prototype._getPhoto =
function FlickrAPI__getPhoto(aListener, aID) {
  var inst = this;
  var myListener = {
    onResult: function myListener_onResult(aXML) {
      var mediaItem = inst._handleInfoResult(aXML);
      aListener.onGetPhoto(mediaItem);
    },
    onError: function myListener_onError(aError) {
      aListener.onError(aError);
    }
  };
  var params = {};
  params.photo_id = aID;
  this.authenticatedCall(myListener, "flickr.photos.getInfo", params, true);
};

FlickrAPI.prototype.refreshMediabarIfHasPrivate =
function FlickrAPI_refreshMediabarIfHasPrivate() {
  // refresh the mediabar if it is open and contains private media
  var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                     .getService(Components.interfaces.nsIWindowMediator);
  var win = wm.getMostRecentWindow("navigator:browser");
  // if the mediabar is open
  if (win &&
      win.document.getElementById("mediabar") &&
      win.document.getElementById("mediabar").getAttribute("hidden") != "true" &&
      win.FlockTopbar.JXCtx().gPhotoDrawer &&
      win.FlockTopbar.JXCtx().gPhotoDrawer.mHasPrivateMedia)
  {
      win.FlockTopbar.JXCtx().gPhotoDrawer.refreshView();
  }
};

/*************************************************************************
 * Media upload
 *************************************************************************/

FlickrAPI.prototype.add2Album =
function FlickrAPI_add2Album(aUploadListener, aUpload, aID) {
  var inst = this;
  var listener = {
    onResult: function add2Al_lis_onResult(aXML) {
      inst.finalizePhoto(aUploadListener, aUpload, aID);
    },
    onError: function add2Al_lis_onError(aError) {
      inst.finalizePhoto(aUploadListener, aUpload, aID);
    }
  };
  var params = {};
  params.photoset_id = aUpload.album;
  params.photo_id = aID;
  this.authenticatedCall(listener, "flickr.photosets.addPhoto", params);
};

FlickrAPI.prototype.reallyCreateAlbum =
function flickrAPI_reallyCreateAlbum(aListener, aTitle, aID) {
  var myListener = {
    onResult: function fAPIrCA_onResult(aXML) {
      var newAlbum = CC[FLOCK_PHOTO_ALBUM_CONTRACTID]
                     .createInstance(CI.flockIPhotoAlbum);
      var photoset = aXML.getElementsByTagName("photoset")[0];
      newAlbum.title = aTitle;
      newAlbum.id = photoset.getAttribute("id");
      aListener.onCreateAlbumResult(newAlbum);
    },
    onError: function fAPIrCA_onError(aXML) {
      aListener.onError(aXML);
    }
  };
  var params = {
    title: aTitle,
    primary_photo_id: aID
  };
  this.authenticatedCall(myListener, "flickr.photosets.create", params);
};

FlickrAPI.prototype._handleInfoResult =
function FlickrAPI__handleInfoResult(aXML) {
  var photo = aXML.getElementsByTagName("photo")[0];
  var owner = aXML.getElementsByTagName("owner")[0];
  var visibility = aXML.getElementsByTagName("visibility")[0];
  var title = aXML.getElementsByTagName("title")[0].firstChild.nodeValue;
  var dates =  aXML.getElementsByTagName("dates")[0];
  var id = photo.getAttribute("id");
  var server = photo.getAttribute("server");
  var secret = photo.getAttribute("secret");
  var lastUpdate = dates.lastupdate;
  var uploadDate = dates.posted;
  var square_url = this.buildPhotoUrl(server, id, secret, "square");
  var small_url = this.buildPhotoUrl(server, id, secret, "small");
  var med_url = this.buildPhotoUrl(server, id, secret, "medium");
  var page_url = this.buildPageUrl(owner.getAttribute("nsid"), id);
  var icon_server = photo.getAttribute("iconserver");
  var newMediaItem = CC["@flock.com/photo;1"]
                     .createInstance(CI.flockIMediaItem);
  newMediaItem.init("flickr", null);
  newMediaItem.webPageUrl = page_url;
  newMediaItem.thumbnail = square_url;
  newMediaItem.midSizePhoto = small_url;
  newMediaItem.largeSizePhoto = med_url;
  newMediaItem.username = owner.getAttribute("username");
  newMediaItem.title = title;
  newMediaItem.id = id;
  newMediaItem.lastUpdate = lastUpdate;
  newMediaItem.uploadDate = parseInt(uploadDate) * 1000;
  if (icon_server == "1") {
    newMediaItem.icon = gStrings["buddyicon"];
  } else {
    newMediaItem.icon = gStrings["staticbuddyicon"]
                    .replace("%iconserver%", icon_server)
                    .replace("%owner%", owner);
  }
  var visibility_ispublic = visibility.getAttribute("ispublic");
  var ispublic = (visibility_ispublic == "1") ? "true" : "false";
  newMediaItem.is_public = ispublic;
  newMediaItem.is_video = false;
  return newMediaItem;
};


FlickrAPI.prototype.finalizePhoto =
function flickrAPI_finalizePhoto(aUploadListener, aUpload, aID) {
  try {
    var getPhotoListener = {
      onGetPhoto: function getPhotoListener_onGetPhoto(aMediaItem) {
        aUploadListener.onUploadFinalized(aUpload, aMediaItem);
      },
      onError: function getPhotoListener_onError(aError) {
        aUploadListener.onError(null);
      }
    };
    this._getPhoto(getPhotoListener, aID);
  } catch(e) {
    this._logger.error(e);
  }
};


FlickrAPI.prototype.processPendingAlbumAdditions =
function FlickrAPI_processPendingAlbumAdditions() {
  if (this.mAddingAlbums) {
    return;
  }
  if (this.mPhotos2Album.length == 0) {
    return;
  }
  this.mAddingAlbums = true;

  var obj = this.mPhotos2Album[0];
  var photoid = obj.photoid;
  var albumid = obj.albumid;
  var inst = this;
  var listener;

  if (this.fakeAlbums[albumid] && !this.realishAlbums[albumid]) {
    listener = {
      onCreateAlbumResult: function listener_onCreateAlbumResult(aAlbum) {
        inst.realishAlbums[albumid] = aAlbum.id;
        inst.mAddingAlbums = false;
      },
      onError: function listener_onError(aError) {
        inst.mAddingAlbums = false;
      }
    };
    var fakeAlbum = this.fakeAlbums[albumid];
    this.reallyCreateAlbum(listener,  fakeAlbum.title, photoid);
  } else {
    if (inst.realishAlbums[albumid]) {
      albumid = inst.realishAlbums[albumid];
    }
    listener = {
      onResult: function listener_onResult(aXML) {
        inst.finalizePhoto(obj.listener, obj.upload, photoid);
        inst.mPhotos2Album.shift();
        inst.mAddingAlbums = false;
        inst.processPendingAlbumAdditions();
        //re-enter to optimize
      },
      onError: function listener_onError(aError) {
        inst.finalizePhoto(obj.listener, obj.upload, photoid);
        inst.mPhotos2Album.shift();
        inst.mAddingAlbums = false;
        //re-enter to optimize?? this is an error condition, por
        //supuesto
      }
    };
    var params = {};
    params.photoset_id = albumid;
    params.photo_id = photoid;
    this.authenticatedCall(listener, "flickr.photosets.addPhoto", params);
  }
};

FlickrAPI.prototype.processPendingUploadTickets =
function FlickrAPI_processPendingUploadTickets() {
  if (this.mCheckTicketsInProcess) {
    return;
  }

  var ticketList = "";

  for (var p in this.mPendingTickets) {
    var obj = this.mPendingTickets[p];
    if (!obj) {
      //really should be removeing these - make it an array?
      continue;
    }
    ticketList += obj.ticketid + ",";
  }
  if (ticketList.length == 0) {
    return;
  }

  this.mCheckTicketsInProcess = true;

  var params = {
    tickets: ticketList
  };

  var inst = this;

  var ticketListener = {
    onResult: function ticketListener_onResult(aXML) {
      inst.mCheckTicketsInProcess = false;
      var ticketList = aXML.getElementsByTagName("ticket");
      for (var i = 0; i < ticketList.length; i++) {
        var ticket = ticketList[i];
        var id = ticket.getAttribute("id");
        var photoid = ticket.getAttribute("photoid");
        var complete = ticket.getAttribute("complete");
        var invalid = ticket.getAttribute("invalid");
        var obj = inst.mPendingTickets[id];

        if (complete =="0") {
          continue;
        }
        else if (invalid) {
          obj.listener.onError(null);
        }
        else if (complete =="1") {
          if (obj.upload.album && obj.upload.album.length>0) {
            obj.photoid = photoid;
            obj.albumid = obj.upload.album;
            obj.listener = obj.listener;
            inst.mPhotos2Album.push(obj);
          }
          else {
            inst.finalizePhoto(obj.listener, obj.upload, photoid);
          }
        }
        else if (complete =="2") {
          obj.listener.onError(null);
        }
        inst.mPendingTickets[id] = null;//that doesn't really remove it tho
      }
    },
    onError: function ticketListener_onError(aXML) {
      inst.mCheckTicketsInProcess = false;
    }
  };

  this.call(ticketListener, "flickr.photos.upload.checkTickets", params);
};

FlickrAPI.prototype.upload =
function FlickrAPI_upload(aListener, aPhoto, aParams, aUpload) {
  var inst = this;
  this.uploader = new FlockPhotoUploader();
  var myListener = {
    onResult: function FlickrAPI_upload_onResult(aResponseText) {
      var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
                   .createInstance(Components.interfaces.nsIDOMParser);
      var xml = parser.parseFromString(aResponseText, "text/xml");
      var rsp = xml.getElementsByTagName("rsp")[0];
      var stat = rsp.getAttribute("stat");
      if (stat != "ok") {
        var err = xml.getElementsByTagName("err")[0];
        var error= inst.getError("SERVICE_ERROR", xml, null);
        aListener.onError(error);
      }
      else {
        var ticketid = xml.getElementsByTagName("ticketid")[0].firstChild.nodeValue;
        inst._logger.info(ticketid + "pre ticketid");
        inst.mPendingTickets[ticketid] = {
          ticketid: ticketid,
          upload: aUpload,
          listener: aListener
        };
        inst._logger.info(ticketid + "post ticketid");
        aListener.onUploadComplete(aUpload);
      }
    },
    onError: function myListener_onError(aErrorCode) {
      if (aErrorCode) {
        aListener.onError(inst.getError("HTTP_ERROR", null, aErrorCode));
      } else {
        aListener.onError(inst.getError("SERVICE_ERROR", null));
      }
    },
    onProgress: function myListener_onProgress(aCurrentProgress) {
      aListener.onProgress(aCurrentProgress);
    }
  };
  this.convertBools(aParams);
  this.convertTags(aParams);

  aParams.auth_token = this.authUser.token;
  aParams.api_key = this.api_key;
  aParams = this.appendSignature(aParams);

  this.uploader.setEndpoint(this.upload_endpoint);
  this.uploader.upload(myListener, aPhoto, aParams);
  return;
};

/*************************************************************************
 * Actual calls to the server
 *************************************************************************/

FlickrAPI.prototype.convertBools =
function FlickrAPI_convertBools(aParams) {
  for (var p in aParams) {
    if (!p.match(/^is/)) {
      continue;
    }
    // I hope that this doesn't break anything
    if (aParams[p]=="true") {
      aParams[p] = "1";
    }
    if (aParams[p]=="false") {
      aParams[p] = "0";
    }
  }
};

FlickrAPI.prototype.convertTags =
function FlickrAPI_convertTags(aParams) {
  for (var p in aParams) {
    if (p != "tags") {
      continue;
    }
    var tags = aParams[p].split(",");
    for (var i = 0; i < tags.length;++i) {
      tags[i] = '"' + tags[i] + '"';
      tags[i] = tags[i].replace(/\"+/g,'"');
    }
    aParams[p] = tags.join(",");
  }
};

FlickrAPI.prototype.authenticatedCall =
function flickrAPI_authCall(aListener, aMethod, aParams, aAuthOptional) {
  if (!aParams) {
    aParams = {};
  }
  if (this.authUser) {
    aParams.auth_token = this.authUser.token;
  }

  if (this.authUser || (aAuthOptional && !this.authAccount)) {
    this.call(aListener, aMethod, aParams);
  } else {
    if (!this.authAccount) {
      throw "not logged in";
    }
    var inst = this;
    var authListener = {
      onSuccess: function authListener_onSuccess() {
        inst.authenticatedCall(aListener, aMethod, aParams);
      },
      onError: function authListener_onError(aFlockError) {
        inst.authAccount = false;
        var svc = CC[FLICKR_CONTRACTID].getService(CI.flockIWebService);
        inst.acUtils.markAllAccountsAsLoggedOut(svc);
        if(!aAuthOptional) {
          aListener.onError(aFlockError);
        }
        inst.call(aListener, aMethod, aParams);
      }
    };
    this.login(this.authAccount, authListener);
  }
};

FlickrAPI.prototype.call =
function FlickrAPI_call(aListener, aMethod, aParams) {
  this.convertBools(aParams);
  this.convertTags(aParams);
  if (aParams == null) {
    aParams = [];
  }
  if (aMethod) {
    aParams.method = aMethod;
  }
  aParams.api_key = this.api_key;
  var paramString = this.getParamString(aParams, (aMethod == null));
  var url = this.endpoint + "?" + paramString;
  this._doCall(aListener, url, null);
};

FlickrAPI.prototype._doCall =
function flApi__doCall(aListener, aUrl, aContent) {
  this._logger.info("Sending " + aUrl);
  this.req = CC["@mozilla.org/xmlextras/xmlhttprequest;1"]
             .createInstance(CI.nsIXMLHttpRequest);
  this.req.QueryInterface(CI.nsIJSXMLHttpRequest);
  this.req.mozBackgroundRequest = true;
  this.req.open("GET", aUrl, true);
  var req = this.req;
  var inst = this;
  this.req.onreadystatechange =
  function flApi__doCall_onreadystatechange(aEvt) {
    if (req.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
      if (Math.floor(req.status/100) == 2) {
         try {
          var responseXML = req.responseXML;
          var rsp = responseXML.getElementsByTagName("rsp")[0];
          var stat = rsp.getAttribute("stat");
          if (stat != "ok") {
            var err = responseXML.getElementsByTagName("err")[0];
            var error = inst.getError("SERVICE_ERROR", responseXML, null);
            aListener.onError(error);
          } else {
            aListener.onResult(responseXML);
          }
        } catch (ex) {
          // error parsing xml
          inst._logger.error(ex);
          aListener.onError(inst.getError("SERVICE_ERROR", null, null));
        }
      } else {
        // HTTP errors (0 for connection lost)
        aListener.onError(inst.getError("HTTP_ERROR", null, req.status));
      }
    }
  };
  this.req.send(null);
};

FlickrAPI.prototype.getError =
function FlickrAPI_getError(aErrorType, aXML, aHTTPErrorCode) {
  var error = CC["@flock.com/error;1"].createInstance(CI.flockIError);
  if  (aErrorType == "HTTP_ERROR") {
    error.errorCode = aHTTPErrorCode;
  } else if (aErrorType == "SERVICE_ERROR") {
    var errorCode;
    var errorMessage;
    var serviceErrorMessage;
    try {
      errorCode = aXML.getElementsByTagName("err")[0].getAttribute("code");
      serviceErrorMessage = aXML.getElementsByTagName("err")[0].getAttribute("msg");
    } catch (ex) {
      errorCode = "999"; // in case the error xml is invalid
    }

    switch (errorCode) {
      case "1":
        error.errorCode = error.PHOTOSERVICE_INVALID_USER;
      break;

      case "3":
        error.errorCode = error.PHOTOSERVICE_UPLOAD_ERROR;
      break;

      case "4":
        error.errorCode = error.PHOTOSERVICE_FILE_IS_OVER_SIZE_LIMIT;
      break;

      case "5":
        error.errorCode = error.PHOTOSERVICE_INVALID_UPLOAD_FILE;
      break;

      case "6":
        error.errorCode = error.PHOTOSERVICE_BANDWIDTH_REACHED;
      break;

      case "10":

      break;

      case "98":
        error.errorCode = error.PHOTOSERVICE_LOGIN_FAILED;
      break;

      case "99":
        error.errorCode = error.PHOTOSERVICE_USER_NOT_LOGGED_IN;
      break;

      case "100":
        error.errorCode = error.PHOTOSERVICE_INVALID_API_KEY;
        break;

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

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

      default:
        error.errorCode = error.PHOTOSERVICE_UNKNOWN_ERROR;
        error.serviceErrorString = serviceErrorMessage;
      break;
    }
  }
  return error;
};

FlickrAPI.prototype.getAuthUrl =
function FlickrAPI_getAuthUrl(aFrob, aPerms) {
  var api_key = this.api_key;
  var params = {
    api_key: api_key,
    perms: aPerms,
    frob: aFrob
  };

  var paramString = this.getParamString(params, true);
  return this.auth_endpoint + "?" + paramString;
};

FlickrAPI.prototype.getParamString =
function FlickrAPI_getParamString(aParams, aNoMethod) {
  aParams = this.appendSignature(aParams);
  var rval = "";
  if (!aNoMethod) {
    rval += "method=" + aParams.method + "&";
  }

  var count = 0;
  for (var p in aParams) {
    if (p.match(/method/)) {
      continue;
    }
    if (count++ != 0) {
      rval += "&";
    }
    rval += encodeURIComponent(p) + "=" + encodeURIComponent(aParams[p]);
  }
  return rval;
};

FlickrAPI.prototype.appendSignature =
function FlickrAPI_appendSignature(aParams) {
  var keys = [];
  for (var p in aParams) {
    keys.push(p);
  }
  keys.sort();
  var preHash = this.api_secret;
  for (var i = 0; i < keys.length; ++i) {
    preHash += keys[i] + aParams[keys[i]];
  }
  this._logger.info("preHash " + preHash);

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

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


FlickrAPI.prototype.buildPhotoUrl =
function FlickrAPI_buildPhotoUrl(server, id, secret, size) {
  var convertedSize = "";
  switch (size) {
    case "square":
      convertedSize = "_s";
      break;
    case "thumbnail":
      convertedSize = "_t";
      break;
    case "small":
      convertedSize = "_m";
      break;
    case "medium":
      convertedSize = "_d";
      break;
    case "large":
      convertedSize = "_b";
      break;
    case "original":
      convertedSize = "_o";
      break;
    default:
      convertedSize = "";
  }
  return gStrings["staticimage"].replace("%server%", server)
                                .replace("%photoid%", id)
                                .replace("%secret%", secret)
                                .replace("%size%", convertedSize);
};

FlickrAPI.prototype.buildPageUrl =
function FlickrAPI_buildPageUrl(aUserID, aPhotoID) {
  return gStrings["photopage"].replace("%accountid%", aUserID)
                              .replace("%photoid%", aPhotoID);
};

/*************************************************************************
 * FlickrUser class
 *************************************************************************/

function FlickrUser() {
  this.token = "";
  this.perms = "";
  this.nsid = "";
  this.id = "";
  this.username = "";
  this.fullname = "";
}

FlickrUser.prototype = {};
