module.exports = Piece

var BLOCK_LENGTH = 1 << 14

function Piece (length) {
  if (!(this instanceof Piece)) return new Piece(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
}

Piece.BLOCK_LENGTH = BLOCK_LENGTH

Piece.prototype.chunkLength = function (i) {
  return i === this._chunks - 1 ? this._remainder : BLOCK_LENGTH
}

Piece.prototype.chunkLengthRemaining = function (i) {
  return this.length - (i * BLOCK_LENGTH)
}

Piece.prototype.chunkOffset = function (i) {
  return i * BLOCK_LENGTH
}

Piece.prototype.reserve = function () {
  if (!this.init()) return -1
  if (this._cancellations.length) return this._cancellations.pop()
  if (this._reservations < this._chunks) return this._reservations++
  return -1
}

Piece.prototype.reserveRemaining = function () {
  if (!this.init()) return -1
  if (this._reservations < this._chunks) {
    var min = this._reservations
    this._reservations = this._chunks
    return min
  }
  return -1
}

Piece.prototype.cancel = function (i) {
  if (!this.init()) return
  this._cancellations.push(i)
}

Piece.prototype.cancelRemaining = function (i) {
  if (!this.init()) return
  this._reservations = i
}

Piece.prototype.get = function (i) {
  if (!this.init()) return null
  return this._buffer[i]
}

Piece.prototype.set = function (i, data, source) {
  if (!this.init()) return false
  var len = data.length
  var blocks = Math.ceil(len / BLOCK_LENGTH)
  for (var j = 0; j < blocks; j++) {
    if (!this._buffer[i + j]) {
      var offset = j * BLOCK_LENGTH
      var splitData = data.slice(offset, offset + BLOCK_LENGTH)
      this._buffered++
      this._buffer[i + j] = splitData
      this.missing -= splitData.length
      if (this.sources.indexOf(source) === -1) {
        this.sources.push(source)
      }
    }
  }
  return this._buffered === this._chunks
}

Piece.prototype.flush = function () {
  if (!this._buffer || this._chunks !== this._buffered) return null
  var buffer = Buffer.concat(this._buffer, this.length)
  this._buffer = null
  this._cancellations = null
  this.sources = null
  this._flushed = true
  return buffer
}

Piece.prototype.init = function () {
  if (this._flushed) return false
  if (this._buffer) return true
  this._buffer = new Array(this._chunks)
  this._cancellations = []
  this.sources = []
  return true
}
