///|
/// A differential fixed-point variable. Set its definition once before execution.
pub struct Variable[D, T, R, S] {
  priv feedback : Feedback[D, T, R, S]
  priv source : Collection[D, T, R]?
}

///|
pub fn[D, T : Lattice, R, S] Scope::variable(
  self : Scope[T, S],
  step : S,
) -> (Variable[D, T, R, S], Collection[D, T, R]) {
  let (feedback, collection) = self.feedback(step)
  ({ feedback, source: None, }, collection)
}

///|
pub fn[D, T : Lattice, R : Abelian, S] Scope::variable_from(
  self : Scope[T, S],
  source : Collection[D, T, R],
  step : S,
) -> (Variable[D, T, R, S], Collection[D, T, R]) {
  let (feedback, collection) = self.feedback(step)
  ({ feedback, source: Some(source.negate()), }, source.concat(collection))
}

///|
/// Feed the definition back once. Variables created with variable_from carry
/// a pre-negated initial source, so those records are not reinjected. Empty
/// variables require no subtraction and support semigroup-only differences.
pub fn[D, T : Lattice, R, S] Variable::set(
  self : Variable[D, T, R, S],
  definition : Collection[D, T, R],
) -> Unit {
  let updates = match self.source {
    None => definition
    Some(source) => definition.concat(source)
  }
  self.feedback.connect(updates)
}

///|
/// Iteration uses a nested Int64 coordinate starting at zero and advancing by one.
/// The returned collection contains changes to the fixed point at each outer time.
pub fn[D, T : Lattice, R : Abelian, S : Compare] Scope::iterate(
  self : Scope[T, S],
  source : Collection[D, T, R],
  logic : (
    Scope[Product[T, Int64], Product[S, Int64]],
    Collection[D, Product[T, Int64], R],
  ) -> Collection[D, Product[T, Int64], R],
) -> Collection[D, T, R] {
  let nested = self.nested(0L, SummaryDomain::int64())
  let child = nested.scope()
  let (variable, collection) = child.variable_from(
    nested.enter(source),
    Product(self.domain.identity, 1L),
  )
  let result = logic(child, collection)
  variable.set(result)
  nested.leave(result)
}