// THIS FILE IS AUTO-GENEREATED! DO NOT MODIFY DIRECTLY!
//
// To edit this file, edit 'async-iter.js' and run 'npm run build:pre'.

import { tee, teeN, iterator } from './common.ts';



// OPERATORS

export function map<A, B>(f: (x: A) => B) {
  return function*(xs: Iterable<A>): IterableIterator<B> {
    for (const x of xs) yield f(x);
  };
}

export function tap<X>(f: (x: X) => any) {
  return function*(xs: Iterable<X>): IterableIterator<X> {
    for (const x of xs) {
      f(x);
      yield x;
    }
  };
}

export const inspect = tap;

export function forEach<X>(f: (x: X) => any) {
  return function(xs: Iterable<X>): void {
    for (const x of xs) f(x);
  };
}

export const subscribe = forEach;

export function reduce<X, R>(f: (acc: R, x: X) => R, init: R) {
  return function(xs: Iterable<X>): R {
    let res = init;
    for (const x of xs) {
      res = f(res, x);
    }
    return res;
  };
}

export function scan<X, R>(f: (acc: R, x: X) => R, init: R) {
  return function*(xs: Iterable<X>): IterableIterator<R> {
    let res = init;
    for (const x of xs) {
      res = f(res, x);
      yield res;
    }
  };
}

export const reducutions = scan;

export function some<X>(p: (x: X) => boolean) {
  return function(xs: Iterable<X>): boolean {
    for (const x of xs) {
      if (p(x)) return true;
    }
    return false;
  };
}

export function every<X>(p: (x: X) => boolean) {
  return function(xs: Iterable<X>): boolean {
    for (const x of xs) {
      if (!p(x)) return false;
    }
    return true;
  };
}

export function filter<X>(p: (x: X) => boolean) {
  return function*(xs: Iterable<X>): IterableIterator<X> {
    for (const x of xs) {
      if (p(x)) yield x;
    }
  };
}

export function partition<X>(p: (x: X) => boolean) {
  return function(xs: Iterable<X>): [IterableIterator<X>, IterableIterator<X>] {
    const [xs1, xs2] = tee(xs);
    return [filter(p)(xs1), filter((x: X) => !p(x))(xs2)];
  };
}

export function skip<X>(n: number) {
  return function*(xs: Iterable<X>): IterableIterator<X> {
    let i = 0;
    for (const x of xs) {
      if (++i <= n) continue;
      yield x;
    }
  };
}

export function take<X>(n: number) {
  return function*(xs: Iterable<X>): IterableIterator<X> {
    let i = 0;
    for (const x of xs) {
      if (++i > n) break;
      yield x;
    }
  };
}

// TODO: rename?
export function partitionAt<X>(n: number) {
  return function(xs: Iterable<X>): [IterableIterator<X>, IterableIterator<X>] {
    const [xs1, xs2] = tee(xs);
    return [take<X>(n)(xs1), skip<X>(n)(xs2)];
  };
}

export const splitAt = partitionAt;

export function skipWhile<X>(f: (x: X) => boolean) {
  return function*(xs: Iterable<X>): IterableIterator<X> {
    const it = iterator(xs);
    let first: X;
    for (const x of it) {
      first = x;
      if (!f(x)) break;
    }
    yield first;
    for (const x of it) yield x;
  };
}

export function takeWhile<X>(f: (x: X) => boolean) {
  return function*(xs: Iterable<X>): IterableIterator<X> {
    for (const x of xs) {
      if (f(x)) yield x;
      else break;
    }
  };
}

export function partitionWhile<X>(f: (x: X) => boolean) {
  return function(xs: Iterable<X>): [IterableIterator<X>, IterableIterator<X>] {
    const [xs1, xs2] = tee(xs);
    return [takeWhile<X>(f)(xs1), skipWhile<X>(f)(xs2)];
  };
}

export function find<X>(p: (x: X) => boolean) {
  return function(xs: Iterable<X>): X | null {
    for (const x of xs) {
      if (p(x)) return x;
    }
    return null;
  };
}

