import {assertEquals} from "@std/assert";
import {
  type EndFunction,
  is,
  isList,
  isIterator,
  iterator,
  keys,
  values,
  entries,
  from,
  fromInvocation,
  fromApplication,
  fromRange,
  toInvokable,
  isOnce,
  isMany,
  toMany,
  isEmpty,
  length,
  size,
  compare,
  isEqual,
  index,
  indexRange,
  get,
  getAll,
  getPath,
  hasPath,
  set,
  swap,
  remove,
  count,
  countAs,
  min,
  max,
  range,
  minEntry,
  maxEntry,
  rangeEntries,
  slice,
  head,
  tail,
  init,
  last,
  left,
  right,
  middle,
  take,
  takeRight,
  takeWhile,
  takeWhileRight,
  drop,
  dropRight,
  dropWhile,
  dropWhileRight,
  includes,
  indexOf,
  lastIndexOf,
  find,
  findRight,
  findAll,
  scanWhile,
  scanWhileRight,
  scanUntil,
  scanUntilRight,
  search,
  searchRight,
  searchAll,
  searchValue,
  searchValueRight,
  searchValueAll,
  searchInfix,
  searchInfixRight,
  searchInfixAll,
  searchSubsequence,
  hasValue,
  hasPrefix,
  hasSuffix,
  hasInfix,
  hasSubsequence,
  forEach,
  some,
  every,
  map,
  reduce,
  filter,
  filterAt,
  reject,
  rejectAt,
  accumulate,
  flat,
  flatMap,
  zip,
  fill,
  push,
  pop,
  shift,
  unshift,
  copy,
  copyWithin,
  moveWithin,
  splice,
  split,
  splitAt,
  cut,
  cutRight,
  cutAt,
  cutAtRight,
  group,
  partition,
  partitionAs,
  chunk,
  cycle,
  repeat,
  reverse,
  rotate,
  intermix,
  interleave,
  interpolate,
  intersperse,
  concat,
  merge,
  join,
  isUnique,
  isDisjoint,
  unique,
  union,
  intersection,
  difference,
  symmetricDifference,
  cartesianProduct,
} from "./index.ts";
import * as xiterable from "./index.ts";




Deno.test("example", () => {
  let x, v, a;
  x = [2, 4, 6, 8];
  v = xiterable.get(x, 1);
  assertEquals(v, 4);
  // → 4

  x = [1, 2, 3, 4];
  a = [...xiterable.swap(x, 0, 1)];
  assertEquals(a, [2, 1, 3, 4]);
  // → [2, 1, 3, 4]

  x = [1, 2, 3];
  a = [...xiterable.cycle(x, 0, 4)];
  assertEquals(a, [1, 2, 3, 1]);
  // → [1, 2, 3, 1]

  x = [1, 2, 3, 4];
  v = xiterable.reduce(x, (acc, v) => acc+v, 0);
  assertEquals(v, 10);
  // → 10
});




// ABOUT
// -----

Deno.test("is", () => {
  let a;
  a = is([1, 2]);
  assertEquals(a, true);
  a = is("ab");
  assertEquals(a, true);
  a = is(1);
  assertEquals(a, false);
});


Deno.test("isList", () => {
  let a;
  a = isList([1, 2]);
  assertEquals(a, true);
  a = isList("ab");
  assertEquals(a, false);
});


Deno.test("isIterator", () => {
  let a;
  const x = [1, 2];
  a = isIterator(x);
  assertEquals(a, false);
  a = isIterator(x[Symbol.iterator]());
  assertEquals(a, true);
  a = isIterator(x.values());
  assertEquals(a, true);
});


Deno.test("iterator", () => {
  let a;
  a = iterator([1, 2]);
  assertEquals(a.next().value, 1);
  a = iterator(new Set([1, 2]));
  assertEquals(a.next().value, 1);
});


Deno.test("keys", () => {
  const x = [1, 2, 3];
  const a = [...keys(x)];
  assertEquals(a, [0, 1, 2]);
});


Deno.test("values", () => {
  const x = [1, 2, 3];
  const a = [...values(x)];
  assertEquals(a, [1, 2, 3]);
});


Deno.test("entries", () => {
  const x = ["A", "B", "C"];
  const a = [...entries(x)];
  assertEquals(a, [[0, "A"], [1, "B"], [2, "C"]]);
});




// GENERATE
// --------

Deno.test("from", () => {
  const x  = [1, 2].values();
  const xi = x[Symbol.iterator]();
  const a  = [...from(xi)];
  assertEquals(a, [1, 2]);
});


Deno.test("fromInvocation", () => {
  let id = 0;
  function getId() {
    return ++id;
  }
  const x = fromInvocation(getId);
  const a = [...take(x, 4)];
  assertEquals(a, [1, 2, 3, 4]);
});


Deno.test("fromApplication", () => {
  let a;
  a = [...take(fromApplication(v => v+2, 2), 4)];
  assertEquals(a, [2, 4, 6, 8]);
  a = [...take(fromApplication(v => v*2, 2), 4)];
  assertEquals(a, [2, 4, 8, 16]);
});


Deno.test("fromRange", () => {
  let a;
  a = [...fromRange(0, 4)];
  assertEquals(a, [0, 1, 2, 3]);
  a = [...fromRange(0, 8, 2)];
  assertEquals(a, [0, 2, 4, 6]);
});




// BEHAVIOR
// --------

Deno.test("toInvokable", () => {
  const x = [1, 2, 3, 4], s = [0];
  const f = toInvokable(x);
  for (let i=0; i<6; i++)
    s.push(f());
  assertEquals(s, [0, 1, 2, 3, 4, undefined, undefined]);
});


Deno.test("isOnce", () => {
  let a;
  const x = [1, 2, 3, 4];
  a = isOnce(x);
  assertEquals(a, false);
  a = isOnce(x.values());
  assertEquals(a, true);
});


