// 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.

// TODO:
// - `to_double` method needs fix. See its comment.

///|
fn[T : Integral] gcd(a : T, b : T) -> T {
  for a = a, b = b {
    if b.signum() == 0 {
      break a
    }
    continue b, a % b
  }
}

///|
/// Creates a rational number from the given numerator and denominator.
///
/// Parameters:
///
/// * `numerator` : The numerator of the rational number.
/// * `denominator` : The denominator of the rational number.
///
/// Returns `Some(rational)` if the denominator is non-zero and the reduced form
/// fits in `T`, where the rational number is automatically reduced to its
/// simplest form with a positive denominator. Returns `None` if the denominator
/// is zero, or if the reduced form cannot be represented in `T` (e.g. its
/// denominator is `2^63` for `Rational[Int64]`).
///
/// Example:
///
/// ```moonbit check
/// test {
///   // Create 3/4
///   let r1 = @rational.new(3L, 4L).unwrap()
///   inspect(r1, content="3/4")
///
///   // Create -1/2 (negative numerator)
///   let r2 = @rational.new(-1L, 2L).unwrap()
///   inspect(r2, content="-1/2")
///
///   // Create -1/2 (negative denominator gets normalized)
///   let r3 = @rational.new(1L, -2L).unwrap()
///   inspect(r3, content="-1/2")
///
///   // Create 1/2 (double negatives cancel out)
///   let r4 = @rational.new(-1L, -2L).unwrap()
///   inspect(r4, content="1/2")
///
///   // Automatic reduction to simplest form
///   let r5 = @rational.new(6L, 9L).unwrap()
///   inspect(r5, content="2/3")
///
///   // Division by zero returns None
///   let r6 = @rational.new(1L, 0L)
///   assert_true(r6 is None)
/// }
/// ```
///
pub fn[T : Integral] new(numerator : T, denominator : T) -> Rational[T]? {
  if denominator.signum() == 0 {
    None
  } else {
    let bn = numerator.to_bigint()
    let bd = denominator.to_bigint()
    let g = gcd(bn, bd)
    let bn = bn / g
    let bd = bd / g
    let (bn, bd) = {
      guard bd < 0N else { (bn, bd) }
      (-bn, -bd)
    }
    match (T::from_bigint(bn), T::from_bigint(bd)) {
      (Some(n), Some(d)) => Some({ numerator: n, denominator: d, })
      _ => None
    }
  }
}

///|
/// Creates a rational number without enforcing invariants.
fn[T : Integral] new_unchecked(numerator : T, denominator : T) -> Rational[T] {
  let gcd = gcd(numerator.abs(), denominator)
  { numerator: numerator / gcd, denominator: denominator / gcd, }
}

///|
/// NOTE: we don't check overflow here, to align with the `op_add` of `Int64`.
/// TODO: add a `checked_add` method.
pub impl[T : Integral] Add for Rational[T] with fn add(self, other) {
  new_unchecked(
    self.numerator * other.denominator + other.numerator * self.denominator,
    self.denominator * other.denominator,
  )
}

///|
/// Subtracts one rational number from another.
///
/// Parameters:
///
/// * `self` : The minuend (rational number to subtract from).
/// * `other` : The subtrahend (rational number to subtract).
///
/// Returns the difference of the two rational numbers.
///
/// Example:
///
/// ```moonbit check
/// test {
///   let a = @rational.new(1L, 2L).unwrap() // 1/2
///   let b = @rational.new(1L, 3L).unwrap() // 1/3
///   inspect(a - b, content="1/6") // 1/2 - 1/3 = 1/6
/// }
/// ```
///
pub impl[T : Integral] Sub for Rational[T] with fn sub(self, other) {
  new_unchecked(
    self.numerator * other.denominator - other.numerator * self.denominator,
    self.denominator * other.denominator,
  )
}

///|
/// Multiplies two rational numbers.
///
/// Parameters:
///
/// * `self` : The first rational number.
/// * `other` : The second rational number to multiply with.
///
/// Returns the product of the two rational numbers.
///
/// Example:
///
/// ```moonbit check
/// test {
///   let a = @rational.new(1L, 2L).unwrap() // 1/2
///   let b = @rational.new(2L, 3L).unwrap() // 2/3
///   let c = a * b // 1/3
///   inspect(c, content="1/3")
/// }
/// ```
///
pub impl[T : Integral] Mul for Rational[T] with fn mul(self, other) {
  new_unchecked(
    self.numerator * other.numerator,
    self.denominator * other.denominator,
  )
}

