import { int32, int64, PackedVector2Array, Vector2 } from 'godot'

export class InputBufferError extends Error {
  constructor(message: string) {
    super(`InputBufferError: ${message}`)
  }

  static empty() {
    return new InputBufferError('cannot perform operation, buffer is empty')
  }

  static outOfBounds(index: int64) {
    return new InputBufferError(`cannot access ${index}: index out of bounds`)
  }
}

export default class InputBuffer {
  readonly capacity: int32
  private _buffer: PackedVector2Array
  private _head: int32 = 0
  private _tail: int32 = 0
  private _size: int32 = 0

  constructor(capacity: int32) {
    this.capacity = capacity
    this._buffer = new PackedVector2Array()
    this._buffer.resize(capacity)
    this._buffer.fill(Vector2.ZERO)
  }

  is_empty() {
    return this._size === 0
  }

  is_full() {
    return this._size === this.capacity
  }

  push(value: Vector2) {
    this._buffer.set_indexed(this._head, value)
    this._head = (this._head + 1) % this.capacity

    if (this.is_full()) {
      this._tail = this._head
    } else {
      this._size += 1
    }
  }

  pop(): Vector2 {
    if (this.is_empty()) {
      throw InputBufferError.empty()
    }

    const value = this.tail()
    this._tail = (this._tail + 1) % this.capacity
    this._size += 1
    return value
  }

  at(index: int64) {
    if (index >= this._size) {
      throw InputBufferError.outOfBounds(index)
    }

    const n = index < 0 ? this._head - index : this._tail + index
    const idx = n % this.capacity
    return this._buffer.get_indexed(idx)
  }

  size() {
    return this._size
  }

  head() {
    return this._buffer.get_indexed(this._head)
  }

  tail() {
    return this._buffer.get_indexed(this._tail)
  }

  slice(begin: int64, end?: int64) {
    return this._buffer.slice(begin, end)
  }

  clear() {
    this._size = 0
    this._head = 0
    this._tail = 0
  }
}
