const { ethers } = require('hardhat');
const { expect } = require('chai');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');

const { generators } = require('../../helpers/random');

const LENGTH = 4;

async function fixture() {
  const mock = await ethers.deployContract('$CircularBuffer');
  await mock.$setup(0, LENGTH);
  return { mock };
}

describe('CircularBuffer', function () {
  beforeEach(async function () {
    Object.assign(this, await loadFixture(fixture));
  });

  it('starts empty', async function () {
    expect(await this.mock.$count(0)).to.equal(0n);
    expect(await this.mock.$length(0)).to.equal(LENGTH);
    expect(await this.mock.$includes(0, ethers.ZeroHash)).to.be.false;
    await expect(this.mock.$last(0, 0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS);
  });

  it('push', async function () {
    const values = Array.from({ length: LENGTH + 3 }, generators.bytes32);

    for (const [i, value] of values.map((v, i) => [i, v])) {
      // push value
      await this.mock.$push(0, value);

      // view of the values
      const pushed = values.slice(0, i + 1);
      const stored = pushed.slice(-LENGTH);
      const dropped = pushed.slice(0, -LENGTH);

      // check count
      expect(await this.mock.$length(0)).to.equal(LENGTH);
      expect(await this.mock.$count(0)).to.equal(stored.length);

      // check last
      for (const j in stored) {
        expect(await this.mock.$last(0, j)).to.equal(stored.at(-j - 1));
      }
      await expect(this.mock.$last(0, stored.length + 1)).to.be.revertedWithPanic(
        PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS,
      );

      // check included and non-included values
      for (const v of stored) {
        expect(await this.mock.$includes(0, v)).to.be.true;
      }
      for (const v of dropped) {
        expect(await this.mock.$includes(0, v)).to.be.false;
      }
      expect(await this.mock.$includes(0, ethers.ZeroHash)).to.be.false;
    }
  });

  it('clear', async function () {
    const value = generators.bytes32();
    await this.mock.$push(0, value);

    expect(await this.mock.$count(0)).to.equal(1n);
    expect(await this.mock.$length(0)).to.equal(LENGTH);
    expect(await this.mock.$includes(0, value)).to.be.true;
    await this.mock.$last(0, 0); // not revert

    await this.mock.$clear(0);

    expect(await this.mock.$count(0)).to.equal(0n);
    expect(await this.mock.$length(0)).to.equal(LENGTH);
    expect(await this.mock.$includes(0, value)).to.be.false;
    await expect(this.mock.$last(0, 0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS);
  });
});
