# Nimbus - Types, data structures and shared utilities used in network sync
#
# Copyright (c) 2018-2024 Status Research & Development GmbH
# Licensed under either of
#  * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
#    http://www.apache.org/licenses/LICENSE-2.0)
#  * MIT license ([LICENSE-MIT](LICENSE-MIT) or
#    http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or
# distributed except according to those terms.

## Efficient set of non-adjacent disjunct intervals
## ================================================
##
## This molule efficiently manages a set `Q` of non-adjacent intervals `I`
## over a scalar type `S`. The elements of the intervals are not required to
## be scalar, yet they need to fulfil some ordering properties and must map
## into `S` by the `-` operator.
##
## Application examples
## --------------------
## * Intervals `I` are sub-ranges of `distinct uint`, scalar `S` is `uint64`
## * Intervals `I` are sub-ranges of `distinct UInt256`, scalar `S` is `Uint256`
## * Intervals `I` art sub-ranges of `uint`, scalar `S` is `uint`
##
## Mathematical heuristic reasoning
## --------------------------------
## Let `S` be a scalar structure isomorphic to a sub-ring of `Z`, the ring of
## integers. Typical representants would be `uint`, `uint64`, `UInt256` when
## seen as residue classes. `S` need not be bounded. We require `0` and `1` in
## `S`.
##
## Define `P` as a finite set of elements with the following properties:
##
## * There is an order relation defined on `P` (ie. `<`, `=` exists and is
##   transitive.)
##
## * Define an interval `I` to be a set of elements of `P` with:
##   + If `a`,`b` are in `I`, `w` in `P` with `a<=w<=b`, then `b` is in `I`.
##   + We write `[a,b]` for the interval `I` with `a=min(I)` and `b=max(I)`.
##   + We have `P=[min(P),max(P)]` (note that `P` is ordered.)
##
## * There is a binary *minus* operation `-:PxP -> S` with the following
##   properties:
##   + If `a`, `b` are in `P` and `a<=b`, then`b-a=card([a,b])-1`. So
##     `a-a=0` for all `a`.
##   + For any `a<max(P)`, there is a unique element `b=min{w|a<w}` with
##     `b-a=1`. We write `a+1` instead of `b`.
##   + Ditto for `a-1` when `min(P)<a`
##
## * The elements of `P` are called points.
##
## Efficiency of managing `Q`
## --------------------------
## The set `Q` of non-adjacent intervals is constructed as follows:
## * For `I`, `J` intervals over `P`, define the envelope `I~J` as
##  `[min(I+J),max(I+J)]` derived by interpolating elements beween `I`
##   and `J`. Clearly, `card(I+J)<=card(I~J)` holds (where `+` denotes the
##   union of sets.)
## * For different `I`, `J` in `Q`, we require `card(I+J)<card(I~J)`. This
##   is the defining property for non-adjacent intervals.
##
## The set of intervals `Q` is implemented based on an `O(log n)` complexity
## `SortedSet` database (where `n` is the size of the database.)
##
## An operation on `Q` involving an interval `I` is of complexity
## `O(log n)+O(card I)` where `card I` is the number of elements in `I`.
## The worst case complexity applies if every other element of `I` is present
## as a single element interval in `Q`. So when merging, or reducing the
## set `Q` by the interval `I`, every other element of `I` that is
## in the set `Q` will be touched. This number of operations is at most
## `1 + (card I) / 2`.
##
## Data type requirements
## ----------------------
## The following operations must be made available when implementing `P`:
##
## * Order relation stuff for points of `P`: `==`, `<`, `cmp`, etc.
## * Maximum and minimum points `high(P)` and `low(P)` must be defined
## * Difference of points: `-:PxP -> S`, ie. `b-a` is of scalar type `S`.
## * Right addition of scalar: `+:PxS -> P`, ie. `a+n` is a point `b` and
##   `b-a` is `n`.
## * The function `$()` must be defined (used for debugging, only)
##
## Additional requirements for the scalar type `S`:
##
## * `S.default` must be the `0` element (of the additive group)
## * `S.default+1` must be the `1` element (of the implied multiplicative
##   group)
## * The scalar space `S` must contain all the numbers `0 .. high(P)-low(P)`
##
## User interface considerations
## -----------------------------
## The data set descriptor is implemented as an object reference. For deep
## copy and deep comparison, the functions `dup()` and `==` are provided.
##
## For any function, the argument points of `P` are assumed be in the
## range `low(P) .. high(P)`. This is not checked explicitely. Using points
## outside this range might have unintended side effects (applicable only
## if `P` is a proper sub-range of a larger data range.)
##
## The data set represents compact intervals `[a,b]` over a point space `P`
## where the length of the largest possible interval is `card(P)` which might
## exceed the highest available scalar `high(S)` from the *NIM* implementation
## of `S`. In order to handle the scalar equivalent of `card(P)`, this package
## always returns the scalar *zero* (from the scalar space `S`) for `card(S)`.
## This makes mathematically sense when `P` is seen as a residue class
## isomorpic to a subclass of `S`.
##

{.push raises: [].}

import
  pkg/results,
  "."/sorted_set

export
  `isRed=`,
  `linkLeft=`,
  `linkRight=`,
  results

const
  NoisyDebuggingOk = false

