// ets_tracing: off
import "../../../Operator";
import { tuple } from "../../../Function";
import * as I from "../../../Iterable";
import * as O from "../../../Option";
import { Stack } from "../../../Stack";
import * as St from "../../../Structural";
import * as A from "../Array";
import * as Tp from "../Tuple";

class Node {
  constructor(color, key, value, left, right, count) {
    this.color = color;
    this.key = key;
    this.value = value;
    this.left = left;
    this.right = right;
    this.count = count;
  }

}

function cloneNode(node) {
  return new Node(node.color, node.key, node.value, node.left, node.right, node.count);
}

function swapNode(n, v) {
  n.key = v.key;
  n.value = v.value;
  n.left = v.left;
  n.right = v.right;
  n.color = v.color;
  n.count = v.count;
}

function repaintNode(node, color) {
  return new Node(color, node.key, node.value, node.left, node.right, node.count);
}

function recountNode(node) {
  var _a, _b, _c, _d;

  node.count = 1 + ((_b = (_a = node.left) === null || _a === void 0 ? void 0 : _a.count) !== null && _b !== void 0 ? _b : 0) + ((_d = (_c = node.right) === null || _c === void 0 ? void 0 : _c.count) !== null && _d !== void 0 ? _d : 0);
}
/**
 * A Red-Black Tree
 */


export class RedBlackTree {
  constructor(ord, root) {
    this.ord = ord;
    this.root = root;
  }

  [Symbol.iterator]() {
    const stack = [];
    let n = this.root;

    while (n) {
      stack.push(n);
      n = n.left;
    }

    return new RedBlackTreeIterator(this, stack, "Forward");
  }

  get [St.hashSym]() {
    return St.hashIterator(this[Symbol.iterator]());
  }

  [St.equalsSym](that) {
    return that instanceof RedBlackTree && size(this) === size(that) && I.corresponds(this, that, St.equals);
  }

}
/**
 * Creates a new Red-Black Tree
 */

export function make(ord) {
  return new RedBlackTree(ord, undefined);
}
/**
 * Returns the length of the tree
 */

export function size(self) {
  var _a, _b;

  return (_b = (_a = self.root) === null || _a === void 0 ? void 0 : _a.count) !== null && _b !== void 0 ? _b : 0;
}
/**
 * Insert a new item into the tree
 */

