import {expect} from "chai";
import {describe, it} from "mocha";
import {
  computeProofGindices,
  computeProofBitstrings,
  sortInOrderBitstrings,
  filterParentBitstrings,
  computeMultiProofBitstrings,
} from "../../../src/proof/util";

describe("computeProofGindices", () => {
  it("simple implementation should match bitstring implementation", () => {
    const gindices = [BigInt(8), BigInt(9), BigInt(14)];
    for (const gindex of gindices) {
      const simple = computeProofGindices(gindex);
      const bitstring = computeProofBitstrings(gindex.toString(2));
      expect(new Set([...bitstring.branch].map((str) => BigInt("0b" + str)))).to.deep.equal(simple.branch);
      expect(new Set([...bitstring.path].map((str) => BigInt("0b" + str)))).to.deep.equal(simple.path);
    }
  });

  it("should properly compute known testcases", () => {
    const testCases = [
      {
        input: BigInt(8),
        output: {branch: new Set([BigInt(9), BigInt(5), BigInt(3)]), path: new Set([BigInt(8), BigInt(4), BigInt(2)])},
      },
      {
        input: BigInt(9),
        output: {branch: new Set([BigInt(8), BigInt(5), BigInt(3)]), path: new Set([BigInt(9), BigInt(4), BigInt(2)])},
      },
      {
        input: BigInt(14),
        output: {
          branch: new Set([BigInt(15), BigInt(6), BigInt(2)]),
          path: new Set([BigInt(14), BigInt(7), BigInt(3)]),
        },
      },
    ];
    for (const {input, output} of testCases) {
      const actual = computeProofGindices(input);
      expect(actual.branch).to.deep.equal(output.branch);
      expect(actual.path).to.deep.equal(output.path);
    }
  });
});

describe("sortInOrderBitstrings", () => {
  it("should properly compute known testcases", () => {
    const testCases = [
      {
        gindices: [BigInt(6), BigInt(15), BigInt(8), BigInt(5), BigInt(9), BigInt(14)],
        bitLength: 4,
        output: [BigInt(8), BigInt(9), BigInt(5), BigInt(6), BigInt(14), BigInt(15)],
      },
    ];
    for (const {gindices, bitLength, output} of testCases) {
      const actual = sortInOrderBitstrings(
        gindices.map((g) => g.toString(2)),
        bitLength
      ).map((str) => BigInt("0b" + str));
      expect(actual).to.deep.equal(output);
    }
  });
});

describe("filterParentBitstrings", () => {
  it("should properly compute known testcases", () => {
    const testCases = [
      {
        input: [BigInt(6), BigInt(15), BigInt(8), BigInt(5), BigInt(9), BigInt(14)],
        output: [BigInt(6), BigInt(15), BigInt(8), BigInt(5), BigInt(9), BigInt(14)],
      },
      {input: [BigInt(8), BigInt(4), BigInt(3), BigInt(2)], output: [BigInt(8), BigInt(3)]},
      {
        input: Array.from({length: 16}, (_, i) => BigInt(i + 1)),
        output: Array.from({length: 8}, (_, i) => BigInt(i + 8 + 1)),
      },
    ];
    for (const {input, output} of testCases) {
      const actual = filterParentBitstrings(input.map((g) => g.toString(2))).map((str) => BigInt("0b" + str));
      expect(new Set(actual)).to.deep.equal(new Set(output));
    }
  });
});

describe("computeMultiProofBitstrings", () => {
  it("should properly compute known testcases", () => {
    const testCases = [
      // testcase from https://github.com/ethereum/eth2.0-specs/blob/v1.0.0/ssz/merkle-proofs.md#merkle-multiproofs
      {
        input: [BigInt(8), BigInt(9), BigInt(14)],
        output: [BigInt(8), BigInt(9), BigInt(5), BigInt(6), BigInt(14), BigInt(15)],
      },
      // order, duplicates, parents shouldn't effect the output
      {
        input: [BigInt(14), BigInt(9), BigInt(3), BigInt(14), BigInt(9), BigInt(8)],
        output: [BigInt(8), BigInt(9), BigInt(5), BigInt(6), BigInt(14), BigInt(15)],
      },
    ];
    for (const {input, output} of testCases) {
      const actual = computeMultiProofBitstrings(input.map((g) => g.toString(2))).map((str) => BigInt("0b" + str));
      expect(actual).to.deep.equal(output);
    }
  });
});