///|
/// Divides one rational number by another.
///
/// Parameters:
///
/// * `self` : The dividend rational number.
/// * `other` : The divisor rational number.
///
/// Returns the quotient of the division as a rational number.
///
/// Example:
///
/// ```moonbit check
/// test {
///   let a = @rational.new(1L, 2L) // 1/2
///   let b = @rational.new(2L, 3L) // 2/3
///   match (a, b) {
///     (Some(x), Some(y)) => {
///       let result = x / y // 3/4
///       inspect(result, content="3/4")
///     }
///     _ => ()
///   }
/// }
/// ```
///
pub impl[T : Integral] Div for Rational[T] with fn div(self, other) {
  if other.numerator == T::from_int(0) {
    abort("division by zero")
  }
  if other.numerator.signum() < 0 {
    new_unchecked(
      self.numerator * -other.denominator,
      self.denominator * -other.numerator,
    )
  } else {
    new_unchecked(
      self.numerator * other.denominator,
      self.denominator * other.numerator,
    )
  }
}

///|
/// Divides one rational number by another, raising an error when the divisor
/// is zero.
///
/// Parameters:
///
/// * `self` : The dividend rational number.
/// * `other` : The divisor rational number.
///
/// Returns the quotient of the division as a rational number.
///
/// Raises `RationalError("division by zero")` if `other` is zero.
///
/// Example:
///
/// ```moonbit check
/// test {
///   let a = @rational.new(1L, 2L)
///   let b = @rational.new(2L, 3L)
///   match (a, b) {
///     (Some(x), Some(y)) => {
///       let result = x.div_checked(y)
///       inspect(result, content="3/4")
///     }
///     _ => ()
///   }
/// }
/// ```
///
#as_free_fn
pub fn[T : Integral] Rational::div_checked(
  self : Rational[T],
  other : Rational[T],
) -> Rational[T] raise RationalError {
  if other.numerator == T::from_int(0) {
    raise RationalError("division by zero")
  }
  if other.numerator.signum() < 0 {
    new_unchecked(
      self.numerator * -other.denominator,
      self.denominator * -other.numerator,
    )
  } else {
    new_unchecked(
      self.numerator * other.denominator,
      self.denominator * other.numerator,
    )
  }
}

///|
/// Returns the multiplicative inverse of a rational number.
///
/// Parameters:
///
/// * `self` : The rational number to find the reciprocal of.
///
/// Returns a new rational number that is the reciprocal of the input.
///
/// Example:
///
/// ```moonbit check
/// test {
///   let half = @rational.new(1L, 2L).unwrap()
///   let two = half.reciprocal()
///   inspect(two, content="2")
///   let negative_third = @rational.new(-1L, 3L).unwrap()
///   let negative_three = negative_third.reciprocal()
///   inspect(negative_three, content="-3")
/// }
/// ```
///
#as_free_fn
pub fn[T : Integral] Rational::reciprocal(self : Rational[T]) -> Rational[T] {
  if self.numerator == T::from_int(0) {
    abort("reciprocal of zero")
  }
  if self.numerator.signum() < 0 {
    new_unchecked(-self.denominator, -self.numerator)
  } else {
    new_unchecked(self.denominator, self.numerator)
  }
}

///|
/// Returns the multiplicative inverse of a rational number, raising an error
/// when the rational number is zero.
///
/// Parameters:
///
/// * `self` : The rational number to find the reciprocal of.
///
/// Returns a new rational number that is the reciprocal of the input.
///
/// Raises `RationalError("reciprocal of zero")` if `self` is zero.
///
/// Example:
///
/// ```moonbit check
/// test {
///   let half = @rational.new(1L, 2L).unwrap()
///   let two = half.reciprocal_checked()
///   inspect(two, content="2")
/// }
/// ```
///
#as_free_fn
pub fn[T : Integral] Rational::reciprocal_checked(
  self : Rational[T],
) -> Rational[T] raise RationalError {
  if self.numerator == T::from_int(0) {
    raise RationalError("reciprocal of zero")
  }
  if self.numerator.signum() < 0 {
    new_unchecked(-self.denominator, -self.numerator)
  } else {
    new_unchecked(self.denominator, self.numerator)
  }
}

///|
/// Returns the negation of a rational number.
///
/// Parameters:
///
/// * `self` : The rational number to negate.
///
/// Returns a new rational number with the opposite sign.
///
/// Example:
///
/// ```moonbit check
/// test {
///   let positive = @rational.new(3L, 4L).unwrap()
///   let negative = -positive
///   inspect(Show::to_string(negative), content="-3/4")
///   let negative_original = @rational.new(-5L, 2L).unwrap()
///   let positive_result = -negative_original
///   inspect(Show::to_string(positive_result), content="5/2")
/// }
/// ```
///
pub impl[T : Integral] Neg for Rational[T] with fn neg(self : Rational[T]) -> Rational[
  T,
] {
  new_unchecked(-self.numerator, self.denominator)
}

