// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
fn add_prefix_zero(s : String, len : Int) -> String {
  let buf = StringBuilder::new(size_hint=len)
  for i = s.length(); i < len; i = i + 1 {
    buf.write_char('0')
  }
  buf.write_string(s)
  buf.to_string()
}

///|
test "add_prefix_zero" {
  inspect(add_prefix_zero("", 2), content="00")
  inspect(add_prefix_zero("1", 0), content="1")
  inspect(add_prefix_zero("1", 1), content="1")
  inspect(add_prefix_zero("1", 2), content="01")
  inspect(add_prefix_zero("1", 3), content="001")
}

///|
fn remove_suffix_zero(s : StringView) -> StringView {
  for state = s {
    match state {
      [_] as x => break x // keep one zero if all are zeros
      [.. head, '0'] => continue head
      x => break x
    }
  }
}

///|
///
/// Formats a nanosecond value as a fractional seconds string for ISO 8601 output.
///
/// Pads the nanosecond value to 9 digits, trims trailing zeros, and prepends a dot.
/// For example:
///   format_nanos(0L)           => ".0"
///   format_nanos(1000100L)     => ".0010001"
///   format_nanos(100_000_000L) => ".1"
///   format_nanos(110_000_000L) => ".11"
///
/// This is used for formatting fractional seconds in time and duration string outputs.
fn format_nanos(nanos : Int64) -> String {
  let nanos_str = nanos.to_string().pad_start(9, '0')
  StringView::add(".", remove_suffix_zero(nanos_str)).to_owned()
}

///|
test "remove_suffix_zero && format_nanos" {
  inspect(remove_suffix_zero(""), content="")
  inspect(remove_suffix_zero("1000100"), content="10001")
  inspect(remove_suffix_zero("000"), content="0")
  inspect(format_nanos(0L), content=".0")
  inspect(format_nanos(1000100L), content=".0010001")
  inspect(format_nanos(100_000_000L), content=".1")
  inspect(format_nanos(110_000_000L), content=".11")
}

///|
fn add_suffix_zero(s : StringView, len : Int) -> String {
  let buf = StringBuilder::new(size_hint=len)
  buf.write_stringview(s)
  for i = s.length(); i < len; i = i + 1 {
    buf.write_char('0')
  }
  buf.to_string()
}

///|
test "add_suffix_zero" {
  inspect(add_suffix_zero("1", 0), content="1")
  inspect(add_suffix_zero("1", 1), content="1")
  inspect(add_suffix_zero("1", 2), content="10")
  inspect(add_suffix_zero("1", 3), content="100")
}

///|
fn[T] int_overflow_err() -> T raise Error {
  raise Failure::Failure("Int overflow")
}

///|
fn[T] int64_overflow_err() -> T raise Error {
  raise Failure::Failure("Int64 overflow")
}

///|
/// FIXME: use checked_add method of Int
fn checked_add_int(x : Int, y : Int) -> Int raise Error {
  let r = x.to_int64() + y.to_int64()
  if r < @int.MIN_VALUE.to_int64() || r > @int.MAX_VALUE.to_int64() {
    int_overflow_err()
  } else {
    r.to_int()
  }
}

///|
/// FIXME: use checked_mul method of Int
fn checked_mul_int(x : Int, y : Int) -> Int raise Error {
  let r = x.to_int64() * y.to_int64()
  if r < @int.MIN_VALUE.to_int64() || r > @int.MAX_VALUE.to_int64() {
    int_overflow_err()
  } else {
    r.to_int()
  }
}

///|
/// FIXME: use checked_add method of Int64
fn checked_add_int64(x : Int64, y : Int64) -> Int64 raise Error {
  let r = x + y
  // Overflow iff both arguments have the opposite sign of the result
  if ((x ^ r) & (y ^ r)) < 0L {
    int64_overflow_err()
  } else {
    r
  }
}

///|
/// FIXME: use checked_mul method of Int64
fn checked_mul_int64(x : Int64, y : Int64) -> Int64 raise Error {
  let r = x * y
  let abs_x = x.abs()
  let abs_y = y.abs()
  if ((abs_x ^ abs_y).reinterpret_as_uint64() >> 31).reinterpret_as_int64() !=
    0L {
    // bits that greater than 2^31 might cause overflow
    if (y != 0L && r / y != x) || (x == @int64.MIN_VALUE && y == -1L) {
      int64_overflow_err()
    }
  }
  r
}

///|
test "checked_add_int" {
  debug_inspect(
    try checked_add_int(1, 1) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(2)",
  )
  debug_inspect(
    try checked_add_int(1, -1) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(0)",
  )
  debug_inspect(
    try checked_add_int(-1, -1) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(-2)",
  )
  debug_inspect(
    try checked_add_int(@int.MAX_VALUE, 1) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content=(
      #|Err(Failure("Int overflow"))
    ),
  )
  debug_inspect(
    try checked_add_int(@int.MIN_VALUE, -1) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content=(
      #|Err(Failure("Int overflow"))
    ),
  )
}

