///|
/// The value of a typed parameter type.
///
/// The value is kept in a closure. The closure writes the value into the
/// slot of the `ParameterType[T]` handle that made it, so reading the value
/// back needs no casts and works on every target. `token` is the token of
/// that handle, so another handle does not run `put`.
pub struct TypedValue {
  priv type_name : String
  priv token : Ref[Unit]
  priv put : () -> Unit
}

///|
/// The name of the parameter type that made the value.
pub fn TypedValue::type_name(self : TypedValue) -> String {
  self.type_name
}

///|
/// A typed parameter type. `define1` to `define8`, `define_with` and
/// `define_type1` to `define_type8` return it. Use it to read the value of a
/// parameter of this type, with its type.
pub struct ParameterType[T] {
  priv name : String
  priv slot : Ref[T?]
  /// Identifies the handle. Each handle has its own token.
  priv token : Ref[Unit]
}

///|
fn[T] ParameterType::new(name : String) -> ParameterType[T] {
  { name, slot: { val: None, }, token: { val: (), }, }
}

///|
/// The name of the parameter type, as used in expressions.
pub fn[T] ParameterType::name(self : ParameterType[T]) -> String {
  self.name
}

///|
/// Wrap a value of this type.
fn[T] ParameterType::wrap(self : ParameterType[T], value : T) -> ParamValue {
  let slot = self.slot
  TypedVal({
    type_name: self.name,
    token: self.token,
    put: () => slot.val = Some(value),
  })
}

///|
/// The value of `param`, or `None` when `param` did not come from this
/// handle.
fn[T] ParameterType::try_get(self : ParameterType[T], param : Param) -> T? {
  guard param.value is TypedVal(typed) &&
    physical_equal(typed.token, self.token) else {
    return None
  }
  self.slot.val = None
  (typed.put)()
  let value = self.slot.val
  self.slot.val = None
  value
}

///|
/// The value of `param`. Raises `WrongParameterType` when `param` did not
/// come from this handle, including a type with the same name in another
/// registry.
pub fn[T] ParameterType::get(
  self : ParameterType[T],
  param : Param,
) -> T raise ParameterTypeError {
  match self.try_get(param) {
    Some(value) => value
    None =>
      raise WrongParameterType(
        expected=self.name,
        actual=parameter_type_name(param.type_),
      )
  }
}

///|
fn parameter_type_name(type_ : ParamType) -> String {
  match type_ {
    Int => "int"
    Float => "float"
    String_ => "string"
    Word => "word"
    Anonymous => "anonymous"
    Double_ => "double"
    Long => "long"
    Byte => "byte"
    Short => "short"
    BigDecimal => "bigdecimal"
    BigInteger => "biginteger"
    Custom(name) => name
  }
}

///|
/// The value of the only parameter of the match that came from `handle`.
/// Raises `MissingParameter` when there is none, and `AmbiguousParameter`
/// when there is more than one.
pub fn[T] Match::get(
  self : Match,
  handle : ParameterType[T],
) -> T raise ParameterTypeError {
  let values = self.get_all(handle)
  match values.length() {
    1 => values[0]
    0 => raise MissingParameter(name=handle.name)
    count => raise AmbiguousParameter(name=handle.name, count~)
  }
}

///|
/// The values of all parameters of the match that came from `handle`, in
/// order.
pub fn[T] Match::get_all(self : Match, handle : ParameterType[T]) -> Array[T] {
  self.params.filter_map(p => handle.try_get(p))
}