type
  IntervalSetError* = enum
    ## Used for debugging only, see `verify()`
    isNoError = 0
    isErrorBogusInterval   ## Illegal interval end points or zero size
    isErrorOverlapping     ## Overlapping intervals in database
    isErrorAdjacent        ## Adjacent intervals, should be joined
    isErrorTotalMismatch   ## Total accumulator register is wrong
    isErrorTotalLastHigh   ## Total accumulator mismatch counting `high(P)`

  Interval*[P,S] = object
    ## Compact interval `[least,last]`
    least, last: P

  IntervalRc*[P,S] = ##\
    ## Handy shortcut, used for interval operation results
    Result[Interval[P,S],void]

  IntervalSetRef*[P,S] = ref object
    ## Set of non-adjacent intervals
    ptsCount: S
      ## data size covered

    leftPos: SortedSet[P,BlockRef[S]]
      ## list of segments, half-open intervals

    lastHigh: bool
      ## `true` iff `high(P)` is in the interval set

  # -----

  Desc[P,S] = ##\
    ## Internal shortcut, interval set
    IntervalSetRef[P,S]

  Segm[P,S] = object
    ## Half open interval `[start,start+size)`
    start: P  ## Start point
    size: S   ## Length of interval

  BlockRef[S] = ref object
    ## Internal, interval set database record reference
    size: S

  DataRef[P,S] = ##\
    ## Internal, shortcut: The `value` part of a successful `SortedSet`
    ## operation, a reference to the stored data record.
    SortedSetItemRef[P,BlockRef[S]]

  Rc[P,S] = ##\
    ## Internal shortcut
    Result[DataRef[P,S],void]

# ------------------------------------------------------------------------------
# Private debugging
# ------------------------------------------------------------------------------

when NoisyDebuggingOk:
  import std/[sequtils, strutils]

  # Forward declarations
  proc verify*[P,S](
    ds: IntervalSetRef[P,S]): Result[void,(RbInfo,IntervalSetError)]

  proc sayImpl(noisy = false; pfx = "***"; args: varargs[string, `$`]) =
    if noisy:
      if args.len == 0:
        echo "*** ", pfx
      elif 0 < pfx.len and pfx[^1] != ' ':
        echo pfx, " ", args.toSeq.join
      else:
        echo pfx, args.toSeq.join

  proc pp[P,S](n: S): string =
    let
      lowS = S.default
      highS = S.default + (high(P) - low(P))
    if highS <= n:
      return "high(S)"
    if (highS - 1) <= n:
      return "high(S)-1"
    if (highS - 1 - 1) == n:
      return "high(S)-2"
    if n <= lowS:
      return "low(S)"
    if n <= (lowS + 1):
      return "low(S)+1"
    if n <= (lowS + 1 + 1):
      return "low(S)+2"
    $n

  proc pp[P,S](pt: P): string =
    template one: untyped = (S.default + 1)
    if high(P) <= pt:
      return "high(P)"
    if (high(P) - one) <= pt:
      return "high(P)-1"
    if (high(P) - one - one) == pt:
      return "high(P)-2"
    if pt <= low(P):
      return "low(P)"
    if pt <= (low(P) + one):
      return "low(P)+1"
    if pt <= (low(P) + one + one):
      return "low(P)+2"
    $pt

  proc pp[P,S](ds: Desc[P,S]): string =
    if ds.isNil:
      "nil"
    else:
      cast[pointer](ds).repr.strip

  proc pp[P,S](seg: Segm[P,S]): string =
    template one: untyped = (S.default + 1)
    "[" & pp[P,S](seg.start) & "," & pp[P,S](seg.start + seg.size) & ")"

  proc pp[P,S](iv: Interval[P,S]): string =
    template one: untyped = (S.default + 1)
    "[" & pp[P,S](iv.least) & "," & pp[P,S](iv.last) & "]"

  proc pp[P,S](kvp: DataRef[P,S]): string =
    Segm[P,S](start: kvp.key, size: kvp.data.size).pp

  proc pp[P,S](p: var SortedSet[P,BlockRef[S]]): string =
    template one: untyped = (S.default + 1)
    result = "{"
    var rc = p.ge(low(P))
    while rc.isOk:
      let (key,blk) = (rc.value.key,rc.value.data)
      if 1 < result.len:
        result &= ","
      result &= "[" & pp[P,S](key) & "," & pp[P,S](key + blk.size) & ")"
      rc = p.gt(key)
    result &= "}"

  var noisy* = false
else:
  var noisy = false

template say(noisy = false; pfx = "***"; v: varargs[untyped]): untyped =
  when NoisyDebuggingOk:
    sayImpl(noisy,pfx, v)
  discard

# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------

template maxSegmSize(): untyped =
  (high(P) - low(P))

template scalarZero(): untyped =
  ## the value `0` from the scalar data type
  (S.default)

template scalarOne(): untyped =
  ## the value `1` from the scalar data type
  (S.default + 1)

func blk[P,S](kvp: DataRef[P,S]): BlockRef[S] =
  kvp.data

func left[P,S](kvp: DataRef[P,S]): P =
  kvp.key

func right[P,S](kvp: DataRef[P,S]): P =
  kvp.key + kvp.blk.size

func len[P,S](kvp: DataRef[P,S]): S =
  kvp.data.size

# -----

func new[P,S](T: type Segm[P,S]; left, right: P): T =
  ## Constructor using `[left,right)` points representation
  T(start: left, size: right - left)

func brew[P,S](T: type Segm[P,S]; left, right: P): Result[T,void] =
  ## Constructor providing `[left, max(left,right)-left)` (if any.)
  if high(P) <= left:
    return err()
  let length =
    if right <= left:
      scalarOne
    elif right < high(P):
      (right - left) + scalarOne
    else:
      (high(P) - left)
  ok(T(start: left, size: length))