Deno.test("isMany", () => {
  let a;
  const x = [1, 2, 3, 4];
  a = isMany(x);
  assertEquals(a, true);
  a = isMany(x.values());
  assertEquals(a, false);
});


Deno.test("toMany", () => {
  let x, y, a, s;
  const w = [1, 2, 3].values(); s = [0];
  a = [...w, ...w];
  // ("w" cant be iterated multiple times)
  assertEquals(a, [1, 2, 3]);
  function* from1ToN(n: number) {
    for (let i=1; i<=n; i++) {
      s.push(i);
      yield i;
    }
  }
  x = from1ToN(3);
  y = toMany(x);
  assertEquals(s, [0]);
  a = [...y];
  assertEquals(a, [1, 2, 3]);
  assertEquals(s, [0, 1, 2, 3]);
  a = [...y];
  assertEquals(a, [1, 2, 3]);
  assertEquals(s, [0, 1, 2, 3]);
  s = [0];
  x = from1ToN(3);
  y = toMany(x, true);
  assertEquals(s, [0, 1, 2, 3]);
  a = [...y];
  assertEquals(a, [1, 2, 3]);
  assertEquals(s, [0, 1, 2, 3]);
  a = [...y];
  assertEquals(a, [1, 2, 3]);
  assertEquals(s, [0, 1, 2, 3]);
});




// LENGTH
// ------

Deno.test("isEmpty", () => {
  let a;
  const x = [1, 2, 3];
  a = isEmpty(x);
  assertEquals(a, false);

  const y: number[] = [];
  a = isEmpty(y);
  assertEquals(a, true);
});


Deno.test("length", () => {
  let a;
  const x = [2, 4, 6, 8];
  a = length(x);
  assertEquals(a, 4);
  a = length(x, 1);
  assertEquals(a, 3);
  a = length(x, 1, 3);
  assertEquals(a, 2);
});


Deno.test("size", () => {
  let a;
  const x = [2, 4, 6, 8];
  a = size(x);
  assertEquals(a, 4);
  a = size(x, 1);
  assertEquals(a, 3);
  a = size(x, 1, 3);
  assertEquals(a, 2);
});


Deno.test("index", () => {
  let a;
  const x = [2, 4, 6, 8];
  a = index(x, 1);
  assertEquals(a, 1);
  a = index(x, -1);
  assertEquals(a, 3);
  a = index(x, -10);
  assertEquals(a, 0);
});


Deno.test("indexRange", () => {
  let a;
  const x = [2, 4, 6, 8];
  a = indexRange(x, 1);
  assertEquals(a, [1, 4]);
  a = indexRange(x, 1, -1);
  assertEquals(a, [1, 3]);
  a = indexRange(x, 1, -10);
  assertEquals(a, [1, 1]);
});




// COMPARE
// -------

Deno.test("compare", () => {
  let y, a;
  const x = [1, 2];
  y = [1, 2, 3];
  a = compare(x, y);
  assertEquals(a, -1);
  y = [1, 2];
  a = compare(x, y);
  assertEquals(a, 0);
  y = [1, -2];
  a = compare(x, y);
  assertEquals(a, 1);
  a = compare(x, y, (a, b) => Math.abs(a) - Math.abs(b));
  assertEquals(a, 0);
  a = compare(x, y, null, v => Math.abs(v));
  assertEquals(a, 0);
});


Deno.test("isEqual", () => {
  let a;
  const x = [1, 2];
  a = isEqual(x, [1, 2]);
  assertEquals(a, true);
  a = isEqual(x, [11, 12]);
  assertEquals(a, false);
  a = isEqual(x, [11, 12], (a, b) => (a % 10) - (b % 10));
  assertEquals(a, true);
  a = isEqual(x, [11, 12], null, v => v % 10);
  assertEquals(a, true);
});




// GET/SET
// -------

Deno.test("get", () => {
  let a;
  const x = [2, 4, 6, 8];
  a = get(x, 1);
  assertEquals(a, 4);
  a = get(x, 3);
  assertEquals(a, 8);
});


Deno.test("getAll", () => {
  let a;
  const x = [2, 4, 6, 8];
  a = [...getAll(x, [1, 2])];
  assertEquals(a, [4, 6]);
  a = [...getAll(x, [1, 3])];
  assertEquals(a, [4, 8]);
});


Deno.test("getPath", () => {
  let a;
  const x = [[2, 4], 6, 8];
  a = getPath(x, [1]);
  assertEquals(a, 6);
  a = getPath(x, [0, 1]);
  assertEquals(a, 4);
  a = getPath(x, [0, 1, 2]);
  assertEquals(a, undefined);
});


Deno.test("hasPath", () => {
  let a;
  const x = [[2, 4], 6, 8];
  a = hasPath(x, [1]);
  assertEquals(a, true);
  a = hasPath(x, [0, 1]);
  assertEquals(a, true);
  a = hasPath(x, [0, 1, 2]);
  assertEquals(a, false);
});


Deno.test("set", () => {
  let a;
  const x = [2, 4, 6, 8];
  a = [...set(x, 1, 40)];
  assertEquals(a, [2, 40, 6, 8]);
  a = [...set(x, 3, 80)];
  assertEquals(a, [2, 4, 6, 80]);
});


Deno.test("swap", () => {
  let a;
  const x = [1, 2, 3, 4];
  a = [...swap(x, 0, 1)];
  assertEquals(a, [2, 1, 3, 4]);
  a = [...swap(x, 0, 3)];
  assertEquals(a, [4, 2, 3, 1]);
});


Deno.test("remove", () => {
  const x = [1, 2, 3, 4, 5];
  const a = [...remove(x, 2)];
  assertEquals(a, [1, 2, 4, 5]);
});




// PROPERTY
// --------

Deno.test("count", () => {
  let a;
  const x = [1, 1, 2, 2, 4];
  a = count(x, v => v % 2===1);
  assertEquals(a, 2);
  a = count(x, v => v % 2===0);
  assertEquals(a, 3);
});