export function findIndex<X>(p: (x: X) => boolean) {
  return function(xs: Iterable<X>): number {
    let i = 0;
    for (const x of xs) {
      if (p(x)) return i;
      i++;
    }
    return -1;
  };
}

export function pluck<X>(key: string | number) {
  return function*(xs: Iterable<Object>): IterableIterator<X | null> {
    for (const x of xs) yield x[key];
  };
}

// like pluck, but accepts an iterable of keys
export function select<X>(keys: Array<string | number>) {
  return function*(xs: Iterable<Object>): IterableIterator<X | null> {
    for (const x of xs) {
      let r = x;
      for (const k of keys) {
        r = r != null ? r[k] : undefined;
      }
      yield r as (X | null);
    }
  };
}

export function unzip2<X, Y>() {
  return function(xs: Iterable<[X, Y]>): [IterableIterator<X>, IterableIterator<Y>] {
    const [xs1, xs2] = tee(xs);
    return [pluck<X>(0)(xs1), pluck<Y>(1)(xs2)];
  };
}

export function unzip3<X, Y, Z>() {
  return function(
    xs: Iterable<[X, Y, Z]>,
  ): [IterableIterator<X>, IterableIterator<Y>, IterableIterator<Z>] {
    const [xs1, xs2, xs3] = teeN(xs, 3);
    return [pluck<X>(0)(xs1), pluck<Y>(1)(xs2), pluck<Z>(2)(xs3)];
  };
}

export function unzip(n: number = 2) {
  return function(xs: Iterable<{}[]>): IterableIterator<{}>[] {
    const xss = teeN(xs, n);
    return xss.map((xs, i) => pluck(i)(xs));
  };
}

export function groupBy<X, K>(f: (x: X) => K) {
  return function(xs: Iterable<X>): Map<K, X[]> {
    const res = new Map<K, X[]>();
    for (const x of xs) {
      const key = f(x);
      if (!res.has(key)) res.set(key, []);
      res.get(key).push(x);
    }
    return res;
  };
}

export function groupByKey<X>(key: string | number) {
  return groupBy<X, string | number>((x: X) => x[key]);
}

export function mapKeys<KA, KB, V>(f: (k: KA) => KB) {
  return function*(xs: Iterable<[KA, V]>): IterableIterator<[KB, V]> {
    for (const [k, v] of xs) yield [f(k), v];
  };
}

export function mapValues<VA, VB, K>(f: (v: VA) => VB) {
  return function*(xs: Iterable<[K, VA]>): IterableIterator<[K, VB]> {
    for (const [k, v] of xs) yield [k, f(v)];
  };
}

export function pairwise<X>() {
  return function*(xs: Iterable<X>): IterableIterator<[X, X]> {
    const it = iterator(xs);
    let prev = (it.next()).value;
    for (const x of it) {
      yield [prev, x];
      prev = x;
    }
  };
}

export function length() {
  return function(xs: Iterable<{}>): number {
    let c = 0;
    for (const _ of xs) c++;
    return c;
  };
}

export function min() {
  return function(xs: Iterable<number>): number {
    let res = Number.POSITIVE_INFINITY;
    for (const x of xs) {
      if (x < res) res = x;
    }
    return res;
  };
}

export function max() {
  return function(xs: Iterable<number>): number {
    let res = Number.NEGATIVE_INFINITY;
    for (const x of xs) {
      if (x > res) res = x;
    }
    return res;
  };
}

export function minMax() {
  return function(xs: Iterable<number>): [number, number] {
    let min = Number.POSITIVE_INFINITY;
    let max = Number.NEGATIVE_INFINITY;
    for (const x of xs) {
      if (x < min) min = x;
      if (x > max) max = x;
    }
    return [min, max];
  };
}

export function minBy<X>(cf: (a: X, b: X) => number) {
  return function(xs: Iterable<X>): X | null {
    const it = iterator(xs);
    const { done, value } = it.next();
    if (done) return null;
    let res = value;
    for (const x of it) if (cf(x, res) < 0) res = x;
    return res;
  };
}