export function insert_(self, key, value) {
  const cmp = self.ord.compare; //Find point to insert new node at

  let n = self.root;
  const n_stack = [];
  const d_stack = [];

  while (n) {
    const d = cmp(key, n.key);
    n_stack.push(n);
    d_stack.push(d);

    if (d <= 0) {
      n = n.left;
    } else {
      n = n.right;
    }
  } //Rebuild path to leaf node


  n_stack.push(new Node("Red", key, value, undefined, undefined, 1));

  for (let s = n_stack.length - 2; s >= 0; --s) {
    const n2 = n_stack[s];

    if (d_stack[s] <= 0) {
      n_stack[s] = new Node(n2.color, n2.key, n2.value, n_stack[s + 1], n2.right, n2.count + 1);
    } else {
      n_stack[s] = new Node(n2.color, n2.key, n2.value, n2.left, n_stack[s + 1], n2.count + 1);
    }
  } //Rebalance tree using rotations


  for (let s = n_stack.length - 1; s > 1; --s) {
    const p = n_stack[s - 1];
    const n3 = n_stack[s];

    if (p.color === "Black" || n3.color === "Black") {
      break;
    }

    const pp = n_stack[s - 2];

    if (pp.left === p) {
      if (p.left === n3) {
        const y = pp.right;

        if (y && y.color === "Red") {
          p.color = "Black";
          pp.right = repaintNode(y, "Black");
          pp.color = "Red";
          s -= 1;
        } else {
          pp.color = "Red";
          pp.left = p.right;
          p.color = "Black";
          p.right = pp;
          n_stack[s - 2] = p;
          n_stack[s - 1] = n3;
          recountNode(pp);
          recountNode(p);

          if (s >= 3) {
            const ppp = n_stack[s - 3];

            if (ppp.left === pp) {
              ppp.left = p;
            } else {
              ppp.right = p;
            }
          }

          break;
        }
      } else {
        const y = pp.right;

        if (y && y.color === "Red") {
          p.color = "Black";
          pp.right = repaintNode(y, "Black");
          pp.color = "Red";
          s -= 1;
        } else {
          p.right = n3.left;
          pp.color = "Red";
          pp.left = n3.right;
          n3.color = "Black";
          n3.left = p;
          n3.right = pp;
          n_stack[s - 2] = n3;
          n_stack[s - 1] = p;
          recountNode(pp);
          recountNode(p);
          recountNode(n3);

          if (s >= 3) {
            const ppp = n_stack[s - 3];

            if (ppp.left === pp) {
              ppp.left = n3;
            } else {
              ppp.right = n3;
            }
          }

          break;
        }
      }
    } else {
      if (p.right === n3) {
        const y = pp.left;

        if (y && y.color === "Red") {
          p.color = "Black";
          pp.left = repaintNode(y, "Black");
          pp.color = "Red";
          s -= 1;
        } else {
          pp.color = "Red";
          pp.right = p.left;
          p.color = "Black";
          p.left = pp;
          n_stack[s - 2] = p;
          n_stack[s - 1] = n3;
          recountNode(pp);
          recountNode(p);

          if (s >= 3) {
            const ppp = n_stack[s - 3];

            if (ppp.right === pp) {
              ppp.right = p;
            } else {
              ppp.left = p;
            }
          }

          break;
        }
      } else {
        const y = pp.left;

        if (y && y.color === "Red") {
          p.color = "Black";
          pp.left = repaintNode(y, "Black");
          pp.color = "Red";
          s -= 1;
        } else {
          p.left = n3.right;
          pp.color = "Red";
          pp.right = n3.left;
          n3.color = "Black";
          n3.right = p;
          n3.left = pp;
          n_stack[s - 2] = n3;
          n_stack[s - 1] = p;
          recountNode(pp);
          recountNode(p);
          recountNode(n3);

          if (s >= 3) {
            const ppp = n_stack[s - 3];

            if (ppp.right === pp) {
              ppp.right = n3;
            } else {
              ppp.left = n3;
            }
          }

          break;
        }
      }
    }
  } //Return new tree


  n_stack[0].color = "Black";
  return new RedBlackTree(self.ord, n_stack[0]);
}
/**
 * Insert a new item into the tree
 */

export function insert(key, value) {
  return self => insert_(self, key, value);
}
/**
 * Visit all nodes inorder until a Some is returned
 */

export function visitFull(node, visit) {
  let current = node;
  let stack = undefined;
  let done = false;

  while (!done) {
    if (current) {
      stack = new Stack(current, stack);
      current = current.left;
    } else if (stack) {
      const v = visit(stack.value.key, stack.value.value);

      if (O.isSome(v)) {
        return v;
      }

      current = stack.value.right;
      stack = stack.previous;
    } else {
      done = true;
    }
  }

  return O.none;
}
/**
 * Visit each node of the tree in order
 */

export function forEach_(self, visit) {
  if (self.root) {
    visitFull(self.root, (key, value) => {
      visit(key, value);
      return O.none;
    });
  }
}
/**
 * Visit each node of the tree in order
 */

export function forEach(visit) {
  return self => forEach_(self, visit);
}
/**
 * Visit nodes greater than or equal to key
 */

export function visitGe(node, min, ord, visit) {
  let current = node;
  let stack = undefined;
  let done = false;

  while (!done) {
    if (current) {
      stack = new Stack(current, stack);

      if (ord.compare(min, current.key) <= 0) {
        current = current.left;
      } else {
        current = undefined;
      }
    } else if (stack) {
      if (ord.compare(min, stack.value.key) <= 0) {
        const v = visit(stack.value.key, stack.value.value);

        if (O.isSome(v)) {
          return v;
        }
      }

      current = stack.value.right;
      stack = stack.previous;
    } else {
      done = true;
    }
  }

  return O.none;
}
/**
 * Visit each node of the tree in order with key greater then or equal to max
 */

