text: "use strict";
  module.exports = Writer;
  
  var util      = require("./util/minimal");
  
  var BufferWriter; // cyclic
  
  var LongBits  = util.LongBits,
      base64    = util.base64,
      utf8      = util.utf8;
  
  /**
   * Constructs a new writer operation instance.
   * @classdesc Scheduled writer operation.
   * @constructor
   * @param {function(*, Uint8Array, number)} fn Function to call
   * @param {number} len Value byte length
   * @param {*} val Value to write
   * @ignore
   */
  function Op(fn, len, val) {
  
      /**
       * Function to call.
       * @type {function(Uint8Array, number, *)}
       */
      this.fn = fn;
  
      /**
       * Value byte length.
       * @type {number}
       */
      this.len = len;
  
      /**
       * Next operation.
       * @type {Writer.Op|undefined}
       */
      this.next = undefined;
  
      /**
       * Value to write.
       * @type {*}
       */
      this.val = val; // type varies
  }
  
  /* istanbul ignore next */
  function noop() {} // eslint-disable-line no-empty-function
  
  /**
   * Constructs a new writer state instance.
   * @classdesc Copied writer state.
   * @memberof Writer
   * @constructor
   * @param {Writer} writer Writer to copy state from
   * @ignore
   */
  function State(writer) {
  
      /**
       * Current head.
       * @type {Writer.Op}
       */
      this.head = writer.head;
  
      /**
       * Current tail.
       * @type {Writer.Op}
       */
      this.tail = writer.tail;
  
      /**
       * Current buffer length.
       * @type {number}
       */
      this.len = writer.len;
  
      /**
       * Next state.
       * @type {State|null}
       */
      this.next = writer.states;
  }
  
  /**
   * Constructs a new writer instance.
   * @classdesc Wire format writer using `Uint8Array` if available, otherwise `Array`.
   * @constructor
   */
  function Writer() {
  
      /**
       * Current length.
       * @type {number}
       */
      this.len = 0;
  
      /**
       * Operations head.
       * @type {Object}
       */
      this.head = new Op(noop, 0, 0);
  
      /**
       * Operations tail
       * @type {Object}
       */
      this.tail = this.head;
  
      /**
       * Linked forked states.
       * @type {Object|null}
       */
      this.states = null;
  
      // When a value is written, the writer calculates its byte length and puts it into a linked
      // list of operations to perform when finish() is called. This both allows us to allocate
      // buffers of the exact required size and reduces the amount of work we have to do compared
      // to first calculating over objects and then encoding over objects. In our case, the encoding
      // part is just a linked list walk calling operations with already prepared values.
  }
  
  var create = function create() {
      return util.Buffer
          ? function create_buffer_setup() {
              return (Writer.create = function create_buffer() {
                  return new BufferWriter();
              })();
          }
          /* istanbul ignore next */
          : function create_array() {
              return new Writer();
          };
  };
  
  /**
   * Creates a new writer.
   * @function
   * @returns {BufferWriter|Writer} A {@link BufferWriter} when Buffers are supported, otherwise a {@link Writer}
   */
  Writer.create = create();
  
  /**
   * Allocates a buffer of the specified size.
   * @param {number} size Buffer size
   * @returns {Uint8Array} Buffer
   */
  Writer.alloc = function alloc(size) {
      return new util.Array(size);
  };
  
  // Use Uint8Array buffer pool in the browser, just like node does with buffers
  /* istanbul ignore else */
  if (util.Array !== Array)
      Writer.alloc = util.pool(Writer.alloc, util.Array.prototype.subarray);
  
  /**
   * Pushes a new operation to the queue.
   * @param {function(Uint8Array, number, *)} fn Function to call
   * @param {number} len Value byte length
   * @param {number} val Value to write
   * @returns {Writer} `this`
   * @private
   */
  Writer.prototype._push = function push(fn, len, val) {
      this.tail = this.tail.next = new Op(fn, len, val);
      this.len += len;
      return this;
  };
  
  function writeByte(val, buf, pos) {
      buf[pos] = val & 255;
  }
  
  function writeVarint32(val, buf, pos) {
      while (val > 127) {
          buf[pos++] = val & 127 | 128;
          val >>>= 7;
      }
      buf[pos] = val;
  }
  
  /**
   * Constructs a new varint writer operation instance.
   * @classdesc Scheduled varint writer operation.
   * @extends Op
   * @constructor
   * @param {number} len Value byte length
   * @param {number} val Value to write
   * @ignore
   */
  function VarintOp(len, val) {
      this.len = len;
      this.next = undefined;
      this.val = val;
  }
  
  VarintOp.prototype = Object.create(Op.prototype);
  VarintOp.prototype.fn = writeVarint32;
  
  /**
   * Writes an unsigned 32 bit value as a varint.
   * @param {number | string} value Value to write
   * @returns {Writer} `this`
   */
  Writer.prototype.uint32 = function write_uint32(value) {
      if (typeof value === "string") {
        value = parseFloat(value);
      }
  
      // here, the call to this.push has been inlined and a varint specific Op subclass is used.
      // uint32 is by far the most frequently used operation and benefits significantly from this.
      this.len += (this.tail = this.tail.next = new VarintOp(
          (value = value >>> 0)
                  < 128       ? 1
          : value < 16384     ? 2
          : value < 2097152   ? 3
          : value < 268435456 ? 4
          :                     5,
      value)).len;
      return this;
  };
  
  /**
   * Writes a signed 32 bit value as a varint.
   * @function
   * @param {number | string} value Value to write
   * @returns {Writer} `this`
   */
  Writer.prototype.int32 = function write_int32(value) {
      if (typeof value === "string") {
          value = parseFloat(value);
      }
      return value < 0
          ? this._push(writeVarint64, 10, LongBits.fromNumber(value)) // 10 bytes per spec
          : this.uint32(value);
  };
  
  /**
   * Writes a 32 bit value as a varint, zig-zag encoded.
   * @param {number} value Value to write
   * @returns {Writer} `this`
   */
  Writer.prototype.sint32 = function write_sint32(value) {
      return this.uint32((value << 1 ^ value >> 31) >>> 0);
  };
  
  function writeVarint64(val, buf, pos) {
      while (val.hi) {
          buf[pos++] = val.lo & 127 | 128;
          val.lo = (val.lo >>> 7 | val.hi << 25) >>> 0;
          val.hi >>>= 7;
      }
      while (val.lo > 127) {
          buf[pos++] = val.lo & 127 | 128;
          val.lo = val.lo >>> 7;
      }
      buf[pos++] = val.lo;
  }
  
  /**
   * Writes an unsigned 64 bit value as a varint.
   * @param {Long|number|string} value Value to write
   * @returns {Writer} `this`
   * @throws {TypeError} If `value` is a string and no long library is present.
   */
  Writer.prototype.uint64 = function write_uint64(value) {
      var bits = LongBits.from(value);
      return this._push(writeVarint64, bits.length(), bits);
  };
  
  /**
   * Writes a signed 64 bit value as a varint.
   * @function
   * @param {Long|number|string} value Value to write
   * @returns {Writer} `this`
   * @throws {TypeError} If `value` is a string and no long library is present.
   */
  Writer.prototype.int64 = Writer.prototype.uint64;
  
  /**
   * Writes a signed 64 bit value as a varint, zig-zag encoded.
   * @param {Long|number|string} value Value to write
   * @returns {Writer} `this`
   * @throws {TypeError} If `value` is a string and no long library is present.
   */
  Writer.prototype.sint64 = function write_sint64(value) {
      var bits = LongBits.from(value).zzEncode();
      return this._push(writeVarint64, bits.length(), bits);
  };
  
  /**
   * Writes a boolish value as a varint.
   * @param {boolean|any} value Value to write
   * @returns {Writer} `this`
   */
  Writer.prototype.bool = function write_bool(value) {
      return this._push(writeByte, 1, value ? 1 : 0);
  };
  
  function writeFixed32(val, buf, pos) {
      buf[pos    ] =  val         & 255;
      buf[pos + 1] =  val >>> 8   & 255;
      buf[pos + 2] =  val >>> 16  & 255;
      buf[pos + 3] =  val >>> 24;
  }
  
  /**
   * Writes an unsigned 32 bit value as fixed 32 bits.
   * @param {number} value Value to write
   * @returns {Writer} `this`
   */
  Writer.prototype.fixed32 = function write_fixed32(value) {
      return this._push(writeFixed32, 4, value >>> 0);
  };
  
  /**
   * Writes a signed 32 bit value as fixed 32 bits.
   * @function
   * @param {number} value Value to write
   * @returns {Writer} `this`
   */
  Writer.prototype.sfixed32 = Writer.prototype.fixed32;
  
  /**
   * Writes an unsigned 64 bit value as fixed 64 bits.
   * @param {Long|number|string} value Value to write
   * @returns {Writer} `this`
   * @throws {TypeError} If `value` is a string and no long library is present.
   */
  Writer.prototype.fixed64 = function write_fixed64(value) {
      var bits = LongBits.from(value);
      return this._push(writeFixed32, 4, bits.lo)._push(writeFixed32, 4, bits.hi);
  };
  
  /**
   * Writes a signed 64 bit value as fixed 64 bits.
   * @function
   * @param {Long|number|string} value Value to write
   * @returns {Writer} `this`
   * @throws {TypeError} If `value` is a string and no long library is present.
   */
  Writer.prototype.sfixed64 = Writer.prototype.fixed64;
  
  /**
   * Writes a float (32 bit).
   * @function
   * @param {number} value Value to write
   * @returns {Writer} `this`
   */
  Writer.prototype.float = function write_float(value) {
      return this._push(util.float.writeFloatLE, 4, value);
  };
  
  /**
   * Writes a double (64 bit float).
   * @function
   * @param {number} value Value to write
   * @returns {Writer} `this`
   */
  Writer.prototype.double = function write_double(value) {
      return this._push(util.float.writeDoubleLE, 8, value);
  };
  
  var writeBytes = util.Array.prototype.set
      ? function writeBytes_set(val, buf, pos) {
          buf.set(val, pos); // also works for plain array values
      }
      /* istanbul ignore next */
      : function writeBytes_for(val, buf, pos) {
          for (var i = 0; i < val.length; ++i)
              buf[pos + i] = val[i];
      };
  
  /**
   * Writes a sequence of bytes.
   * @param {Uint8Array|string} value Buffer or base64 encoded string to write
   * @returns {Writer} `this`
   */
  Writer.prototype.bytes = function write_bytes(value) {
      var len = value.length >>> 0;
      if (!len)
          return this._push(writeByte, 1, 0);
      if (util.isString(value)) {
          var buf = Writer.alloc(len = base64.length(value));
          base64.decode(value, buf, 0);
          value = buf;
      }
      return this.uint32(len)._push(writeBytes, len, value);
  };
  
  /**
   * Writes a string.
   * @param {string} value Value to write
   * @returns {Writer} `this`
   */
  Writer.prototype.string = function write_string(value) {
      var len = utf8.length(value);
      return len
          ? this.uint32(len)._push(utf8.write, len, value)
          : this._push(writeByte, 1, 0);
  };
  
  /**
   * Forks this writer's state by pushing it to a stack.
   * Calling {@link Writer#reset|reset} or {@link Writer#ldelim|ldelim} resets the writer to the previous state.
   * @returns {Writer} `this`
   */
  Writer.prototype.fork = function fork() {
      this.states = new State(this);
      this.head = this.tail = new Op(noop, 0, 0);
      this.len = 0;
      return this;
  };
  
  /**
   * Resets this instance to the last state.
   * @returns {Writer} `this`
   */
  Writer.prototype.reset = function reset() {
      if (this.states) {
          this.head   = this.states.head;
          this.tail   = this.states.tail;
          this.len    = this.states.len;
          this.states = this.states.next;
      } else {
          this.head = this.tail = new Op(noop, 0, 0);
          this.len  = 0;
      }
      return this;
  };
  
  /**
   * Resets to the last state and appends the fork state's current write length as a varint followed by its operations.
   * @returns {Writer} `this`
   */
  Writer.prototype.ldelim = function ldelim() {
      var head = this.head,
          tail = this.tail,
          len  = this.len;
      this.reset().uint32(len);
      if (len) {
          this.tail.next = head.next; // skip noop
          this.tail = tail;
          this.len += len;
      }
      return this;
  };
  
  /**
   * Finishes the write operation.
   * @returns {Uint8Array} Finished buffer
   */
  Writer.prototype.finish = function finish() {
      var head = this.head.next, // skip noop
          buf  = this.constructor.alloc(this.len),
          pos  = 0;
      while (head) {
          head.fn(head.val, buf, pos);
          pos += head.len;
          head = head.next;
      }
      // this.head = this.tail = null;
      return buf;
  };
  
  Writer._configure = function(BufferWriter_) {
      BufferWriter = BufferWriter_;
      Writer.create = create();
      BufferWriter._configure();
  };
  