///|
/// Returns the absolute value of a rational number.
///
/// Parameters:
///
/// * `self` : The rational number to get the absolute value of.
///
/// Returns a new rational number representing the absolute value of the input.
///
/// Example:
///
/// ```moonbit check
/// test {
///   let positive = @rational.new(3L, 4L).unwrap()
///   let negative = @rational.new(-3L, 4L).unwrap()
///   inspect(positive.abs(), content="3/4")
///   inspect(negative.abs(), content="3/4")
/// }
/// ```
///
#as_free_fn
pub fn[T : Integral] Rational::abs(self : Rational[T]) -> Rational[T] {
  new_unchecked(self.numerator.abs(), self.denominator)
}

///|
/// Converts the rational number to its approximate double-precision
/// floating-point representation.
///
/// Parameters:
///
/// * `self` : The rational number to convert.
///
/// Returns the double-precision floating-point approximation of the rational
/// number.
///
/// Example:
///
/// ```moonbit check
/// test {
///   let rational = @rational.new(1L, 2L).unwrap()
///   inspect(rational.to_double(), content="0.5")
///   let negative = @rational.new(-3L, 4L).unwrap()
///   inspect(negative.to_double(), content="-0.75")
/// }
/// ```
///
pub fn Rational64::to_double(self : Rational64) -> Double {
  // TODO: complete algorithm
  self.numerator.to_double() / self.denominator.to_double()
}

///|
fn[T] nan_error() -> T raise RationalError {
  raise RationalError("Rational::from_double: cannot convert NaN")
}

///|
fn[T] overflow_error() -> T raise RationalError {
  raise RationalError("Rational::from_double: overflow")
}

///|
/// Error type for rational number operations.
///
/// Constructor:
///
/// * `RationalError(String)` : Error with a descriptive message.
///
/// Example:
///
/// ```moonbit check
/// test {
///   let error = RationalError::RationalError("division by zero")
///   debug_inspect(
///     error,
///     content=(
///       #|RationalError("division by zero")
///     ),
///   )
/// }
/// ```
///
pub(all) suberror RationalError {
  RationalError(String)
} derive(Debug, ToJson(style="legacy"))

///|
/// Compares two `RationalError` values for equality.
///
/// Parameters:
///
/// * `self` : The first `RationalError` to compare.
/// * `other` : The second `RationalError` to compare.
///
/// Returns `true` if both `RationalError` values contain the same error message,
/// `false` otherwise.
///
/// Example:
///
/// ```moonbit check
/// test {
///   let error1 = @rational.RationalError("division by zero")
///   let error2 = @rational.RationalError("division by zero")
///   let error3 = @rational.RationalError("overflow")
///   inspect(error1 == error2, content="true")
///   inspect(error1 == error3, content="false")
/// }
/// ```
///
pub impl Eq for RationalError with fn equal(
  self : RationalError,
  other : RationalError,
) -> Bool {
  match (self, other) {
    (RationalError(e1), RationalError(e2)) => e1 == e2
  }
}

///|
/// Converts a floating-point number to its rational representation using a
/// continued fraction algorithm.
///
/// Parameters:
///
/// * `value` : The double-precision floating-point number to convert to a
/// rational.
///
/// Returns a rational number that approximates the input value.
///
/// Throws an error of type `RationalError` if the input is NaN or if the
/// conversion would result in integer overflow.
///
/// Example:
///
/// ```moonbit check
/// test {
///   // Convert 0.5 to rational 1/2
///   let half = @rational.from_double(0.5)
///   inspect(half, content="1/2")
///
///   // Convert 0.333... to a close rational approximation
///   let third = @rational.from_double(1.0 / 3.0)
///   inspect(third, content="1/3")
/// }
/// ```
///
pub fn from_double(value : Double) -> Rational64 raise RationalError {
  // continued fraction algorithm
  // Ported from https://github.com/rust-num/num
  if value.is_nan() {
    nan_error()
  }
  let sign = if value < 0.0 { -1L } else { 1L }
  let value = value.abs()
  let mut q = value
  let mut n0 = 0L
  let mut d0 = 1L
  let mut n1 = 1L
  let mut d1 = 0L
  let t_max = @int64.MAX_VALUE
  let t_max_f = t_max.to_double()
  let epsilon = 1.0 / t_max_f
  let max_iteration = 30
  let max_error = 10.0e-20

  // Overflow
  if q > t_max_f {
    overflow_error()
  }
  for _ in 0..= -9223372036854775808.0 && q < 9223372036854775808.0) {
      break // overflow
    }
    let a = q.to_int64()
    let a_f = a.to_double()
    let f = q - a_f

    // Prevent overflow
    if !(a == 0L) &&
      (
        n1 > t_max / a ||
        d1 > t_max / a ||
        a * n1 > t_max - n0 ||
        a * d1 > t_max - d0
      ) {
      break
    }
    let n = a * n1 + n0
    let d = a * d1 + d0
    n0 = n1
    d0 = d1
    n1 = n
    d1 = d
    let g = gcd(n1, d1)
    if !(g == 0L) {
      n1 = n1 / g
      d1 = d1 / g
    }

    // Close enough?
    let (n_f, d_f) = (n.to_double(), d.to_double())
    if (n_f / d_f - value).abs() < max_error {
      break
    }

    // Prevent division by ~0
    if f < epsilon {
      break
    }
    q = 1.0 / f
  }
  // Overflow
  if d1 == 0L {
    overflow_error()
  }
  match new(sign * n1, d1) {
    Some(r) => r
    None => abort("Impossible to reach")
  }
}