export function forEachGe_(self, min, visit) {
  if (self.root) {
    visitGe(self.root, min, self.ord, (key, value) => {
      visit(key, value);
      return O.none;
    });
  }
}
/**
 * Visit each node of the tree in order with key greater then or equal to max
 */

export function forEachGe(min, visit) {
  return self => forEachGe_(self, min, visit);
}
/**
 * Visit nodes lower than key
 */

export function visitLt(node, max, ord, visit) {
  let current = node;
  let stack = undefined;
  let done = false;

  while (!done) {
    if (current) {
      stack = new Stack(current, stack);
      current = current.left;
    } else if (stack && ord.compare(max, stack.value.key) > 0) {
      const v = visit(stack.value.key, stack.value.value);

      if (O.isSome(v)) {
        return v;
      }

      current = stack.value.right;
      stack = stack.previous;
    } else {
      done = true;
    }
  }

  return O.none;
}
/**
 * Visit each node of the tree in order with key lower then max
 */

export function forEachLt_(self, max, visit) {
  if (self.root) {
    visitLt(self.root, max, self.ord, (key, value) => {
      visit(key, value);
      return O.none;
    });
  }
}
/**
 * Visit each node of the tree in order with key lower then max
 */

export function forEachLt(max, visit) {
  return self => forEachLt_(self, max, visit);
}
/**
 * Visit nodes with key lower than max and greater then or equal to min
 */

export function visitBetween(node, min, max, ord, visit) {
  let current = node;
  let stack = undefined;
  let done = false;

  while (!done) {
    if (current) {
      stack = new Stack(current, stack);

      if (ord.compare(min, current.key) <= 0) {
        current = current.left;
      } else {
        current = undefined;
      }
    } else if (stack && ord.compare(max, stack.value.key) > 0) {
      if (ord.compare(min, stack.value.key) <= 0) {
        const v = visit(stack.value.key, stack.value.value);

        if (O.isSome(v)) {
          return v;
        }
      }

      current = stack.value.right;
      stack = stack.previous;
    } else {
      done = true;
    }
  }

  return O.none;
}
/**
 * Visit each node of the tree in order with key lower than max and greater then or equal to min
 */

export function forEachBetween_(self, min, max, visit) {
  if (self.root) {
    visitBetween(self.root, min, max, self.ord, (key, value) => {
      visit(key, value);
      return O.none;
    });
  }
}
/**
 * Visit each node of the tree in order with key lower than max and greater then or equal to min
 */

export function forEachBetween(min, max, visit) {
  return self => forEachBetween_(self, min, max, visit);
}
/**
 * Fix up a double black node in a tree
 */

