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

import Foundation

/**
 
 RatTerm is an immutable representation of a term in a single-variable
 polynomial expression. The term has the form C*x^E where C is a rational
 number and E is an integer.

 **AbstractState**: 
 A RatTerm, t, can be notated by the pair (C . E), where C is the coefficient
 of t, and E is the exponent of t.
 
 The zero RatTerm, (0 . 0), is the only RatTerm that may have a zero
 coefficient. For example, (0 . 7) is an invalid RatTerm and an attempt to
 construct such a RatTerm (through the constructor or arithmetic operations on
 existing RatTerms) will return the semantically equivalent RatTerm (0 . 0).
 For example, (1 . 7) + (-1 . 7) = (0 . 0).
 
 (0 . 0), (1 . 0), (1 . 1), (1 . 3), (3/4 . 17), (7/2 . -1), and (NaN . 74)
 are all valid RatTerms, corresponding to the polynomial terms "0", "1", "x",
 "x^3", "3/4*x^17", "7/2*x^-1" and "NaN*x^74", respectively.
 */
public struct RatTerm : Equatable, CustomStringConvertible {
  
  /// Coefficient of this term.
  public let coeff : RatNum
  
  /// Exponent of this term.
  public let exponent : Int
  
  // Abstraction Function:
  // For a given RatTerm t, "coefficient of t" is synonymous with
  // t.coeff, and, likewise, "exponent of t" is synonymous with t.expt.
  // All RatTerms with a zero coefficient are represented by the
  // zero RatTerm, z, which has zero for its coefficient AND exponent.
  
  // Representation Invariant RI(self):
  // (self.coeff == RatNum.zero) ==> self.expt == 0
  
  /// A constant holding a Not-a-Number (NaN) value of type RatTerm
  public static let nan = RatTerm(coeff: RatNum.nan, exponent: 0)
  
  /// A constant holding a zero value of type RatTerm
  public static let zero = RatTerm(coeff: RatNum.zero, exponent: 0)
  
  /**
   - Parameter c: the coefficient of the RatTerm to be constructed.
   - Parameter e: the exponent of the RatTerm to be constructed.
   
   **Effects**: Constructs a new RatTerm t, with t.coeff = c, and if
             c == RatNum.zero, then t.expt = 0, otherwise t.expt = e
   */
  init(coeff c: RatNum, exponent e: Int) {
    if c == RatNum.zero {
      // If coefficient is zero, must set exponent to zero.
      coeff = RatNum.zero
      exponent = 0
    } else {
      coeff = c
      exponent = e
    }
    checkRep()
  }
  
  /// true if and only if self has NaN as a coefficient.
  public var isNaN : Bool {
    // TODO: Write Me
    // preconditionFailure("Not implemented yet.")
    // Remove the following line:
    return false

  }
  
  ///true if and only if self has zero as a coefficient.
  public var isZero : Bool {
    // TODO: Write Me
    // preconditionFailure("Not implemented yet.")
    // Remove the following line:
    return false
  }
  
  /**
    A String representation of the expression represented by this RatTerm:
   
      * There is no whitespace in the returned string.
      * If the term is itself zero, the returned string will just be "0".
      * If self.isNaN, then the returned string will be just "NaN"
   
    The string for a non-zero, non-NaN RatTerm is in the form "C*x^E" where C
    is a valid string representation of a `RatNum` and E is an integer.
    UNLESS:
   
      1. the exponent E is zero, in which case T takes the form "C"
      2. the exponent E is one, in which case T takes the form "C*x"
      3. the coefficient C is one, in which case T
         takes the form "x^E" or "x" (if E is one) or "1" (if E is zero).
   
    Valid example outputs include "3/2*x^2", "-1/2", "0", and "NaN".
   */
  public var description: String {
    if isNaN {
      return "NaN"
    } else {
      let (sign, normalized) = coeff.isNegative ? ("-", -coeff) : ("", coeff)
      switch (normalized, exponent) {
      case (RatNum.one, 1):      return "\"(sign)x"
      case (let c, 0):           return "\"(sign)\"(c)"
      case (RatNum.one, let e):  return "\"(sign)x^\"(e)"
      case (let c, 1):           return "\"(sign)\"(c)*x"
      case (let c, let e):       return "\"(sign)\"(c)*x^\"(e)"
      }
    }
  }
  
  /**
    Returns the value of this RatTerm, evaluated at d.
   
    - Parameter d: The value at which to evaluate this term.
   
    - Returns: the value of this polynomial when evaluated at 'd'. For example,
            "3*x^2" evaluated at 2 is 12. if (self.isNaN == true), return
            Double.NaN
   */
  public func eval(_ d: Double) -> Double {
    // TODO: Write Me
    // preconditionFailure("RatTerm.eval not implemented yet.")
    // Remove the following line:
    return Double.nan
  }
  
  /// - Returns: a RatTerm equals to (-rhs). If rhs is NaN, then returns NaN.
  public static prefix func -(rhs: RatTerm) -> RatTerm {
    // TODO: Write Me
    // preconditionFailure("RatTerm unary - not implemented yet.")
    // Remove the following line:
    return RatTerm.nan
  }
  
  /**
   Addition operation.
   
   - Parameter lhs : The first operand.
   - Parameter rhs : The second operand.
   
   - Returns: A RatTerm equals to (lhs + rhs), or:
              if either argument is NaN, then returns NaN, or:
              if (lhs.expt != rhs.expt) and neither argument is zero or
                 NaN, then returns nil.
   */
  public static func +(lhs: RatTerm, rhs: RatTerm) -> RatTerm? {
    // TODO: Write Me
    // preconditionFailure("RatTerm + not implemented yet.")
    // Remove the following line:
    return RatTerm.nan
  }
  
