//
//  RatNum.swift
//  RatNum
//

import Foundation

/**
 
 **AbstractState**: `RatNum` represents an *immutable* rational number.
 It includes all of the elements in the set of rationals, as well
 as the special "NaN" (not-a-number) element that results from
 division by zero.
 
 The "NaN" element is special in many ways.  Any arithmetic
 operation (such as addition) involving "NaN" will return "NaN".
 With respect to comparison operations, such as less-than, "NaN" is
 considered equal to itself, and larger than all other rationals.
 
 Examples of RatNums include "-1/13", "53/7", "4", "NaN", and "0".

 */
public struct RatNum : Comparable, CustomStringConvertible {
  private let numer : Int
  private let denom : Int
  
  // Abstraction Function:
  //   A RatNum r is NaN if r.denom = 0, (r.numer / r.denom) otherwise.
  // (An abstraction function explains what the state of the fields in a
  // RatNum represents.  In this case, a rational number can be
  // understood as the result of dividing two integers, or not-a-number
  // if we would be dividing by zero.)
  
  // Representation Invariant: RI(self):
  //   (self.denom >= 0) &&
  //   (self.denom > 0 ==> there does not exist integer i > 1 such that
  //                    self.numer mod i = 0 and self.denom mod i = 0;)
  //   In other words,
  //     * self.denom is always non-negative.
  //     * self.numer/self.denom is in reduced form (assuming self.denom is not zero).
  
  /** A constant holding a Not-a-Number (NaN) value of type RatNum */
  public static let nan = RatNum(1, 0)
  
  /** A constant holding a zero value of type RatNum */
  public static let zero = RatNum(0)
  
  /** A constant holding a one value of type RatNum */
  public static let one = RatNum(1)
  
  /**

   **Effect**: If d = 0, constructs a new RatNum = NaN.  Else
   constructs a new RatNum = (n / d).

   - Parameter n: The numerator of the new RatNum.
   - Parameter d: The denominator of the new RatNum.
   
   */
  public init(_ n: Int, _ d: Int) {
    // special case for zero denominator; gcd(n,d) requires d != 0
    if (d == 0) {
      numer = n;
      denom = 0;
      
    } else {
      
      // reduce ratio to lowest terms
      let g = RatNum.gcd(n,d);
      let n = n / g;
      let d = d / g;
      
      if (d < 0) {
        numer = -n;
        denom = -d;
      } else {
        numer = n;
        denom = d;
      }
    }
    checkRep();
  }
  
  /**
   
   **Effect**: Constructs a new RatNum = n.
   
   - Parameter n: The value of the new RatNum.
   
   */
  public init (_ n: Int) {
    self.init(n, 1)
    checkRep()
  }
  
  /**
   Makes a RatNum from a string describing it.
   
   **Requires**: 'description' is an instance of a string, with no spaces,
   of the form:
   - "NaN"
   - "N/M", where N and M are both integers in
   decimal notation, and M != 0, or
   - "N", where N is an integer in decimal
   notation.

   - Parameter description: A string of the format described in the `requires`
   clause.
   
   - Returns: Either:
   * NaN if description = "NaN".
   * A RatNum r = ( N / M ), letting M be 1 in the case
   where only "N" is passed in.
   
   */
  public init?(_ description: String) {
    if (description == "NaN") {
      self.init(1, 0)
    } else {
      // slash, need to parse the two parts separately
      if let slashIndex = description.index(of: "/") {
        if let n = Int(description.prefix(upTo: slashIndex)),
          let d = Int(description[description.index(after: slashIndex)...]) {
          self.init(n,d)
        } else {
          return nil
        }
      } else {
        // not NaN, and no slash, must be an Integer
        if let n = Int(description) {
          self.init(n)
        } else {
          return nil
        }
      }
    }
  }
  
  /**
   Checks that the representation invariant holds (if any).
   */
  private func checkRep() {
    assert (denom >= 0, "Denominator of a RatNum cannot be less than zero");
    
    if (denom > 0) {
      let gcd = RatNum.gcd(numer, denom);
      assert (gcd == 1 || gcd == -1, "RatNum not in lowest form");
    }
  }
  