function fixDoubleBlack(stack) {
  let n, p, s, z;

  for (let i = stack.length - 1; i >= 0; --i) {
    n = stack[i];

    if (i === 0) {
      n.color = "Black";
      return;
    } //console.log("visit node:", n.key, i, stack[i].key, stack[i-1].key)


    p = stack[i - 1];

    if (p.left === n) {
      //console.log("left child")
      s = p.right;

      if (s && s.right && s.right.color === "Red") {
        //console.log("case 1: right sibling child red")
        s = p.right = cloneNode(s);
        z = s.right = cloneNode(s.right);
        p.right = s.left;
        s.left = p;
        s.right = z;
        s.color = p.color;
        n.color = "Black";
        p.color = "Black";
        z.color = "Black";
        recountNode(p);
        recountNode(s);

        if (i > 1) {
          const pp = stack[i - 2];

          if (pp.left === p) {
            pp.left = s;
          } else {
            pp.right = s;
          }
        }

        stack[i - 1] = s;
        return;
      } else if (s && s.left && s.left.color === "Red") {
        //console.log("case 1: left sibling child red")
        s = p.right = cloneNode(s);
        z = s.left = cloneNode(s.left);
        p.right = z.left;
        s.left = z.right;
        z.left = p;
        z.right = s;
        z.color = p.color;
        p.color = "Black";
        s.color = "Black";
        n.color = "Black";
        recountNode(p);
        recountNode(s);
        recountNode(z);

        if (i > 1) {
          const pp = stack[i - 2];

          if (pp.left === p) {
            pp.left = z;
          } else {
            pp.right = z;
          }
        }

        stack[i - 1] = z;
        return;
      }

      if (s && s.color === "Black") {
        if (p.color === "Red") {
          //console.log("case 2: black sibling, red parent", p.right.value)
          p.color = "Black";
          p.right = repaintNode(s, "Red");
          return;
        } else {
          //console.log("case 2: black sibling, black parent", p.right.value)
          p.right = repaintNode(s, "Red");
          continue;
        }
      } else if (s) {
        //console.log("case 3: red sibling")
        s = cloneNode(s);
        p.right = s.left;
        s.left = p;
        s.color = p.color;
        p.color = "Red";
        recountNode(p);
        recountNode(s);

        if (i > 1) {
          const pp = stack[i - 2];

          if (pp.left === p) {
            pp.left = s;
          } else {
            pp.right = s;
          }
        }

        stack[i - 1] = s;
        stack[i] = p;

        if (i + 1 < stack.length) {
          stack[i + 1] = n;
        } else {
          stack.push(n);
        }

        i = i + 2;
      }
    } else {
      //console.log("right child")
      s = p.left;

      if (s && s.left && s.left.color === "Red") {
        //console.log("case 1: left sibling child red", p.value, p._color)
        s = p.left = cloneNode(s);
        z = s.left = cloneNode(s.left);
        p.left = s.right;
        s.right = p;
        s.left = z;
        s.color = p.color;
        n.color = "Black";
        p.color = "Black";
        z.color = "Black";
        recountNode(p);
        recountNode(s);

        if (i > 1) {
          const pp = stack[i - 2];

          if (pp.right === p) {
            pp.right = s;
          } else {
            pp.left = s;
          }
        }

        stack[i - 1] = s;
        return;
      } else if (s && s.right && s.right.color === "Red") {
        //console.log("case 1: right sibling child red")
        s = p.left = cloneNode(s);
        z = s.right = cloneNode(s.right);
        p.left = z.right;
        s.right = z.left;
        z.right = p;
        z.left = s;
        z.color = p.color;
        p.color = "Black";
        s.color = "Black";
        n.color = "Black";
        recountNode(p);
        recountNode(s);
        recountNode(z);

        if (i > 1) {
          const pp = stack[i - 2];

          if (pp.right === p) {
            pp.right = z;
          } else {
            pp.left = z;
          }
        }

        stack[i - 1] = z;
        return;
      }

      if (s && s.color === "Black") {
        if (p.color === "Red") {
          //console.log("case 2: black sibling, red parent")
          p.color = "Black";
          p.left = repaintNode(s, "Red");
          return;
        } else {
          //console.log("case 2: black sibling, black parent")
          p.left = repaintNode(s, "Red");
          continue;
        }
      } else if (s) {
        //console.log("case 3: red sibling")
        s = cloneNode(s);
        p.left = s.right;
        s.right = p;
        s.color = p.color;
        p.color = "Red";
        recountNode(p);
        recountNode(s);

        if (i > 1) {
          const pp = stack[i - 2];

          if (pp.right === p) {
            pp.right = s;
          } else {
            pp.left = s;
          }
        }

        stack[i - 1] = s;
        stack[i] = p;

        if (i + 1 < stack.length) {
          stack[i + 1] = n;
        } else {
          stack.push(n);
        }

        i = i + 2;
      }
    }
  }
}
/**
 * Stateful iterator
 */


export class RedBlackTreeIterator {
  constructor(self, stack, direction) {
    this.self = self;
    this.stack = stack;
    this.direction = direction;
    this.count = 0;
  }
  /**
   * Clones the iterator
   */


  clone() {
    return new RedBlackTreeIterator(this.self, this.stack.slice(), this.direction);
  }
  /**
   * Reverse the traversal direction
   */


  reversed() {
    return new RedBlackTreeIterator(this.self, this.stack.slice(), this.direction === "Forward" ? "Backward" : "Forward");
  }
  /**
   * Iterator next
   */


