text: /*! torrent-piece. MIT License. WebTorrent LLC <https://webtorrent.io/opensource> */
  const BLOCK_LENGTH = 1 << 14
  
  class Piece {
    constructor (length) {
      this.length = length
      this.missing = length
      this.sources = null
  
      this._chunks = Math.ceil(length / BLOCK_LENGTH)
      this._remainder = (length % BLOCK_LENGTH) || BLOCK_LENGTH
      this._buffered = 0
      this._buffer = null
      this._cancellations = null
      this._reservations = 0
      this._flushed = false
    }
  
    chunkLength (i) {
      return i === this._chunks - 1 ? this._remainder : BLOCK_LENGTH
    }
  
    chunkLengthRemaining (i) {
      return this.length - (i * BLOCK_LENGTH)
    }
  
    chunkOffset (i) {
      return i * BLOCK_LENGTH
    }
  
    reserve () {
      if (!this.init()) return -1
      if (this._cancellations.length) return this._cancellations.pop()
      if (this._reservations < this._chunks) return this._reservations++
      return -1
    }
  
    reserveRemaining () {
      if (!this.init()) return -1
      if (this._cancellations.length || this._reservations < this._chunks) {
        let min = this._reservations
        while (this._cancellations.length) {
          min = Math.min(min, this._cancellations.pop())
        }
        this._reservations = this._chunks
        return min
      }
      return -1
    }
  
    cancel (i) {
      if (!this.init()) return
      this._cancellations.push(i)
    }
  
    cancelRemaining (i) {
      if (!this.init()) return
      this._reservations = i
    }
  
    get (i) {
      if (!this.init()) return null
      return this._buffer[i]
    }
  
    set (i, data, source) {
      if (!this.init()) return false
      const len = data.length
      const blocks = Math.ceil(len / BLOCK_LENGTH)
      for (let j = 0; j < blocks; j++) {
        if (!this._buffer[i + j]) {
          const offset = j * BLOCK_LENGTH
          const splitData = data.slice(offset, offset + BLOCK_LENGTH)
          this._buffered++
          this._buffer[i + j] = splitData
          this.missing -= splitData.length
          if (!this.sources.includes(source)) {
            this.sources.push(source)
          }
        }
      }
      return this._buffered === this._chunks
    }
  
    flush () {
      if (!this._buffer || this._chunks !== this._buffered) return null
      const buffer = Buffer.concat(this._buffer, this.length)
      this._buffer = null
      this._cancellations = null
      this.sources = null
      this._flushed = true
      return buffer
    }
  
    init () {
      if (this._flushed) return false
      if (this._buffer) return true
      this._buffer = new Array(this._chunks)
      this._cancellations = []
      this.sources = []
      return true
    }
  }
  
  Object.defineProperty(Piece, 'BLOCK_LENGTH', { value: BLOCK_LENGTH })
  
  module.exports = Piece
  