func left[P,S](iv: Segm[P,S]): P =
  iv.start

func right[P,S](iv: Segm[P,S]): P =
  iv.start + iv.size

func len[P,S](iv: Segm[P,S]): S =
  iv.size

# ------

func incPt[P,S](a: var P; n: S) =
  ## Might not be generally available for point `P` and scalar `S`
  a = a + n

func maxPt[P](a, b: P): P =
  ## Instead of max() which might not be generally available
  if a < b: b else: a

func minPt[P](a, b: P): P =
  ## Instead of min() which might not be generally available
  if a < b: a else: b

# ------

func new[P,S](T: type Interval[P,S]; kvp: DataRef[P,S]): T =
  T(least: kvp.left, last: kvp.right - scalarOne)

# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------

func overlapOrLeftJoin[P,S](ds: Desc[P,S]; l, r: P): Rc[P,S] =
  ## Find and return
  ## * either the rightmost interval `[a,b)` which overlaps `r`
  ## * or `[a,b)` with `b==l`
  doAssert l <= r
  let rc = ds.leftPos.le(r) # search for `max(a) <= r`
  if rc.isOk:
    # note that `b` is the first point outside right of `[a,b)`
    let b = rc.value.right
    if l <= b:
      return ok(rc.value)
  err()

func overlapOrLeftJoin[P,S](ds: Desc[P,S]; iv: Segm[P,S]): Rc[P,S] =
  ds.overlapOrLeftJoin(iv.left, iv.right)


func overlap[P,S](ds: Desc[P,S]; l, r: P): Rc[P,S] =
  ## Find and return the rightmost `[l,r)` overlapping interval `[a,b)`.
  doAssert l < r
  let rc = ds.leftPos.lt(r) # search for `max(a) < r`
  if rc.isOk:
    # note that `b` is the first point outside right of `[a,b)`
    let b = rc.value.right
    if l < b:
      return ok(rc.value)
  err()

func overlap[P,S](ds: Desc[P,S]; iv: Segm[P,S]): Rc[P,S] =
  ds.overlap(iv.left, iv.right)

# ------------------------------------------------------------------------------
# Private transfer function helpers
# ------------------------------------------------------------------------------

func findInlet[P,S](ds: Desc[P,S]; iv: Segm[P,S]): Segm[P,S] =
  ## Find largest sub-segment of `iv` fully contained in another segment
  ## of the argument database.
  ##
  ## If the `src` argument is `nil`, the argument interval `iv` is returned.
  ## If there is no overlapping segment, the empty interval
  ##`[iv.start,iv.start)` is returned.

  # Handling edge cases
  if ds.isNil:
    return iv

  let rc = ds.overlap(iv)
  if rc.isErr:
    return Segm[P,S].new(iv.left, iv.left)

  let p = rc.value
  Segm[P,S].new(maxPt(p.left,iv.left), minPt(p.right,iv.right))


func merge[P,S](ds: Desc[P,S]; iv: Segm[P,S]): Segm[P,S] =
  ## Merges argument interval into into database and returns
  ## the segment really added (if any)

  if ds.isNil:
    return iv

  let p = block:
    let rc = ds.overlapOrLeftJoin(iv)
    if rc.isErr:
      let rx = ds.leftPos.insert(iv.left)
      rx.value.data = BlockRef[S](size: iv.len)
      ds.ptsCount += iv.len
      return iv
    rc.value # `rc.value.data` is a reference to the database record

  doAssert p.blk.size <= ds.ptsCount

  if p.right < iv.right:
    #
    #     iv:     ...----------------)
    #     p:      ...-----)
    #
    let extend = iv.right - p.right
    p.blk.size += extend   # update database
    ds.ptsCount += extend  # update database
    #
    #     iv:     ...----------------)
    #     p:      ...----------------)
    #     result:          [---------)
    #
    return Segm[P,S].new(p.right - extend, iv.right)

  # now: iv.right <= p.right and p.left <= iv.left:
  if p.left <= iv.left:
    #
    #     iv:         [--------)
    #     p:      [-------------------)
    #     result:     o
    #
    return Segm[P,S].new(iv.left, iv.left) # empty interval

  # now: iv.right <= p.right and iv.left < p.left
  if p.left < iv.right:
    #
    #     iv:     [-----------------)
    #     p:              [--------------)
    #     result: [------)
    #
    result = Segm[P,S].new(iv.left, p.left)
  else:
    #     iv:     [------)
    #     p:              [--------------)
    #     result: [------)
    #
    result = iv

  # No need for interval `p` anymore.
  doAssert p.left == result.right
  ds.ptsCount -= p.len
  discard ds.leftPos.delete(p.left)

  # Check whether there is an `iv` left overlapping interval `q` that can be
  # merged.
  #
  # Note that the deleted `p` was not fully contained in `iv`. So any overlap
  # must be a predecessor. Also, the right end point of the `iv` interval is
  # not part of any predecessor because it was adjacent to, or overlapping with
  # the deleted interval `p`.
  let rc = ds.overlapOrLeftJoin(iv.left, iv.right - scalarOne)
  if rc.isOk and iv.left <= rc.value.right:
    let q = rc.value
    #
    #   iv:         [------...
    #   p:                  [------)    // deleted
    #   q:       [----)
    #   result:     [------)
    #
    result = Segm[P,S].new(q.right, result.right)
    #
    #   iv:         [------...
    #   p:                  [------)    // deleted
    #   q:       [----)
    #   result:        [---)
    #
    # extend `q` to join `result` and `p`, now
    let exLen = result.len + p.len
    q.blk.size += exLen
    ds.ptsCount += exLen
    #
    #   iv:         [------...
    #   p:                  [------)    // deleted
    #   q:       [-----------------)
    #   result:       [----)
    #
  else:
    # So `iv` is fully isolated, i.e. there is no join or overlap. And `iv`
    # joins or overlaps the deleted `p` but does not exceed its right end.
    #
    #   iv:         [-----------)
    #   p:                  [------)    // deleted
    #   result:       [----)
    #
    let s = BlockRef[S](size: p.right - iv.left)
    ds.leftPos.insert(iv.left).value.data = s
    ds.ptsCount += s.size
    #
    #   iv:         [------)
    #   p:                  [------)    // deleted
    #   result:       [----)
    #   s:          [--------------)