Deno.test("countAs", () => {
  let x, a;
  x = [1, 1, 2, 2, 4];
  a = countAs(x);
  assertEquals(a, new Map([[1, 2], [2, 2], [4, 1]]));
  x = [1, 2, 3, 4];
  a = countAs(x, v => v % 2);
  assertEquals(a, new Map([[1, 2], [0, 2]]));
});


Deno.test("min", () => {
  let a;
  const x = [1, 2, -3, -4];
  a = min(x);
  assertEquals(a, -4);
  a = min(x, (a, b) => Math.abs(a) - Math.abs(b));
  assertEquals(a, 1);
  a = min(x, null, v => Math.abs(v));
  assertEquals(a, 1);
});


Deno.test("max", () => {
  let a;
  const x = [1, 2, -3, -4];
  a = max(x);
  assertEquals(a, 2);
  a = max(x, (a, b) => Math.abs(a) - Math.abs(b));
  assertEquals(a, -4);
  a = max(x, null, v => Math.abs(v));
  assertEquals(a, -4);
});


Deno.test("range", () => {
  let a;
  const x = [1, 2, -3, -4];
  a = range(x);
  assertEquals(a, [-4, 2]);
  a = range(x, (a, b) => Math.abs(a) - Math.abs(b));
  assertEquals(a, [1, -4]);
  a = range(x, null, v => Math.abs(v));
  assertEquals(a, [1, -4]);
});


Deno.test("minEntry", () => {
  let a;
  const x = [1, 2, -3, -4];
  a = minEntry(x);
  assertEquals(a, [3, -4]);
  a = minEntry(x, (a, b) => Math.abs(a) - Math.abs(b));
  assertEquals(a, [0, 1]);
  a = minEntry(x, null, v => Math.abs(v));
  assertEquals(a, [0, 1]);
});


Deno.test("maxEntry", () => {
  let a;
  const x = [1, 2, -3, -4];
  a = maxEntry(x);
  assertEquals(a, [1, 2]);
  a = maxEntry(x, (a, b) => Math.abs(a) - Math.abs(b));
  assertEquals(a, [3, -4]);
  a = maxEntry(x, null, v => Math.abs(v));
  assertEquals(a, [3, -4]);
});


Deno.test("rangeEntries", () => {
  let a;
  const x = [1, 2, -3, -4];
  a = rangeEntries(x);
  assertEquals(a, [[3, -4], [1, 2]]);
  a = rangeEntries(x, (a, b) => Math.abs(a) - Math.abs(b));
  assertEquals(a, [[0, 1], [3, -4]]);
  a = rangeEntries(x, null, v => Math.abs(v));
  assertEquals(a, [[0, 1], [3, -4]]);
});




// PART
// ----

Deno.test("slice", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = [...slice(x, 2)];
  assertEquals(a, [3, 4, 5]);
  a = [...slice(x, 2, 4)];
  assertEquals(a, [3, 4]);
  a = [...slice(x, -3, -1)];
  assertEquals(a, [3, 4]);
});


Deno.test("head", () => {
  let a;
  a = head([1, 2, 3]);
  assertEquals(a, 1);
  a = head([], -1);
  assertEquals(a, -1);
});


Deno.test("tail", () => {
  let x, a;
  x = [1, 2, 3];
  a = [...tail(x)];
  assertEquals(a, [2, 3]);
  x = [1];
  a = [...tail(x)];
  assertEquals(a, []);
});


Deno.test("init", () => {
  let a;
  a = [...init([1, 2, 3])];
  assertEquals(a, [1, 2]);
  a = [...init([1])];
  assertEquals(a, []);
});


Deno.test("last", () => {
  let a;
  a = last([1, 2, 3]);
  assertEquals(a, 3);
  a = last([], -1);
  assertEquals(a, -1);
});


Deno.test("left", () => {
  let a;
  const x = [1, 2, 3];
  a = [...left(x, 1)];
  assertEquals(a, [1]);
  a = [...left(x, 2)];
  assertEquals(a, [1, 2]);
});


Deno.test("right", () => {
  let a;
  const x = [1, 2, 3];
  a = [...right(x, 1)];
  assertEquals(a, [3]);
  a = [...right(x, 2)];
  assertEquals(a, [2, 3]);
});


Deno.test("middle", () => {
  let a;
  const x = [1, 2, 3];
  a = [...middle(x, 1, 1)];
  assertEquals(a, [2]);
  a = [...middle(x, 1, 2)];
  assertEquals(a, [2, 3]);
});


Deno.test("take", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = [...take(x, 2)];
  assertEquals(a, [1, 2]);
  a = [...take(x, 3)];
  assertEquals(a, [1, 2, 3]);
});


Deno.test("takeRight", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = [...takeRight(x, 2)];
  assertEquals(a, [4, 5]);
  a = [...takeRight(x, 3)];
  assertEquals(a, [3, 4, 5]);
});


Deno.test("takeWhile", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = [...takeWhile(x, v => v < 3)];
  assertEquals(a, [1, 2]);
  a = [...takeWhile(x, v => v < 4)];
  assertEquals(a, [1, 2, 3]);
});


Deno.test("takeWhileRight", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = [...takeWhileRight(x, v => v >= 3)];
  assertEquals(a, [3, 4, 5]);
  a = [...takeWhileRight(x, v => v >= 4)];
  assertEquals(a, [4, 5]);
});


Deno.test("drop", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = [...drop(x, 2)];
  assertEquals(a, [3, 4, 5]);
  a = [...drop(x, 3)];
  assertEquals(a, [4, 5]);
});


Deno.test("dropRight", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = [...dropRight(x, 2)];
  assertEquals(a, [1, 2, 3]);
  a = [...dropRight(x, 3)];
  assertEquals(a, [1, 2]);
});


Deno.test("dropWhile", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = [...dropWhile(x, v => v < 3)];
  assertEquals(a, [3, 4, 5]);
  a = [...dropWhile(x, v => v < 4)];
  assertEquals(a, [4, 5]);
});


