text: 'use strict'
  const MiniPass = require('minipass')
  const EE = require('events').EventEmitter
  const fs = require('fs')
  
  // for writev
  const binding = process.binding('fs')
  const writeBuffers = binding.writeBuffers
  const FSReqWrap = binding.FSReqWrap
  
  const _autoClose = Symbol('_autoClose')
  const _close = Symbol('_close')
  const _ended = Symbol('_ended')
  const _fd = Symbol('_fd')
  const _finished = Symbol('_finished')
  const _flags = Symbol('_flags')
  const _flush = Symbol('_flush')
  const _handleChunk = Symbol('_handleChunk')
  const _makeBuf = Symbol('_makeBuf')
  const _mode = Symbol('_mode')
  const _needDrain = Symbol('_needDrain')
  const _onerror = Symbol('_onerror')
  const _onopen = Symbol('_onopen')
  const _onread = Symbol('_onread')
  const _onwrite = Symbol('_onwrite')
  const _open = Symbol('_open')
  const _path = Symbol('_path')
  const _pos = Symbol('_pos')
  const _queue = Symbol('_queue')
  const _read = Symbol('_read')
  const _readSize = Symbol('_readSize')
  const _reading = Symbol('_reading')
  const _remain = Symbol('_remain')
  const _size = Symbol('_size')
  const _write = Symbol('_write')
  const _writing = Symbol('_writing')
  const _defaultFlag = Symbol('_defaultFlag')
  
  class ReadStream extends MiniPass {
    constructor (path, opt) {
      opt = opt || {}
      super(opt)
  
      this.writable = false
  
      if (typeof path !== 'string')
        throw new TypeError('path must be a string')
  
      this[_fd] = typeof opt.fd === 'number' ? opt.fd : null
      this[_path] = path
      this[_readSize] = opt.readSize || 16*1024*1024
      this[_reading] = false
      this[_size] = typeof opt.size === 'number' ? opt.size : Infinity
      this[_remain] = this[_size]
      this[_autoClose] = typeof opt.autoClose === 'boolean' ?
        opt.autoClose : true
  
      if (typeof this[_fd] === 'number')
        this[_read]()
      else
        this[_open]()
    }
  
    get fd () { return this[_fd] }
    get path () { return this[_path] }
  
    write () {
      throw new TypeError('this is a readable stream')
    }
  
    end () {
      throw new TypeError('this is a readable stream')
    }
  
    [_open] () {
      fs.open(this[_path], 'r', (er, fd) => this[_onopen](er, fd))
    }
  
    [_onopen] (er, fd) {
      if (er)
        this[_onerror](er)
      else {
        this[_fd] = fd
        this.emit('open', fd)
        this[_read]()
      }
    }
  
    [_makeBuf] () {
      return Buffer.allocUnsafe(Math.min(this[_readSize], this[_remain]))
    }
  
    [_read] () {
      if (!this[_reading]) {
        this[_reading] = true
        const buf = this[_makeBuf]()
        /* istanbul ignore if */
        if (buf.length === 0) return process.nextTick(() => this[_onread](null, 0, buf))
        fs.read(this[_fd], buf, 0, buf.length, null, (er, br, buf) =>
          this[_onread](er, br, buf))
      }
    }
  
    [_onread] (er, br, buf) {
      this[_reading] = false
      if (er)
        this[_onerror](er)
      else if (this[_handleChunk](br, buf))
        this[_read]()
    }
  
    [_close] () {
      if (this[_autoClose] && typeof this[_fd] === 'number') {
        fs.close(this[_fd], _ => this.emit('close'))
        this[_fd] = null
      }
    }
  
    [_onerror] (er) {
      this[_reading] = true
      this[_close]()
      this.emit('error', er)
    }
  
    [_handleChunk] (br, buf) {
      let ret = false
      // no effect if infinite
      this[_remain] -= br
      if (br > 0)
        ret = super.write(br < buf.length ? buf.slice(0, br) : buf)
  
      if (br === 0 || this[_remain] <= 0) {
        ret = false
        this[_close]()
        super.end()
      }
  
      return ret
    }
  
    emit (ev, data) {
      switch (ev) {
        case 'prefinish':
        case 'finish':
          break
  
        case 'drain':
          if (typeof this[_fd] === 'number')
            this[_read]()
          break
  
        default:
          return super.emit(ev, data)
      }
    }
  }
  
  class ReadStreamSync extends ReadStream {
    [_open] () {
      let threw = true
      try {
        this[_onopen](null, fs.openSync(this[_path], 'r'))
        threw = false
      } finally {
        if (threw)
          this[_close]()
      }
    }
  
    [_read] () {
      let threw = true
      try {
        if (!this[_reading]) {
          this[_reading] = true
          do {
            const buf = this[_makeBuf]()
            /* istanbul ignore next */
            const br = buf.length === 0 ? 0 : fs.readSync(this[_fd], buf, 0, buf.length, null)
            if (!this[_handleChunk](br, buf))
              break
          } while (true)
          this[_reading] = false
        }
        threw = false
      } finally {
        if (threw)
          this[_close]()
      }
    }
  
    [_close] () {
      if (this[_autoClose] && typeof this[_fd] === 'number') {
        try {
          fs.closeSync(this[_fd])
        } catch (er) {}
        this[_fd] = null
        this.emit('close')
      }
    }
  }
  
  class WriteStream extends EE {
    constructor (path, opt) {
      opt = opt || {}
      super(opt)
      this.readable = false
      this[_writing] = false
      this[_ended] = false
      this[_needDrain] = false
      this[_queue] = []
      this[_path] = path
      this[_fd] = typeof opt.fd === 'number' ? opt.fd : null
      this[_mode] = opt.mode === undefined ? 0o666 : opt.mode
      this[_pos] = typeof opt.start === 'number' ? opt.start : null
      this[_autoClose] = typeof opt.autoClose === 'boolean' ?
        opt.autoClose : true
  
      // truncating makes no sense when writing into the middle
      const defaultFlag = this[_pos] !== null ? 'r+' : 'w'
      this[_defaultFlag] = opt.flags === undefined
      this[_flags] = this[_defaultFlag] ? defaultFlag : opt.flags
  
      if (this[_fd] === null)
        this[_open]()
    }
  
    get fd () { return this[_fd] }
    get path () { return this[_path] }
  
    [_onerror] (er) {
      this[_close]()
      this[_writing] = true
      this.emit('error', er)
    }
  
    [_open] () {
      fs.open(this[_path], this[_flags], this[_mode],
        (er, fd) => this[_onopen](er, fd))
    }
  
    [_onopen] (er, fd) {
      if (this[_defaultFlag] &&
          this[_flags] === 'r+' &&
          er && er.code === 'ENOENT') {
        this[_flags] = 'w'
        this[_open]()
      } else if (er)
        this[_onerror](er)
      else {
        this[_fd] = fd
        this.emit('open', fd)
        this[_flush]()
      }
    }
  
    end (buf, enc) {
      if (buf)
        this.write(buf, enc)
  
      this[_ended] = true
  
      // synthetic after-write logic, where drain/finish live
      if (!this[_writing] && !this[_queue].length &&
          typeof this[_fd] === 'number')
        this[_onwrite](null, 0)
    }
  
    write (buf, enc) {
      if (typeof buf === 'string')
        buf = new Buffer(buf, enc)
  
      if (this[_ended]) {
        this.emit('error', new Error('write() after end()'))
        return false
      }
  
      if (this[_fd] === null || this[_writing] || this[_queue].length) {
        this[_queue].push(buf)
        this[_needDrain] = true
        return false
      }
  
      this[_writing] = true
      this[_write](buf)
      return true
    }
  
    [_write] (buf) {
      fs.write(this[_fd], buf, 0, buf.length, this[_pos], (er, bw) =>
        this[_onwrite](er, bw))
    }
  
    [_onwrite] (er, bw) {
      if (er)
        this[_onerror](er)
      else {
        if (this[_pos] !== null)
          this[_pos] += bw
        if (this[_queue].length)
          this[_flush]()
        else {
          this[_writing] = false
  
          if (this[_ended] && !this[_finished]) {
            this[_finished] = true
            this[_close]()
            this.emit('finish')
          } else if (this[_needDrain]) {
            this[_needDrain] = false
            this.emit('drain')
          }
        }
      }
    }
  
    [_flush] () {
      if (this[_queue].length === 0) {
        if (this[_ended])
          this[_onwrite](null, 0)
      } else if (this[_queue].length === 1)
        this[_write](this[_queue].pop())
      else {
        const iovec = this[_queue]
        this[_queue] = []
        writev(this[_fd], iovec, this[_pos],
          (er, bw) => this[_onwrite](er, bw))
      }
    }
  
    [_close] () {
      if (this[_autoClose] && typeof this[_fd] === 'number') {
        fs.close(this[_fd], _ => this.emit('close'))
        this[_fd] = null
      }
    }
  }
  
  class WriteStreamSync extends WriteStream {
    [_open] () {
      let fd
      try {
        fd = fs.openSync(this[_path], this[_flags], this[_mode])
      } catch (er) {
        if (this[_defaultFlag] &&
            this[_flags] === 'r+' &&
            er && er.code === 'ENOENT') {
          this[_flags] = 'w'
          return this[_open]()
        } else
          throw er
      }
      this[_onopen](null, fd)
    }
  
    [_close] () {
      if (this[_autoClose] && typeof this[_fd] === 'number') {
        try {
          fs.closeSync(this[_fd])
        } catch (er) {}
        this[_fd] = null
        this.emit('close')
      }
    }
  
    [_write] (buf) {
      try {
        this[_onwrite](null,
          fs.writeSync(this[_fd], buf, 0, buf.length, this[_pos]))
      } catch (er) {
        this[_onwrite](er, 0)
      }
    }
  }
  
  const writev = (fd, iovec, pos, cb) => {
    const done = (er, bw) => cb(er, bw, iovec)
    const req = new FSReqWrap()
    req.oncomplete = done
    binding.writeBuffers(fd, iovec, pos, req)
  }
  
  exports.ReadStream = ReadStream
  exports.ReadStreamSync = ReadStreamSync
  
  exports.WriteStream = WriteStream
  exports.WriteStreamSync = WriteStreamSync
  