///|
/// A lazy Fisher-Yates permutation uses memory proportional to draws, not domain.
priv struct UniqueDomain {
  generator : Generator
  mut remaining : Int
  swaps : Map[Int, Int]
  values : Array[Value]
  alphabet : Array[Char]
  reference_time : String
}

///|
fn unique_domain(
  generator : Generator,
  tables : Map[String, Table],
  reference_time : String,
) -> UniqueDomain? {
  let values : Array[Value] = []
  let alphabet : Array[Char] = []
  let size = match generator {
    IntegerRange(min, max) | DateOffset(min, max) =>
      (max.to_int64() - min.to_int64() + 1L).to_int()
    BooleanChance(chance) => {
      if chance < 1000 {
        values.push(Boolean(false))
      }
      if chance > 0 {
        values.push(Boolean(true))
      }
      values.length()
    }
    Constant(Null) => return None
    Constant(value) => {
      values.push(value)
      1
    }
    Choice(choices) => {
      let seen : Map[String, Bool] = Map([])
      for value in choices {
        if !seen.contains(value.key()) {
          seen[value.key()] = true
          values.push(value)
        }
      }
      values.length()
    }
    Reference(target, key) => {
      for row in tables.get(target).unwrap().rows {
        if row.get(key) is Some(value) {
          values.push(value)
        }
      }
      values.length()
    }
    Pattern(chars, length) => {
      for c in chars.iter() {
        if !alphabet.contains(c) {
          alphabet.push(c)
        }
      }
      let mut count = 1L
      for _ in 0.. 2147483647L {
          return None
        }
      }
      count.to_int()
    }
    _ => return None
  }
  Some({
    generator,
    remaining: size,
    swaps: Map([]),
    values,
    alphabet,
    reference_time,
  })
}

///|
fn UniqueDomain::draw(self : UniqueDomain, rng : Random) -> Value? {
  if self.remaining == 0 {
    return None
  }
  let slot = rng.below(self.remaining).unwrap()
  let index = self.swaps.get(slot).unwrap_or(slot)
  let last = self.remaining - 1
  self.swaps[slot] = self.swaps.get(last).unwrap_or(last)
  self.remaining = last
  match self.generator {
    IntegerRange(min, _) => Some(Integer(min + index))
    DateOffset(min, _) =>
      Some(
        Text(
          reference_date(self.reference_time)
          .unwrap()
          .add_days(min + index)
          .unwrap()
          .to_string(),
        ),
      )
    Pattern(_, length) => {
      let out = StringBuilder()
      let mut rank = index
      for _ in 0.. Some(self.values[index])
  }
}