///|
/// The operators: `⊙̂(v, v')` and `⊖̂(v)`.
///
/// Arithmetic is Python's, with three things this port has to supply that
/// MoonBit does not:
///
///   * **`//` and `%` floor.** MoonBit's `/` and `%` on `BigInt` and `Int64`
///     truncate toward zero; Python's floor toward negative infinity, so the
///     remainder takes the sign of the DIVISOR: `-7 // 2` is `-4` and
///     `7 % -2` is `-1`.
///   * **`bool` is not a number.** `True + 1`, `True < 2` and `-False` are
///     UNDEFINED, even though Python accepts all three. Undefined and not
///     `TypeError`: a termination kind is "named after the exception the same
///     program raises under Python in the same circumstances"
///     (`operational-semantics.tex`), and Python raises nothing for `True + 1`
///     -- it says 2. This is the same rule that makes `True == 1` undefined,
///     and the conformance suite has a test for that.
///   * **An integer and a float compare exactly**, not by converting the
///     integer, so a large integer is not made equal to the double nearest it.
///
/// `+` and `*` are tried over sequences first, because they are not
/// arithmetic there. The spec's arithmetic table is a `\todo` reading
/// "Arithmetic, as in Python ... aborts TypeError where an operand is not a
/// number", and the two halves disagree about `"a" + "b"`: the sentence was
/// written for `1 + "a"`, which the conformance suite tests and which IS a
/// TypeError in Python. Concatenation is not, and CPython is the oracle for
/// what `run` prints, so "as in Python" is the half that decides.
pub fn binop(op : @ast.Operator, a : Value, b : Value) -> Outcome {
  match seq_binop(op, a, b) {
    Some(o) => o
    Option::None =>
      match (as_number(a), as_number(b)) {
        (Some(x), Some(y)) => arith(op, x, y)
        // A bool reaching an operator is the one case where Python has an
        // answer and this does not, so it is undefined rather than an abort.
        _ if bool_has_answer(op, a, b) => Stuck(no_rule_for_bool(op.symbol()))
        // Everything left -- `1 + "a"`, `"a" - "b"`, `None + None`, and
        // `True + None` with it -- is a TypeError under Python too.
        _ => Aborts(TypeError)
      }
  }
}

///|
fn is_bool(v : Value) -> Bool {
  v is Bool(_)
}

///|
/// Whether Python answers this because it reads a bool as an int.
///
/// Asked by substituting: put an integer where each bool is and see whether
/// the operands become a pair this file HAS a rule for. That is exactly what
/// Python does -- `bool` is a subclass of `int` there -- so the substitution
/// answers the question without a second table to keep in step. `True + 1`
/// becomes `1 + 1` and is undefined; `True + None` becomes `1 + None`, which
/// has no rule here for the same reason Python raises TypeError, and so it
/// keeps aborting.
fn bool_has_answer(op : @ast.Operator, a : Value, b : Value) -> Bool {
  if !(is_bool(a) || is_bool(b)) {
    return false
  }
  let one : Value = Int(1N)
  let a = if is_bool(a) { one } else { a }
  let b = if is_bool(b) { one } else { b }
  if seq_binop(op, a, b) is Some(_) {
    return true
  }
  as_number(a) is Some(_) && as_number(b) is Some(_)
}

///|
fn no_rule_for_bool(symbol : String) -> String {
  "'" + symbol + "' on a bool: PurePy does not make a bool a number"
}