  next() {
    const entry = this.entry;
    this.count++;

    if (this.direction === "Forward") {
      this.moveNext();
    } else {
      this.movePrev();
    }

    return O.fold_(entry, () => ({
      done: true,
      value: this.count
    }), kv => ({
      done: false,
      value: kv
    }));
  }
  /**
   * Returns the key
   */


  get key() {
    if (this.stack.length > 0) {
      return O.some(this.stack[this.stack.length - 1].key);
    }

    return O.none;
  }
  /**
   * Returns the value
   */


  get value() {
    if (this.stack.length > 0) {
      return O.some(this.stack[this.stack.length - 1].value);
    }

    return O.none;
  }
  /**
   * Returns the key
   */


  get entry() {
    if (this.stack.length > 0) {
      return O.some(tuple(this.stack[this.stack.length - 1].key, this.stack[this.stack.length - 1].value));
    }

    return O.none;
  }
  /**
   * Returns the position of this iterator in the sorted list
   */


  get index() {
    let idx = 0;
    const stack = this.stack;

    if (stack.length === 0) {
      const r = this.self.root;

      if (r) {
        return r.count;
      }

      return 0;
    } else if (stack[stack.length - 1].left) {
      idx = stack[stack.length - 1].left.count;
    }

    for (let s = stack.length - 2; s >= 0; --s) {
      if (stack[s + 1] === stack[s].right) {
        ++idx;

        if (stack[s].left) {
          idx += stack[s].left.count;
        }
      }
    }

    return idx;
  }
  /**
   * Advances iterator to next element in list
   */


  moveNext() {
    const stack = this.stack;

    if (stack.length === 0) {
      return;
    }

    let n = stack[stack.length - 1];

    if (n.right) {
      n = n.right;

      while (n) {
        stack.push(n);
        n = n.left;
      }
    } else {
      stack.pop();

      while (stack.length > 0 && stack[stack.length - 1].right === n) {
        n = stack[stack.length - 1];
        stack.pop();
      }
    }
  }
  /**
   * Checks if there is a next element
   */


  get hasNext() {
    const stack = this.stack;

    if (stack.length === 0) {
      return false;
    }

    if (stack[stack.length - 1].right) {
      return true;
    }

    for (let s = stack.length - 1; s > 0; --s) {
      if (stack[s - 1].left === stack[s]) {
        return true;
      }
    }

    return false;
  }
  /**
   * Advances iterator to previous element in list
   */


  movePrev() {
    const stack = this.stack;

    if (stack.length === 0) {
      return;
    }

    let n = stack[stack.length - 1];

    if (n && n.left) {
      n = n.left;

      while (n) {
        stack.push(n);
        n = n.right;
      }
    } else {
      stack.pop();

      while (stack.length > 0 && stack[stack.length - 1].left === n) {
        n = stack[stack.length - 1];
        stack.pop();
      }
    }
  }
  /**
   * Checks if there is a previous element
   */


  get hasPrev() {
    const stack = this.stack;

    if (stack.length === 0) {
      return false;
    }

    if (stack[stack.length - 1].left) {
      return true;
    }

    for (let s = stack.length - 1; s > 0; --s) {
      if (stack[s - 1].right === stack[s]) {
        return true;
      }
    }

    return false;
  }

}
/**
 * Returns the first entry in the tree
 */

export function getFirst(tree) {
  let n = tree.root;
  let c = tree.root;

  while (n) {
    c = n;
    n = n.left;
  }

  return c ? O.some(Tp.tuple(c.key, c.value)) : O.none;
}
/**
 * Returns the last entry in the tree
 */

export function getLast(tree) {
  let n = tree.root;
  let c = tree.root;

  while (n) {
    c = n;
    n = n.right;
  }

  return c ? O.some(Tp.tuple(c.key, c.value)) : O.none;
}
/**
 * Returns an iterator that points to the element i of the tree
 */