export function maxBy<X>(cf: (a: X, b: X) => number) {
  return function(xs: Iterable<X>): X | null {
    const it = iterator(xs);
    const { done, value } = it.next();
    if (done) return null;
    let res = value;
    for (const x of it) if (cf(x, res) > 0) res = x;
    return res;
  };
}

export function minMaxBy<X>(cf: (a: X, b: X) => number) {
  return function(xs: Iterable<X>): [X | null, X | null] {
    const it = iterator(xs);
    const { done, value } = it.next();
    if (done) return [null, null];
    let min = value;
    let max = value;
    for (const x of it) {
      if (cf(x, min) < 0) min = x;
      if (cf(x, max) > 0) max = x;
    }
    return [min, max];
  };
}

export function minByScan<X>(cf: (a: X, b: X) => number) {
  return function*(xs: Iterable<X>): IterableIterator<X | null> {
    const it = iterator(xs);
    const { done, value } = it.next();
    if (done) yield null;
    let res = value;
    for (const x of it) {
      if (cf(x, res) < 0) res = x;
      yield res;
    }
  };
}

export function maxByScan<X>(cf: (a: X, b: X) => number) {
  return function*(xs: Iterable<X>): IterableIterator<X | null> {
    const it = iterator(xs);
    const { done, value } = it.next();
    if (done) yield null;
    let res = value;
    for (const x of it) {
      if (cf(x, res) > 0) res = x;
      yield res;
    }
  };
}

export function sum(zero: number = 0) {
  return function(xs: Iterable<number>): number {
    let res = zero;
    for (const x of xs) res += x;
    return res;
  };
}

export function replaceWhen<X, Y>(pf: (x: X) => boolean, ys: Iterable<Y>) {
  return function*(xs: Iterable<X>): IterableIterator<X | Y> {
    for (const [x, y] of zip2(xs, ys)) {
      if (!pf(x)) yield x;
      else yield y;
    }
  };
}

export function grouped<X>(n: number, step: number = n) {
  return function*(xs: Iterable<X>): IterableIterator<X[]> {
    let group = [];
    for (const x of xs) {
      group.push(x);
      if (group.length === n) {
        yield [...group];
        for (let i = 0; i < step; i++) group.shift();
      }
    }
  };
}

export function startWith<X>(...as: X[]) {
  return function*(xs: Iterable<X>): IterableIterator<X> {
    for (const a of as) yield a;
    for (const x of xs) yield x;
  };
}

export function endWith<X>(...zs: X[]) {
  return function*(xs: Iterable<X>): IterableIterator<X> {
    for (const x of xs) yield x;
    for (const z of zs) yield z;
  };
}

export function sort<X>(cf: (a: X, b: X) => number) {
  return function*(xs: Iterable<X>): IterableIterator<X> {
    let arr = [];
    for (const x of xs) arr.push(x);
    for (const x of arr.sort(cf)) yield x;
  };
}

export function sortScan<X>(cf: (a: X, b: X) => number) {
  return function*(xs: Iterable<X>): IterableIterator<X[]> {
    let arr = [];
    for (const x of xs) {
      arr.push(x);
      yield [...arr.sort(cf)];
    }
  };
}

export function flatMap<A, B>(f: (x: A) => B) {
  return function*(xss: Iterable<Iterable<A>>): IterableIterator<B> {
    for (const xs of xss) for (const x of xs) yield f(x);
  };
}

export function distinctUntilChanged<X>(comp: (a: X, b: X) => boolean = (a, b) => a === b) {
  return function*(xs: Iterable<X>): IterableIterator<X> {
    const it = iterator(xs);
    let { done, value: initial } = it.next();
    if (done) return;
    yield initial;
    for (const x of it) if (!comp(x, initial)) yield (initial = x);
  };
}

export function unique<X>(comp: (a: X, b: X) => boolean = (a, b) => a === b) {
  return function*(xs: Iterable<X>): IterableIterator<X> {
    const arr = [];
    for (const x of xs) arr.push(x);
    const unq = arr.filter((x, i, self) => self.findIndex(y => comp(x, y)) === i);
    for (const u of unq) yield u;
  };
}

