///|
/// A three-valued operand: TRUE, FALSE, or unknown (NULL or MISSING). Any other
/// value is a `TypeFailure`. Logic does not tell NULL from MISSING: the
/// result of an unknown operand is NULL (partiql-tests logical.ion).
priv enum Truth {
  True
  False
  Unknown
}

///|
fn truth(v : @value.Value) -> Truth raise OpFailure {
  match boxed(v) {
    Bool(true) => True
    Bool(false) => False
    Null | Missing => Unknown
    _ =>
      raise TypeFailure("a logical operator on a value that is not a boolean")
  }
}

///|
/// FALSE if either is FALSE; else NULL if either is unknown; else TRUE.
pub fn and_(a : @value.Value, b : @value.Value) -> @value.Value raise OpFailure {
  match (truth(a), truth(b)) {
    (False, _) | (_, False) => @value.Value::from_bool(false)
    (Unknown, _) | (_, Unknown) => Null
    _ => @value.Value::from_bool(true)
  }
}

///|
/// TRUE if either is TRUE; else NULL if either is unknown; else FALSE.
pub fn or_(a : @value.Value, b : @value.Value) -> @value.Value raise OpFailure {
  match (truth(a), truth(b)) {
    (True, _) | (_, True) => @value.Value::from_bool(true)
    (Unknown, _) | (_, Unknown) => Null
    _ => @value.Value::from_bool(false)
  }
}

///|
/// SQL `NOT`: negate a boolean; NULL and MISSING both produce NULL after unboxing Ion.
/// Raise `TypeFailure` for a present non-boolean operand.
pub fn not_(a : @value.Value) -> @value.Value raise OpFailure {
  match truth(a) {
    True => @value.Value::from_bool(false)
    False => @value.Value::from_bool(true)
    Unknown => Null
  }
}

///|
/// `IS [NOT] TRUE | FALSE | UNKNOWN` (`None`): a non-boolean is neither TRUE
/// nor FALSE nor UNKNOWN.
pub fn bool_test(v : @value.Value, wanted : Bool?, negated~ : Bool) -> Bool {
  let x = boxed(v) catch { _ => v }
  let holds = match (wanted, x) {
    (Some(w), Bool(b)) => w == b
    (None, Null | Missing) => true
    _ => false
  }
  holds != negated
}