///|
/// A composable immutable set-algebra execution plan.
pub(all) enum BitmapPlan {
  Source(RoaringBitmap)
  Union(BitmapPlan, BitmapPlan)
  Intersection(BitmapPlan, BitmapPlan)
  Difference(BitmapPlan, BitmapPlan)
  Xor(BitmapPlan, BitmapPlan)
  Take(BitmapPlan, Int)
  Drop(BitmapPlan, Int)
}

///|
pub fn BitmapPlan::evaluate(self : BitmapPlan) -> RoaringBitmap {
  match self {
    Source(bitmap) => bitmap
    Union(left, right) => left.evaluate().union(right.evaluate())
    Intersection(left, right) => left.evaluate().intersection(right.evaluate())
    Difference(left, right) => left.evaluate().difference(right.evaluate())
    Xor(left, right) => left.evaluate().xor(right.evaluate())
    Take(input, count) => input.evaluate().take(count)
    Drop(input, count) => input.evaluate().drop(count)
  }
}

///|
pub fn BitmapPlan::estimated_cardinality(self : BitmapPlan) -> Int {
  self.evaluate().cardinality()
}

///|
/// Static shape information for a plan before execution.
pub(all) struct BitmapPlanStats {
  nodes : Int
  depth : Int
  sources : Int
}

///|
/// Count nodes, depth, and source leaves without materializing a bitmap.
pub fn BitmapPlan::stats(self : BitmapPlan) -> BitmapPlanStats {
  match self {
    Source(_) => { nodes: 1, depth: 1, sources: 1 }
    Take(input, _) | Drop(input, _) => {
      let child = input.stats()
      { nodes: child.nodes + 1, depth: child.depth + 1, sources: child.sources }
    }
    Union(left, right)
    | Intersection(left, right)
    | Difference(left, right)
    | Xor(left, right) => {
      let a = left.stats()
      let b = right.stats()
      {
        nodes: a.nodes + b.nodes + 1,
        depth: (if a.depth > b.depth { a.depth } else { b.depth }) + 1,
        sources: a.sources + b.sources,
      }
    }
  }
}

///|
/// Render a stable compact prefix description for diagnostics.
pub fn BitmapPlan::describe(self : BitmapPlan) -> String {
  match self {
    Source(bitmap) => "source(" + bitmap.cardinality().to_string() + ")"
    Union(left, right) =>
      "union(" + left.describe() + "," + right.describe() + ")"
    Intersection(left, right) =>
      "intersection(" + left.describe() + "," + right.describe() + ")"
    Difference(left, right) =>
      "difference(" + left.describe() + "," + right.describe() + ")"
    Xor(left, right) => "xor(" + left.describe() + "," + right.describe() + ")"
    Take(input, count) =>
      "take(" + count.to_string() + "," + input.describe() + ")"
    Drop(input, count) =>
      "drop(" + count.to_string() + "," + input.describe() + ")"
  }
}