<?php

/*
 * By adding type hints and enabling strict type checking, code can become
 * easier to read, self-documenting and reduce the number of potential bugs.
 * By default, type declarations are non-strict, which means they will attempt
 * to change the original type to match the type specified by the
 * type-declaration.
 *
 * In other words, if you pass a string to a function requiring a float,
 * it will attempt to convert the string value to a float.
 *
 * To enable strict mode, a single declare directive must be placed at the top
 * of the file.
 * This means that the strictness of typing is configured on a per-file basis.
 * This directive not only affects the type declarations of parameters, but also
 * a function's return type.
 *
 * For more info review the Concept on strict type checking in the PHP track
 * <link>.
 *
 * To disable strict typing, comment out the directive below.
 */

declare(strict_types=1);

class CircularBuffer {

  /**
   * @var int Array capacity.
   */
  private int $cap = 0;

  /**
   * @var \SplFixedArray Storage, faster than plain array and fixed size.
   */
  private SplFixedArray $data;

  /**
   * @var int Start of data with $this->>data.
   */
  private int $base = 0;

  /**
   * @var int Number of data items in $this->>data.
   */
  private int $length = 0;

  public function __construct(int $length) {
    $this->cap = $length;
    $this->data = new SplFixedArray($length);
  }

  public function clear(): void {
    $this->data = new SplFixedArray($this->cap);
    $this->base = 0;
    $this->length = 0;
  }

  /**
   * @throws \BufferEmptyError
   */
  public function read() {
    if ($this->length === 0) {
      throw new BufferEmptyError();
    }
    $res = $this->data[$this->base];
    $this->base = ($this->base + 1) % $this->cap;
    $this->length--;
    return $res;
  }

  /**
   * @throws \BufferFullError
   */
  public function write($item): void {
    if ($this->length == $this->cap) {
      throw new BufferFullError();
    }
    $this->data[($this->base+$this->length)%$this->cap] = $item;
    $this->length++;
  }

  public function forceWrite($item): void {
    $this->data[($this->base+$this->length)%$this->cap] = $item;
    if ($this->length === $this->cap) {
      $this->base = ($this->base+1)%$this->cap;
    }
    $this->length = min($this->cap, $this->length+1);
  }
}

class BufferFullError extends Exception {

}

class BufferEmptyError extends Exception {

}