// Unmarshall and unwrapping.

//Provides: caml_unwrap_value_from_string
//Requires: caml_failwith, caml_marshal_constants
//Requires: caml_int64_float_of_bits, caml_int64_of_bytes, caml_new_string
//Requires: caml_jsbytes_of_string
var caml_unwrap_value_from_string = function (){
  function StringReader (s, i) { this.s = caml_jsbytes_of_string(s); this.i = i; }
  StringReader.prototype = {
    read8u:function () { return this.s.charCodeAt(this.i++); },
    read8s:function () { return this.s.charCodeAt(this.i++) << 24 >> 24; },
    read16u:function () {
      var s = this.s, i = this.i;
      this.i = i + 2;
      return (s.charCodeAt(i) << 8) | s.charCodeAt(i + 1)
    },
    read16s:function () {
      var s = this.s, i = this.i;
      this.i = i + 2;
      return (s.charCodeAt(i) << 24 >> 16) | s.charCodeAt(i + 1);
    },
    read32u:function () {
      var s = this.s, i = this.i;
      this.i = i + 4;
      return ((s.charCodeAt(i) << 24) | (s.charCodeAt(i+1) << 16) |
              (s.charCodeAt(i+2) << 8) | s.charCodeAt(i+3)) >>> 0;
    },
    read32s:function () {
      var s = this.s, i = this.i;
      this.i = i + 4;
      return (s.charCodeAt(i) << 24) | (s.charCodeAt(i+1) << 16) |
             (s.charCodeAt(i+2) << 8) | s.charCodeAt(i+3);
    },
    readstr:function (len) {
      var i = this.i;
      this.i = i + len;
      return caml_new_string(this.s.substring(i, i + len));
    }
  }
  function caml_float_of_bytes (a) {
    return caml_int64_float_of_bits (caml_int64_of_bytes (a));
  }
  var late_unwrap_mark = {};
  return function (apply_unwrapper, s, ofs) {
    var reader = new StringReader (s, ofs);
    var magic = reader.read32u ();
    if (magic != 0x8495A6BE)
      caml_failwith("unwrap_value: bad object "
                    + window.btoa(reader.s.slice(0,1024)));
    var block_len = reader.read32u ();
    var num_objects = reader.read32u ();
    var size_32 = reader.read32u ();
    var size_64 = reader.read32u ();
    var stack = [];
    var intern_obj_table = new Array(num_objects+1);
    var obj_counter = 1;
    intern_obj_table[0] = [];
    function register_sharing (anc, d, v) {
      // If the value v is marked for late unwrapping, register an
      // occurrence of it in anc.
      if (v[0] === 0 && v.length >= 2 &&
          v[v.length-1] instanceof Array &&
          v[v.length-1].length == 4 &&
          v[v.length-1][2] === late_unwrap_mark) {
        v[v.length-1][3] = [0, [0, anc, d], v[v.length-1][3]];
      }
      return v;
    }
    function intern_rec (v0, d) {
      var cst = caml_marshal_constants;
      var code = reader.read8u ();
      if (code >= cst.PREFIX_SMALL_INT) {
        if (code >= cst.PREFIX_SMALL_BLOCK) {
          var tag = code & 0xF;
          var size = (code >> 4) & 0x7;
          var v = [tag];
          if (size == 0) return v;
          intern_obj_table[obj_counter] = v;
          stack.push(obj_counter++, size);
          return v;
        } else
          return (code & 0x3F);
      } else {
        if (code >= cst.PREFIX_SMALL_STRING) {
          var len = code & 0x1F;
          var v = reader.readstr (len);
          intern_obj_table[obj_counter++] = v;
          return v;
        } else {
          switch(code) {
          case cst.CODE_INT8:
            return reader.read8s ();
          case cst.CODE_INT16:
            return reader.read16s ();
          case cst.CODE_INT32:
            return reader.read32s ();
          case cst.CODE_INT64:
            caml_failwith("unwrap_value: integer too large");
            break;
          case cst.CODE_SHARED8:
            var ofs = reader.read8u ();
            return register_sharing(v0, d, intern_obj_table[obj_counter - ofs]);
          case cst.CODE_SHARED16:
            var ofs = reader.read16u ();
            return register_sharing(v0, d, intern_obj_table[obj_counter - ofs]);
          case cst.CODE_SHARED32:
            var ofs = reader.read32u ();
            return register_sharing(v0, d, intern_obj_table[obj_counter - ofs]);
          case cst.CODE_BLOCK32:
            var header = reader.read32u ();
            var tag = header & 0xFF;
            var size = header >> 10;
            var v = [tag];
            if (size == 0) return v;
            intern_obj_table[obj_counter] = v;
            stack.push(obj_counter++, size);
            return v;
          case cst.CODE_BLOCK64:
            caml_failwith ("unwrap_value: data block too large");
            break;
          case cst.CODE_STRING8:
            var len = reader.read8u();
            var v = reader.readstr (len);
            intern_obj_table[obj_counter++] = v;
            return v;
          case cst.CODE_STRING32:
            var len = reader.read32u();
            var v = reader.readstr (len);
            intern_obj_table[obj_counter++] = v;
            return v;
          case cst.CODE_DOUBLE_LITTLE:
            var t = [];
            for (var i = 0;i < 8;i++) t[7 - i] = reader.read8u ();
            var v = caml_float_of_bytes (t);
            intern_obj_table[obj_counter++] = v;
            return v;
          case cst.CODE_DOUBLE_BIG:
            var t = [];
            for (var i = 0;i < 8;i++) t[i] = reader.read8u ();
            var v = caml_float_of_bytes (t);
            intern_obj_table[obj_counter++] = v;
            return v;
          case cst.CODE_DOUBLE_ARRAY8_LITTLE:
            var len = reader.read8u();
            var v = [0];
            intern_obj_table[obj_counter++] = v;
            for (var i = 1;i <= len;i++) {
              var t = [];
              for (var j = 0;j < 8;j++) t[7 - j] = reader.read8u();
              v[i] = caml_float_of_bytes (t);
            }
            return v;
          case cst.CODE_DOUBLE_ARRAY8_BIG:
            var len = reader.read8u();
            var v = [0];
            intern_obj_table[obj_counter++] = v;
            for (var i = 1;i <= len;i++) {
              var t = [];
              for (var j = 0;j < 8;j++) t[j] = reader.read8u();
              v [i] = caml_float_of_bytes (t);
            }
            return v;
          case cst.CODE_DOUBLE_ARRAY32_LITTLE:
            var len = reader.read32u();
            var v = [0];
            intern_obj_table[obj_counter++] = v;
            for (var i = 1;i <= len;i++) {
              var t = [];
              for (var j = 0;j < 8;j++) t[7 - j] = reader.read8u();
              v[i] = caml_float_of_bytes (t);
            }
            return v;
          case cst.CODE_DOUBLE_ARRAY32_BIG:
            var len = reader.read32u();
            var v = [0];
            for (var i = 1;i <= len;i++) {
              var t = [];
              for (var j = 0;j < 8;j++) t[j] = reader.read8u();
              v [i] = caml_float_of_bytes (t);
            }
            return v;
          case cst.CODE_CODEPOINTER:
          case cst.CODE_INFIXPOINTER:
            caml_failwith ("unwrap_value: code pointer");
            break;
          case cst.CODE_CUSTOM:
          case 0x18: //cst.CODE_CUSTOM_LEN:
          case 0x19: //cst.CODE_CUSTOM_FIXED:
            var c, s = "";
            while ((c = reader.read8u ()) != 0) s += String.fromCharCode (c);
            switch(s) {
            case "_j":
              // Int64
              var t = [];
              for (var j = 0;j < 8;j++) t[j] = reader.read8u();
              var v = caml_int64_of_bytes (t);
              if (intern_obj_table) intern_obj_table[obj_counter++] = v;
              return v;
            case "_i":
              // Int32
              var v = reader.read32s ();
              if (intern_obj_table) intern_obj_table[obj_counter++] = v;
              return v;
            default:
              caml_failwith("input_value: unknown custom block identifier");
            }
          default:
            caml_failwith ("unwrap_value: ill-formed message");
          }
        }
      }
    }
    stack.push(0,0);
    while (stack.length > 0) {
      var size = stack.pop();
      var ofs = stack.pop();
      var v = intern_obj_table[ofs];
      var d = v.length;
      if (size + 1 == d) {
        var ancestor = intern_obj_table[stack[stack.length-2]];
        // See Eliom_wrap.ml.
        if (v[0] === 0 && size >= 2 &&
            v[size] instanceof Array && v[size].length == 3 &&
            v[size][2] === intern_obj_table[2] /*unwrap_mark*/) {
          var unwrapped_v = apply_unwrapper(v[size], v);
          if (unwrapped_v === 0) {
            // No unwrapper is registered, so replace the unwrap
            // marker v[size] by a late_unwrap marker
            //   (unwrapper_id, "late_unwrap_mark")
            // and register an occurrence in ancestor
            v[size] =
              [0, v[size][1], late_unwrap_mark,
               [0, [0, ancestor, ancestor.length - 1], 0]];
          } else {
            v = unwrapped_v[1];
          }
          intern_obj_table[ofs] = v;
          ancestor[ancestor.length-1] = v;
        }
      } else {
        stack.push(ofs, size);
        v[d] = intern_rec (v, d);
      }
    }
    if(intern_obj_table[0][0].length != 3)
      caml_failwith ("unwrap_value: incorrect value");
    return intern_obj_table[0][0][2];
  }
}();