func deleteInlet[P,S](ds: Desc[P,S]; iv: Segm[P,S]) =
  ## Delete fully contained interval
  if not ds.isNil and 0 < iv.len:

    let
      p = ds.overlap(iv).value  # `p.blk` is a reference into database
      right = p.right           # fix the right end for later trailer handling

    # [iv) fully contained in [p)
    doAssert p.left <= iv.left and iv.right <= p.right

    if p.left == iv.left:
      #
      #    iv:   [--------------)
      #    p:    [---------------...     // deleting
      #
      discard ds.leftPos.delete(p.left)
      ds.ptsCount -= p.len
    else:
      #    iv:           [-------)
      #    p:    [----------------...
      #
      let chop = p.right - iv.left # positive as iv.left<iv.right<=p.right
      p.blk.size -= chop           # update database
      ds.ptsCount -= chop             # update database
      #
      #    iv:           [-------)
      #    p.blk: [-----)         ...)
      #                              ^
      #                              |
      #                              right

    # Correct: re-add trailer
    if iv.right < right:
      #
      #    iv:   ...-------)
      #    p:       [---------------)    // may have been deleted in `==` clause
      #    s:               [-------)    // adding to database
      #
      let s = BlockRef[S](size: right - iv.right)
      ds.leftPos.insert(iv.right).value.data = s
      ds.ptsCount += s.size

# ------------------------------------------------------------------------------
# Private transfer() function implementation for merge/reduce
# ------------------------------------------------------------------------------

func transferImpl[P,S](src, trg: Desc[P,S]; iv: Segm[P,S]): S =
  ## From the `src` argument database, delete the data segment/interval
  ## `[start,start+length)` and merge it into the `trg` argument database.
  ## Not both  arguments `src` and `trg` must be `nil`.
  doAssert not (src.isNil and trg.isNil)

  var pfx = iv

  while 0 < pfx.len:
    # Find sub-interval of `[pfx)` fully contained in a `src` database interval
    var fromIv = src.findInlet(pfx)

    # Chop right end from [pfx) -> [pfx) + [fromIv)
    pfx = Segm[P,S].new(pfx.left, fromIv.left)

    # Move the `fromIv` interval from `src` to `trg` database
    while 0 < fromIv.len:
      # Merge sub-interval `[fromIv)` into `trg` database
      let toIv = trg.merge(fromIv)

      # Chop right end from [fromIv) -> [fromIv) + [toIv)
      fromIv = Segm[P,S].new(fromIv.left, toIv.left)

      # Delete merged sub-interval from `src` database (if any)
      src.deleteInlet(toIv)

      result += toIv.len

# ------------------------------------------------------------------------------
# Private covered() function implementation
# ------------------------------------------------------------------------------

func coveredImpl[P,S](ds: IntervalSetRef[P,S]; start: P; length: S): S =
  ## Calulate the accumulated size of the interval `[start,start+length)`
  ## covered by intervals in the set `ds`. The result cannot exceed the
  ## argument `length` (of course.)
  var iv = Segm[P,S](start: start, size: length)

  while 0 < iv.len:
    let rc = ds.overlap(iv)
    if rc.isErr:
      break

    let p = rc.value

    # Now `p` is the right most interval overlapping `iv`
    if p.left <= iv.left:
      if p.right <= iv.right:
        #
        #    iv:             [----------------)
        #    p:         [-------------)
        #    overlap:        <------->
        #
        result.incPt p.right - iv.left
      else:
        #    iv:             [--------)
        #    p:         [--------------------)
        #    overlap:        <------->
        #
        result.incPt iv.len
      break
    else:
      if iv.right < p.right:
        #
        #    iv:        [--------------)
        #    p:              [--------------)
        #    overlap:        <-------->
        #
        result.incPt iv.right - p.left
      else:
        #    iv:        [----------------------)
        #    p:              [----------)
        #    overlap:        <--------->
        #
        result.incPt p.len

      iv.size = p.left - iv.left
      #      iv:        [---)
      #      p:              [----------)

# ------------------------------------------------------------------------------
# Public constructor, clone, etc.
# ------------------------------------------------------------------------------

func init*[P,S](T: type IntervalSetRef[P,S]): T =
  ## Interval set constructor.
  new result
  result.leftPos.init()