Deno.test("dropWhileRight", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = [...dropWhileRight(x, v => v >= 3)];
  assertEquals(a, [1, 2]);
  a = [...dropWhileRight(x, v => v >= 4)];
  assertEquals(a, [1, 2, 3]);
});




// FIND
// ----

Deno.test("includes", () => {
  let a;
  const x = [1, 2, -3];
  a = includes(x, 3);
  assertEquals(a, false);
  a = includes(x, -3);
  assertEquals(a, true);
});


Deno.test("indexOf", () => {
  let x, a;
  x = [1, 2, 3, 2, 5];
  a = indexOf(x, 2);
  assertEquals(a, 1);
  x = [1, 2, 3, 2, 5];
  a = indexOf(x, 2, 2);
  assertEquals(a, 3);
});


Deno.test("lastIndexOf", () => {
  let x, a;
  x = [1, 2, 3, 2, 5];
  a = lastIndexOf(x, 2);
  assertEquals(a, 3);
  x = [1, 2, 3, 2, 5];
  a = lastIndexOf(x, 2, 2);
  assertEquals(a, 1);
});


Deno.test("find", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = find(x, v => v % 2===0);
  assertEquals(a, 2);
  a = find(x, v => v % 2===1);
  assertEquals(a, 1);
});


Deno.test("findRight", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = findRight(x, v => v % 2===0);
  assertEquals(a, 4);
  a = findRight(x, v => v % 2===1);
  assertEquals(a, 5);
});


Deno.test("findAll", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = [...findAll(x, v => v % 2===1)];
  assertEquals(a, [1, 3, 5]);
  a = [...findAll(x, v => v % 2===0)];
  assertEquals(a, [2, 4]);
});


Deno.test("scanWhile", () => {
  let a;
  const x = [1, 1, 2, 2, 3, 3, 4, 4];
  a = scanWhile(x, v => v % 2!==0);
  assertEquals(a, 2);
  a = scanWhile(x, v => v % 3!==0);
  assertEquals(a, 4);
  a = scanWhile(x, v => v % 6!==0);
  assertEquals(a, 8);
});


Deno.test("scanWhileRight", () => {
  let a;
  const x = [1, 1, 2, 2, 3, 3, 4, 4];
  a = scanWhileRight(x, v => v % 3!==0);
  assertEquals(a, 6);
  a = scanWhileRight(x, v => v % 2!==0);
  assertEquals(a, 8);
  a = scanWhileRight(x, v => v % 6!==0);
  assertEquals(a, 0);
});


Deno.test("scanUntil", () => {
  let a;
  const x = [1, 1, 2, 2, 3, 3, 4, 4];
  a = scanUntil(x, v => v % 2===0);
  assertEquals(a, 2);
  a = scanUntil(x, v => v % 3===0);
  assertEquals(a, 4);
  a = scanUntil(x, v => v % 6===0);
  assertEquals(a, 8);
});


Deno.test("scanUntilRight", () => {
  let a;
  const x = [1, 1, 2, 2, 3, 3, 4, 4];
  a = scanUntilRight(x, v => v % 3===0);
  assertEquals(a, 6);
  a = scanUntilRight(x, v => v % 2===0);
  assertEquals(a, 8);
  a = scanUntilRight(x, v => v % 6===0);
  assertEquals(a, 0);
});


Deno.test("search", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = search(x, v => v % 2===0);
  assertEquals(a, 1);
  a = search(x, v => v % 2===1);
  assertEquals(a, 0);
});


Deno.test("searchRight", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = searchRight(x, v => v % 2===0);
  assertEquals(a, 3);
  a = searchRight(x, v => v % 2===1);
  assertEquals(a, 4);
});


Deno.test("searchAll", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = [...searchAll(x, v => v % 2===0)];
  assertEquals(a, [1, 3]);
  a = [...searchAll(x, v => v % 2===1)];
  assertEquals(a, [0, 2, 4]);
});


Deno.test("searchValue", () => {
  let x, a;
  x = [1, 2, 3, 2, 5];
  a = searchValue(x, 2);
  assertEquals(a, 1);
  x = [1, -2, 3, 2, 5];
  a = searchValue(x, 2, (a, b) => Math.abs(a) - Math.abs(b));
  assertEquals(a, 1);
  x = [1, -2, 3, 2, 5];
  a = searchValue(x, 2, null, v => Math.abs(v));
  assertEquals(a, 1);
});


Deno.test("searchValueRight", () => {
  let x, a;
  x = [1, 2, 3, 2, 5];
  a = searchValueRight(x, 2);
  assertEquals(a, 3);
  x = [1, 2, 3, -2, 5];
  a = searchValueRight(x, 2, (a, b) => Math.abs(a) - Math.abs(b));
  assertEquals(a, 3);
  a = searchValueRight(x, 2, null, v => Math.abs(v));
  assertEquals(a, 3);
});


Deno.test("searchValueAll", () => {
  let x, a;
  x = [1, 2, 3, 2, 5];
  a = [...searchValueAll(x, 2)];
  assertEquals(a, [1, 3]);
  x = [1, 2, 3, -2, 5];
  a = [...searchValueAll(x, 2, (a, b) => Math.abs(a) - Math.abs(b))];
  assertEquals(a, [1, 3]);
  a = [...searchValueAll(x, 2, null, v => Math.abs(v))];
  assertEquals(a, [1, 3]);
});


Deno.test("searchInfix", () => {
  let y, a;
  const x = [1, 2, 3, 4];
  y = [2, 3];
  a = searchInfix(x, y);
  assertEquals(a, 1);
  y = [-2, -3];
  a = searchInfix(x, y);
  assertEquals(a, -1);
  a = searchInfix(x, y, (a, b) => Math.abs(a) - Math.abs(b));
  assertEquals(a, 1);
  a = searchInfix(x, y, null, v => Math.abs(v));
  assertEquals(a, 1);
});