export function at_(tree, idx, direction = "Forward") {
  return {
    ord: tree.ord,
    [Symbol.iterator]: () => {
      if (idx < 0) {
        return new RedBlackTreeIterator(tree, [], direction);
      }

      let n = tree.root;
      const stack = [];

      while (n) {
        stack.push(n);

        if (n.left) {
          if (idx < n.left.count) {
            n = n.left;
            continue;
          }

          idx -= n.left.count;
        }

        if (!idx) {
          return new RedBlackTreeIterator(tree, stack, direction);
        }

        idx -= 1;

        if (n.right) {
          if (idx >= n.right.count) {
            break;
          }

          n = n.right;
        } else {
          break;
        }
      }

      return new RedBlackTreeIterator(tree, [], direction);
    }
  };
}
/**
 * Returns an iterator that points to the element i of the tree
 */

export function at(idx) {
  return tree => at_(tree, idx);
}
/**
 * Returns the element i of the tree
 */

export function getAt_(tree, idx) {
  if (idx < 0) {
    return O.none;
  }

  let n = tree.root;
  let node = undefined;

  while (n) {
    node = n;

    if (n.left) {
      if (idx < n.left.count) {
        n = n.left;
        continue;
      }

      idx -= n.left.count;
    }

    if (!idx) {
      return O.some(Tp.tuple(node.key, node.value));
    }

    idx -= 1;

    if (n.right) {
      if (idx >= n.right.count) {
        break;
      }

      n = n.right;
    } else {
      break;
    }
  }

  return O.none;
}
/**
 * Returns the element i of the tree
 */

export function getAt(idx) {
  return tree => getAt_(tree, idx);
}
/**
 * Returns an iterator that traverse entries with keys less then or equal to key
 */

export function le_(tree, key, direction = "Forward") {
  return {
    ord: tree.ord,
    [Symbol.iterator]: () => {
      const cmp = tree.ord.compare;
      let n = tree.root;
      const stack = [];
      let last_ptr = 0;

      while (n) {
        const d = cmp(key, n.key);
        stack.push(n);

        if (d <= 0) {
          last_ptr = stack.length;
        }

        if (d <= 0) {
          n = n.left;
        } else {
          n = n.right;
        }
      }

      stack.length = last_ptr;
      return new RedBlackTreeIterator(tree, stack, direction);
    }
  };
}
/**
 * Returns an iterator that traverse entries with keys less then or equal to key
 */

export function le(key, direction = "Forward") {
  return tree => le_(tree, key, direction);
}
/**
 * Returns an iterator that traverse entries with keys less then key
 */

export function lt_(tree, key, direction = "Forward") {
  return {
    ord: tree.ord,
    [Symbol.iterator]: () => {
      const cmp = tree.ord.compare;
      let n = tree.root;
      const stack = [];
      let last_ptr = 0;

      while (n) {
        const d = cmp(key, n.key);
        stack.push(n);

        if (d > 0) {
          last_ptr = stack.length;
        }

        if (d <= 0) {
          n = n.left;
        } else {
          n = n.right;
        }
      }

      stack.length = last_ptr;
      return new RedBlackTreeIterator(tree, stack, direction);
    }
  };
}
/**
 * Returns an iterator that traverse entries with keys less then key
 */

export function lt(key, direction = "Forward") {
  return tree => lt_(tree, key, direction);
}
/**
 * Returns an iterator that traverse entries with keys greater then or equal to key
 */

export function ge_(tree, key, direction = "Forward") {
  return {
    ord: tree.ord,
    [Symbol.iterator]: () => {
      const cmp = tree.ord.compare;
      let n = tree.root;
      const stack = [];
      let last_ptr = 0;

      while (n) {
        const d = cmp(key, n.key);
        stack.push(n);

        if (d <= 0) {
          last_ptr = stack.length;
        }

        if (d <= 0) {
          n = n.left;
        } else {
          n = n.right;
        }
      }

      stack.length = last_ptr;
      return new RedBlackTreeIterator(tree, stack, direction);
    }
  };
}
/**
 * Returns an iterator that traverse entries with keys greater then or equal to key
 */

export function ge(key, direction = "Forward") {
  return tree => ge_(tree, key, direction);
}
/**
 * Returns an iterator that traverse entries with keys greater then or equal to key
 */