func clone*[P,S](ds: IntervalSetRef[P,S]): IntervalSetRef[P,S] =
  ## Return a copy of the interval list. Beware, this might be slow as it
  ## needs to copy every interval record.
  result = Desc[P,S].init()
  result.ptsCount = ds.ptsCount
  result.lastHigh = ds.lastHigh

  var # using fast traversal
    walk = SortedSetWalkRef[P,BlockRef[S]].init(ds.leftPos)
    rc = walk.first
  while rc.isOk:
    result.leftPos.insert(rc.value.key)
      .value.data = BlockRef[S](size: rc.value.data.size)
    rc = walk.next
  # optional clean up, see comments on the destroy() directive
  walk.destroy

func `==`*[P,S](a, b: IntervalSetRef[P,S]): bool =
  ## Compare interval sets for equality. Beware, this can be slow. Every
  ## interval record has to be checked.
  if a.ptsCount == b.ptsCount and
     a.leftPos.len == b.leftPos.len and
     a.lastHigh == b.lastHigh:
    result = true
    if 0 < a.ptsCount and addr(a.leftPos) != addr(b.leftPos):
      var # using fast traversal
        aWalk = SortedSetWalkRef[P,BlockRef[S]].init(a.leftPos)
        aRc = aWalk.first()
      while aRc.isOk:
        let bRc = b.leftPos.eq(aRc.value.key)
        if bRc.isErr or aRc.value.data.size != bRc.value.data.size:
          result = false
          break
        aRc = aWalk.next()
      # optional clean up, see comments on the destroy() directive
      aWalk.destroy()

func clear*[P,S](ds: IntervalSetRef[P,S]) =
  ## Clear the interval set.
  ds.ptsCount = scalarZero
  ds.lastHigh = false
  ds.leftPos.clear()

func new*[P,S](T: type Interval[P,S]; minPt, maxPt: P): T =
  ## Create interval `[minPt,max(minPt,maxPt)]`
  Interval[P,S](least: minPt, last: max(minPt, maxPt))

# ------------------------------------------------------------------------------
# Public interval operations add, remove, erc.
# ------------------------------------------------------------------------------

func merge*[P,S](ds: IntervalSetRef[P,S]; minPt, maxPt: P): S =
  ## For the argument interval `I` implied as `[minPt,max(minPt,maxPt)]`,
  ## merge `I` with the intervals of the argument set `ds`. The function
  ## returns the accumulated number of points that were added to some
  ## interval (i.e. which were not contained in any interval of `ds`.)
  ##
  ## If the argument interval `I` is `[low(P),high(P)]` and is fully merged,
  ## the scalar *zero* is returned instead of `high(P)-low(P)+1` (which might
  ## not exisit in `S`.).
  let rc = Segm[P,S].brew(minPt, maxPt)
  if rc.isOk:
    result = transferImpl[P,S](src=nil, trg=ds, iv=rc.value)

  if not ds.lastHigh and high(P) <= max(minPt,maxPt):
    ds.lastHigh = true
    if result < maxSegmSize:
      result += scalarOne
    else:
      result = scalarZero

func merge*[P,S](ds: IntervalSetRef[P,S]; iv: Interval[P,S]): S =
  ## Variant of `merge()`
  ds.merge(iv.least, iv.last)


func reduce*[P,S](ds: IntervalSetRef[P,S]; minPt, maxPt: P): S =
  ## For the argument interval `I` implied as `[minPt,max(minPt,maxPt)]`,
  ## remove the points from `I` from intervals of the argument set `ds`.
  ## The function returns the accumulated number of elements removed (i.e.
  ## which were previously contained in some interval of `ds`.)
  ##
  ## If the argument interval `I` is `[low(P),high(P)]` and is fully removed,
  ## the scalar *zero* is returned instead of `high(P)-low(P)+1` (which might
  ## not exisit in `S`.).
  let rc = Segm[P,S].brew(minPt, maxPt)
  if rc.isOk:
    result = transferImpl[P,S](src=ds, trg=nil, iv=rc.value)

  if ds.lastHigh and high(P) <= max(minPt,maxPt):
    ds.lastHigh = false
    if result < maxSegmSize:
      result += scalarOne
    else:
      result = scalarZero

func reduce*[P,S](ds: IntervalSetRef[P,S]; iv: Interval[P,S]): S =
  ## Variant of `reduce()`
  ds.reduce(iv.least, iv.last)


func covered*[P,S](ds: IntervalSetRef[P,S]; minPt, maxPt: P): S =
  ## For the argument interval `I` implied as `[minPt,max(minPt,maxPt)]`,
  ## calulate the accumulated points `I` contained in some interval in the
  ## set `ds`. The return value is the same as that for `reduce()` (only
  ## that `ds` is left unchanged, here.)
  let rc = Segm[P,S].brew(minPt, maxPt)
  if rc.isOk:
    result = ds.coveredImpl(rc.value.left, rc.value.size)

  if ds.lastHigh and high(P) <= max(minPt,maxPt):
    if result < maxSegmSize:
      result += scalarOne
    else:
      result = scalarZero

func covered*[P,S](ds: IntervalSetRef[P,S]; iv: Interval[P,S]): S =
  ## Variant of `covered()`
  ds.covered(iv.least, iv.last)


func ge*[P,S](ds: IntervalSetRef[P,S]; minPt: P): IntervalRc[P,S] =
  ## Find smallest interval in the set `ds` with start point (i.e. minimal
  ## value in the interval as a set) greater or equal the argument `minPt`.
  let rc = ds.leftPos.ge(minPt)
  if rc.isOk:
    # Check for fringe case intervals [a,b] + [high(P),high(P)]
    if high(P) <= rc.value.right and ds.lastHigh:
      return ok(Interval[P,S].new(rc.value.left, high(P)))
    return ok(Interval[P,S].new(rc.value))
  if ds.lastHigh:
    const preHigh = high(P) - scalarOne
    if ds.covered(preHigh,preHigh) == scalarZero:
      return ok(Interval[P,S].new(high(P),high(P)))
  err()

