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

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

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

CU.import("resource:///modules/FlockStringBundleHelpers.jsm");
CU.import("resource:///modules/FlockSvcUtils.jsm");

const ONET_API_CLASS_NAME = "Flock Onet API";
const ONET_API_CLASS_ID = Components.ID("{7A66AF86-4A7C-4C35-9CB1-D1A026CC5F73}");
const ONET_API_CONTRACT_ID = "@flock.com/webservice/api/onet;1";
const ONET_API_MODULE_NAME = "onetAPI";
const ONET_API_ENDPOINT = "http://foto.onet.pl/flock/api";

const FLOCK_PHOTO_ALBUM_CONTRACTID  = "@flock.com/photo-album;1";
const FLOCK_ERROR_CONTRACTID = "@flock.com/error;1";
const XMLHTTPREQUEST_CONTRACTID = "@mozilla.org/xmlextras/xmlhttprequest;1";
const XMLHTTPREQUEST_READYSTATE_COMPLETED = 4;

/**************************************************************************
 * Component:  OnetAPI
 **************************************************************************/

// Constructor
function OnetAPI() {
  this.authenticated = false;
  FlockSvcUtils.getLogger(this);
  this._logger.init("OnetAPI");
  this._logger.debug("constructor");

  var wsa = FlockSvcUtils.flockIWebServiceAPI;
  wsa.addDefaultMethod(this, "getRequestMethod");
}

OnetAPI.prototype = new FlockXPCOMUtils.genericComponent(
  ONET_API_CLASS_NAME,
  ONET_API_CLASS_ID,
  ONET_API_CONTRACT_ID,
  OnetAPI,
  0,
  [
    CI.flockIAuthenticatedAPI,
    CI.flockIOnetAPI,
    CI.flockIWebServiceAPI,
  ]
);

/**************************************************************************
 * OnetAPI: private members
 **************************************************************************/

OnetAPI.prototype._UTFtoISO = {0x00A0:0xA0,0x0104:0xA1,0x02D8:0xA2,0x0141:0xA3,0x00A4:0xA4,0x013D:0xA5,0x015A:0xA6,0x00A7:0xA7,0x00A8:0xA8,0x0160:0xA9,0x015E:0xAA,0x0164:0xAB,0x0179:0xAC,0x00AD:0xAD,0x017D:0xAE,0x017B:0xAF,0x00B0:0xB0,0x0105:0xB1,0x02DB:0xB2,0x0142:0xB3,0x00B4:0xB4,0x013E:0xB5,0x015B:0xB6,0x02C7:0xB7,0x00B8:0xB8,0x0161:0xB9,0x015F:0xBA,0x0165:0xBB,0x017A:0xBC,0x02DD:0xBD,0x017E:0xBE,0x017C:0xBF,0x0154:0xC0,0x00C1:0xC1,0x00C2:0xC2,0x0102:0xC3,0x00C4:0xC4,0x0139:0xC5,0x0106:0xC6,0x00C7:0xC7,0x010C:0xC8,0x00C9:0xC9,0x0118:0xCA,0x00CB:0xCB,0x011A:0xCC,0x00CD:0xCD,0x00CE:0xCE,0x010E:0xCF,0x0110:0xD0,0x0143:0xD1,0x0147:0xD2,0x00D3:0xD3,0x00D4:0xD4,0x0150:0xD5,0x00D6:0xD6,0x00D7:0xD7,0x0158:0xD8,0x016E:0xD9,0x00DA:0xDA,0x0170:0xDB,0x00DC:0xDC,0x00DD:0xDD,0x0162:0xDE,0x00DF:0xDF,0x0155:0xE0,0x00E1:0xE1,0x00E2:0xE2,0x0103:0xE3,0x00E4:0xE4,0x013A:0xE5,0x0107:0xE6,0x00E7:0xE7,0x010D:0xE8,0x00E9:0xE9,0x0119:0xEA,0x00EB:0xEB,0x011B:0xEC,0x00ED:0xED,0x00EE:0xEE,0x010F:0xEF,0x0111:0xF0,0x0144:0xF1,0x0148:0xF2,0x00F3:0xF3,0x00F4:0xF4,0x0151:0xF5,0x00F6:0xF6,0x00F7:0xF7,0x0159:0xF8,0x016F:0xF9,0x00FA:0xFA,0x0171:0xFB,0x00FC:0xFC,0x00FD:0xFD,0x0163:0xFE,0x02D9:0xFF};

