// The Ion Schema Language constraint model.
//
// The model is ISL 2.0: the loader translates each ISL 1.0 construct into the
// ISL 2.0 constraints with the same meaning. Constructs the loader does not
// recognize are reported as unsupported rather than silently ignored, as the
// specification requires.

///|
/// An occurrence range: how many times a matching element or field must (or may)
/// appear.
pub struct Occurs {
  lower : Int32
  upper : Int32?
} derive(Debug)

///|
/// An occurrence range with an optional upper bound (`None` is unbounded).
pub fn Occurs::new(lower : Int32, upper : Int32?) -> Occurs {
  { lower, upper, }
}

///|
/// Exactly once.
pub fn Occurs::required() -> Occurs {
  { lower: 1, upper: Some(1), }
}

///|
/// Zero or one time.
pub fn Occurs::optional() -> Occurs {
  { lower: 0, upper: Some(1), }
}

///|
/// Whether `count` is within the range.
pub fn Occurs::allows(self : Occurs, count : Int32) -> Bool {
  if count < self.lower {
    return false
  }
  match self.upper {
    Some(upper) => count <= upper
    None => true
  }
}

///|
/// A named field within a `fields` constraint.
pub struct FieldConstraint {
  name : String
  ty : Constraint
  occurs : Occurs
} derive(Debug)

///|
/// A constraint over a value.
pub(all) enum Constraint {
  /// A built-in type: a value of one of these Ion types, not null. `int` is
  /// `IsType([Int])`, and `number` is `IsType([Int, Float, Decimal])`.
  IsType(Array[@ion.IonType])
  /// `$null_or::X`: the value is null, or satisfies `X`.
  NullOr(Constraint)
  /// A reference to a type defined elsewhere in the schema.
  TypeRef(String)
  /// Every element of a list or S-expression, or field value of a struct,
  /// satisfies the constraint.
  Element(Constraint)
  /// The struct's fields, by name.
  Fields(Array[FieldConstraint])
  /// `valid_values: [...]`.
  ValidValues(Array[@ion.IonValue])
  /// A `range::[lower, upper]` of numbers or timestamps in `valid_values`.
  InRange(ValueRange)
  /// `container_length`: the number of elements or fields.
  ContainerLength(Int32, Int32?)
  /// `codepoint_length`: the number of code points in a string or symbol.
  CodepointLength(Int32, Int32?)
  /// All of the nested constraints hold.
  AllOf(Array[Constraint])
  /// At least one of the nested constraints holds.
  AnyOf(Array[Constraint])
  /// Exactly one of the nested constraints holds.
  OneOf(Array[Constraint])
  /// The nested constraint does not hold.
  Not(Constraint)
  /// `nothing`: a constraint that no value satisfies.
  Nothing
  /// `document`: a document, not a value; see `Schema::validate_document`.
  IsDocument
  /// `regex`: a string or symbol has a match for the regular expression.
  Matches(Regex)
  /// `byte_length`: the number of bytes in a blob or clob.
  ByteLength(Int32, Int32?)
  /// `utf8_byte_length`: the number of bytes in the UTF-8 encoding of a
  /// string or symbol.
  Utf8ByteLength(Int32, Int32?)
  /// `precision`: the number of digits in a decimal's coefficient.
  Precision(Int32, Int32?)
  /// `exponent`: a decimal's exponent.
  Exponent(Int32, Int32?)
  /// `timestamp_precision`: a timestamp's precision, as an integer: `-4` for
  /// year, `-3` month, `-2` day, `-1` minute, `0` second, and the number of
  /// fractional digits beyond that (`3` is millisecond).
  TimestampPrecision(Int32, Int32?)
  /// `timestamp_offset`: a timestamp's offset is one of these. A timestamp
  /// with no time has the unknown offset.
  TimestampOffsets(Array[@ion.TimestampOffset])
  /// `ieee754_float`: a float that the IEEE 754 format holds exactly.
  Ieee754Float(FloatFormat)
  /// `field_names`: each field name of a struct, as a symbol, satisfies the
  /// constraint.
  FieldNames(Constraint)
  /// `field_names: distinct::...`: no field name repeats.
  DistinctFieldNames
  /// `element: distinct::...`: no two elements of a container, or field
  /// values of a struct, are equivalent.
  DistinctElements
  /// `fields: closed::{...}`: the struct has no field but these.
  ClosedFields(Array[String])
  /// `annotations`: the value's annotations, as a list of symbols, satisfy
  /// the constraint.
  Annotations(Constraint)
  /// `ordered_elements`: the elements of a list or S-expression, in order,
  /// are each type repeated as many times as its range allows.
  OrderedElements(Array[(Constraint, Occurs)])
  /// `contains: [ ... ]`: the container holds every listed value.
  Contains(Array[@ion.IonValue])
} derive(Debug)

///|
/// An IEEE 754 binary interchange format.
pub(all) enum FloatFormat {
  Binary16
  Binary32
  Binary64
} derive(Eq, Debug)

///|
/// A constraint that always holds, used for `$any`.
pub fn Constraint::any() -> Constraint {
  AllOf([])
}

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

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

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

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

///|
pub extend FloatFormat with Eq::{equal, not_equal}