// Copyright 2009 Google Inc.  All Rights Reserved.

/**
 * @fileoverview PHP Object Notation libary.
 * @author jahng@google.com (Sunju Jahng)
 */

var PHON = new function () {
}

PHON.serialize = function (object) {
  switch (object) {
    case null:
      return "N;";
  }
    switch (typeof object) {
    case "boolean":
      return "b:" + (object ? "1;" : "0;");
    case "number":
      if ((object % 1) == 0)
        return "i:" + object + ";";
      else
        return "d:" + object + ";";
    case "string":
      return "s:" + this.strlen(object) + ':"' + object + '";';
    case "object":
      if (object.constructor == Array) {
        var length = 0;
        var notation = "";
        for (var key in object) {
          length++;
          if ((Number(key) == NaN) || ((Number(key) % 1) != 0))
            notation += this.serialize(key);
          else
            notation += this.serialize(Number(key));
          notation += this.serialize(object[key]);
        }
        return "a:" + length + ":{" + notation + "}";
      } else {
        var className = object.toString();
        var length = 0;
        var notation = "";
        for (var key in object) {
          length++;
          notation += this.serialize(key);
          notation += this.serialize(object[key]);
        }
        return "O:" + className.length + ':"' + className + '":' + length + ":{" + notation + "}";
      }
      break;
    default:
      alert(typeof object);
      break;
  }
  return null;
}

PHON.unserialize = function (notation) {
  return this._unserialize(notation)[0];
}

PHON._unserialize = function (notation) {
  switch (notation.charAt(0)) {
    case "N":
      return [null, notation.substr(2)];
    case "b":
      return [notation.substr(2, 1) == "1", notation.substr(4)];
    case "i":
    case "d":
      var value = notation.substring(2, notation.indexOf(";"));
      return [Number(value), notation.substr(value.length + 3)];
    case "s":
      var length = this._getLength(notation);
      var start = 4 + String(length).length;
      for (var end = start; (end = notation.indexOf('"', end)) > 0; end++) {
        if (this.strlen(notation.substring(start, end)) == length)
          return [notation.substring(start, end), notation.substr(end + 2)];
      }
      break;
    case "a":
      var length = this._getLength(notation);
      notation = notation.substr(String(length).length + 4);
      var array = new Array();
      var key;
      var value;
      for (var i = 0; i < length; i++) {
        key = this._unserialize(notation);
        notation = key[1];
        value = this._unserialize(notation);
        notation = value[1];
        array[key[0]] = value[0];
      }
      return [array, notation.substr(1)];
    case "O":
      var length = this._getLength(notation);
      var className = notation.substr(String(length).length + 4, length);
      notation  = notation.substr(String(length).length + 6 + length);
      var properties = Number(notation.substring(0, notation.indexOf(":")));
      notation = notation.substr(String(properties).length + 2);

      var object = new Object(className);
      var property;
      var value;
      for (var i = 0; i < properties; i++) {
        key = this._unserialize(notation);
        notation = key[1];
        value = this._unserialize(notation);
        notation = value[1];
        object[key[0]] = value[0];
      }
      return [object, notation.substr(1)];
  }
  return [undefined, null];
}

PHON.strlen = function (str) {
  var len = 0;
  for (var i = 0; i < str.length; i++) {
    if (str.charCodeAt(i) < 0x80)
      len += 1;
    else if (str.charCodeAt(i) < 0x800)
      len += 2;
    else if (str.charCodeAt(i) < 0x10000)
      len += 3;
    else
      len += 4;
  }
  return len;
}

PHON._getLength = function (notation) {
  return Number(notation.substring(2, notation.indexOf(":", 3)));
}