Deno.test("searchInfixRight", () => {
  let y, a;
  const x = [1, 2, 3, 4];
  y = [2, 3];
  a = searchInfixRight(x, y);
  assertEquals(a, 1);
  y = [-2, -3];
  a = searchInfixRight(x, y);
  assertEquals(a, -1);
  a = searchInfixRight(x, y, (a, b) => Math.abs(a) - Math.abs(b));
  assertEquals(a, 1);
  a = searchInfixRight(x, y, null, v => Math.abs(v));
  assertEquals(a, 1);
});


Deno.test("searchInfixAll", () => {
  let y, a;
  const x = [1, 2, 3, 4, -2, -3];
  y = [2, 3];
  a = [...searchInfixAll(x, y)];
  assertEquals(a, [1]);
  y = [-2, -3];
  a = [...searchInfixAll(x, y)];
  assertEquals(a, [4]);
  a = [...searchInfixAll(x, y, (a, b) => Math.abs(a) - Math.abs(b))];
  assertEquals(a, [1, 4]);
  a = [...searchInfixAll(x, y, null, v => Math.abs(v))];
  assertEquals(a, [1, 4]);
});


Deno.test("searchSubsequence", () => {
  let a;
  const x = [1, 2, 3, 4];
  a = searchSubsequence(x, [2, 4]);
  assertEquals(a, 1);
  a = searchSubsequence(x, [-2, -4]);
  assertEquals(a, -1);
  a = searchSubsequence(x, [-2, -4], (a, b) => Math.abs(a) - Math.abs(b));
  assertEquals(a, 1);
  a = searchSubsequence(x, [-2, -4], null, v => Math.abs(v));
  assertEquals(a, 1);
});


Deno.test("hasValue", () => {
  let a;
  const x = [1, 2, -3];
  a = hasValue(x, 3);
  assertEquals(a, false);
  a = hasValue(x, 3, (a, b) => Math.abs(a) - Math.abs(b));
  assertEquals(a, true);
  a = hasValue(x, 3, null, v => Math.abs(v));
  assertEquals(a, true);
});


Deno.test("hasPrefix", () => {
  let a;
  const x = [1, 2, 3, 4];
  a = hasPrefix(x, [1, 2]);
  assertEquals(a, true);
  a = hasPrefix(x, [-1, -2]);
  assertEquals(a, false);
  a = hasPrefix(x, [-1, -2], (a, b) => Math.abs(a) - Math.abs(b));
  assertEquals(a, true);
  a = hasPrefix(x, [-1, -2], null, v => Math.abs(v));
  assertEquals(a, true);
});


Deno.test("hasSuffix", () => {
  let a;
  const x = [1, 2, 3, 4];
  a = hasSuffix(x, [3, 4]);
  assertEquals(a, true);
  a = hasSuffix(x, [-3, -4]);
  assertEquals(a, false);
  a = hasSuffix(x, [-3, -4], (a, b) => Math.abs(a) - Math.abs(b));
  assertEquals(a, true);
  a = hasSuffix(x, [-3, -4], null, v => Math.abs(v));
  assertEquals(a, true);
});


Deno.test("hasInfix", () => {
  let a;
  const x = [1, 2, 3, 4];
  a = hasInfix(x, [2, 3]);
  assertEquals(a, true);
  a = hasInfix(x, [-2, -3]);
  assertEquals(a, false);
  a = hasInfix(x, [-2, -3], (a, b) => Math.abs(a) - Math.abs(b));
  assertEquals(a, true);
  a = hasInfix(x, [-2, -3], null, v => Math.abs(v));
  assertEquals(a, true);
});


Deno.test("hasSubsequence", () => {
  let a;
  const x = [1, 2, 3, 4];
  a = hasSubsequence(x, [2, 4]);
  assertEquals(a, true);
  a = hasSubsequence(x, [-2, -4]);
  assertEquals(a, false);
  a = hasSubsequence(x, [-2, -4], (a, b) => Math.abs(a) - Math.abs(b));
  assertEquals(a, true);
  a = hasSubsequence(x, [-2, -4], null, v => Math.abs(v));
  assertEquals(a, true);
});




// FUNCTIONAL
// ----------

Deno.test("forEach", () => {
  let sum = 0;
  const x = [1, 2, -3, -4];
  forEach(x, v => sum += v);
  assertEquals(sum, -4);
});


Deno.test("some", () => {
  let a;
  const x = [1, 2, -3, -4];
  a = some(x, v => v > 10);
  assertEquals(a, false);
  a = some(x, v => v < 0);
  assertEquals(a, true);
});


Deno.test("every", () => {
  let a;
  const x = [1, 2, -3, -4];
  a = every(x, v => v > 0);
  assertEquals(a, false);
  a = every(x, v => v > -10);
  assertEquals(a, true);
});


Deno.test("map", () => {
  const x = [1, 2, 3, 4];
  const a = [...map(x, v => v * 2)];
  assertEquals(a, [2, 4, 6, 8]);
});


Deno.test("reduce", () => {
  let a;
  const x = [1, 2, 3, 4];
  a = reduce(x, (acc, v) => acc+v, 0);
  assertEquals(a, 10);
  a = reduce(x, (acc, v) => acc+v, 100);
  assertEquals(a, 110);
});


Deno.test("filter", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = [...filter(x, v => v % 2===1)];
  assertEquals(a, [1, 3, 5]);
  a = [...filter(x, v => v % 2===0)];
  assertEquals(a, [2, 4]);
});


Deno.test("filterAt", () => {
  let a;
  const x = [2, 4, 6, 8];
  a = [...filterAt(x, [1, 2])];
  assertEquals(a, [4, 6]);
  a = [...filterAt(x, [1, 3])];
  assertEquals(a, [4, 8]);
});


Deno.test("reject", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = [...reject(x, v => v % 2===1)];
  assertEquals(a, [2, 4]);
  a = [...reject(x, v => v % 2===0)];
  assertEquals(a, [1, 3, 5]);
});


