// OVERLAPS (SQL-92 8.11; spec M4d3 ยง4.5).

///|
/// The two elements of an OVERLAPS operand: a list or a bag (stored order) of
/// exactly two elements; the first NULL, MISSING or a datetime, the second
/// also an interval. MISSING elements act as NULL.
fn period_elements(
  v : @value.Value,
) -> (@value.Value, @value.Value) raise OpFailure {
  let items = match v {
    List(xs) => xs
    Bag(b) => Array::from_iter(b.items().iter())
    _ => raise TypeFailure("an OVERLAPS operand must be a list or bag of 2")
  }
  if items.length() != 2 {
    raise TypeFailure("an OVERLAPS operand must have exactly 2 elements")
  }
  let first = boxed(items[0])
  let second = boxed(items[1])
  if !first.is_absent() && !is_datetime(first) {
    raise TypeFailure("the start of an OVERLAPS period must be a datetime")
  }
  if !second.is_absent() && !is_datetime(second) && !is_interval(second) {
    raise TypeFailure(
      "the end of an OVERLAPS period must be a datetime or an interval",
    )
  }
  (
    if first.is_absent() {
      Null
    } else {
      first
    },
    if second.is_absent() {
      Null
    } else {
      second
    },
  )
}

///|
/// Start and end of a period: the second element, or start + interval.
fn period_end(
  start : @value.Value,
  second : @value.Value,
) -> @value.Value raise OpFailure {
  if is_interval(second) {
    if start is Null {
      Null
    } else {
      shift_datetime(start, second, negative=false)
    }
  } else {
    second
  }
}

///|
/// The order of two non-NULL datetimes of one family; `None` when either is
/// NULL.
fn period_order(
  a : @value.Value,
  b : @value.Value,
  zone : ZoneOffset,
) -> Int32? raise OpFailure {
  if a is Null || b is Null {
    return None
  }
  Some(zoned_instant(a, zone).compare(zoned_instant(b, zone)))
}

///|
fn and3(a : Bool?, b : Bool?) -> Bool? {
  match (a, b) {
    (Some(false), _) | (_, Some(false)) => Some(false)
    (Some(true), Some(true)) => Some(true)
    _ => None
  }
}

///|
fn or3(a : Bool?, b : Bool?) -> Bool? {
  match (a, b) {
    (Some(true), _) | (_, Some(true)) => Some(true)
    (Some(false), Some(false)) => Some(false)
    _ => None
  }
}

///|
fn not3(a : Bool?) -> Bool? {
  a.map(x => !x)
}

///|
/// `x OVERLAPS y`.
pub fn overlaps(
  args : Array[@value.Value],
  zone~ : ZoneOffset,
) -> @value.Value raise OpFailure {
  guard args.length() == 2 else {
    raise DataFailure("OVERLAPS takes 2 operands")
  }
  let x = boxed(args[0])
  let y = boxed(args[1])
  if x is Missing || y is Missing {
    return Missing
  }
  if x is Null || y is Null {
    return Null
  }
  let (d1, second1) = period_elements(x)
  let (d2, second2) = period_elements(y)
  let e1 = period_end(d1, second1)
  let e2 = period_end(d2, second2)
  // Every non-NULL endpoint must be of one family: TIME with TIME, DATE or
  // TIMESTAMP with each other.
  let points = [d1, e1, d2, e2].filter(p => !(p is Null))
  let times = points.iter().all(p => p is Time(_))
  let instants = points.iter().all(p => is_point_in_time(p))
  if !times && !instants {
    raise TypeFailure("OVERLAPS periods of datetimes that do not compare")
  }
  // SQL-92 8.11 GR 3: the earlier endpoint is the start.
  let (s1, t1) = if d1 is Null ||
    (period_order(e1, d1, zone) is Some(c) && c < 0) {
    (e1, d1)
  } else {
    (d1, e1)
  }
  let (s2, t2) = if d2 is Null ||
    (period_order(e2, d2, zone) is Some(c) && c < 0) {
    (e2, d2)
  } else {
    (d2, e2)
  }
  let gt = (a, b) => period_order(a, b, zone).map(c => c > 0)
  let ge = (a, b) => period_order(a, b, zone).map(c => c >= 0)
  let eq = (a, b) => period_order(a, b, zone).map(c => c == 0)
  let ne = (a, b) => period_order(a, b, zone).map(c => c != 0)
  let first = and3(gt(s1, s2), not3(and3(ge(s1, t2), ge(t1, t2))))
  let second = and3(gt(s2, s1), not3(and3(ge(s2, t1), ge(t2, t1))))
  let third = and3(eq(s1, s2), or3(ne(t1, t2), eq(t1, t2)))
  match or3(or3(first, second), third) {
    Some(b) => @value.Value::from_bool(b)
    None => Null
  }
}