'use strict'

const { StringPrototypeSlice, SymbolIterator, TypedArrayPrototypeSet, Uint8Array } = require('../../ours/primordials')

const { Buffer } = require('buffer')

const { inspect } = require('../../ours/util')

module.exports = class BufferList {
  constructor() {
    this.head = null
    this.tail = null
    this.length = 0
  }

  push(v) {
    const entry = {
      data: v,
      next: null
    }
    if (this.length > 0) this.tail.next = entry
    else this.head = entry
    this.tail = entry
    ++this.length
  }

  unshift(v) {
    const entry = {
      data: v,
      next: this.head
    }
    if (this.length === 0) this.tail = entry
    this.head = entry
    ++this.length
  }

  shift() {
    if (this.length === 0) return
    const ret = this.head.data
    if (this.length === 1) this.head = this.tail = null
    else this.head = this.head.next
    --this.length
    return ret
  }

  clear() {
    this.head = this.tail = null
    this.length = 0
  }

  join(s) {
    if (this.length === 0) return ''
    let p = this.head
    let ret = '' + p.data

    while ((p = p.next) !== null) ret += s + p.data

    return ret
  }

  concat(n) {
    if (this.length === 0) return Buffer.alloc(0)
    const ret = Buffer.allocUnsafe(n >>> 0)
    let p = this.head
    let i = 0

    while (p) {
      TypedArrayPrototypeSet(ret, p.data, i)
      i += p.data.length
      p = p.next
    }

    return ret
  } // Consumes a specified amount of bytes or characters from the buffered data.

  consume(n, hasStrings) {
    const data = this.head.data

    if (n < data.length) {
      // `slice` is the same for buffers and strings.
      const slice = data.slice(0, n)
      this.head.data = data.slice(n)
      return slice
    }

    if (n === data.length) {
      // First chunk is a perfect match.
      return this.shift()
    } // Result spans more than one buffer.

    return hasStrings ? this._getString(n) : this._getBuffer(n)
  }

  first() {
    return this.head.data
  }

  *[SymbolIterator]() {
    for (let p = this.head; p; p = p.next) {
      yield p.data
    }
  } // Consumes a specified amount of characters from the buffered data.

  _getString(n) {
    let ret = ''
    let p = this.head
    let c = 0

    do {
      const str = p.data

      if (n > str.length) {
        ret += str
        n -= str.length
      } else {
        if (n === str.length) {
          ret += str
          ++c
          if (p.next) this.head = p.next
          else this.head = this.tail = null
        } else {
          ret += StringPrototypeSlice(str, 0, n)
          this.head = p
          p.data = StringPrototypeSlice(str, n)
        }

        break
      }

      ++c
    } while ((p = p.next) !== null)

    this.length -= c
    return ret
  } // Consumes a specified amount of bytes from the buffered data.

  _getBuffer(n) {
    const ret = Buffer.allocUnsafe(n)
    const retLen = n
    let p = this.head
    let c = 0

    do {
      const buf = p.data

      if (n > buf.length) {
        TypedArrayPrototypeSet(ret, buf, retLen - n)
        n -= buf.length
      } else {
        if (n === buf.length) {
          TypedArrayPrototypeSet(ret, buf, retLen - n)
          ++c
          if (p.next) this.head = p.next
          else this.head = this.tail = null
        } else {
          TypedArrayPrototypeSet(ret, new Uint8Array(buf.buffer, buf.byteOffset, n), retLen - n)
          this.head = p
          p.data = buf.slice(n)
        }

        break
      }

      ++c
    } while ((p = p.next) !== null)

    this.length -= c
    return ret
  } // Make sure the linked list only shows the minimal necessary information.

  [Symbol.for('nodejs.util.inspect.custom')](_, options) {
    return inspect(this, {
      ...options,
      // Only inspect one level.
      depth: 0,
      // It should not recurse.
      customInspect: false
    })
  }
}