Deno.test("rejectAt", () => {
  let a;
  const x = [2, 4, 6, 8];
  a = [...rejectAt(x, [1, 2])];
  assertEquals(a, [2, 8]);
  a = [...rejectAt(x, [1, 3])];
  assertEquals(a, [2, 6]);
});


Deno.test("accumulate", () => {
  let a;
  const x = [1, 2, 3, 4];
  a = [...accumulate(x, (acc, v) => acc+v)];
  assertEquals(a, [1, 3, 6, 10]);
  a = [...accumulate(x, (acc, v) => acc+v, 100)];
  assertEquals(a, [101, 103, 106, 110]);
});


Deno.test("flat", () => {
  let a;
  const x = [[1, 2], [3, [4, [5]]]];
  a = [...flat(x)];
  assertEquals(a, [1, 2, 3, 4, 5]);
  a = [...flat(x, 1)];
  assertEquals(a, [1, 2, 3, [4, [5]]]);
  a = [...flat(x, 2)];
  assertEquals(a, [1, 2, 3, 4, [5]]);
});


Deno.test("flatMap", () => {
  let a;
  const x = [[1, 2], [3, [4, [5]]]];
  a = [...flatMap(x)];
  assertEquals(a, [1, 2, 3, [4, [5]]]);
  a = [...flatMap(x, v => flat(v as Iterable<unknown>, 1))];
  assertEquals(a, [1, 2, 3, 4, [5]]);
  a = [...flatMap(x, v => flat(v as Iterable<unknown>, 2))];
  assertEquals(a, [1, 2, 3, 4, 5]);
});


Deno.test("zip", () => {
  let a;
  const x = [1, 2, 3];
  const y = [4, 5];
  a = [...zip([x, y])];
  assertEquals(a, [[1, 4], [2, 5]]);  // (shortest)
  const b = [...zip([x, y], ([a, b]) => a + b)];
  assertEquals(b, [5, 7]);
  a = [...zip([x, y], null, some)];
  assertEquals(a, [[1, 4], [2, 5]]);  // (shortest)
  a = [...zip([x, y], null, every, 0)];
  assertEquals(a, [[1, 4], [2, 5], [3, 0]]);  // (longest)
  a = [...zip([x, y], null, head as EndFunction, 0)];
  assertEquals(a, [[1, 4], [2, 5], [3, 0]]);  // (first)
});




// MANIPULATION
// ------------

Deno.test("fill", () => {
  let a;
  const x = [1, 2, 3, 4];
  a = [...fill(x, 2)];
  assertEquals(a, [2, 2, 2, 2]);
  a = [...fill(x, 2, 1)];
  assertEquals(a, [1, 2, 2, 2]);
  a = [...fill(x, 2, 1, 3)];
  assertEquals(a, [1, 2, 2, 4]);
});


Deno.test("push", () => {
  let a;
  const x = [1, 2];
  a = [...push(x, 3)];
  assertEquals(a, [1, 2, 3]);
  a = [...push(x, 3, 4)];
  assertEquals(a, [1, 2, 3, 4]);
});


Deno.test("pop", () => {
  let x, a;
  x = [1, 2, 3];
  a = [...pop(x)];
  assertEquals(a, [1, 2]);
  x = [1, 2, 3, 4];
  a = [...pop(x)];
  assertEquals(a, [1, 2, 3]);
});


Deno.test("shift", () => {
  let x, a;
  x = [1, 2, 3];
  a = [...shift(x)];
  assertEquals(a, [2, 3]);
  x = [1, 2, 3, 4];
  a = [...shift(x)];
  assertEquals(a, [2, 3, 4]);
});


Deno.test("unshift", () => {
  let a;
  const x = [3, 4];
  a = [...unshift(x, 2)];
  assertEquals(a, [2, 3, 4]);
  a = [...unshift(x, 1, 2)];
  assertEquals(a, [1, 2, 3, 4]);
});


Deno.test("copy", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  const y = [10, 20, 30];
  a = [...copy(x, y)];
  assertEquals(a, [10, 20, 30, 4, 5]);
  a = [...copy(x, y, 1)];
  assertEquals(a, [1, 10, 20, 30, 5]);
  a = [...copy(x, y, 1, 1)];
  assertEquals(a, [1, 20, 30, 4, 5]);
  a = [...copy(x, y, 1, 1, 2)];
  assertEquals(a, [1, 20, 3, 4, 5]);
});


Deno.test("copyWithin", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = [...copyWithin(x, 3)];
  assertEquals(a, [1, 2, 3, 1, 2]);
  a = [...copyWithin(x, 3, 1)];
  assertEquals(a, [1, 2, 3, 2, 3]);
  a = [...copyWithin(x, 3, 1, 2)];
  assertEquals(a, [1, 2, 3, 2, 5]);
});


Deno.test("moveWithin", () => {
  let a;
  const x = [1, 2, 3, 4, 5, 6];
  a = [...moveWithin(x, 0, 1, 3)];
  assertEquals(a, [2, 3, 1, 4, 5, 6]);  // (2,3 to left)
  a = [...moveWithin(x, 6, 3, 5)];
  assertEquals(a, [1, 2, 3, 6, 4, 5]);  // (4,5 to right)
});


Deno.test("splice", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = [...splice(x, 2)];
  assertEquals(a, [1, 2]);
  a = [...splice(x, 2, 2)];
  assertEquals(a, [1, 2, 5]);
  a = [...splice(x, 2, 2, 30, 40)];
  assertEquals(a, [1, 2, 30, 40, 5]);
});


Deno.test("split", () => {
  let x, a;
  x = [1, 2, 2, 3, 5, 4, 4, 7];
  a = [...split(x, v => v % 2 === 0)];
  assertEquals(a, [[1], [3, 5], [7]]);
  x = [2, 4, 5, 6, 8];
  a = [...split(x, v => v % 2 === 0)];
  assertEquals(a, [[5]]);
});