///|
/// Returns the smallest integer greater than or equal to the rational number.
///
/// Parameters:
///
/// * `self` : The rational number to ceiling.
///
/// Returns the ceiling of the rational number as a value of type `T`.
///
/// Example:
///
/// ```moonbit check
/// test {
///   let r1 = @rational.new(3L, 2L).unwrap() // 3/2 = 1.5
///   inspect(r1.ceil(), content="2")
///   let r2 = @rational.new(-3L, 2L).unwrap() // -3/2 = -1.5
///   inspect(r2.ceil(), content="-1")
///   let r3 = @rational.new(4L, 2L).unwrap() // 4/2 = 2.0
///   inspect(r3.ceil(), content="2")
/// }
/// ```
///
#as_free_fn
pub fn[T : Integral] Rational::ceil(self : Rational[T]) -> T {
  let sign = if self.numerator.signum() < 0 {
    T::from_int(-1)
  } else {
    T::from_int(1)
  }
  let quotient = self.numerator / self.denominator
  if self.numerator % self.denominator == T::from_int(0) {
    quotient
  } else {
    quotient + (T::from_int(1) + sign) / T::from_int(2)
  }
}

///|
/// Computes the largest integer that is less than or equal to the rational
/// number.
///
/// Parameters:
///
/// * `self` : The rational number to floor.
///
/// Returns the largest value of type `T` that is less than or equal to `self`.
///
/// Example:
///
/// ```moonbit check
/// test {
///   let r1 = @rational.new(7L, 3L).unwrap() // 7/3 ≈ 2.33
///   inspect(r1.floor(), content="2")
///   let r2 = @rational.new(-7L, 3L).unwrap() // -7/3 ≈ -2.33
///   inspect(r2.floor(), content="-3")
///   let r3 = @rational.new(6L, 3L).unwrap() // 6/3 = 2.0
///   inspect(r3.floor(), content="2")
/// }
/// ```
///
#as_free_fn
pub fn[T : Integral] Rational::floor(self : Rational[T]) -> T {
  let sign = if self.numerator.signum() < 0 {
    T::from_int(-1)
  } else {
    T::from_int(1)
  }
  let quotient = self.numerator / self.denominator
  if self.numerator % self.denominator == T::from_int(0) {
    quotient
  } else {
    quotient + (T::from_int(-1) + sign) / T::from_int(2)
  }
}

///|
/// Truncates a rational number towards zero, returning the integer part.
///
/// Parameters:
///
/// * `self` : The rational number to truncate.
///
/// Returns the integer part of the rational number as a value of type `T`.
///
/// Example:
///
/// ```moonbit check
/// test {
///   let a = @rational.new(7L, 3L).unwrap() // 7/3 = 2.333...
///   inspect(a.trunc(), content="2")
///   let b = @rational.new(-7L, 3L).unwrap() // -7/3 = -2.333...
///   inspect(b.trunc(), content="-2")
///   let c = @rational.new(5L, 2L).unwrap() // 5/2 = 2.5
///   inspect(c.trunc(), content="2")
///   let d = @rational.new(-5L, 2L).unwrap() // -5/2 = -2.5
///   inspect(d.trunc(), content="-2")
/// }
/// ```
///
#as_free_fn
pub fn[T : Integral] Rational::trunc(self : Rational[T]) -> T {
  if self.numerator.signum() < 0 {
    -(-self.numerator / self.denominator)
  } else {
    self.numerator / self.denominator
  }
}