export function gt_(tree, key, direction = "Forward") {
  return {
    ord: tree.ord,
    [Symbol.iterator]: () => {
      const cmp = tree.ord.compare;
      let n = tree.root;
      const stack = [];
      let last_ptr = 0;

      while (n) {
        const d = cmp(key, n.key);
        stack.push(n);

        if (d < 0) {
          last_ptr = stack.length;
        }

        if (d < 0) {
          n = n.left;
        } else {
          n = n.right;
        }
      }

      stack.length = last_ptr;
      return new RedBlackTreeIterator(tree, stack, direction);
    }
  };
}
/**
 * Returns an iterator that traverse entries with keys greater then or equal to key
 */

export function gt(key, direction = "Forward") {
  return tree => gt_(tree, key, direction);
}
/**
 * Traverse the tree backwards
 */

export function backwards(self) {
  return {
    ord: self.ord,
    [Symbol.iterator]: () => {
      const stack = [];
      let n = self.root;

      while (n) {
        stack.push(n);
        n = n.right;
      }

      return new RedBlackTreeIterator(self, stack, "Backward");
    }
  };
}
/**
 * Get the values of the tree
 */

export function values_(self, direction = "Forward") {
  const begin = self[Symbol.iterator]();
  let count = 0;
  return {
    [Symbol.iterator]: () => values_(self, direction),
    next: () => {
      count++;
      const entry = begin.value;

      if (direction === "Forward") {
        begin.moveNext();
      } else {
        begin.movePrev();
      }

      return O.fold_(entry, () => ({
        value: count,
        done: true
      }), entry => ({
        value: entry,
        done: false
      }));
    }
  };
}
/**
 * Get the values of the tree
 */

export function values(direction = "Forward") {
  return self => values_(self, direction);
}
/**
 * Get the keys of the tree
 */

export function keys_(self, direction = "Forward") {
  const begin = self[Symbol.iterator]();
  let count = 0;
  return {
    [Symbol.iterator]: () => keys_(self, direction),
    next: () => {
      count++;
      const entry = begin.key;

      if (direction === "Forward") {
        begin.moveNext();
      } else {
        begin.movePrev();
      }

      return O.fold_(entry, () => ({
        value: count,
        done: true
      }), entry => ({
        value: entry,
        done: false
      }));
    }
  };
}
/**
 * Get the keys of the tree
 */

export function keys(direction = "Forward") {
  return self => keys_(self, direction);
}
export function from(...args) {
  let tree = args.length === 2 ? make(args[1]) : make(args[0].ord);

  for (const [k, v] of args[0]) {
    tree = insert_(tree, k, v);
  }

  return tree;
}
/**
 * Finds the item with key if it exists
 */

export function find_(tree, key) {
  const cmp = tree.ord.compare;
  let n = tree.root;
  const res = [];

  while (n) {
    const d = cmp(key, n.key);

    if (d === 0 && St.equals(key, n.key)) {
      res.push(n.value);
    }

    if (d <= 0) {
      n = n.left;
    } else {
      n = n.right;
    }
  }

  return A.reverse(res);
}
/**
 * Finds the item with key if it exists
 */

export function find(key) {
  return tree => find_(tree, key);
}
/**
 * Finds the item with key if it exists
 */

export function findFirst_(tree, key) {
  const cmp = tree.ord.compare;
  let n = tree.root;

  while (n) {
    const d = cmp(key, n.key);

    if (St.equals(key, n.key)) {
      return O.some(n.value);
    }

    if (d <= 0) {
      n = n.left;
    } else {
      n = n.right;
    }
  }

  return O.none;
}
/**
 * Finds the item with key if it exists
 */

export function findFirst(key) {
  return tree => findFirst_(tree, key);
}
/**
 * Finds the item with key if it exists
 */

export function has_(tree, key) {
  return findFirst_(tree, key)._tag === "Some";
}
/**
 * Finds the item with key if it exists
 */

export function has(key) {
  return tree => has_(tree, key);
}
/**
 * Removes entry with key
 */