func ge*[P,S](ds: IntervalSetRef[P,S]): IntervalRc[P,S] =
  ## Find the interval with the least elements of type `P` (if any.)
  ds.ge(low(P))

func le*[P,S](ds: IntervalSetRef[P,S]; maxPt: P): IntervalRc[P,S] =
  ## Find largest interval in the set `ds` with end point (i.e. maximal
  ## value in the interval as a set) smaller or equal to the argument `maxPt`.
  let rc = ds.leftPos.le(maxPt)
  if rc.isOk:
    # only the left end of segment [left,right) is guaranteed to be <= maxPt
    if rc.value.right - scalarOne <= maxPt:
      if ds.lastHigh:
        if high(P) <= maxPt:
          # Check for fringe case intervals [a,b] gap [high(P),high(P)] <= maxPt
          if rc.value.right < high(P):
            return ok(Interval[P,S].new(high(P),high(P)))
          # Check for fringe case intervals [a,b] + [high(P),high(P)] <= maxPt
          if high(P) <= rc.value.right:
            return ok(Interval[P,S].new(rc.value.left,high(P)))
        # So maxPt < high(P)
        if high(P) <= rc.value.right:
          # Now `maxPt` is fully within the inner part of `[left,high(P)]`
          return err()
      return ok(Interval[P,S].new(rc.value))
    # find the next smaller one
    let xc = ds.leftPos.lt(rc.value.key)
    if xc.isOk:
      return ok(Interval[P,S].new(xc.value))
  # lone interval
  if high(P) <= maxPt and ds.lastHigh:
    return ok(Interval[P,S].new(high(P),high(P)))
  err()

func le*[P,S](ds: IntervalSetRef[P,S]): IntervalRc[P,S] =
  ## Find the interval with the largest elements of type `P` (if any.)
  ds.le(high(P))


func envelope*[P,S](ds: IntervalSetRef[P,S]; pt: P): IntervalRc[P,S] =
  ## Find the interval that contains the argument point `pt` (if any)
  let rc = ds.leftPos.le(pt)
  if rc.isOk:
    if ds.lastHigh and high(P) <= rc.value.right:
      # This interval ranges `[left,high(P)]`, so `pt` is certainly contained
      return ok(Interval[P,S].new(rc.value.left,high(P)))
    if pt < rc.value.right:
      return ok(Interval[P,S].new(rc.value))
    # Otherwise: interval `[left,right)` ends before `pt`
  if ds.lastHigh and high(P) <= pt:
    return ok(Interval[P,S].new(high(P),high(P)))
  err()


func delete*[P,S](ds: IntervalSetRef[P,S]; minPt: P): IntervalRc[P,S] =
  ## Find the interval `[minPt,maxPt]` for some point `maxPt` in the interval
  ## set `ds` and remove it from `ds`. The function returns the deleted
  ## interval (if any.)
  block:
    let rc = ds.leftPos.delete(minPt)
    if rc.isOk:
      ds.ptsCount -= rc.value.len
      # Check for fringe case intervals [a,b]+[high(P),high(P)]
      if high(P) <= rc.value.right and ds.lastHigh:
        ds.lastHigh = false
        return ok(Interval[P,S].new(rc.value.left,high(P)))
      return ok(Interval[P,S].new(rc.value))
  if high(P) <= minPt and ds.lastHigh:
    # delete isolated point
    let rc = ds.leftPos.lt(minPt)
    if rc.isErr or rc.value.right < high(P):
      ds.lastHigh = false
      return ok(Interval[P,S].new(high(P),high(P)))
  err()


iterator increasing*[P,S](
    ds: IntervalSetRef[P,S];
    minPt = low(P)
      ): Interval[P,S] =
  ## Iterate in increasing order through intervals with points greater or
  ## equal than the argument point `minPt`.
  ##
  ## :Note:
  ##    When running in a loop it is *ok* to delete the current interval and
  ##    any interval already visited. Intervals not visited yet must not be
  ##    deleted as the loop would become unpredictable.
  var rc = ds.leftPos.ge(minPt)
  if rc.isErr:
    if ds.lastHigh:
      yield Interval[P,S].new(high(P),high(P))
  else:
    while rc.isOk:
      let key = rc.value.key
      if high(P) <= rc.value.right and ds.lastHigh:
        yield Interval[P,S].new(rc.value.left,high(P))
      else:
        yield Interval[P,S].new(rc.value)
      rc = ds.leftPos.gt(key)

iterator decreasing*[P,S](
    ds: IntervalSetRef[P,S];
    maxPt = high(P)
      ): Interval[P,S] =
  ## Iterate in decreasing order through intervals with points less or equal
  ## than the argument point `maxPt`.
  ##
  ## See the note at the `increasing()` function comment about deleting items.
  var rc = ds.leftPos.le(maxPt)
  if rc.isErr:
    if ds.lastHigh:
      yield Interval[P,S].new(high(P),high(P))
  else:
    let key = rc.value.key
    # last entry: check for additional point
    if high(P) <= rc.value.right and ds.lastHigh:
      yield Interval[P,S].new(rc.value.left,high(P))
    else:
      yield Interval[P,S].new(rc.value)
    # find the next smaller one
    rc = ds.leftPos.lt(key)

    while rc.isOk:
      let key = rc.value.key
      yield Interval[P,S].new(rc.value)
      rc = ds.leftPos.lt(key)