Deno.test("splitAt", () => {
  let x, a;
  x = [1, 2, 2, 3, 5, 4, 4, 7];
  a = [...splitAt(x, [1, 2, 5, 6])];
  assertEquals(a, [[1], [3, 5], [7]]);
  x = [2, 4, 5, 6, 8];
  a = [...splitAt(x, [0, 1, 3, 4])];
  assertEquals(a, [[5]]);
});


Deno.test("cut", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = [...cut(x, v => v % 2===0)];
  assertEquals(a, [[1], [2, 3], [4, 5]]);
  a = [...cut(x, v => v % 2===1)];
  assertEquals(a, [[], [1, 2], [3, 4], [5]]);
});


Deno.test("cutRight", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = [...cutRight(x, v => v % 2===0)];
  assertEquals(a, [[1, 2], [3, 4], [5]]);
  a = [...cutRight(x, v => v % 2===1)];
  assertEquals(a, [[1], [2, 3], [4, 5], []]);
});


Deno.test("cutAt", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = [...cutAt(x, [1, 3])];
  assertEquals(a, [[1], [2, 3], [4, 5]]);
  a = [...cutAt(x, [0, 4])];
  assertEquals(a, [[], [1, 2, 3, 4], [5]]);
});


Deno.test("cutAtRight", () => {
  let a;
  const x = [1, 2, 3, 4, 5];
  a = [...cutAtRight(x, [1, 3])];
  assertEquals(a, [[1, 2], [3, 4], [5]]);
  a = [...cutAtRight(x, [0, 4])];
  assertEquals(a, [[1], [2, 3, 4, 5], []]);
});


Deno.test("group", () => {
  let a;
  const x = [1, 2, 2, -2, -2, 4];
  a = [...group(x)];
  assertEquals(a, [[1], [2, 2], [-2, -2], [4]]);
  a = [...group(x, (a, b) => Math.abs(a) - Math.abs(b))];
  assertEquals(a, [[1], [2, 2, -2, -2], [4]]);
  a = [...group(x, null, v => Math.abs(v))];
  assertEquals(a, [[1], [2, 2, -2, -2], [4]]);
});


Deno.test("partition", () => {
  let x, a;
  x = [1, 2, 3, 4];
  a = partition(x, v => v % 2===0);
  assertEquals(a, [[2, 4], [1, 3]]);
  x = [1, 2, 3, 4, 5];
  a = partition(x, v => v % 2===1);
  assertEquals(a, [[1, 3, 5], [2, 4]]);
});


Deno.test("partitionAs", () => {
  let x;
  x = [1, 2, 3, 4];
  const a = partitionAs(x, v => v % 2===0);
  assertEquals(a, new Map([[false, [1, 3]], [true, [2, 4]]]))
  x = [1, 2, 3, 4, 5];
  const b = partitionAs(x, v => v % 3);
  assertEquals(b, new Map([[1, [1, 4]], [2, [2, 5]], [0, [3]]]));
});


Deno.test("chunk", () => {
  let a;
  const x = [1, 2, 3, 4, 5, 6, 7, 8];
  a = [...chunk(x, 3)];
  assertEquals(a, [[1, 2, 3], [4, 5, 6], [7, 8]]);
  a = [...chunk(x, 2, 3)];
  assertEquals(a, [[1, 2], [4, 5], [7, 8]]);
  a = [...chunk(x, 4, 3)];
  assertEquals(a, [[1, 2, 3, 4], [4, 5, 6, 7], [7, 8]]);
});


Deno.test("cycle", () => {
  let a;
  const x = [1, 2, 3];
  a = [...cycle(x, 0, 2)];
  assertEquals(a, [1, 2]);
  a = [...cycle(x, 0, 4)];
  assertEquals(a, [1, 2, 3, 1]);
  a = [...cycle(x, 1, 6)];
  assertEquals(a, [2, 3, 1, 2, 3, 1]);
});


Deno.test("repeat", () => {
  let a;
  const x = [1, 2];
  a = [...repeat(x, 2)];
  assertEquals(a, [1, 2, 1, 2]);
  a = [...repeat(x, 3)];
  assertEquals(a, [1, 2, 1, 2, 1, 2]);
});


Deno.test("reverse", () => {
  const x = [1, 2, 3, 4];
  const a = [...reverse(x)];
  assertEquals(a, [4, 3, 2, 1]);
});


Deno.test("rotate", () => {
  let a;
  const x = [1, 2, 3, 4];
  a = [...rotate(x, 1)];
  assertEquals(a, [2, 3, 4, 1]);
  a = [...rotate(x, 2)];
  assertEquals(a, [3, 4, 1, 2]);
  a = [...rotate(x, -1)];
  assertEquals(a, [4, 1, 2, 3]);
});


Deno.test("intermix", () => {
  let a;
  const x = [1, 2, 3, 4];
  const y = [10, 20, 30];
  a = [...intermix(x, [10])];
  assertEquals(a, [1, 10, 2, 10, 3, 10, 4]);
  a = [...intermix(x, y)];
  assertEquals(a, [1, 10, 2, 20, 3, 30, 4]);
  a = [...intermix(x, y, 2)];
  assertEquals(a, [1, 2, 10, 3, 4]);
  a = [...intermix(x, y, 1, 2)];
  assertEquals(a, [1, 10, 20, 2, 30, 10, 3, 20, 30, 4]);
});


Deno.test("interleave", () => {
  let y, a;
  const x = [1, 2, 3, 4];
  y = [10, 20, 30, 40];
  a = [...interleave([x, y])];
  assertEquals(a, [1, 10, 2, 20, 3, 30, 4, 40]);
  y = [10, 20];
  a = [...interleave([x, y])];
  assertEquals(a, [1, 10, 2, 20, 3, 4]);
});


Deno.test("interpolate", () => {
  const x = [1, 2, 3];
  const a = [...interpolate(x, (a, b) => (a + b)/2)];
  assertEquals(a, [1, 1.5, 2, 2.5, 3]);
});