///|
/// `+` and `*` over sequences: concatenation and repetition. `None` where the
/// pair is not one of them, which sends the operands back to arithmetic and,
/// failing that, to `TypeError` -- so `1 + "a"`, `"a" - "b"` and `[1] + (2,)`
/// all keep the answer Python gives them.
///
/// The two operands of `+` must be the same kind: a list and a tuple do not
/// concatenate in Python either. For `*` a `bool` is not a count, for the
/// same reason it is not a number; `True * 2` stays undefined.
fn seq_binop(op : @ast.Operator, a : Value, b : Value) -> Outcome? {
  match op {
    Add =>
      match (a, b) {
        (Str(x), Str(y)) => Some(Val(Str(x + y)))
        (List(x), List(y)) => Some(Val(List(concat(x, y))))
        (Tuple(x), Tuple(y)) => Some(Val(Tuple(concat(x, y))))
        _ => Option::None
      }
    // `%` on a string is printf-style formatting in Python, not arithmetic
    // and not an error: `"%d" % 3` is `"3"`. PurePy models no formatting, so
    // the semantics has no rule -- and saying so is the honest answer, where
    // `Aborts(TypeError)` would be a claim about CPython that is false.
    Mod =>
      match a {
        Str(_) =>
          Some(Stuck("'%' on a string: PurePy has no string formatting"))
        _ => Option::None
      }
    Mult =>
      match (a, b) {
        (Str(s), Int(n)) | (Int(n), Str(s)) =>
          Some(
            match repeat_count(n) {
              Some(k) => {
                let out = StringBuilder()
                for _ in 0.. too_long
            },
          )
        (List(xs), Int(n)) | (Int(n), List(xs)) =>
          Some(
            match repeat_count(n) {
              Some(k) => Val(List(repeat(xs, k)))
              Option::None => too_long
            },
          )
        (Tuple(xs), Int(n)) | (Int(n), Tuple(xs)) =>
          Some(
            match repeat_count(n) {
              Some(k) => Val(Tuple(repeat(xs, k)))
              Option::None => too_long
            },
          )
        _ => Option::None
      }
    _ => Option::None
  }
}

///|
/// Python raises `OverflowError` where a repetition count does not fit an
/// index, and that is not a termination kind, so it has no rule here.
let too_long : Outcome = Stuck("a repetition count too large to build")

///|
/// A repetition count as a length: Python treats every non-positive count as
/// zero, and `None` where the count is too large to be one.
fn repeat_count(n : BigInt) -> Int? {
  if n <= 0N {
    Some(0)
  } else if n > BigInt::from_int(@int.MAX_VALUE) {
    Option::None
  } else {
    Some(n.to_int())
  }
}

///|
fn concat(x : Array[Value], y : Array[Value]) -> Array[Value] {
  let out : Array[Value] = []
  for v in x {
    out.push(v)
  }
  for v in y {
    out.push(v)
  }
  out
}

///|
fn repeat(xs : Array[Value], k : Int) -> Array[Value] {
  let out : Array[Value] = []
  for _ in 0.. Num? {
  match v {
    Int(n) => Some(I(n))
    Float(d) => Some(F(d))
    _ => None
  }
}

///|
/// A BigInt as a double, correctly rounded -- which is what Python's
/// `float(n)` gives. MoonBit's `BigInt` has no conversion, so it goes through
/// the decimal text, which `parse_double` rounds correctly.
pub fn big_to_double(n : BigInt) -> Double {
  @string.parse_double(n.to_string()) catch {
    // Out of a double's range: Python raises OverflowError, which is not a
    // termination kind, so the caller treats it as undefined.
    _ => if n < 0N { @double.neg_infinity } else { @double.infinity }
  }
}

///|
fn to_double(x : Num) -> Double {
  match x {
    I(n) => big_to_double(n)
    F(d) => d
  }
}

///|
fn arith(op : @ast.Operator, a : Num, b : Num) -> Outcome {
  match (a, b) {
    (I(x), I(y)) => int_arith(op, x, y)
    _ => float_arith(op, to_double(a), to_double(b))
  }
}

///|
fn int_arith(op : @ast.Operator, x : BigInt, y : BigInt) -> Outcome {
  match op {
    Add => Val(Int(x + y))
    Sub => Val(Int(x - y))
    Mult => Val(Int(x * y))
    Div =>
      if y == 0N {
        Aborts(ZeroDivisionError)
      } else {
        Val(Float(big_to_double(x) / big_to_double(y)))
      }
    FloorDiv =>
      if y == 0N {
        Aborts(ZeroDivisionError)
      } else {
        Val(Int(floor_div(x, y)))
      }
    Mod =>
      if y == 0N {
        Aborts(ZeroDivisionError)
      } else {
        Val(Int(floor_mod(x, y)))
      }
    Pow =>
      if y < 0N {
        // A negative exponent gives a float, and `0 ** -1` divides by zero.
        if x == 0N {
          Aborts(ZeroDivisionError)
        } else {
          Val(Float(@math.pow(big_to_double(x), big_to_double(y))))
        }
      } else {
        Val(Int(x.pow(y)))
      }
    _ => Stuck("binary operator '" + op.symbol() + "'")
  }
}

///|
/// Python's `//` on integers: floor, not truncation.
pub fn floor_div(x : BigInt, y : BigInt) -> BigInt {
  let q = x / y
  // MoonBit truncates toward zero. When the signs differ and the division was
  // not exact, the floor is one lower.
  if x % y != 0N && (x < 0N) != (y < 0N) {
    q - 1N
  } else {
    q
  }
}

///|
/// Python's `%` on integers: the remainder takes the sign of the divisor.
pub fn floor_mod(x : BigInt, y : BigInt) -> BigInt {
  let r = x % y
  if r != 0N && (r < 0N) != (y < 0N) {
    r + y
  } else {
    r
  }
}

///|
fn float_arith(op : @ast.Operator, x : Double, y : Double) -> Outcome {
  match op {
    Add => Val(Float(x + y))
    Sub => Val(Float(x - y))
    Mult => Val(Float(x * y))
    Div => if y == 0.0 { Aborts(ZeroDivisionError) } else { Val(Float(x / y)) }
    FloorDiv =>
      if y == 0.0 {
        Aborts(ZeroDivisionError)
      } else {
        Val(Float((x / y).floor()))
      }
    Mod =>
      if y == 0.0 {
        Aborts(ZeroDivisionError)
      } else {
        Val(Float(float_mod(x, y)))
      }
    Pow =>
      // A negative base with a fractional exponent is a complex number in
      // mathematics and a ValueError in Python; neither is a termination
      // kind, so it has no rule here.
      if x < 0.0 && y != y.floor() {
        Stuck("a negative number raised to a fractional power")
      } else {
        Val(Float(@math.pow(x, y)))
      }
    _ => Stuck("binary operator '" + op.symbol() + "'")
  }
}

///|
/// Python's `%` on floats: the result takes the sign of the divisor.
pub fn float_mod(x : Double, y : Double) -> Double {
  let r = x - (x / y).floor() * y
  // The subtraction can land exactly on the divisor through rounding; Python
  // never returns a remainder equal to the divisor.
  let r = if r != 0.0 && (r < 0.0) != (y < 0.0) { r + y } else { r }
  // A ZERO remainder takes the sign of the divisor too, which the correction
  // above cannot do because it is guarded on a non-zero: `4.0 % -2.0` is
  // -0.0 in Python, and `repr` tells the two zeroes apart.
  if r == 0.0 {
    if y < 0.0 {
      -0.0
    } else {
      0.0
    }
  } else {
    r
  }
}

// ---------------------------------------------------------------------------

///|
/// `not`, unary `+` and unary `-`.
pub fn unop(op : @ast.UnaryOp, v : Value) -> Outcome {
  match op {
    Not =>
      match v {
        Bool(b) => Val(Bool(!b))
        // Truthiness is Python's, not PurePy's: `not 1` has no rule.
        _ => Stuck("'not' applied to " + v.kind_name())
      }
    // `-False` is 0 in Python, so a bool here is undefined for the reason
    // `True + 1` is: there is no exception to name.
    UAdd =>
      match v {
        Int(_) | Float(_) => Val(v)
        Bool(_) => Stuck(no_rule_for_bool("+"))
        _ => Aborts(TypeError)
      }
    USub =>
      match v {
        Int(n) => Val(Int(-n))
        Float(d) => Val(Float(-d))
        Bool(_) => Stuck(no_rule_for_bool("-"))
        _ => Aborts(TypeError)
      }
    Invert => Stuck("unary operator '~'")
  }
}

// ---------------------------------------------------------------------------

///|
/// `==`, `!=`, `in`, `not in` and the four orderings.
pub fn compare(op : @ast.CmpOp, a : Value, b : Value) -> Outcome {
  match op {
    // The `==` operator has a rule where `eq` has none: two nans compare
    // False rather than being undefined.
    Eq =>
      if both_nan(a, b) {
        Val(Bool(false))
      } else {
        match eq(a, b) {
          Some(r) => Val(Bool(r))
          None => Stuck("== between " + a.kind_name() + " and " + b.kind_name())
        }
      }
    NotEq =>
      if both_nan(a, b) {
        Val(Bool(true))
      } else {
        match eq(a, b) {
          Some(r) => Val(Bool(!r))
          None => Stuck("!= between " + a.kind_name() + " and " + b.kind_name())
        }
      }
    In =>
      match contains(b, a) {
        Some(r) => Val(Bool(r))
        None => Stuck("'in' over " + b.kind_name())
      }
    NotIn =>
      match contains(b, a) {
        Some(r) => Val(Bool(!r))
        None => Stuck("'not in' over " + b.kind_name())
      }
    // Every ordering against a nan is False in Python -- `nan < 1.0` and
    // `nan >= nan` alike -- which the `==` and `!=` rules above say in their
    // own way. `order` cannot: it answers with a three-way comparison, and a
    // nan has no place in one. So the case is taken before it is asked.
    Lt | LtE | Gt | GtE if numeric_nan(a, b) => Val(Bool(false))
    Lt | LtE | Gt | GtE =>
      match order(a, b) {
        Some(c) =>
          Val(
            Bool(
              match op {
                Lt => c < 0
                LtE => c <= 0
                Gt => c > 0
                _ => c >= 0
              },
            ),
          )
        None =>
          Stuck(
            "'" +
            op.symbol() +
            "' between " +
            a.kind_name() +
            " and " +
            b.kind_name(),
          )
      }
    // The sieve rejects `is` and `is not` as not yet supported (#81).
    Is | IsNot => Stuck("identity operator '" + op.symbol() + "'")
  }
}

///|
fn both_nan(a : Value, b : Value) -> Bool {
  match (a, b) {
    (Float(x), Float(y)) => x.is_nan() && y.is_nan()
    _ => false
  }
}

///|
/// Two numbers, at least one of them a nan. `nan < "a"` is NOT one: Python
/// raises TypeError there, and an unordered pair of kinds stays undefined
/// whatever the numbers in it are doing.
fn numeric_nan(a : Value, b : Value) -> Bool {
  fn is_num(v : Value) -> Bool {
    v is (Int(_) | Float(_))
  }

  fn is_nan(v : Value) -> Bool {
    match v {
      Float(d) => d.is_nan()
      _ => false
    }
  }

  is_num(a) && is_num(b) && (is_nan(a) || is_nan(b))
}

///|
/// Three-way comparison, `None` where the two values have no order.
///
/// Numbers with numbers, strings by code point, lists with lists and tuples
/// with tuples lexicographically. A `nan` anywhere makes the pair unordered,
/// which is how Python's comparisons all come out False.
pub fn order(a : Value, b : Value) -> Int? {
  match (a, b) {
    (Int(x), Int(y)) => Some(x.compare(y))
    (Float(x), Float(y)) =>
      if x.is_nan() || y.is_nan() {
        None
      } else if x < y {
        Some(-1)
      } else if x > y {
        Some(1)
      } else {
        Some(0)
      }
    (Int(x), Float(y)) => int_float_order(x, y)
    (Float(x), Int(y)) =>
      match int_float_order(y, x) {
        Some(c) => Some(-c)
        None => None
      }
    (Str(x), Str(y)) => Some(@basic.lexical_compare(x, y))
    (List(xs), List(ys)) | (Tuple(xs), Tuple(ys)) => order_elems(xs, ys)
    _ => None
  }
}

///|
/// An integer against a float, exactly.
fn int_float_order(n : BigInt, d : Double) -> Int? {
  if d.is_nan() {
    return None
  }
  if d.is_pos_inf() {
    return Some(-1)
  }
  if d.is_neg_inf() {
    return Some(1)
  }
  let f = d.floor()
  match exact_integer(f) {
    None => None
    Some(m) => {
      let c = n.compare(m)
      if c != 0 {
        Some(c)
      } else if d > f {
        // The integer equals the floor, and the float has a fraction above it.
        Some(-1)
      } else {
        Some(0)
      }
    }
  }
}

///|
/// Lexicographic order over two sequences, as Python compares them: the first
/// unequal pair decides, and otherwise the shorter is smaller.
fn order_elems(xs : Array[Value], ys : Array[Value]) -> Int? {
  let n = if xs.length() < ys.length() { xs.length() } else { ys.length() }
  for i in 0.. return None
      Some(true) => ()
      Some(false) =>
        return match order(xs[i], ys[i]) {
          Some(c) => Some(c)
          None => None
        }
    }
  }
  if xs.length() == ys.length() {
    Some(0)
  } else if xs.length() < ys.length() {
    Some(-1)
  } else {
    Some(1)
  }
}