export function uniqueSorted<X>(comp: (a: X, b: X) => boolean = (a, b) => a === b) {
  return function*(xs: Iterable<X>): IterableIterator<X> {
    const arr = [];
    for (const x of xs) arr.push(x);
    arr.sort();
    for (const x of distinctUntilChanged(comp)(arr)) yield x;
  };
}

// CONSTRUCTORS

export function* range(start = 0, end = Number.MAX_SAFE_INTEGER, step = 1): IterableIterator<number> {
  for (let i = start; end > start ? i < end : i > end; i += step) yield i;
}

// TODO: rename to `entries`?
export function* enumerate<X>(xs: Iterable<X>): IterableIterator<[number, X]> {
  let i = 0;
  for (const x of xs) yield [i++, x];
}

export function* concat<X>(...xss: Iterable<X>[]): IterableIterator<X> {
  for (const xs of xss) for (const x of xs) yield x;
}

export const chain = concat;

export function* zip2<X, Y>(xs: Iterable<X>, ys: Iterable<Y>): IterableIterator<[X, Y]> {
  const xit = iterator(xs);
  const yit = iterator(ys);
  while (true) {
    const [xr, yr] = [xit.next(), yit.next()];
    if (xr.done || yr.done) break;
    yield [xr.value, yr.value];
  }
}

export function* zip3<X, Y, Z>(
  xs: Iterable<X>,
  ys: Iterable<Y>,
  zs: Iterable<Z>,
): IterableIterator<[X, Y, Z]> {
  const xit = iterator(xs);
  const yit = iterator(ys);
  const zit = iterator(zs);
  while (true) {
    const [xr, yr, zr] = [xit.next(), yit.next(), zit.next()];
    if (xr.done || yr.done || zr.done) break;
    yield [xr.value, yr.value, zr.value];
  }
}

export function* zip(...xss: Iterable<{}>[]): IterableIterator<{}[]> {
  const its = xss.map(iterator);
  while (true) {
    const rs = its.map(it => it.next());
    if (rs.some(r => r.done)) break;
    yield rs.map(r => r.value);
  }
}

// TODO: rename? Is this how regular zip commonly works?
export function* zipOuter(...xss: Iterable<{}>[]): IterableIterator<{}[]> {
  const its = xss.map(iterator);
  while (true) {
    const rs = its.map(it => it.next());
    if (rs.every(r => r.done)) break;
    yield rs.map(r => r.value);
  }
}

export function* product2<A, B>(as: Iterable<A>, bs: Iterable<B>): IterableIterator<[A, B]> {
  // if (as === bs) [as, bs] = tee(as);
  let bs2: Iterable<B>;
  for (const a of as) {
    [bs, bs2] = tee(bs);
    for (const b of bs2) {
      yield [a, b];
    }
  }
}

export function* product3<A, B, C>(
  as: Iterable<A>,
  bs: Iterable<B>,
  cs: Iterable<C>,
): IterableIterator<[A, B, C]> {
  // if (as === bs) [as, bs] = tee(as);
  // if (as === cs) [as, cs] = tee(as);
  // if (bs === cs) [bs, cs] = tee(bs);
  let bs2: Iterable<B>;
  let cs2: Iterable<C>;
  for (const a of as) {
    [bs, bs2] = tee(bs);
    for (const b of bs2) {
      [cs, cs2] = tee(cs);
      for (const c of cs2) {
        yield [a, b, c];
      }
    }
  }
}

// TODO: generalize to n parameters
export function* product(xs: Iterable<{}>, ...xss: Iterable<{}>[]): IterableIterator<{}[]> {
  throw new Error('Not implemented');
}

// TODO: other name (look at python itertools?)
// TODO: fix implementation
export function* combinations2<X>(xs: Iterable<X>): IterableIterator<[X, X]> {
  let [as, bs] = tee(xs);

  let bs2: Iterable<X>;
  let i = 1;
  for (const a of as) {
    [bs, bs2] = tee(bs);
    for (const b of skip<X>(i++)(bs2)) {
      yield [a, b];
    }
  }
}

