{ depot, lib, ... }:

let

  inherit (depot.nix.runTestsuite)
    runTestsuite
    it
    assertEq
    ;

  inherit (depot.users.sterni.nix)
    int
    string
    fun
    ;

  testBounds = it "checks minBound and maxBound" [
    (assertEq "maxBound is the maxBound" true
      (int.maxBound + 1 < int.maxBound))
    (assertEq "minBound is the minBound" true
      (int.minBound - 1 > int.minBound))
    (assertEq "maxBound overflows to minBound"
      (int.maxBound + 1)
      int.minBound)
    (assertEq "minBound overflows to maxBound"
      (int.minBound - 1)
      int.maxBound)
  ];

  expectedBytes = [
    "00"
    "01"
    "02"
    "03"
    "04"
    "05"
    "06"
    "07"
    "08"
    "09"
    "0A"
    "0B"
    "0C"
    "0D"
    "0E"
    "0F"
    "10"
    "11"
    "12"
    "13"
    "14"
    "15"
    "16"
    "17"
    "18"
    "19"
    "1A"
    "1B"
    "1C"
    "1D"
    "1E"
    "1F"
    "20"
    "21"
    "22"
    "23"
    "24"
    "25"
    "26"
    "27"
    "28"
    "29"
    "2A"
    "2B"
    "2C"
    "2D"
    "2E"
    "2F"
    "30"
    "31"
    "32"
    "33"
    "34"
    "35"
    "36"
    "37"
    "38"
    "39"
    "3A"
    "3B"
    "3C"
    "3D"
    "3E"
    "3F"
    "40"
    "41"
    "42"
    "43"
    "44"
    "45"
    "46"
    "47"
    "48"
    "49"
    "4A"
    "4B"
    "4C"
    "4D"
    "4E"
    "4F"
    "50"
    "51"
    "52"
    "53"
    "54"
    "55"
    "56"
    "57"
    "58"
    "59"
    "5A"
    "5B"
    "5C"
    "5D"
    "5E"
    "5F"
    "60"
    "61"
    "62"
    "63"
    "64"
    "65"
    "66"
    "67"
    "68"
    "69"
    "6A"
    "6B"
    "6C"
    "6D"
    "6E"
    "6F"
    "70"
    "71"
    "72"
    "73"
    "74"
    "75"
    "76"
    "77"
    "78"
    "79"
    "7A"
    "7B"
    "7C"
    "7D"
    "7E"
    "7F"
    "80"
    "81"
    "82"
    "83"
    "84"
    "85"
    "86"
    "87"
    "88"
    "89"
    "8A"
    "8B"
    "8C"
    "8D"
    "8E"
    "8F"
    "90"
    "91"
    "92"
    "93"
    "94"
    "95"
    "96"
    "97"
    "98"
    "99"
    "9A"
    "9B"
    "9C"
    "9D"
    "9E"
    "9F"
    "A0"
    "A1"
    "A2"
    "A3"
    "A4"
    "A5"
    "A6"
    "A7"
    "A8"
    "A9"
    "AA"
    "AB"
    "AC"
    "AD"
    "AE"
    "AF"
    "B0"
    "B1"
    "B2"
    "B3"
    "B4"
    "B5"
    "B6"
    "B7"
    "B8"
    "B9"
    "BA"
    "BB"
    "BC"
    "BD"
    "BE"
    "BF"
    "C0"
    "C1"
    "C2"
    "C3"
    "C4"
    "C5"
    "C6"
    "C7"
    "C8"
    "C9"
    "CA"
    "CB"
    "CC"
    "CD"
    "CE"
    "CF"
    "D0"
    "D1"
    "D2"
    "D3"
    "D4"
    "D5"
    "D6"
    "D7"
    "D8"
    "D9"
    "DA"
    "DB"
    "DC"
    "DD"
    "DE"
    "DF"
    "E0"
    "E1"
    "E2"
    "E3"
    "E4"
    "E5"
    "E6"
    "E7"
    "E8"
    "E9"
    "EA"
    "EB"
    "EC"
    "ED"
    "EE"
    "EF"
    "F0"
    "F1"
    "F2"
    "F3"
    "F4"
    "F5"
    "F6"
    "F7"
    "F8"
    "F9"
    "FA"
    "FB"
    "FC"
    "FD"
    "FE"
    "FF"
  ];

  hexByte = i: string.fit { width = 2; char = "0"; } (int.toHex i);

  hexInts = [
    { left = 0; right = "0"; }
    { left = 1; right = "1"; }
    { left = 11; right = "B"; }
    { left = 123; right = "7B"; }
    { left = 9000; right = "2328"; }
    { left = 2323; right = "913"; }
    { left = 4096; right = "1000"; }
    { left = int.maxBound; right = "7FFFFFFFFFFFFFFF"; }
    { left = int.minBound; right = "-8000000000000000"; }
  ];

  testHex = it "checks conversion to hex" (lib.flatten [
    (lib.imap0
      (i: hex: [
        (assertEq "hexByte ${toString i} == ${hex}" (hexByte i) hex)
        (assertEq "${toString i} == fromHex ${hex}" i (int.fromHex hex))
      ])
      expectedBytes)
    (builtins.map
      ({ left, right }: [
        (assertEq "toHex ${toString left} == ${right}" (int.toHex left) right)
        (assertEq "${toString left} == fromHex ${right}" left (int.fromHex right))
      ])
      hexInts)
  ]);

  testBasic = it "checks basic int operations" [
    (assertEq "122 is even" (int.even 122 && !(int.odd 122)) true)
    (assertEq "123 is odd" (int.odd 123 && !(int.even 123)) true)
  ];

  expNumbers = [
    { left = -3; right = 0.125; }
    { left = -2; right = 0.25; }
    { left = -1; right = 0.5; }
    { left = 0; right = 1; }
    { left = 1; right = 2; }
    { left = 2; right = 4; }
    { left = 3; right = 8; }
    { left = 4; right = 16; }
    { left = 5; right = 32; }
    { left = 16; right = 65536; }
  ];

  testExp = it "checks exponentiation"
    (builtins.map
      ({ left, right }:
        assertEq
          "2 ^ ${toString left} == ${toString right}"
          (int.exp 2 left)
          right)
      expNumbers);

  shifts = [
    { a = 2; b = 5; c = 64; op = "<<"; }
    { a = -2; b = 5; c = -64; op = "<<"; }
    { a = 123; b = 4; c = 1968; op = "<<"; }
    { a = 1; b = 8; c = 256; op = "<<"; }
    { a = 256; b = 8; c = 1; op = ">>"; }
    { a = 374; b = 2; c = 93; op = ">>"; }
    { a = 2; b = 2; c = 0; op = ">>"; }
    { a = 99; b = 9; c = 0; op = ">>"; }
  ];

  checkShift = { a, b, c, op }@args:
    let
      f = string.match op {
        "<<" = int.bitShiftL;
        ">>" = int.bitShiftR;
      };
    in
    assertEq "${toString a} ${op} ${toString b} == ${toString c}" (f a b) c;

  checkShiftRDivExp = n:
    assertEq "${toString n} >> 5 == ${toString n} / 2 ^ 5"
      (int.bitShiftR n 5)
      (n / (int.exp 2 5));

  checkShiftLMulExp = n:
    assertEq "${toString n} >> 6 == ${toString n} * 2 ^ 6"
      (int.bitShiftL n 5)
      (n * (int.exp 2 5));

  testBit = it "checks bitwise operations" (lib.flatten [
    (builtins.map checkShift shifts)
    (builtins.map checkShiftRDivExp [
      1
      2
      3
      5
      7
      23
      1623
      238
      34
      348
      2834
      834
      348
    ])
    (builtins.map checkShiftLMulExp [
      1
      2
      3
      5
      7
      23
      384
      3
      2
      5991
      85109
      38
    ])
  ]);

  divisions = [
    { a = 2; b = 1; c = 2; rem = 0; }
    { a = 2; b = 2; c = 1; rem = 0; }
    { a = 20; b = 10; c = 2; rem = 0; }
    { a = 12; b = 5; c = 2; rem = 2; }
    { a = 23; b = 4; c = 5; rem = 3; }
  ];

  checkQuot = n: { a, b, c, rem }: [
    (assertEq "${n}: quot result" (int.quot a b) c)
    (assertEq "${n}: rem result" (int.rem a b) rem)
    (assertEq "${n}: quotRem law" ((int.quot a b) * b + (int.rem a b)) a)
  ];

  testQuotRem = it "checks integer quotient and remainder"
    (lib.flatten [
      (builtins.map (checkQuot "+a / +b") divisions)
      (builtins.map
        (fun.rl (checkQuot "-a / +b") (x: x // {
          a = -x.a;
          c = -x.c;
          rem = -x.rem;
        }))
        divisions)
      (builtins.map
        (fun.rl (checkQuot "+a / -b") (x: x // {
          b = -x.b;
          c = -x.c;
        }))
        divisions)
      (builtins.map
        (fun.rl (checkQuot "-a / -b") (x: x // {
          a = -x.a;
          b = -x.b;
          rem = -x.rem;
        }))
        divisions)
    ]);

in
runTestsuite "nix.int" [
  testBounds
  testHex
  testBasic
  testExp
  testBit
  testQuotRem
]
