text: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements. See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership. The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License. You may obtain a copy of the License at
   *
   *   http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied. See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  
  var log = require('./log');
  var Int64 = require('node-int64');
  var Thrift = require('./thrift');
  var Type = Thrift.Type;
  var util = require("util");
  
  var Int64Util = require('./int64_util');
  var json_parse = require('./json_parse');
  
  var InputBufferUnderrunError = require('./input_buffer_underrun_error');
  
  module.exports = TJSONProtocol;
  
  /**
   * Initializes a Thrift JSON protocol instance.
   * @constructor
   * @param {Thrift.Transport} trans - The transport to serialize to/from.
   * @classdesc Apache Thrift Protocols perform serialization which enables cross
   * language RPC. The Protocol type is the JavaScript browser implementation
   * of the Apache Thrift TJSONProtocol.
   * @example
   *     var protocol  = new Thrift.Protocol(transport);
   */
  function TJSONProtocol(trans) {
    this.tstack = [];
    this.tpos = [];
    this.rstack = [];
    this.rpos = [];
  
    this.trans = trans;
  };
  
  /**
   * Thrift IDL type Id to string mapping.
   * @readonly
   * @see {@link Thrift.Type}
   */
  TJSONProtocol.Type = {};
  TJSONProtocol.Type[Type.BOOL] = '"tf"';
  TJSONProtocol.Type[Type.BYTE] = '"i8"';
  TJSONProtocol.Type[Type.I16] = '"i16"';
  TJSONProtocol.Type[Type.I32] = '"i32"';
  TJSONProtocol.Type[Type.I64] = '"i64"';
  TJSONProtocol.Type[Type.DOUBLE] = '"dbl"';
  TJSONProtocol.Type[Type.STRUCT] = '"rec"';
  TJSONProtocol.Type[Type.STRING] = '"str"';
  TJSONProtocol.Type[Type.MAP] = '"map"';
  TJSONProtocol.Type[Type.LIST] = '"lst"';
  TJSONProtocol.Type[Type.SET] = '"set"';
  
  /**
   * Thrift IDL type string to Id mapping.
   * @readonly
   * @see {@link Thrift.Type}
   */
  TJSONProtocol.RType = {};
  TJSONProtocol.RType.tf = Type.BOOL;
  TJSONProtocol.RType.i8 = Type.BYTE;
  TJSONProtocol.RType.i16 = Type.I16;
  TJSONProtocol.RType.i32 = Type.I32;
  TJSONProtocol.RType.i64 = Type.I64;
  TJSONProtocol.RType.dbl = Type.DOUBLE;
  TJSONProtocol.RType.rec = Type.STRUCT;
  TJSONProtocol.RType.str = Type.STRING;
  TJSONProtocol.RType.map = Type.MAP;
  TJSONProtocol.RType.lst = Type.LIST;
  TJSONProtocol.RType.set = Type.SET;
  
  /**
   * The TJSONProtocol version number.
   * @readonly
   * @const {number} Version
   * @memberof Thrift.Protocol
   */
  TJSONProtocol.Version = 1;
  
  TJSONProtocol.prototype.flush = function() {
    this.writeToTransportIfStackIsFlushable();
    return this.trans.flush();
  };
  
  TJSONProtocol.prototype.writeToTransportIfStackIsFlushable = function() {
    if (this.tstack.length === 1) {
      this.trans.write(this.tstack.pop());
    }
  };
  
  /**
   * Serializes the beginning of a Thrift RPC message.
   * @param {string} name - The service method to call.
   * @param {Thrift.MessageType} messageType - The type of method call.
   * @param {number} seqid - The sequence number of this call (always 0 in Apache Thrift).
   */
  TJSONProtocol.prototype.writeMessageBegin = function(name, messageType, seqid) {
    this.tstack.push([TJSONProtocol.Version, '"' + name + '"', messageType, seqid]);
  
    // Record client seqid to find callback again
    if (this._seqid) {
      // TODO better logging log warning
      log.warning('SeqId already set', { 'name': name });
    } else {
      this._seqid = seqid;
      this.trans.setCurrSeqId(seqid);
    }
  };
  
  /**
   * Serializes the end of a Thrift RPC message.
   */
  TJSONProtocol.prototype.writeMessageEnd = function() {
    var obj = this.tstack.pop();
  
    this.wobj = this.tstack.pop();
    this.wobj.push(obj);
  
    this.wbuf = '[' + this.wobj.join(',') + ']';
  
    // we assume there is nothing more to come so we write
    this.trans.write(this.wbuf);
  
    if (this._seqid) {
        this._seqid = null;
    } else {
        log.warning('No seqid to unset');
    }
  };
  
  /**
   * Serializes the beginning of a struct.
   * @param {string} name - The name of the struct.
   */
  TJSONProtocol.prototype.writeStructBegin = function(name) {
    this.tpos.push(this.tstack.length);
    this.tstack.push({});
  };
  
  /**
   * Serializes the end of a struct.
   */
  TJSONProtocol.prototype.writeStructEnd = function() {
    var p = this.tpos.pop();
    var struct = this.tstack[p];
    var str = '{';
    var first = true;
    for (var key in struct) {
      if (first) {
        first = false;
      } else {
        str += ',';
      }
  
      str += key + ':' + struct[key];
    }
  
    str += '}';
    this.tstack[p] = str;
  
    this.writeToTransportIfStackIsFlushable();
  };
  
  /**
   * Serializes the beginning of a struct field.
   * @param {string} name - The name of the field.
   * @param {Thrift.Protocol.Type} fieldType - The data type of the field.
   * @param {number} fieldId - The field's unique identifier.
   */
  TJSONProtocol.prototype.writeFieldBegin = function(name, fieldType, fieldId) {
    this.tpos.push(this.tstack.length);
    this.tstack.push({ 'fieldId': '"' +
      fieldId + '"', 'fieldType': TJSONProtocol.Type[fieldType]
    });
  };
  
  /**
   * Serializes the end of a field.
   */
  TJSONProtocol.prototype.writeFieldEnd = function() {
    var value = this.tstack.pop();
    var fieldInfo = this.tstack.pop();
  
    if (':' + value === ":[object Object]") {
      this.tstack[this.tstack.length - 1][fieldInfo.fieldId] = '{' +
        fieldInfo.fieldType + ':' + JSON.stringify(value) + '}';
    } else {
      this.tstack[this.tstack.length - 1][fieldInfo.fieldId] = '{' +
        fieldInfo.fieldType + ':' + value + '}';
    }
    this.tpos.pop();
  
    this.writeToTransportIfStackIsFlushable();
  };
  
  /**
   * Serializes the end of the set of fields for a struct.
   */
  TJSONProtocol.prototype.writeFieldStop = function() {
  };
  
  /**
   * Serializes the beginning of a map collection.
   * @param {Thrift.Type} keyType - The data type of the key.
   * @param {Thrift.Type} valType - The data type of the value.
   * @param {number} [size] - The number of elements in the map (ignored).
   */
  TJSONProtocol.prototype.writeMapBegin = function(keyType, valType, size) {
    //size is invalid, we'll set it on end.
    this.tpos.push(this.tstack.length);
    this.tstack.push([TJSONProtocol.Type[keyType], TJSONProtocol.Type[valType], 0]);
  };
  
  /**
   * Serializes the end of a map.
   */
  TJSONProtocol.prototype.writeMapEnd = function() {
    var p = this.tpos.pop();
  
    if (p == this.tstack.length) {
      return;
    }
  
    if ((this.tstack.length - p - 1) % 2 !== 0) {
      this.tstack.push('');
    }
  
    var size = (this.tstack.length - p - 1) / 2;
  
    this.tstack[p][this.tstack[p].length - 1] = size;
  
    var map = '}';
    var first = true;
    while (this.tstack.length > p + 1) {
      var v = this.tstack.pop();
      var k = this.tstack.pop();
      if (first) {
        first = false;
      } else {
        map = ',' + map;
      }
  
      if (! isNaN(k)) { k = '"' + k + '"'; } //json "keys" need to be strings
      map = k + ':' + v + map;
    }
    map = '{' + map;
  
    this.tstack[p].push(map);
    this.tstack[p] = '[' + this.tstack[p].join(',') + ']';
  
    this.writeToTransportIfStackIsFlushable();
  };
  
  /**
   * Serializes the beginning of a list collection.
   * @param {Thrift.Type} elemType - The data type of the elements.
   * @param {number} size - The number of elements in the list.
   */
  TJSONProtocol.prototype.writeListBegin = function(elemType, size) {
    this.tpos.push(this.tstack.length);
    this.tstack.push([TJSONProtocol.Type[elemType], size]);
  };
  
  /**
   * Serializes the end of a list.
   */
  TJSONProtocol.prototype.writeListEnd = function() {
    var p = this.tpos.pop();
  
    while (this.tstack.length > p + 1) {
      var tmpVal = this.tstack[p + 1];
      this.tstack.splice(p + 1, 1);
      this.tstack[p].push(tmpVal);
    }
  
    this.tstack[p] = '[' + this.tstack[p].join(',') + ']';
  
    this.writeToTransportIfStackIsFlushable();
  };
  
  /**
   * Serializes the beginning of a set collection.
   * @param {Thrift.Type} elemType - The data type of the elements.
   * @param {number} size - The number of elements in the list.
   */
  TJSONProtocol.prototype.writeSetBegin = function(elemType, size) {
      this.tpos.push(this.tstack.length);
      this.tstack.push([TJSONProtocol.Type[elemType], size]);
  };
  
  /**
   * Serializes the end of a set.
   */
  TJSONProtocol.prototype.writeSetEnd = function() {
    var p = this.tpos.pop();
  
    while (this.tstack.length > p + 1) {
      var tmpVal = this.tstack[p + 1];
      this.tstack.splice(p + 1, 1);
      this.tstack[p].push(tmpVal);
    }
  
    this.tstack[p] = '[' + this.tstack[p].join(',') + ']';
  
    this.writeToTransportIfStackIsFlushable();
  };
  
  /** Serializes a boolean */
  TJSONProtocol.prototype.writeBool = function(bool) {
    this.tstack.push(bool ? 1 : 0);
  };
  
  /** Serializes a number */
  TJSONProtocol.prototype.writeByte = function(byte) {
    this.tstack.push(byte);
  };
  
  /** Serializes a number */
  TJSONProtocol.prototype.writeI16 = function(i16) {
    this.tstack.push(i16);
  };
  
  /** Serializes a number */
  TJSONProtocol.prototype.writeI32 = function(i32) {
    this.tstack.push(i32);
  };
  
  /** Serializes a number */
  TJSONProtocol.prototype.writeI64 = function(i64) {
    if (i64 instanceof Int64) {
      this.tstack.push(Int64Util.toDecimalString(i64));
    } else {
      this.tstack.push(i64);
    }
  };
  
  /** Serializes a number */
  TJSONProtocol.prototype.writeDouble = function(dub) {
    this.tstack.push(dub);
  };
  
  /** Serializes a string */
  TJSONProtocol.prototype.writeString = function(arg) {
    // We do not encode uri components for wire transfer:
    if (arg === null) {
        this.tstack.push(null);
    } else {
        if (typeof arg === 'string') {
          var str = arg;
        } else if (arg instanceof Buffer) {
          var str = arg.toString('utf8');
        } else {
          throw new Error('writeString called without a string/Buffer argument: ' + arg);
        }
  
        // concat may be slower than building a byte buffer
        var escapedString = '';
        for (var i = 0; i < str.length; i++) {
            var ch = str.charAt(i);      // a single double quote: "
            if (ch === '\"') {
                escapedString += '\\\"'; // write out as: \"
            } else if (ch === '\\') {    // a single backslash: \
                escapedString += '\\\\'; // write out as: \\
            /* Currently escaped forward slashes break TJSONProtocol.
             * As it stands, we can simply pass forward slashes into
             * our strings across the wire without being escaped.
             * I think this is the protocol's bug, not thrift.js
             * } else if(ch === '/') {   // a single forward slash: /
             *  escapedString += '\\/';  // write out as \/
             * }
             */
            } else if (ch === '\b') {    // a single backspace: invisible
                escapedString += '\\b';  // write out as: \b"
            } else if (ch === '\f') {    // a single formfeed: invisible
                escapedString += '\\f';  // write out as: \f"
            } else if (ch === '\n') {    // a single newline: invisible
                escapedString += '\\n';  // write out as: \n"
            } else if (ch === '\r') {    // a single return: invisible
                escapedString += '\\r';  // write out as: \r"
            } else if (ch === '\t') {    // a single tab: invisible
                escapedString += '\\t';  // write out as: \t"
            } else {
                escapedString += ch;     // Else it need not be escaped
            }
        }
        this.tstack.push('"' + escapedString + '"');
    }
  };
  
  /** Serializes a string */
  TJSONProtocol.prototype.writeBinary = function(arg) {
    if (typeof arg === 'string') {
      var buf = new Buffer(arg, 'binary');
    } else if (arg instanceof Buffer ||
               Object.prototype.toString.call(arg) == '[object Uint8Array]')  {
      var buf = arg;
    } else {
      throw new Error('writeBinary called without a string/Buffer argument: ' + arg);
    }
    this.tstack.push('"' + buf.toString('base64') + '"');
  };
  
  /**
   * @class
   * @name AnonReadMessageBeginReturn
   * @property {string} fname - The name of the service method.
   * @property {Thrift.MessageType} mtype - The type of message call.
   * @property {number} rseqid - The sequence number of the message (0 in Thrift RPC).
   */
  /**
   * Deserializes the beginning of a message.
   * @returns {AnonReadMessageBeginReturn}
   */
  TJSONProtocol.prototype.readMessageBegin = function() {
  
    //Borrow the inbound transport buffer and ensure data is present/consistent
    var transBuf = this.trans.borrow();
    if (transBuf.readIndex >= transBuf.writeIndex) {
      throw new InputBufferUnderrunError();
    }
    var cursor = transBuf.readIndex;
  
    if (transBuf.buf[cursor] !== 0x5B) { //[
      throw new Error("Malformed JSON input, no opening bracket");
    }
  
    //Parse a single message (there may be several in the buffer)
    //  TODO: Handle characters using multiple code units
    cursor++;
    var openBracketCount = 1;
    var inString = false;
    for (; cursor < transBuf.writeIndex; cursor++) {
      var chr = transBuf.buf[cursor];
      //we use hexa charcode here because data[i] returns an int and not a char
      if (inString) {
        if (chr === 0x22) { //"
          inString = false;
        } else if (chr === 0x5C) { //\
          //escaped character, skip
          cursor += 1;
        }
      } else {
        if (chr === 0x5B) { //[
          openBracketCount += 1;
        } else if (chr === 0x5D) { //]
          openBracketCount -= 1;
          if (openBracketCount === 0) {
            //end of json message detected
            break;
          }
        } else if (chr === 0x22) { //"
          inString = true;
        }
      }
    }
  
    if (openBracketCount !== 0) {
      // Missing closing bracket. Can be buffer underrun.
      throw new InputBufferUnderrunError();
    }
  
    //Reconstitute the JSON object and conume the necessary bytes
    this.robj = json_parse(transBuf.buf.slice(transBuf.readIndex, cursor+1).toString());
    this.trans.consume(cursor + 1 - transBuf.readIndex);
  
    //Verify the protocol version
    var version = this.robj.shift();
    if (version != TJSONProtocol.Version) {
      throw new Error('Wrong thrift protocol version: ' + version);
    }
  
    //Objectify the thrift message {name/type/sequence-number} for return
    // and then save the JSON object in rstack
    var r = {};
    r.fname = this.robj.shift();
    r.mtype = this.robj.shift();
    r.rseqid = this.robj.shift();
    this.rstack.push(this.robj.shift());
    return r;
  };
  
  /** Deserializes the end of a message. */
  TJSONProtocol.prototype.readMessageEnd = function() {
  };
  
  /**
   * Deserializes the beginning of a struct.
   * @param {string} [name] - The name of the struct (ignored)
   * @returns {object} - An object with an empty string fname property
   */
  TJSONProtocol.prototype.readStructBegin = function() {
    var r = {};
    r.fname = '';
  
    //incase this is an array of structs
    if (this.rstack[this.rstack.length - 1] instanceof Array) {
      this.rstack.push(this.rstack[this.rstack.length - 1].shift());
    }
  
    return r;
  };
  
  /** Deserializes the end of a struct. */
  TJSONProtocol.prototype.readStructEnd = function() {
    this.rstack.pop();
  };
  
  /**
   * @class
   * @name AnonReadFieldBeginReturn
   * @property {string} fname - The name of the field (always '').
   * @property {Thrift.Type} ftype - The data type of the field.
   * @property {number} fid - The unique identifier of the field.
   */
  /**
   * Deserializes the beginning of a field.
   * @returns {AnonReadFieldBeginReturn}
   */
  TJSONProtocol.prototype.readFieldBegin = function() {
    var r = {};
  
    var fid = -1;
    var ftype = Type.STOP;
  
    //get a fieldId
    for (var f in (this.rstack[this.rstack.length - 1])) {
      if (f === null) {
        continue;
      }
  
      fid = parseInt(f, 10);
      this.rpos.push(this.rstack.length);
  
      var field = this.rstack[this.rstack.length - 1][fid];
  
      //remove so we don't see it again
      delete this.rstack[this.rstack.length - 1][fid];
  
      this.rstack.push(field);
  
      break;
    }
  
    if (fid != -1) {
      //should only be 1 of these but this is the only
      //way to match a key
      for (var i in (this.rstack[this.rstack.length - 1])) {
        if (TJSONProtocol.RType[i] === null) {
          continue;
        }
  
        ftype = TJSONProtocol.RType[i];
        this.rstack[this.rstack.length - 1] = this.rstack[this.rstack.length - 1][i];
      }
    }
  
    r.fname = '';
    r.ftype = ftype;
    r.fid = fid;
  
    return r;
  };
  
  /** Deserializes the end of a field. */
  TJSONProtocol.prototype.readFieldEnd = function() {
    var pos = this.rpos.pop();
  
    //get back to the right place in the stack
    while (this.rstack.length > pos) {
      this.rstack.pop();
    }
  };
  
  /**
   * @class
   * @name AnonReadMapBeginReturn
   * @property {Thrift.Type} ktype - The data type of the key.
   * @property {Thrift.Type} vtype - The data type of the value.
   * @property {number} size - The number of elements in the map.
   */
  /**
   * Deserializes the beginning of a map.
   * @returns {AnonReadMapBeginReturn}
   */
  TJSONProtocol.prototype.readMapBegin = function() {
    var map = this.rstack.pop();
    var first = map.shift();
    if (first instanceof Array) {
      this.rstack.push(map);
      map = first;
      first = map.shift();
    }
  
    var r = {};
    r.ktype = TJSONProtocol.RType[first];
    r.vtype = TJSONProtocol.RType[map.shift()];
    r.size = map.shift();
  
  
    this.rpos.push(this.rstack.length);
    this.rstack.push(map.shift());
  
    return r;
  };
  
  /** Deserializes the end of a map. */
  TJSONProtocol.prototype.readMapEnd = function() {
    this.readFieldEnd();
  };
  
  /**
   * @class
   * @name AnonReadColBeginReturn
   * @property {Thrift.Type} etype - The data type of the element.
   * @property {number} size - The number of elements in the collection.
   */
  /**
   * Deserializes the beginning of a list.
   * @returns {AnonReadColBeginReturn}
   */
  TJSONProtocol.prototype.readListBegin = function() {
    var list = this.rstack[this.rstack.length - 1];
  
    var r = {};
    r.etype = TJSONProtocol.RType[list.shift()];
    r.size = list.shift();
  
    this.rpos.push(this.rstack.length);
    this.rstack.push(list.shift());
  
    return r;
  };
  
  /** Deserializes the end of a list. */
  TJSONProtocol.prototype.readListEnd = function() {
    var pos = this.rpos.pop() - 2;
    var st = this.rstack;
    st.pop();
    if (st instanceof Array && st.length > pos && st[pos].length > 0) {
      st.push(st[pos].shift());
    }
  };
  
  /**
   * Deserializes the beginning of a set.
   * @returns {AnonReadColBeginReturn}
   */
  TJSONProtocol.prototype.readSetBegin = function() {
    return this.readListBegin();
  };
  
  /** Deserializes the end of a set. */
  TJSONProtocol.prototype.readSetEnd = function() {
    return this.readListEnd();
  };
  
  TJSONProtocol.prototype.readBool = function() {
    return this.readValue() == '1';
  };
  
  TJSONProtocol.prototype.readByte = function() {
    return this.readI32();
  };
  
  TJSONProtocol.prototype.readI16 = function() {
    return this.readI32();
  };
  
  TJSONProtocol.prototype.readI32 = function(f) {
    return +this.readValue();
  }
  
  /** Returns the next value found in the protocol buffer */
  TJSONProtocol.prototype.readValue = function(f) {
    if (f === undefined) {
      f = this.rstack[this.rstack.length - 1];
    }
  
    var r = {};
  
    if (f instanceof Array) {
      if (f.length === 0) {
        r.value = undefined;
      } else {
        r.value = f.shift();
      }
    } else if (!(f instanceof Int64) && f instanceof Object) {
      for (var i in f) {
        if (i === null) {
          continue;
        }
        this.rstack.push(f[i]);
        delete f[i];
  
        r.value = i;
        break;
      }
    } else {
      r.value = f;
      this.rstack.pop();
    }
  
    return r.value;
  };
  
  TJSONProtocol.prototype.readI64 = function() {
    var n = this.readValue()
    if (typeof n === 'string') {
      // Assuming no one is sending in 1.11111e+33 format
      return Int64Util.fromDecimalString(n);
    } else {
      return new Int64(n);
    }
  };
  
  TJSONProtocol.prototype.readDouble = function() {
    return this.readI32();
  };
  
  TJSONProtocol.prototype.readBinary = function() {
    return new Buffer(this.readValue(), 'base64');
  };
  
  TJSONProtocol.prototype.readString = function() {
    return this.readValue();
  };
  
  /**
   * Returns the underlying transport.
   * @readonly
   * @returns {Thrift.Transport} The underlying transport.
   */
  TJSONProtocol.prototype.getTransport = function() {
    return this.trans;
  };
  
  /**
   * Method to arbitrarily skip over data
   */
  TJSONProtocol.prototype.skip = function(type) {
    throw new Error('skip not supported yet');
  };
  