OnetAPI.prototype._HexStr = "0123456789ABCDEF";

/*************************************************************************
 * OnetAPI: flockIWebServiceAPI Implementation
 *************************************************************************/

// DEFAULT: AString getRequestMethod (in long aRequestMethod)

/**
 * void call(in AString aApiMethod,
 *           in nsISupports aParams,
 *           in nsISupports aPostVars,
 *           in nsISupports aRequestMethod,
 *           in flockIListener aFlockListener);
 * @see flockIWebServiceAPI#call
 */
OnetAPI.prototype.call =
function OnetAPI_call(aApiMethod,
                      aParams,
                      aPostVars,
                      aRequestMethod,
                      aFlockListener)
{
  var api = this;
  this.req = CC[XMLHTTPREQUEST_CONTRACTID]
             .createInstance(CI.nsIXMLHttpRequest);
  this.req.QueryInterface(CI.nsIJSXMLHttpRequest);

  var myParams = this.prepare(aApiMethod,
                             { wrappedJSObject: aParams }).wrappedJSObject;

  if (aApiMethod == "createAlbum") {
    // The API requires the envelope parameter to be encrypted for the
    // createAlbum method
    myParams.envelope = encodeURIComponent(myParams.envelope);
  }

  var url = ONET_API_ENDPOINT
          + "?cid=" + myParams.cid
          + "&rid=" + myParams.rid
          + "&fnc=" + myParams.fnc
          + "&rdr=" + myParams.rdr
          + "&envenc=" + myParams.envenc
          + "&envelope=" + myParams.envelope;

  this.req.open(this.getRequestMethod(aRequestMethod), url, true);
  var req = this.req;
  this.req.onreadystatechange =
  function doCallJSON_onreadystatechange(aEvt) {
    api._logger.debug(".call() ReadyState = " + req.readyState);
    if (req.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
      var status = req.status;
      api._logger.debug(".call() Status = " + status);
      if (Math.floor(status) / 100 == 2) {
        api._logger.debug(".call() response = \n" + req.responseText);
        var s = new CU.Sandbox("about:blank");
        var result = CU.evalInSandbox('['+req.responseText+']', s)[0];
        if (result && result.status && result.status.code != 0) {
          api._logger.debug(".call() Error");
          var error = api.getError(result.status.code, result.status.text);
          aFlockListener.onError(error, arguments.callee.name);
        } else {
          api._logger.debug(".call() Success");
          // Pass back wrappedJSObject for XPCOM support
          aFlockListener.onSuccess({ wrappedJSObject: result.data },
                                   arguments.callee.name);
        }
      } else {
        // HTTP errors (0 for connection lost)
        api._logger.debug(".call() HTTP Error");
        aFlockListener.onError(api.getError(status),
                               arguments.callee.name);
      }
    }
  }
  this._logger.debug(".call(): to url: " + url);
  this.req.send(null);
};

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

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

  return error;
};

/**
 * flockIError getError(in AString aErrorCode);
 * @see flockIWebServiceAPI#getError
 */
OnetAPI.prototype.getError =
function OnetAPI_getError(aErrorCode, aErrorMessage) {
  var error = CC[FLOCK_ERROR_CONTRACTID].createInstance(CI.flockIError);
  this._logger.debug(".getError() OnetAPI error code: " + aErrorCode + "\n");

  /* From the Onet API
   *  0 = OK
   * -1 = Incorrect/incomplete parameters
   * -2 = Server error
   * -3 = User not logged in
   * -4 = Quota exceeded
   */
  switch (aErrorCode) {
    case -1:
      error.errorCode = CI.flockIError.PHOTOSERVICE_INVALID_QUERY;
      break;
    case -3:
      error.errorCode = CI.flockIError.PHOTOSERVICE_USER_NOT_LOGGED_IN;
      error.serviceName = "Onet";
      break;
    case -4:
      error.errorCode = CI.flockIError
                          .PHOTOSERVICE_PHOTOS_IN_ALBUM_LIMIT_REACHED;
      break;
    case -2:
      error.errorCode = CI.flockIError.PHOTOSERVICE_UNAVAILABLE;
      break;
    case 999:
      error.errorCode = CI.flockIError.PHOTOSERVICE_UNKNOWN_ERROR;
      break;
    default:
      error.errorCode = aErrorCode;
      error.serviceErrorString = aErrorMessage;
      break;
  }
  return error;
};