export function* combinations3<X>(xs: Iterable<X>): IterableIterator<[X, X, X]> {
  throw Error('Not implemented');
  // let [as, bs, cs] = teeN(xs, 3);

  // let bs2: Iterable<X>;
  // let cs2: Iterable<X>;
  // let i = 1;
  // let j = 2;
  // for (const a of as) {
  //   [bs, bs2] = tee(bs);
  //   for (const b of skip<X>(i++)(bs2)) {
  //     [cs, cs2] = tee(cs);
  //     for (const c of skip<X>(j++)(cs2)) {
  //       yield [a, b, c];
  //     }
  //   }
  // }
}

export function* combinations(xs: Iterable<{}>, r: number = 2): IterableIterator<{}[]> {
  throw Error('Not implemented');
}

export function* combinationsWithReplacement2<X>(xs: Iterable<X>): IterableIterator<[X, X]> {
  let [as, bs] = tee(xs);

  let bs2: Iterable<X>;
  let i = 0;
  for (const a of as) {
    [bs, bs2] = tee(bs);
    for (const b of skip<X>(i++)(bs2)) {
      yield [a, b];
    }
  }
}

export function* combinationsWithReplacement3<X>(xs: Iterable<X>): IterableIterator<[X, X, X]> {
  throw Error('Not implemented');
  // let [as, bs, cs] = teeN(xs, 3);

  // let bs2: Iterable<X>;
  // let cs2: Iterable<X>;
  // let i = 0;
  // let j = 0;
  // for (const a of as) {
  //   [bs, bs2] = tee(bs);
  //   for (const b of skip<X>(i++)(bs2)) {
  //     [cs, cs2] = tee(cs);
  //     for (const c of skip<X>(j++)(cs2)) {
  //       yield [a, b, c];
  //     }
  //   }
  // }
}

export function* combinationsWithReplacement(xs: Iterable<{}>, r: number = 2): IterableIterator<{}[]> {
  throw Error('Not implemented');
}

export function* permutations2<X>(xs: Iterable<X>): IterableIterator<[X, X]> {
  throw Error('Not implemented');
}

export function* permutations3<X>(xs: Iterable<X>): IterableIterator<[X, X, X]> {
  throw Error('Not implemented');
}

export function* permutations(xs: Iterable<{}>, r: number = 2): IterableIterator<{}[]> {
  throw Error('Not implemented');
}

export function* constantly<X>(value: X): IterableIterator<X> {
  while (true) yield value;
}

export function* cycle<X>(xs: Iterable<X>): IterableIterator<X> {
  let xs2: Iterable<X>;
  while (true) {
    [xs, xs2] = tee(xs);
    for (const x of xs2) yield x;
  }
}

export function* repeat<X>(xs: Iterable<X>, n: number): IterableIterator<X> {
  let xs2: Iterable<X>;
  for (let i = 0; i < n; i++) {
    [xs, xs2] = tee(xs);
    for (const x of xs2) yield x;
  }
}

export function* interleave2<X, Y>(xs: Iterable<X>, ys: Iterable<Y>): IterableIterator<X | Y> {
  const itx = iterator(xs);
  const ity = iterator(ys);
  while (true) {
    const rx = itx.next();
    if (rx.done) break;
    else yield rx.value;
    const ry = ity.next();
    if (ry.done) break;
    else yield ry.value;
  }
}

export function* interleave3<X, Y, Z>(
  xs: Iterable<X>,
  ys: Iterable<Y>,
  zs: Iterable<Z>,
): IterableIterator<X | Y | Z> {
  const itx = iterator(xs);
  const ity = iterator(ys);
  const itz = iterator(zs);
  while (true) {
    const rx = itx.next();
    if (rx.done) break;
    else yield rx.value;
    const ry = ity.next();
    if (ry.done) break;
    else yield ry.value;
    const rz = itz.next();
    if (rz.done) break;
    else yield rz.value;
  }
}