  /**
   
   Returns the greatest common divisor of 'a' and 'b'.
   
   **Requires**: b != 0

   - Parameter a: The first number for which to find the GCD.
   - Parameter b: The second number for which to find the GCD.
   
   - Returns: d such that a % d = 0 and b % d = 0
   */
  static private func gcd(_ a: Int, _ b: Int) -> Int {
    precondition(b != 0)
    // Euclid's method
    func gcdHelper(_ a: Int, _ b:Int) -> Int {
      if (b == 0) {
        return a;
      } else {
        return gcdHelper(b, a%b)
      }
    }
    return gcdHelper(a,b)
  }
  
  /// true iff self is NaN (not-a-number)
  public var isNaN : Bool {
    return denom == 0
  }
  
  /// true iff self > 0
  public var isPositive : Bool {
    return self > RatNum.zero
  }
  
  /// true iff self < 0
  public var isNegative : Bool {
    return self < RatNum.zero
  }
  
  /**
   An approximate value of this rational.
   Note that "NaN" is mapped to
   [Double.nan](https://developer.apple.com/documentation/swift/floatingpoint/1641652-nan),
   which is treated in a special manner by several arithmetic operations,
   such as the comparison and equality operators.
   */
  public var asDouble : Double {
    if isNaN {
      return Double.nan
    } else {
      return Double(numer)/Double(denom)
    }
  }
  
  /**
   A String representing self, in reduced terms.
   It will either be "NaN", or it will take on
   either of the forms "N" or "N/M", where N and M are both
   integers in decimal notation and M != 0.
   */
  public var description: String {
    if isNaN {
      return "NaN"
    } else if denom == 1 {
      return String(numer)
    } else {
      return "\"(numer)/\"(denom)"
    }
  }
  
  /// - Returns: true iff lhs < rhs
  public static func <(lhs: RatNum, rhs: RatNum) -> Bool {
    return (rhs.isNaN) ||
      !lhs.isNaN && (lhs - rhs).numer < 0
  }
  
  /// - Returns: true iff lhs == rhs
  public static func ==(lhs: RatNum, rhs: RatNum) -> Bool {
    return lhs.isNaN && rhs.isNaN ||
      lhs.numer == rhs.numer && lhs.denom == rhs.denom
  }
  
  /// - Returns: lhs + rhs, or NaN if either lhs or rhs is NaN
  public static func +(lhs: RatNum, rhs: RatNum) -> RatNum {
    // a/b + x/y = ay/by + bx/by = (ay + bx)/by
    let (a, b) = (lhs.numer, lhs.denom)
    let (x, y) = (rhs.numer, rhs.denom)
    return RatNum(a * y + x * b) / RatNum(b * y)
  }
  
  /// - Returns: lhs - rhs, or NaN if either lhs or rhs is NaN
  public static func -(lhs: RatNum, rhs: RatNum) -> RatNum {
    // a/b + x/y = ay/by + bx/by = (ay + bx)/by
    let (a, b) = (lhs.numer, lhs.denom)
    let (x, y) = (rhs.numer, rhs.denom)
    return RatNum(a * y - x * b) / RatNum(b * y)
  }
  
  /// - Returns: lhs * rhs, or NaN if either lhs or rhs is NaN
  public static func *(lhs: RatNum, rhs: RatNum) -> RatNum {
    // (a/b) / (x/y) = ay/bx
    return RatNum(lhs.numer * rhs.numer, lhs.denom * rhs.denom)
  }
  
  /// - Returns: lhs / rhs, or NaN if either lhs or rhs is NaN or rhs == 0
  public static func /(lhs: RatNum, rhs: RatNum) -> RatNum {
    // (a/b) / (x/y) = ay/bx
    if (rhs.isNaN) {
      return RatNum.nan
    } else {
      return RatNum(lhs.numer * rhs.denom, lhs.denom * rhs.numer)
    }
  }
  
  /// - Returns: the additive inverse of n (ie, 0 - n)
  public static prefix func -(n: RatNum) -> RatNum {
    return RatNum(-n.numer, n.denom)
  }
}