// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2007
// http://flock.com
//
// This file may be used under the terms of 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

var EXPORTED_SYMBOLS = ["flockGoogleAuth"];

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

const 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 FLOCK_APPLICATION_ID = "flock-flockbrowser-2.0";
const XMLHTTPREQUEST_CONTRACTID = "@mozilla.org/xmlextras/xmlhttprequest;1";
const XMLHTTPREQUEST_READYSTATE_COMPLETED = 4;
const FLOCK_ERROR_CONTRACTID = "@flock.com/error;1";

const ERROR_PHOTOSERVICE_PHOTOS_LIMIT_REACHED = "20";

function flockGoogleAuth(aContext) {
  this._logger = CC["@flock.com/logger;1"].createInstance(CI.flockILogger),
  this._logger.init(aContext + "googleauth");

  this._acUtils = CC["@flock.com/account-utils;1"]
                 .getService(CI.flockIAccountUtils)
}

flockGoogleAuth.prototype.authenticate =
function FGA_authenticate(aFlockListener, aAuthCredentials, aLoginUrl) {
  this._logger.debug("authenticate() " + aAuthCredentials.authAccountId);
  var pw = this._acUtils.getPassword(aAuthCredentials.urn + ":" +
                                     aAuthCredentials.username);
  var url = (!aLoginUrl)?API_LOGIN_URL:aLoginUrl;
  var emailUN = (pw) ? pw.username.split("@")[0]
                     : aAuthCredentials.authAccountId;
  var params = {
    accountType: "HOSTED_OR_GOOGLE",
    service: aAuthCredentials.service,
    source: FLOCK_APPLICATION_ID,
    Email: emailUN,
    Passwd: (pw) ? pw.password : null
  };

  var api = this;
  var dcListener = {
    onSuccess: function dcl_onSuccess(aResponseText) {
      api._logger.debug("dcl_onSuccess() - " + aResponseText.match(/Auth=(.+)/));
      if (aResponseText.match(/Auth=(.+)/)) {
        var authKey = RegExp.$1;
        api._logger.debug("setting authKey = " + authKey);
        aFlockListener.onSuccess(authKey);
      } else {
        aFlockListener.onError(null);
      }
    },
    onError: function dcl_onError(aFlockError) {
      api._logger.debug("dcl_onError()");
      aFlockListener.onError(aFlockError);
    }
  };
  this._doLoginCall(dcListener, url, params);
}

flockGoogleAuth.prototype.doCall =
function FGA_doCall(aAuthKey, aListener, aUrl, aParams) {
  this._logger.debug("doCall() " + aAuthKey);

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

  this.req = CC[XMLHTTPREQUEST_CONTRACTID]
             .createInstance(CI.nsIXMLHttpRequest);
  this.req.open("GET", url, true);
  this._sendRequest(aAuthKey, this.req, aListener, null);
}

flockGoogleAuth.prototype.doXMLCall =
function FGA_doXMLCall(aAuthKey, aListener, aUrl, aParams, aXml, aMethod) {
  this._logger.debug("_doXMLCall()");

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

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

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

  if (aParams && aParams.contentType) {
    this.req.setRequestHeader("Content-Type", aParams.contentType);
  }
  this._sendRequest(aAuthKey, this.req, aListener, aXml.toString());
}

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

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

  return error;
}

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

  return error;
}

flockGoogleAuth.prototype._doLoginCall =
function FGA_doLoginCall(aListener, aUrl, aParams) {
  this._logger.debug("_doLoginCall()");
  var api = this;
  if (!aParams) {
    aParams = {};
  }
  
  var paramStr = this._getParamString(aParams);
  api._logger.debug("_doLoginCall --" + paramStr);
  api.req = CC[XMLHTTPREQUEST_CONTRACTID]
            .createInstance(CI.nsIXMLHttpRequest);
  api._logger.debug("sending url - " + aUrl + "\nwith params - " + paramStr);
  api.req.open("POST", aUrl, true);
  api.req.setRequestHeader("Content-Type",
                           "application/x-www-form-urlencoded");
  api.req.onreadystatechange = function login_onready(aEvt) {
    if (api.req.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
      var status = api.req.status;
      api._logger.debug("._doLoginCall() Status = " + status);
      var responseText = api.req.responseText;
      api._logger.debug("._doLoginCall() response = \n" + responseText);
      if (Math.floor(status/100) == 2) {
        try {
          if (responseText.match(/Error=(.+)/)) {
            api._logger.debug(".doLoginCall - api returned an error");
            aListener.onError(api._getError(RegExp.$1));
          } else {
            api._logger.debug("._doLoginCall() onSuccess \n");
            aListener.onSuccess(responseText);
          }
        } catch (ex) {
          // error parsing xml
          api._logger.error("._doLoginCall() Parse error = " + ex);
          aListener.onError(api._getError(null));
        }
      } else {
        // HTTP errors (0 for connection lost)
        api._logger.debug("._doLoginCall() HTTP Error - " + 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(status));
        }
      }
    }
  }
  api.req.send(paramStr);
}

flockGoogleAuth.prototype._sendRequest =
function FGA_sendRequest(aAuthKey, aReq, aListener, aBody) {
  // Add auth header if api is logged in
  if (aAuthKey) {
    var hdrValue = "GoogleLogin auth=" + aAuthKey;
    aReq.setRequestHeader("Authorization", hdrValue);
  }
  var api = this;
  aReq.onreadystatechange = function SR_onreadystatechange(aEvt) {
    if (aReq.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
      var status = aReq.status;
      api._logger.debug("._sendRequest() Status = " + status);
      var responseText = aReq.responseText;
      api._logger.debug("._sendRequest() response = \n" + responseText);
      // 201 status = created
      if (Math.floor(aReq.status/100) == 2) {
        try {
          if (aReq.responseXML) {
            api._logger.debug("._sendRequest() HTTP Parsing success");
            aListener.onResult(aReq.responseXML);
          } else {
            api._logger.debug("._sendRequest - api returned no XML");
            aListener.onError(api._getError(null));
          }
        } catch (ex) {
          // Error parsing xml
          api._logger.error("._sendRequest() Parse error = " + ex);
          aListener.onError(api._getError(null));
        }
      } else {
        // HTTP errors (0 for connection lost)
        api._logger.debug("._sendRequest() HTTP Error - " + status);
        if (responseText.match(/Error=(.+)/)) {
          api._logger.debug("._sendRequest - api returned a login error");
          aListener.onError(api._getError(RegExp.$1));
        } else {
          aListener.onError(api._getHTTPError(status));
        }
      }
    }
  }
  aReq.send(aBody);
}

flockGoogleAuth.prototype._getParamString =
function FGA_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(/_/g, "-"))
           + "=" + encodeURIComponent(aParams[p]);
    } else {
      rval += encodeURIComponent(p) + "=" + encodeURIComponent(aParams[p]);
    }
  }

  return rval;
}