export function* interleave(...xss: Iterable<{}>[]): IterableIterator<{}> {
  const its = xss.map(iterator);
  // Throwback to the 90s
  outerloop: while (true) {
    for (const it of its) {
      const { done, value } = it.next();
      // Yup, this just happened
      if (done) break outerloop;
      else yield value;
    }
  }
}

// https://github.com/Microsoft/TypeScript/issues/17718#issuecomment-402931751
export function pipe<T1>(x: T1): T1;
export function pipe<T1, T2>(x: T1, f1: (x: T1) => T2): T2;
export function pipe<T1, T2, T3>(x: T1, f1: (x: T1) => T2, f2: (x: T2) => T3): T3;
export function pipe<T1, T2, T3, T4>(x: T1, f1: (x: T1) => T2, f2: (x: T2) => T3, f3: (x: T3) => T4): T4;
export function pipe<T1, T2, T3, T4, T5>(
  x: T1,
  f1: (x: T1) => T2,
  f2: (x: T2) => T3,
  f3: (x: T3) => T4,
  f4: (x: T4) => T5,
): T5;
export function pipe<T1, T2, T3, T4, T5, T6>(
  x: T1,
  f1: (x: T1) => T2,
  f2: (x: T2) => T3,
  f3: (x: T3) => T4,
  f4: (x: T4) => T5,
  f5: (x: T5) => T6,
): T6;
export function pipe<T1, T2, T3, T4, T5, T6, T7>(
  x: T1,
  f1: (x: T1) => T2,
  f2: (x: T2) => T3,
  f3: (x: T3) => T4,
  f4: (x: T4) => T5,
  f5: (x: T5) => T6,
  f6: (x: T6) => T7,
): T7;
export function pipe<T1, T2, T3, T4, T5, T6, T7, T8>(
  x: T1,
  f1: (x: T1) => T2,
  f2: (x: T2) => T3,
  f3: (x: T3) => T4,
  f4: (x: T4) => T5,
  f5: (x: T5) => T6,
  f6: (x: T6) => T7,
  f7: (x: T7) => T8,
): T8;
export function pipe<T1, T2, T3, T4, T5, T6, T7, T8, T9>(
  x: T1,
  f1: (x: T1) => T2,
  f2: (x: T2) => T3,
  f3: (x: T3) => T4,
  f4: (x: T4) => T5,
  f5: (x: T5) => T6,
  f6: (x: T6) => T7,
  f7: (x: T7) => T8,
  f8: (x: T8) => T9,
): T9;
export function pipe<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(
  x: T1,
  f1: (x: T1) => T2,
  f2: (x: T2) => T3,
  f3: (x: T3) => T4,
  f4: (x: T4) => T5,
  f5: (x: T5) => T6,
  f6: (x: T6) => T7,
  f7: (x: T7) => T8,
  f8: (x: T8) => T9,
  f9: (x: T9) => T10,
): T10;
export function pipe<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>(
  x: T1,
  f1: (x: T1) => T2,
  f2: (x: T2) => T3,
  f3: (x: T3) => T4,
  f4: (x: T4) => T5,
  f5: (x: T5) => T6,
  f6: (x: T6) => T7,
  f7: (x: T7) => T8,
  f8: (x: T8) => T9,
  f9: (x: T9) => T10,
  f10: (x: T10) => T11,
): T11;
export function pipe<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>(
  x: T1,
  f1: (x: T1) => T2,
  f2: (x: T2) => T3,
  f3: (x: T3) => T4,
  f4: (x: T4) => T5,
  f5: (x: T5) => T6,
  f6: (x: T6) => T7,
  f7: (x: T7) => T8,
  f8: (x: T8) => T9,
  f9: (x: T9) => T10,
  f10: (x: T10) => T11,
  f11: (x: T11) => T12,
): T12;

export function pipe(x: any, ...fs: Function[]): any {
  let res = x;
  for (const f of fs) res = f(res);
  return res;
}