///|
/// Returns the fractional part of a rational number, which is the remainder
/// after removing the integer part.
///
/// Parameters:
///
/// * `self` : The rational number to get the fractional part from.
///
/// Returns a new rational number representing the fractional part of `self`.
/// This is equivalent to `self - self.trunc()`.
///
/// Example:
///
/// ```moonbit check
/// test {
///   let r1 = @rational.new(7L, 2L).unwrap() // 7/2 = 3.5
///   inspect(r1.fract(), content="1/2") // fractional part is 0.5 = 1/2
///   let r2 = @rational.new(-7L, 2L).unwrap() // -7/2 = -3.5  
///   inspect(r2.fract(), content="-1/2") // fractional part is -0.5 = -1/2
///   let r3 = @rational.new(5L, 1L).unwrap() // 5/1 = 5
///   inspect(r3.fract(), content="0") // fractional part is 0
/// }
/// ```
///
#as_free_fn
pub fn[T : Integral] Rational::fract(self : Rational[T]) -> Rational[T] {
  new_unchecked(self.numerator % self.denominator, self.denominator)
}

///|
/// Converts a rational number to its string representation.
///
/// Parameters:
///
/// * `self` : The rational number to convert.
/// * `logger` : The output logger to write the string representation to.
///
/// The string representation follows these rules:
///
/// * If the numerator is zero, outputs "0"
/// * If the denominator is 1 (i.e., the rational is an integer), outputs just
/// the numerator
/// * Otherwise, outputs in the format "numerator/denominator"
///
/// Example:
///
/// ```moonbit check
/// test {
///   let zero = @rational.new(0L, 1).unwrap()
///   inspect(zero, content="0")
///   let integer = @rational.new(5L, 1).unwrap()
///   inspect(integer, content="5")
///   let fraction = @rational.new(3L, 4).unwrap()
///   inspect(fraction, content="3/4")
///   let negative = @rational.new(-7L, 3).unwrap()
///   inspect(negative, content="-7/3")
/// }
/// ```
///
pub impl[T : Integral] Show for Rational[T] with fn output(self, logger) {
  if self.numerator == T::from_int(0) {
    logger.write_char('0')
  } else if self.denominator == T::from_int(1) {
    Show::output(self.numerator, logger)
  } else {
    Show::output(self.numerator, logger)
    logger.write_char('/')
    Show::output(self.denominator, logger)
  }
}

///|
pub impl[T : Integral] Debug for Rational[T] with fn to_repr(self) {
  Repr::literal(Show::to_string(self))
}

///|
pub impl[T : Integral] @quickcheck.Arbitrary for Rational[T] with fn arbitrary(
  size,
  rs,
) {
  let numerator : T = @quickcheck.Arbitrary::arbitrary(size, rs)
  let denominator : T = {
    let d : T = @quickcheck.Arbitrary::arbitrary(size, rs)
    if d.signum() <= 0 {
      -d + T::from_int(1) // Ensure positive denominator
    } else {
      d
    }
  }
  new_unchecked(numerator, denominator)
}

///|
/// Checks whether the rational number represents an integer value.
///
/// Parameters:
///
/// * `self` : The rational number to check.
///
/// Returns `true` if the rational number is an integer (has a denominator of 1),
/// `false` otherwise.
///
/// Examples:
///
/// ```moonbit check
/// test {
///   let half = @rational.new(1L, 2L).unwrap()
///   inspect(half.is_integer(), content="false")
///   let whole = @rational.new(5L, 1L).unwrap()
///   inspect(whole.is_integer(), content="true")
///   let zero = @rational.new(0L, 1L).unwrap()
///   inspect(zero.is_integer(), content="true")
/// }
/// ```
///
#as_free_fn
pub fn[T : Integral] Rational::is_integer(self : Rational[T]) -> Bool {
  self.denominator == T::from_int(1)
}

///|
#deprecated
pub extend Rational with Add::{add}

///|
#deprecated
pub extend Rational with Sub::{sub}

///|
#deprecated
pub extend Rational with Mul::{mul}

///|
#deprecated
pub extend Rational with Div::{div}

///|
#deprecated
pub extend Rational with Neg::{neg}

///|
#deprecated
pub extend RationalError with Debug::{to_repr}

///|
#deprecated
pub extend RationalError with ToJson::{to_json}

///|
#deprecated
pub extend RationalError with Eq::{not_equal, equal}

///|
#deprecated
pub extend Rational with Show::{to_string, output}

///|
#deprecated
pub extend Rational with Debug::{to_repr}

///|
#deprecated
pub extend Rational with @quickcheck.Arbitrary::{arbitrary}