///|
test "checked_mul_int" {
  debug_inspect(
    try checked_mul_int(2, 3) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(6)",
  )
  debug_inspect(
    try checked_mul_int(2, -3) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(-6)",
  )
  debug_inspect(
    try checked_mul_int(-2, -3) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(6)",
  )
  debug_inspect(
    try checked_mul_int(@int.MAX_VALUE, 2) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content=(
      #|Err(Failure("Int overflow"))
    ),
  )
  debug_inspect(
    try checked_mul_int(@int.MIN_VALUE, 2) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content=(
      #|Err(Failure("Int overflow"))
    ),
  )
}

///|
test "checked_add_int64" {
  debug_inspect(
    try checked_add_int64(1L, 1L) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(2)",
  )
  debug_inspect(
    try checked_add_int64(1L, -1L) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(0)",
  )
  debug_inspect(
    try checked_add_int64(-1L, -1L) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(-2)",
  )
  debug_inspect(
    try checked_add_int64(1L, @int64.MAX_VALUE) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content=(
      #|Err(Failure("Int64 overflow"))
    ),
  )
  debug_inspect(
    try checked_add_int64(-1L, @int64.MIN_VALUE) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content=(
      #|Err(Failure("Int64 overflow"))
    ),
  )
}

///|
test "checked_mul_int64" {
  debug_inspect(
    try checked_mul_int64(2L, 3L) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(6)",
  )
  debug_inspect(
    try checked_mul_int64(2L, -3L) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(-6)",
  )
  debug_inspect(
    try checked_mul_int64(-2L, -3L) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(6)",
  )
  debug_inspect(
    try checked_mul_int64(@int64.MAX_VALUE, 2L) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content=(
      #|Err(Failure("Int64 overflow"))
    ),
  )
  debug_inspect(
    try checked_mul_int64(@int64.MIN_VALUE, 2L) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content=(
      #|Err(Failure("Int64 overflow"))
    ),
  )
}

///|
fn floor(x : Double) -> Double {
  if x.is_pos_inf() || x.is_neg_inf() || x.is_nan() {
    return x
  }
  let n = x.to_int64()
  let d = n.to_double()
  if x >= 0.0 || x == d {
    d
  } else {
    d - 1.0
  }
}

///|
fn floor_div_int(x : Int, y : Int) -> Int raise Error {
  if y == 0 {
    raise Failure::Failure("division by zero")
  }
  let r = x.to_double() / y.to_double()
  floor(r).to_int()
}

///|
test "floor_div_int" {
  debug_inspect(
    try floor_div_int(1, 10) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(0)",
  )
  debug_inspect(
    try floor_div_int(-1, 10) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(-1)",
  )
  debug_inspect(
    try floor_div_int(9, 10) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(0)",
  )
  debug_inspect(
    try floor_div_int(-9, 10) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(-1)",
  )
  debug_inspect(
    try floor_div_int(0, 10) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(0)",
  )
  debug_inspect(
    try floor_div_int(10, 0) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content=(
      #|Err(Failure("division by zero"))
    ),
  )
}

///|
fn floor_div_int64(x : Int64, y : Int64) -> Int64 raise Error {
  if y == 0L {
    fail("division by zero")
  }
  let r = x.to_double() / y.to_double()
  floor(r).to_int64()
}

///|
test "floor_div_int64" {
  debug_inspect(
    try floor_div_int64(1L, 10L) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(0)",
  )
  debug_inspect(
    try floor_div_int64(-1L, 10L) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(-1)",
  )
  debug_inspect(
    try floor_div_int64(9L, 10L) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(0)",
  )
  debug_inspect(
    try floor_div_int64(-9L, 10L) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(-1)",
  )
  debug_inspect(
    try floor_div_int64(0L, 10L) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(0)",
  )
  assert_true(
    try floor_div_int64(10L, 0L) catch {
      _ => true
    } noraise {
      _ => false
    },
  )
}

///|
fn floor_mod_int(x : Int, y : Int) -> Int raise Error {
  x - floor_div_int(x, y) * y
}

///|
test "floor_mod_int" {
  debug_inspect(
    try floor_mod_int(1, 10) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(1)",
  )
  debug_inspect(
    try floor_mod_int(-1, 10) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(9)",
  )
  debug_inspect(
    try floor_mod_int(11, 10) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(1)",
  )
  debug_inspect(
    try floor_mod_int(-11, 10) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(9)",
  )
  debug_inspect(
    try floor_mod_int(0, 10) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(0)",
  )
  debug_inspect(
    try floor_mod_int(10, 0) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content=(
      #|Err(Failure("division by zero"))
    ),
  )
}

///|
fn floor_mod_int64(x : Int64, y : Int64) -> Int64 raise Error {
  x - floor_div_int64(x, y) * y
}

///|
test "floor_mod_int64" {
  debug_inspect(
    try floor_mod_int64(1L, 10L) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(1)",
  )
  debug_inspect(
    try floor_mod_int64(-1L, 10L) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(9)",
  )
  debug_inspect(
    try floor_mod_int64(11L, 10L) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(1)",
  )
  debug_inspect(
    try floor_mod_int64(-11L, 10L) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(9)",
  )
  debug_inspect(
    try floor_mod_int64(0L, 10L) catch {
      e => Err(e)
    } noraise {
      value => Ok(value)
    },
    content="Ok(0)",
  )
  assert_true(
    try floor_mod_int64(10L, 0L) catch {
      _ => true
    } noraise {
      _ => false
    },
  )
}