# ------------------------------------------------------------------------------
# Public interval operators
# ------------------------------------------------------------------------------

func `==`*[P,S](iv, jv: Interval[P,S]): bool =
  ## Compare intervals for equality
  iv.least == jv.least and iv.last == jv.last

func `==`*[P,S](iv: IntervalRc[P,S]; jv: Interval[P,S]): bool =
  ## Variant of `==`
  if iv.isOk:
    return iv.value == jv

func `==`*[P,S](iv: Interval[P,S]; jv: IntervalRc[P,S]): bool =
  ## Variant of `==`
  if jv.isOk:
    return iv == jv.value

func `==`*[P,S](iv, jv: IntervalRc[P,S]): bool =
  ## Variant of `==`
  if iv.isOk:
    if jv.isOk:
      return iv.value == jv.value
    # false
  else:
    return jv.isErr
  # false

# ------

func `*`*[P,S](iv, jv: Interval[P,S]): IntervalRc[P,S] =
  ## Intersect itervals `iv` and `jv` if this operation results in a
  ## non-emty interval. Note that the `*` operation is associative, i.e.
  ## ::
  ##  iv * jv * kv == (iv * jv) * kv == iv * (jv * kv)
  ##
  if jv.least <= iv.last and iv.least <= jv.last:
    # intervals overlap
    return ok(Interval[P,S].new(
      maxPt(jv.least,iv.least), minPt(jv.last,iv.last)))
  err()

func `*`*[P,S](iv: IntervalRc[P,S]; jv: Interval[P,S]): IntervalRc[P,S] =
  ## Variant of `*`
  if iv.isOk:
    return iv.value * jv
  err()

func `*`*[P,S](iv: Interval[P,S]; jv: IntervalRc[P,S]): IntervalRc[P,S] =
  ## Variant of `*`
  if jv.isOk:
    return iv * jv.value
  err()

func `*`*[P,S](iv, jv: IntervalRc[P,S]): IntervalRc[P,S] =
  ## Variant of `*`
  if iv.isOk and jv.isOk:
    return iv.value * jv.value
  err()

# ------

func `+`*[P,S](iv, jv: Interval[P,S]): IntervalRc[P,S] =
  ## Merge intervals `iv` and `jv` if this operation results in an interval.
  ## Note that the `+` operation is *not* associative, i.e.
  ## ::
  ##  iv + jv + kv == (iv + jv) + kv  is not necessarly  iv + (jv + kv)
  ##
  if iv.least <= jv.least:
    if jv.least - scalarOne <= iv.last:
      #
      #  iv:    [--------]
      #  jv:          [...[-----...
      #
      return ok(Interval[P,S].new(iv.least, maxPt(iv.last,jv.last)))

  else: # jv.least < iv.least
    if iv.least - scalarOne <= jv.last:
      #
      #  iv:          [...[-----...
      #  jv:    [--------]
      #
      return ok(Interval[P,S].new(jv.least, maxPt(iv.last,jv.last)))

  err()

func `+`*[P,S](iv: IntervalRc[P,S]; jv: Interval[P,S]): IntervalRc[P,S] =
  ## Variant of `+`
  if iv.isOk:
    return iv.value + jv
  err()

func `+`*[P,S](iv: Interval[P,S]; jv: IntervalRc[P,S]): IntervalRc[P,S] =
  ## Variant of `+`
  if jv.isOk:
    return iv + jv.value
  err()

func `+`*[P,S](iv, jv: IntervalRc[P,S]): IntervalRc[P,S] =
  ## Variant of `+`
  if iv.isOk and jv.isOk:
    return iv.value + jv.value
  err()

# ------

func `-`*[P,S](iv, jv: Interval[P,S]): IntervalRc[P,S] =
  ## Return the interval `iv` reduced by elements of `jv` if this operation
  ## results in a non-empty interval.
  ## Note that the `-` operation is *not* associative, i.e.
  ## ::
  ##  iv - jv - kv == (iv - jv) - kv  is not necessarly  iv - (jv - kv)
  ##
  if iv.least <= jv.least:
    if jv.least <= iv.last and iv.last <= jv.last:
      #
      #  iv:    [--------------]
      #  jv:          [------------]
      #
      if iv.least < jv.least:
        return ok(Interval[P,S].new(iv.least, jv.least - scalarOne))
      # otherwise empty set => error

    elif iv.last < jv.least:
      #
      #  iv:    [--------]
      #  jv:              [------------]
      #
      return ok(iv)

    else: # so jv.least <= iv.last and jv.last < iv.last
      #
      #  iv:    [--------------]
      #  jv:          [------]
      #
      discard # error

  else: # jv.least < iv.least
    if iv.least <= jv.last and jv.last <= iv.last:
      #
      #  iv:          [------------]
      #  jv:    [--------------]
      #
      if jv.last < iv.last:
        return ok(Interval[P,S].new(jv.last + scalarOne, iv.last))
      # otherwise empty set => error

    elif jv.last < iv.least:
      #
      #  iv:              [------------]
      #  jv:    [--------]
      #
      return ok(iv)

    else: # so iv.least <= jv.last and iv.last < jv.last
      #
      #  iv:          [------]
      #  jv:    [--------------]
      #
      discard # error

  err()

func `-`*[P,S](iv: IntervalRc[P,S]; jv: Interval[P,S]): IntervalRc[P,S] =
  ## Variant of `-`
  if iv.isOk:
    return iv.value - jv
  err()