export function removeFirst_(self, key) {
  const cmp = self.ord.compare;
  let node = self.root;
  const stack = [];

  while (node) {
    const d = cmp(key, node.key);
    stack.push(node);

    if (St.equals(key, node.key)) {
      node = undefined;
    } else if (d <= 0) {
      node = node.left;
    } else {
      node = node.right;
    }
  }

  if (stack.length === 0) {
    return self;
  }

  const cstack = new Array(stack.length);
  let n = stack[stack.length - 1];
  cstack[cstack.length - 1] = new Node(n.color, n.key, n.value, n.left, n.right, n.count);

  for (let i = stack.length - 2; i >= 0; --i) {
    n = stack[i];

    if (n.left === stack[i + 1]) {
      cstack[i] = new Node(n.color, n.key, n.value, cstack[i + 1], n.right, n.count);
    } else {
      cstack[i] = new Node(n.color, n.key, n.value, n.left, cstack[i + 1], n.count);
    }
  } //Get node


  n = cstack[cstack.length - 1]; //console.log("start remove: ", n.value)
  //If not leaf, then swap with previous node

  if (n.left && n.right) {
    //console.log("moving to leaf")
    //First walk to previous leaf
    const split = cstack.length;
    n = n.left;

    while (n.right) {
      cstack.push(n);
      n = n.right;
    } //Copy path to leaf


    const v = cstack[split - 1];
    cstack.push(new Node(n.color, v.key, v.value, n.left, n.right, n.count));
    cstack[split - 1].key = n.key;
    cstack[split - 1].value = n.value; //Fix up stack

    for (let i = cstack.length - 2; i >= split; --i) {
      n = cstack[i];
      cstack[i] = new Node(n.color, n.key, n.value, n.left, cstack[i + 1], n.count);
    }

    cstack[split - 1].left = cstack[split];
  } //console.log("stack=", cstack.map(function(v) { return v.value }))
  //Remove leaf node


  n = cstack[cstack.length - 1];

  if (n.color === "Red") {
    //Easy case: removing red leaf
    //console.log("RED leaf")
    const p = cstack[cstack.length - 2];

    if (p.left === n) {
      p.left = null;
    } else if (p.right === n) {
      p.right = null;
    }

    cstack.pop();

    for (let i = 0; i < cstack.length; ++i) {
      cstack[i]._count--;
    }

    return new RedBlackTree(self.ord, cstack[0]);
  } else {
    if (n.left || n.right) {
      //Second easy case:  Single child black parent
      //console.log("BLACK single child")
      if (n.left) {
        swapNode(n, n.left);
      } else if (n.right) {
        swapNode(n, n.right);
      } //Child must be red, so repaint it black to balance color


      n.color = "Black";

      for (let i = 0; i < cstack.length - 1; ++i) {
        cstack[i]._count--;
      }

      return new RedBlackTree(self.ord, cstack[0]);
    } else if (cstack.length === 1) {
      //Third easy case: root
      //console.log("ROOT")
      return new RedBlackTree(self.ord, undefined);
    } else {
      //Hard case: Repaint n, and then do some nasty stuff
      //console.log("BLACK leaf no children")
      for (let i = 0; i < cstack.length; ++i) {
        cstack[i]._count--;
      }

      const parent = cstack[cstack.length - 2];
      fixDoubleBlack(cstack); //Fix up links

      if (parent.left === n) {
        parent.left = null;
      } else {
        parent.right = null;
      }
    }
  }

  return new RedBlackTree(self.ord, cstack[0]);
}
/**
 * Removes entry with key
 */

export function removeFirst(key) {
  return tree => removeFirst_(tree, key);
}
/**
 * Reduce a state over the map entries
 */

export function reduceWithIndex_(map, z, f) {
  let x = z;

  for (const [k, v] of map) {
    x = f(x, k, v);
  }

  return x;
}
/**
 * Reduce a state over the map entries
 *
 * @ets_data_first reduceWithIndex_
 */

export function reduceWithIndex(z, f) {
  return map => reduceWithIndex_(map, z, f);
}
/**
 * Reduce a state over the map entries
 */

export function reduce_(map, z, f) {
  return reduceWithIndex_(map, z, (z1, _, v) => f(z1, v));
}
/**
 * Reduce a state over the map entries
 *
 * @ets_data_first reduceWithIndex_
 */

export function reduce(z, f) {
  return map => reduce_(map, z, f);
}
//# sourceMappingURL=index.js.map