  /**
   Subtraction operation.
   
   - Parameter lhs : The first operand.
   - Parameter rhs : The second operand.
   
   - Returns: A RatTerm equals to (lhs - rhs), or:
             if either argument is NaN, then returns NaN, or:
             if (lhs.expt != rhs.expt) and neither argument is zero or
             NaN, then returns nil.
   */
  public static func -(lhs: RatTerm, rhs: RatTerm) -> RatTerm? {
    // TODO: Write Me
    // preconditionFailure("RatTerm - not implemented yet.")
    // Remove the following line:
    return RatTerm.nan
  }
  
  /**
   Multiplication operation.
   
   - Parameter lhs : The first operand.
   - Parameter rhs : The second operand.
   
   - Returns: A RatTerm equals to (lhs * rhs). If either argument is NaN, then
   returns NaN.
   */
  public static func *(lhs: RatTerm, rhs: RatTerm) -> RatTerm {
    // TODO: Write Me
    // preconditionFailure("RatTerm * not implemented yet.")
    // Remove the following line:
    return RatTerm.nan
  }
  
  /**
   Division operation.
   
   - Parameter lhs : The first operand.
   - Parameter rhs : The second operand.
   
   - Returns: A RatTerm equals to (lhs / rhs). If either argument is NaN, then
   returns NaN.
   */
  public static func /(lhs: RatTerm, rhs: RatTerm) -> RatTerm {
    // TODO: Write Me
    // preconditionFailure("RatTerm / not implemented yet.")
    // Remove the following line:
    return RatTerm.nan
  }
  
  /**
   Return the derivative of this RatTerm.
   
   Given a term, a*x^b, the derivative of the term is: (a*b)*x^(b-1)
   for b > 0 and 0 for b == 0 (Do not worry about the case when "b < 0". The
   caller of this function, RatPoly, contains a rep.
   invariant stating that b is never less than 0.)
   
   **Requires**: If self is not NaN, then expt >= 0.
   
   - Returns: a RatTerm that, q, such that q = dy/dx, where self == y. In other
   words, q is the derivative of self. If self.isNaN, then return
   some q such that q.isNaN
  */
  public func differentiate() -> RatTerm {
    // TODO: Write Me
    // preconditionFailure("RatTerm.differentiate not implemented yet.")
    // Remove the following line:
    return RatTerm.nan
  }
  
  /**
   Returns the antiderivative of this RatTerm.
   
   Given a term, a*x^b, (where b >= 0) the antiderivative of the
   term is: a/(b+1)*x^(b+1) (Do not worry about the case when b < 0.
   The caller of this function, RatPoly, contains a rep. invariant
   stating that b is never less than 0.)
   
   **Requires**: If self is not NaN, then expt >= 0.
   
   - Returns: a RatTerm, q, such that dq/dx = self where the constant of
   intergration is assumed to be 0. In other words, q is the
   antiderivative of self. If self.isNaN, then return some q such
   that q.isNaN
   */
  public func antiDerivative() -> RatTerm {
    // TODO: Write Me
    // preconditionFailure("RatTerm.antiDerivative not implemented yet.")
    // Remove the following line:
    return RatTerm.nan
  }
  
  
  /**
   Builds a new RatTerm, given a descriptive String.
   
   - Parameter description: A string of the format described in the @requires clause.
   
   **Requires**: 'description' is an instance of a string with no spaces that
   expresses a RatTerm in the form defined by the `description`
   property of `RatTerm`
   
   Valid inputs include "0", "x", and "-5/3*x^3", and "NaN".
   
   - Returns: a RatTerm t such that t.toString() = description
   */
  public init?(_ description: String) {
    
    if (description == "NaN") {
      self.init(coeff: RatNum.nan, exponent: 0)
    } else {
      // Term is: "R" or "R*x" or "R*x^N" or "x^N" or "x",
      // where R is a rational num and N is an integer.
      
      // First we parse the coefficient
      var coeff : RatNum?
      if let multIndex = description.index(of: "*") {
        // "R*x" or "R*x^N"
        coeff = RatNum(String(description.prefix(upTo: multIndex)))
      } else {
        // "R" or "x^N" or "x"
        if description.contains("x") {
          if let negIndex = description.index(of: "-") {
            //            assert(negIndex == description.indices.startIndex,
            //                   "Minus sign, '-', not allowed in the middle of input string: \"(description)")
            coeff = negIndex == description.indices.startIndex ? RatNum(-1) : nil
          } else {
            coeff = RatNum(1)
          }
        } else {
          // "R"
          coeff = RatNum(description)
        }
      }
      
      var exponent : Int?
      if let powIndex = description.index(of: "^") {
        // "R*x^N" or "x^N"
        exponent = Int(description[description.index(after:powIndex)...])
      } else {
        // "R" or "R*x" or "x"
        if description.contains("x") {
          // "R*x" or "x"
          exponent = 1
        } else {
          // "R"
          exponent = 0
        }
      }
      if let c = coeff, let e = exponent {
        self.init(coeff: c, exponent: e)
      } else {
        return nil
      }
    }
  }
  
  /**
   Standard equality operation.
   
   - Returns: true iff lhs and rhs represent the same RatTerm. Note that
   all NaN RatTerms are equal.
   */
  public static func ==(lhs: RatTerm, rhs: RatTerm) -> Bool {
    return lhs.isNaN && rhs.isNaN ||
      lhs.coeff == rhs.coeff && lhs.exponent == rhs.exponent
  }
  
  /**
   Checks that the representation invariant holds (if any).
   */
  private func checkRep() {
    assert(coeff != RatNum.zero || exponent == 0, "coeff is zero while expt is \"(exponent)")
  }
}