func `-`*[P,S](iv: Interval[P,S]; jv: IntervalRc[P,S]): IntervalRc[P,S] =
  ## Variant of `-`
  if jv.isOk:
    return iv - jv.value
  err()

func `-`*[P,S](iv, jv: IntervalRc[P,S]): IntervalRc[P,S] =
  ## Variant of `-`
  if iv.isOk and jv.isOk:
    return iv.value - jv.valu
  err()

# ------------------------------------------------------------------------------
# Public getters
# ------------------------------------------------------------------------------

func len*[P,S](iv: Interval[P,S]): S =
  ## Cardinality (ie. length) of argument interval `iv`. If the argument
  ## interval `iv` is `[low(P),high(P)]`, the return value will be the scalar
  ## *zero* (there are no empty intervals in this implementation.)
  if low(P) == iv.least and high(P) == iv.last:
    scalarZero
  else:
    (iv.last - iv.least) + scalarOne

func minPt*[P,S](iv: Interval[P,S]): P =
  ## Left end, smallest point of `P` contained in the interval
  iv.least

func maxPt*[P,S](iv: Interval[P,S]): P =
  ## Right end, largest point of `P` contained in the interval
  iv.last

func total*[P,S](ds: IntervalSetRef[P,S]): S =
  ## Accumulated size covered by intervals in the interval set `ds`.
  ##
  ## In the special case when there is only the single interval
  ## `[low(P),high(P)]` in the interval set, the return value will be the
  ## scalar *zero* (there are no empty intervals in this implementation.)
  if not ds.lastHigh:
    ds.ptsCount
  elif maxSegmSize <= ds.ptsCount:
    scalarZero
  else:
    ds.ptsCount + scalarOne

func chunks*[P,S](ds: IntervalSetRef[P,S]): int =
  ## Number of disjunkt intervals (aka chunks) in the interval set `ds`.
  result = ds.leftPos.len
  if ds.lastHigh:
    # check for isolated interval [high(P),high(P)]
    if result == 0 or ds.leftPos.le(high(P)).value.right < high(P):
      result.inc

# ------------------------------------------------------------------------------
# Public debugging functions
# ------------------------------------------------------------------------------

func `$`*[P,S](p: DataRef[P,S]): string =
  ## Needed by `ds.verify()` for printing error messages
  "[" & $p.left & "," & $p.right & ")"

proc verify*[P,S](
    ds: IntervalSetRef[P,S]
      ): Result[void,(RbInfo,IntervalSetError)] =
  ## Verifyn interval set data structure
  try:
    let rc = ds.leftPos.verify
    if rc.isErr:
      return err((rc.error[1],isNoError))
  except CatchableError as e:
    raiseAssert $e.name & ": " & e.msg

  block:
    var
      count = scalarZero
      maxPt: P
      first = true
    for iv in ds.increasing:
      noisy.say "***", "verify(fwd)", " maxPt=", maxPt, " iv=", iv.pp
      if not(low(P) <= iv.least and iv.least <= iv.last and iv.last <= high(P)):
        noisy.say "***", "verify(fwd)", " error=", isErrorBogusInterval
        return err((rbOk,isErrorBogusInterval))
      if first:
        first = false
      elif iv.least <= maxPt:
        noisy.say "***", "verify(fwd)", " error=", isErrorOverlapping
        return err((rbOk,isErrorOverlapping))
      elif iv.least <= maxPt + scalarOne:
        noisy.say "***", "verify(fwd)", " error=", isErrorAdjacent
        return err((rbOk,isErrorAdjacent))
      maxPt = iv.last
      if iv.least == low(P) and iv.last == high(P):
        if ds.lastHigh and 0 < count or not ds.lastHigh and count == 0:
          return err((rbOk,isErrorTotalLastHigh))
        count += high(P) - low(P)
      elif iv.last == high(P) and ds.lastHigh:
        count += (iv.len - 1)
      else:
        count += iv.len

    if count != ds.ptsCount:
      noisy.say "***", "verify(fwd)",
        " error=", isErrorTotalMismatch,
        " count=", pp[P,S](ds.ptsCount),
        " expected=",  pp[P,S](count)
      return err((rbOk,isErrorTotalMismatch))

  block:
    var
      count = scalarZero
      minPt: P
      last = true
    for iv in ds.decreasing:
      #noisy.say "***", "verify(rev)", " minPt=", minPt, " iv=", iv.pp
      if not(low(P) <= iv.least and iv.least <= iv.last and iv.last <= high(P)):
        return err((rbOk,isErrorBogusInterval))
      if last:
        last = false
      elif minPt <= iv.least:
        return err((rbOk,isErrorOverlapping))
      elif minPt + scalarOne <= iv.least:
        return err((rbOk,isErrorAdjacent))
      minPt = iv.least
      if iv.least == low(P) and iv.last == high(P):
        if ds.lastHigh and 0 < count or not ds.lastHigh and count == 0:
          return err((rbOk,isErrorTotalLastHigh))
        count += high(P) - low(P)
      elif iv.last == high(P) and ds.lastHigh:
        count += (iv.len - 1)
      else:
        count += iv.len

    if count != ds.ptsCount:
      noisy.say "***", "verify(rev)",
        " error=", isErrorTotalMismatch,
        " count=", pp[P,S](ds.ptsCount),
        " expected=",  pp[P,S](count)
      return err((rbOk,isErrorTotalMismatch))

  ok()

# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------
