/*
 * JSON <==> protobuf
 * A.Omelianchuk, 01-jan-2021
 */

// in uint8buf starting from startpos and return new startpos
function putVarIntBuf(number, uint8buf, startpos) {
  let n = makeInteger(number);
  while (1) {
    let cb = n & 127;
    n >>= 7;
    if (n) cb |= 128;
    uint8buf[startpos++] = cb;
    if (!n) break;
  }
  return startpos;
}

function makeInteger(num) {
  let n = 0;
  if (typeof num === 'number' && !isNaN(num)) n |= num;
  if (n < 0) n = 1;
  return n;
}

function JSON2protobuf(obj) {
  /*
   * input - array like [ { "format":0, // 0 = varInt, 5 = fixed32, 2=bytes others ignored
   * "value":12, // if number - acc to format, if array - will be format=2, if
   * arrayBuffer - format=2 just bytes "number":34 // protobuf field number } ...
   * multiple ]
   *
   * returns uint8buffer
   *
   */
  const ab = new ArrayBuffer(300); // cause i use 256 bytes buffers
  var data = new Uint8Array(ab);
  let pos = 0;

  function putVarInt(num) {
    pos = putVarIntBuf(num, data, pos);
  }

  function putFixed32(num) {
    let n = makeInteger(num);
    for (let u = 4; u--; ) {
      data[pos++] = n & 255;
      n >>= 8;
    }
  }

  function putUint8array(buf) {
    let len = 0;
    if (ArrayBuffer.isView(buf)) len = buf.length;
    putVarInt(len);
    let p = 0;
    while (len--) data[pos++] = buf[p++];
  }

  if (obj)
    if (typeof obj == 'object')
      if (Array.isArray(obj))
        while (1) {
          let t = obj.shift();
          if (t) {
            let tnum = makeInteger(t.number);
            if (t.number) {
              let ty = t.format;
              if (typeof t.value == 'string' && t.value[0] >= '0' && t.value[0] <= '9') t.value = parseInt(t.value);
              if (typeof t.value != 'number') t.format = 2;
              else if (t.format != 5) t.format = 0;
              let tn = (t.number << 3) + (t.format & 7);
              putVarInt(tn);
              switch (t.format) {
                case 0: // varint
                  putVarInt(t.value);
                  break;
                case 2: // bytes
                  if (ArrayBuffer.isView(t.value)) putUint8array(t.value);
                  else putUint8array(JSON2protobuf(t.value));
                  break;
                case 5: // u32
                  putFixed32(t.value);
                  break;
              }
            }
          } else break; // done with array
        }
  return data.subarray(0, pos);
}

function getVarIntBuf(msg, pos) {
  let v = 0;
  let shift = 0;
  while (pos < msg.length) {
    let t = msg[pos];
    pos++;
    v += (t & 127) << shift;
    if (!(t & 128)) break;
    shift += 7;
  }
  return {
    v: v,
    p: pos,
  };
}

function protobuf2JSON(msg) {
  /*
   * input - uint8buffer
   * returns array like [ { "format":0, // 0 = varInt, 5 = fixed32, others ignored
   * "value":12, // number or subarray - attempt to decode format=2 as submessage
   * "bytes": uint8array if submessage or bytes
   * ...
   * multiple ]
   *
   */

  if (!ArrayBuffer.isView(msg)) return [];
  let pos = 0;
  var ans = [];

  function getVarInt() {
    let v = 0;
    let shift = 0;
    while (pos < msg.length) {
      let t = msg[pos];
      pos++;
      if (shift < 28) v += (t & 127) << shift;
      else if (shift == 28) {
        v += (t & 7) << shift;
        if (t & 8) v += 2147483648;
      } // else skip byte
      if (!(t & 128)) break;
      shift += 7;
    }
    // if(v & (1<<31)) v+=0x80000000;
    return v;
  }

  function get32Int() {
    let v = 0;
    let shift = 0;
    while (pos < msg.length) {
      let t = msg[pos];
      pos++;
      v += t << shift;
      if (shift >= 24) break;
      shift += 8;
    }
    return v;
  }

  while (pos < msg.length) {
    // consume data
    let numtyp = getVarInt();
    let typ = numtyp & 7;
    let num = numtyp >> 3;
    var element = {
      format: typ,
      number: num,
    };
    switch (typ) {
      case 0: // varint
        element.value = getVarInt();
        break;
      case 1: // fixed64
        {
          let len = 8;
          if (len + pos >= msg.length) len = msg.length - pos;
          element.bytes = msg.subarray(pos, pos + len);
          element.value = null;
          pos += len;
        }
        break;
      case 5: // fixed32
        element.value = get32Int();
        break;
      case 2: // bytes = sub struct
        element.length = getVarInt();
        element.bytes = msg.subarray(pos, pos + element.length);
        element.value = protobuf2JSON(element.bytes);
        pos += element.length;
        break;
      default:
        element.bytes = msg.subarray(pos, msg.length);
        pos = msg.length;
        element.value = null;
        break;
    }
    ans.push(element);
  }
  return ans;
} // parseMessage