/*************************************************************************
 * OnetAPI: flockIAuthenticatedAPI Implementation
 *************************************************************************/

/**
 * void authenticate(in nsILoginInfo aCredentials,
 *                   in flockIListener aFlockListener);
 * @see flockIAuthenticatedAPI#authenticate
 */
OnetAPI.prototype.authenticate =
function OnetAPI_authenticate(aCredentials, aFlockListener) {
  this._logger.debug(".authenticate("
                     + ((aCredentials) ? aCredentials.username : "")
                     + ", ...)");
  this.authenticated = true;
  var inst = this;
  var loginListener = {
    onSuccess: function OnetAPI_loginListener_onSuccess(aSubject) {
      aFlockListener.onSuccess(null, arguments.callee.name);
    },
    onError: function OnetAPI_loginListener_onError(aFlockError) {
      inst.authenticated = false;
      inst._logger.debug(".login() error: " + aFlockError.errorString);
      aFlockListener.onError(aFlockError, arguments.callee.name);
    }
  };
  this.call("login",
            {"login": aCredentials.username, "pass": aCredentials.password},
            null,
            CI.flockIWebServiceAPI.GET,
            loginListener);
};

/**
 * void deauthenticate();
 * @see flockIAuthenticatedAPI#deauthenticate
 */
OnetAPI.prototype.deauthenticate =
function OnetAPI_deauthenticate() {
  this.authenticated = false;
};

/*************************************************************************
 * OnetAPI: flockIOnetAPI Implementation
 *************************************************************************/

/**
 * nsISupports prepare(in AString aMethodName, in nsISupports aParams);
 * @call flockIOnetAPI#prepare
 */
OnetAPI.prototype.prepare =
function OnetAPI_prepare(aMethodName, aParams) {
  var serializedParams =
    this._utf2isoEntities(this._serialize(aParams.wrappedJSObject));

  var preparedParams = {
    fnc: aMethodName,
    rdr: "fjs",
    cid: "1",
    rid: "1",
    envenc: "1",
    envelope: serializedParams
  };
  return { wrappedJSObject: preparedParams };
};

/**
 * void getAlbums(in flockIListener aFlockListener,
 *                in AString aUsername);
 * @call flockIOnetAPI#getAlbums
 */
OnetAPI.prototype.getAlbums =
function OnetAPI_getAlbums(aFlockListener, aUsername) {
  this.call("getAlbums",
            {},
            null,
            CI.flockIWebServiceAPI.GET,
            aFlockListener);
};

/**
 * void createAlbum(in flockIListener aFlockListener,
 *                  in AString aUid,
 *                  in AString aAlbumTitle);
 * @call flockIOnetAPI#createAlbum
 */
OnetAPI.prototype.createAlbum =
function OnetAPI_createAlbum(aFlockListener, aUid, aAlbumTitle) {
  this._logger.debug(arguments.callee.name + "('" + aUid + "','"
                                                  + aAlbumTitle + "' ...)");
  var api = this;
  var createAlbumListener = {
    onSuccess: function OnetAPI_createAlbum_onSuccess(aSubject) {
      var album = aSubject.wrappedJSObject;
      api._logger.debug(arguments.callee.name);
      api._logger.debug(".createAlbum() Created, albumId = " + album.albumId);
      var newAlbum = CC[FLOCK_PHOTO_ALBUM_CONTRACTID]
                    .createInstance(CI.flockIPhotoAlbum);
      newAlbum.title = aAlbumTitle;
      newAlbum.id = album.albumId;
      aFlockListener.onSuccess(newAlbum, arguments.callee.name);
    },
    onError: function OnetAPI_createAlbum_onError(aFlockError) {
      api._logger.debug(".createAlbum() error: " + aFlockError.errorString);
      aFlockListener.onError(aFlockError, arguments.callee.name);
    }
  }

  var params = {
    name: aAlbumTitle,
    catId: 0,
    public: true
  };

  this.call("createAlbum",
            params,
            null,
            CI.flockIWebServiceAPI.GET,
            createAlbumListener);
};

