// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// Copyright 2020 Keith Cirkel. All rights reserved. MIT license.
// This implementation is a port of https://deno.land/x/varint@v2.0.0 by @keithamus

import { assertEquals, assertThrows } from "jsr:@std/assert@^0.215.0";
import {
  decode,
  decode32,
  encode,
  MaxUInt64,
  MaxVarIntLen64,
} from "./varint.ts";

function encodeDecode(i: number | bigint) {
  const [buf, n] = encode(i, new Uint8Array(MaxVarIntLen64));
  const fn = (typeof i === "bigint") ? decode : decode32;
  const [j, m] = fn(buf);
  assertEquals(i, j, `${fn.name}(encode(${i})): ${i} !== ${j}`);
  assertEquals(n, m, `${fn.name}(encode(${i})): buffer lengths ${n} !== ${m}`);
}

Deno.test("decode() handles empty buff", () => {
  assertThrows(() => decode(Uint8Array.of()), RangeError);
});

Deno.test("decode() handles manual", () => {
  assertEquals(decode(Uint8Array.of(172, 2)), [300n, 2]);
});
Deno.test("decode() handles max size", () => {
  assertEquals(
    decode(Uint8Array.of(255, 255, 255, 255, 255, 255, 255, 255, 255, 1)),
    [18446744073709551615n, 10],
  );
});
Deno.test("decode() throws on overflow", () => {
  assertThrows(
    () => decode(Uint8Array.of(255, 255, 255, 255, 255, 255, 255, 255, 255, 2)),
    RangeError,
  );
});
Deno.test("decode() handles with offset", () => {
  assertEquals(
    decode(
      Uint8Array.of(
        255,
        255,
        255,
        255,
        255,
        255,
        255,
        255,
        255,
        255,
        255,
        255,
        255,
        1,
      ),
      4,
    ),
    [18446744073709551615n, 14],
  );
});
Deno.test("decode32() handles manual", () => {
  assertEquals(decode32(Uint8Array.of(172, 2)), [300, 2]);
});
Deno.test("decode32() handles max size", () => {
  assertEquals(
    decode32(Uint8Array.of(255, 255, 255, 255, 15, 0, 0, 0, 0, 0)),
    [4294967295, 5],
  );
});
Deno.test("decode32() throws on overflow", () => {
  assertThrows(
    () =>
      decode32(Uint8Array.of(255, 255, 255, 255, 255, 255, 255, 255, 15, 0)),
    RangeError,
  );
});
Deno.test("decode32() handles with offset", () => {
  assertEquals(
    decode32(Uint8Array.of(255, 255, 255, 255, 255, 255, 255, 255, 15, 0), 4),
    [4294967295, 9],
  );
});
Deno.test("encode() handles manual", () => {
  assertEquals(encode(300, new Uint8Array(2)), [Uint8Array.of(172, 2), 2]);
  assertEquals(
    encode(4294967295),
    [Uint8Array.of(255, 255, 255, 255, 15), 5],
  );
  assertEquals(
    encode(18446744073709551615n),
    [Uint8Array.of(255, 255, 255, 255, 255, 255, 255, 255, 255, 1), 10],
  );
});
Deno.test("encode() throws on overflow uint64", () => {
  assertThrows(() => encode(1e+30), RangeError, "overflows uint64");
});
Deno.test("encode() throws on overflow with negative", () => {
  assertThrows(() => encode(-1), RangeError, "signed input given");
});
Deno.test("encode() encodes with offset", () => {
  let uint = new Uint8Array(3);
  assertEquals(
    encode(300, uint, 1),
    [Uint8Array.of(172, 2), 3],
  );
  assertEquals(uint, Uint8Array.of(0, 172, 2));
  uint = new Uint8Array(MaxVarIntLen64);
  uint[0] = uint[1] = uint[2] = 12;
  assertEquals(
    encode(4294967295, uint, 3),
    [Uint8Array.of(255, 255, 255, 255, 15), 8],
  );
  assertEquals(uint, Uint8Array.of(12, 12, 12, 255, 255, 255, 255, 15, 0, 0));
});
Deno.test("encodeDecode() handles BigInt", () => {
  for (
    const i of [
      0n,
      1n,
      2n,
      10n,
      20n,
      63n,
      64n,
      65n,
      127n,
      128n,
      129n,
      255n,
      256n,
      257n,
      300n,
      18446744073709551615n,
    ]
  ) {
    encodeDecode(i);
  }
  for (let i = 0x7n; i < MaxUInt64; i <<= 1n) {
    encodeDecode(i);
  }
});
Deno.test("encodeDecode() handles decode32", () => {
  for (
    const i of [
      0,
      1,
      2,
      10,
      20,
      63,
      64,
      65,
      127,
      128,
      129,
      255,
      256,
      257,
      300,
      4294967295,
    ]
  ) {
    encodeDecode(i);
  }
  for (let i = 0x7; i > 0; i <<= 1) {
    encodeDecode(i);
  }
});
