// Checked Int64 arithmetic for Python ints.
//
// Python ints are unbounded while the executor's `Value::Int` is an Int64. Rather than
// wrapping silently, every integer operation whose exact result falls outside Int64
// raises `int64_overflow` (an `OverflowError` whose message names the Int64 limit).

///|
let int64_min : Int64 = -9223372036854775807L - 1L

///|
/// The error for an integer result outside the Int64 range.
fn int64_overflow(what : String) -> PyException {
  PyException(
    "OverflowError",
    "integer result of \{what} is outside the Int64 range supported by this port (Python ints are unbounded)",
  )
}

///|
fn checked_add(x : Int64, y : Int64) -> Int64 raise PyException {
  let r = x + y
  if ((x ^ r) & (y ^ r)) < 0L {
    raise int64_overflow("\{x} + \{y}")
  }
  r
}

///|
fn checked_sub(x : Int64, y : Int64) -> Int64 raise PyException {
  let r = x - y
  if ((x ^ y) & (x ^ r)) < 0L {
    raise int64_overflow("\{x} - \{y}")
  }
  r
}

///|
fn checked_mul(x : Int64, y : Int64) -> Int64 raise PyException {
  if x == 0L || y == 0L {
    return 0L
  }
  if (x == -1L && y == int64_min) || (y == -1L && x == int64_min) {
    raise int64_overflow("\{x} * \{y}")
  }
  let r = x * y
  if r / y != x {
    raise int64_overflow("\{x} * \{y}")
  }
  r
}

///|
fn checked_neg(x : Int64) -> Int64 raise PyException {
  if x == int64_min {
    raise int64_overflow("-(\{x})")
  }
  -x
}

///|
/// `x // y` (floor division) for `y != 0`.
fn checked_floordiv(x : Int64, y : Int64) -> Int64 raise PyException {
  if y == -1L {
    return checked_neg(x)
  }
  floor_div64(x, y)
}

///|
/// `x % y` (sign of the divisor) for `y != 0`.
fn py_mod64(x : Int64, y : Int64) -> Int64 {
  if y == -1L {
    return 0L
  }
  x - floor_div64(x, y) * y
}

///|
/// `x << y` for `y >= 0`.
fn checked_shl(x : Int64, y : Int64) -> Int64 raise PyException {
  if x == 0L {
    return 0L
  }
  if y >= 63L {
    raise int64_overflow("\{x} << \{y}")
  }
  let r = x << y.to_int()
  if r >> y.to_int() != x {
    raise int64_overflow("\{x} << \{y}")
  }
  r
}

///|
/// `x ** y` for `y >= 0`.
fn checked_pow(x : Int64, y : Int64) -> Int64 raise PyException {
  let mut r = 1L
  let mut base = x
  let mut e = y
  while e > 0L {
    if e % 2L == 1L {
      r = checked_mul(r, base) catch {
        _ => raise int64_overflow("\{x} ** \{y}")
      }
    }
    e = e / 2L
    if e > 0L {
      base = checked_mul(base, base) catch {
        _ => raise int64_overflow("\{x} ** \{y}")
      }
    }
  }
  r
}

///|
/// Python `int(d)` for a finite float, if it fits Int64.
fn float_to_int64(d : Double) -> Int64 raise PyException {
  let t = d.trunc()
  // 2**63 is exactly representable; Int64 covers [-2**63, 2**63)
  if t >= 9223372036854775808.0 || t < -9223372036854775808.0 {
    raise int64_overflow("int(\{float_repr(d)})")
  }
  t.to_int64()
}

///|
/// `int(s)` parsing (base 10; Python's rules for signs, `_` separators, white space and
/// Unicode digits); `None` if `s` isn't an integer literal or doesn't fit Int64.
fn parse_py_int_literal(s : String) -> Int64? {
  if @core.is_int_str(s) {
    @core.parse_int_str(s)
  } else {
    None
  }
}

///|
/// `a + b`, saturated to the Int64 range (for LIMIT/OFFSET bounds, which only matter up
/// to the number of rows).
fn sat_add64(a : Int64, b : Int64) -> Int64 {
  let r = a + b
  if ((a ^ r) & (b ^ r)) < 0L {
    if a < 0L {
      int64_min
    } else {
      9223372036854775807L
    }
  } else {
    r
  }
}

///|
/// `v` clamped to the Int range: slicing a row list by a bound beyond Int behaves like
/// slicing by the clamped bound.
fn clamp_int(v : Int64) -> Int {
  if v > 2147483647L {
    2147483647
  } else if v < -2147483648L {
    -2147483648
  } else {
    v.to_int()
  }
}