/**
 * void getFeed(in flockIListener aFlockListener, in AString aFeedURL);
 * @call flockIOnetAPI#getFeed
 */
OnetAPI.prototype.getFeed =
function OnetAPI_getFeed(aFlockListener, aFeedURL) {
  this._logger.debug(".getFeed() aFeedURL = " + aFeedURL);

  var inst = this;
  var req = CC[XMLHTTPREQUEST_CONTRACTID]
            .createInstance(CI.nsIXMLHttpRequest)
            .QueryInterface(CI.nsIJSXMLHttpRequest);
  req.open("GET", aFeedURL, true);
  req.onreadystatechange = function getFeed_onreadystatechange(aEvt) {
    inst._logger.debug(".getFeed() ReadyState = " + req.readyState);
    if (req.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
      var status = req.status;
      inst._logger.debug("._getFeed() Status = " + status);
      if (Math.floor(status) / 100 == 2) {
        var responseText = req.responseText;
        inst._logger.debug(".getFeed() response = \n" + responseText);
        var parser = CC["@mozilla.org/xmlextras/domparser;1"]
                     .createInstance(CI.nsIDOMParser);
        var xml = parser.parseFromString(responseText, "text/xml");
        aFlockListener.onSuccess(xml, arguments.callee.name);
      } else {
        // HTTP errors (0 for connection lost)
        inst._logger.debug(".getFeed() HTTP Error");
        aFlockListener.onError(inst.getError(status),
                               arguments.callee.name);
      }
    }
  }
  this._logger.debug("._getFeed(): to url: " + aFeedURL);
  req.send(null);
};

/**************************************************************************
 * OnetAPI: private members
 **************************************************************************/

OnetAPI.prototype._toHex =
function OnetAPI__toHex(aNumber, aPad) {
  var mod, hexStr = "";
  while (aNumber) {
    mod = aNumber%0x10;
    hexStr = this._HexStr.charAt(mod) + hexStr;
    aNumber = (aNumber - mod) / 0x10;
  }

  if (aPad && hexStr.length < aPad) {
    var padLen = aPad - hexStr.length;
    for (var i = 0; i < padLen; i++) {
      hexStr = "0" + hexStr;
    }
  }
  return hexStr;
};

OnetAPI.prototype._utf2isoEntities =
function OnetAPI__utf2isoEntities(aString, aIgnore) {
  var retStr = "";
  var code;
  for (var i = 0; i < aString.length; i++) {
    code = aString.charCodeAt(i);
    if (code >= 0x20 && code <= 0x7e) {
      retStr += aString.charAt(i);
    } else if (this._UTFtoISO[code]) {
      retStr += "#x" + this._toHex(this._UTFtoISO[code]) + ";";
    } else {
      retStr += aIgnore ? "" : "?";
    }
  }
  return retStr;
};

OnetAPI.prototype._serialize = 
function OnetAPI__serialize(sdata) {
  var sString = "";
  var tData = typeof(sdata);
  switch (tData) {
     case "undefined":
     case "unknown":
       sString = "N;";
       break;
     case "string":
       sString = "s:" + sdata.length + ':"' + sdata + '";';
       break;
     case "object":
        if (sdata === null) {
          sString = "N;";
          break;
        }
        var tabCnt = 0;
        var inArr = "";
        /*jslint forin: false */
        for (var i in sdata) {
          var inArrData = this._serialize(sdata[i]);
          if (inArrData) {
            tabCnt++;
            if (i == parseInt(i,10)) {
              inArr += "i:" + i + ";";
            } else {
              inArr += "s:" + i.length + ':"' + i + '";';
            }
            inArr += inArrData;
         }
         if (tabCnt) {
           sString = "a:" + String(tabCnt) + ":{" + inArr + "}";
         }
       }
        /*jslint forin: true */
       break;
     case "number":
        if (sdata != Math.floor(sdata)) {
          sString = "d:" + String(sdata) + ";";
        } else {
          sString = "i:" + String(sdata) + ";";
        }
       break;
     case "boolean":
       sString = "b:" + (sdata ? "1" : "0") + ";";
       break;
  }
  return sString;
};

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

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

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