///|
pub struct PatternSet {
  entries : Array[Pattern]
  complete : Bool
  visited : Int32
} derive(Debug)

///|
pub extend PatternSet with @debug.Debug::{to_repr}

///|
priv struct Enumeration {
  entries : Array[Pattern]
  limit : Int32
  mut visited : Int32
  mut complete : Bool
}

///|
fn enumerate_counts(
  p : Problem,
  si : Int32,
  at : Int32,
  capacity : Int32,
  counts : Array[Int32],
  pieces : Int32,
  e : Enumeration,
) -> Unit {
  if !e.complete {
    return
  }
  if e.visited >= e.limit {
    e.complete = false
    return
  }
  e.visited += 1
  if at == p.demands.length() {
    if pieces > 0 {
      match pattern(p, si, counts) {
        Ok(a) => e.entries.push(a)
        Err(_) => ()
      }
    }
    return
  }
  let d = p.demands[at]
  let max_count = if d.material == p.stocks[si].material {
    d.quantity.min(capacity / (d.length + p.kerf))
  } else {
    0
  }
  for n in 0..<=max_count {
    counts[at] = n
    enumerate_counts(
      p,
      si,
      at + 1,
      capacity - n * (d.length + p.kerf),
      counts,
      pieces + n,
      e,
    )
    if !e.complete {
      break
    }
  }
  counts[at] = 0
}

///|
/// Enumerates all nonempty feasible demand-count patterns, unless the visit budget ends.
pub fn enumerate_patterns(
  p : Problem,
  visit_limit~ : Int32 = 50000,
) -> Result[PatternSet, String] {
  if visit_limit < 1 || visit_limit > 1000000 {
    return Err("enumeration visit budget must be 1..1000000")
  }
  let e = Enumeration::{
    entries: [],
    limit: visit_limit,
    visited: 0,
    complete: true,
  }
  for si, s in p.stocks {
    if s.quantity != 0 {
      let counts = Array::make(p.demands.length(), 0)
      enumerate_counts(
        p,
        si,
        0,
        s.length - s.trim_start - s.trim_end,
        counts,
        0,
        e,
      )
    }
    if !e.complete {
      break
    }
  }
  Ok({ entries: e.entries, complete: e.complete, visited: e.visited, })
}

///|
pub fn PatternSet::patterns(self : PatternSet) -> Array[Pattern] {
  self.entries.map(copy_pattern)
}

///|
pub fn PatternSet::complete(self : PatternSet) -> Bool {
  self.complete
}

///|
pub fn PatternSet::visited(self : PatternSet) -> Int32 {
  self.visited
}