Deno.test("intersperse", () => {
  let x, a;
  x = [1, 2, 3];
  a = [...intersperse(x, 10)];
  assertEquals(a, [1, 10, 2, 10, 3]);
  x = [1, 2, 3, 4];
  a = [...intersperse(x, 10)];
  assertEquals(a, [1, 10, 2, 10, 3, 10, 4]);
});




// COMBINE
// -------

Deno.test("concat", () => {
  let a;
  const x = [1, 2];
  const y = [3, 4];
  a = [...concat(x, y)];
  assertEquals(a, [1, 2, 3, 4]);
  a = [...concat(x, y, y)];
  assertEquals(a, [1, 2, 3, 4, 3, 4]);
});


Deno.test("merge", () => {
  let y, a;
  const x = [1, 3, 5, 7];
  y = [2, 4, 8];
  a = [...merge([x, y])];
  assertEquals(a, [
    1, 2, 3, 4,
    5, 7, 8
  ]);
  y = [-2, -4, -8];
  a = [...merge([x, y], (a, b) => Math.abs(a) - Math.abs(b))];
  assertEquals(a, [
    1, -2,  3, -4,
    5,  7, -8
  ]);
  a = [...merge([x, y], null, v => Math.abs(v))];
  assertEquals(a, [
    1, -2,  3, -4,
    5,  7, -8
  ]);
});


Deno.test("join", () => {
  let a;
  const x = [1, 2];
  a = join(x);
  assertEquals(a, "1,2");
  a = join(x, " : ");
  assertEquals(a, "1 : 2");
});




// SET OPERATIONS
// --------------

Deno.test("isUnique", () => {
  let a;
  const x = [1, 2, -1, -2];
  a = isUnique(x);
  assertEquals(a, true);
  a = isUnique(x, (a, b) => Math.abs(a) - Math.abs(b));
  assertEquals(a, false);
  a = isUnique(x, null, v => Math.abs(v));
  assertEquals(a, false);
});


Deno.test("isDisjoint", () => {
  let a;
  const x = [1, 2, 3, 4];
  a = isDisjoint(x, [2, 5]);
  assertEquals(a, false);
  a = isDisjoint(x, [-2, -5]);
  assertEquals(a, true);
  a = isDisjoint(x, [-2, -5], (a, b) => Math.abs(a) - Math.abs(b));
  assertEquals(a, false);
  a = isDisjoint(x, [-2, -5], null, v => Math.abs(v));
  assertEquals(a, false);
});


Deno.test("unique", () => {
  let x, a;
  x = [1, 2, 3, 4, 2, 3];
  a = [...unique(x)];
  assertEquals(a, [1, 2, 3, 4]);
  x = [1, 2, 3, 4, -2, -3];
  a = [...unique(x)];
  assertEquals(a, [1, 2, 3, 4, -2, -3]);
  a = [...unique(x, (a, b) => Math.abs(a) - Math.abs(b))];
  assertEquals(a, [1, 2, 3, 4]);
  a = [...unique(x, null, v => Math.abs(v))];
  assertEquals(a, [1, 2, 3, 4]);
});


Deno.test("union", () => {
  let y, a;
  const x = [1, 2, 3, 4];
  y = [3, 4, 5];
  a = [...union(x, y)];
  assertEquals(a, [1, 2, 3, 4, 5]);
  y = [-3, -4, -5];
  a = [...union(x, y)];
  assertEquals(a, [
     1,  2,  3, 4,
    -3, -4, -5
  ]);
  a = [...union(x, y, (a, b) => Math.abs(a) - Math.abs(b))];
  assertEquals(a, [1, 2, 3, 4, -5]);
  a = [...union(x, y, null, v => Math.abs(v))];
  assertEquals(a, [1, 2, 3, 4, -5]);
});


Deno.test("intersection", () => {
  let y, a;
  const x = [1, 2, 3, 4];
  y = [2, 3, 5];
  a = [...intersection(x, y)];
  assertEquals(a, [2, 3]);
  y = [-2, -3, -5];
  a = [...intersection(x, y, (a, b) => Math.abs(a) - Math.abs(b))];
  assertEquals(a, [2, 3]);
  a = [...intersection(x, y, null, v => Math.abs(v))];
  assertEquals(a, [2, 3]);
});


Deno.test("difference", () => {
  let y, a;
  const x = [1, 2, 3, 4, 5];
  y = [2, 4];
  a = [...difference(x, y)];
  assertEquals(a, [1, 3, 5]);
  y = [-2, -4];
  a = [...difference(x, y)];
  assertEquals(a, [1, 2, 3, 4, 5]);
  a = [...difference(x, y, (a, b) => Math.abs(a) - Math.abs(b))];
  assertEquals(a, [1, 3, 5]);
  a = [...difference(x, y, null, v => Math.abs(v))];
  assertEquals(a, [1, 3, 5]);
});


Deno.test("symmetricDifference", () => {
  let y, a;
  const x = [1, 2, 3, 4];
  y = [3, 4, 5];
  a = [...symmetricDifference(x, y)];
  assertEquals(a, [1, 2, 5]);
  y = [-3, -4, -5];
  a = [...symmetricDifference(x, y)];
  assertEquals(a, [
     1,  2,  3, 4,
    -3, -4, -5
  ]);
  a = [...symmetricDifference(x, y, (a, b) => Math.abs(a) - Math.abs(b))];
  assertEquals(a, [1, 2, -5]);
  a = [...symmetricDifference(x, y, null, v => Math.abs(v))];
  assertEquals(a, [1, 2, -5]);
});


Deno.test("cartesianProduct", () => {
  let a;
  const x = [1, 2, 3];
  const y = [10, 20, 30];
  a = [...cartesianProduct([x, y])];
  assertEquals(a, [
    [1, 10], [1, 20],
    [1, 30], [2, 10],
    [2, 20], [2, 30],
    [3, 10], [3, 20],
    [3, 30]
  ]);
  a = [...cartesianProduct([x, y], ([a, b]) => a + b)];
  assertEquals(a, [
    11, 21, 31, 12, 22,
    32, 13, 23, 33
  ]);
});
