//
//  RatPolyStackTest.swift
//  RatNumTests
//

import XCTest
@testable import RatNum

/**
 * This class contains a set of test cases that can be used to test the
 * implementation of the RatPolyStack class.
 *
 * Some Test Methods are relatively longer in order to simulate proper stack functionality
 */
class RatPolyStackTest: XCTestCase {
  
  // create a new poly that is a constant (doesn't depend on x)
  private func constantPoly(_ constant : Int) -> RatPoly {
    return RatPoly(c: constant, e: 0)
  }
  
  // create stack of single-digit constant polys
  private func stack(_ desc : String) -> RatPolyStack {
    let s = RatPolyStack()
    
    for c in desc.reversed() {
      let v: String = String(c)
      let x: RatPoly = constantPoly(Int(v, radix:10)!)
      s.push(x)
    }
    
    return s
  }
  
  // Compares 's' to a string describing its values.
  // desc MUST be a sequence of decimal number chars.
  // Example call: assertStackIs(myStack, "123")
  //
  // NOTE: THIS CAN FAIL WITH A WORKING STACK IF RatPoly.description IS BROKEN!
  private func assertStackIs(_ s: RatPolyStack, _ desc: String, file: StaticString = #file, line: UInt = #line) -> Void {
    XCTAssertEqual(desc.count, s.count)
    
    let zipped = zip(s, desc).enumerated()
    
    for (i,(p,num)) in zipped {
      XCTAssertEqual(p,constantPoly(Int(String(num))!), "Elem \"(i) was \"(p), Expected \"(num), (Expected Stack:\"(desc)", file: file, line: line)
    }
  }
  
  ///////////////////////////////////////////////////////////////////////////////////////
  ////  Constructor
  ///////////////////////////////////////////////////////////////////////////////////////
  
  public func testCtor() -> Void {
    let stk1 = RatPolyStack()
    XCTAssertEqual(0, stk1.count)
  }
  
  ///////////////////////////////////////////////////////////////////////////////////////
  ////  Push
  ///////////////////////////////////////////////////////////////////////////////////////
  
  public func testPush() -> Void {
    let stk1 = RatPolyStack()
    stk1.push(constantPoly(0))
    
    assertStackIs(stk1, "0")
    
    stk1.push(constantPoly(0))
    assertStackIs(stk1, "00")
    
    stk1.push(constantPoly(1))
    assertStackIs(stk1, "100")
    
    for s in ["3", "24", "134"] {
      assertStackIs(stack(s), s)
    }
  }
  
  public func testPushCheckForSharingTwixtStacks() -> Void {
    let stk1 = RatPolyStack()
    let stk2 = stack("123")
    assertStackIs(stk1, "")
    assertStackIs(stk2, "123")
    
    stk1.push(constantPoly(0))
    assertStackIs(stk1, "0")
    assertStackIs(stk2, "123")
    
    stk1.push(constantPoly(0))
    assertStackIs(stk1, "00")
    assertStackIs(stk2, "123")
    
    stk1.push(constantPoly(1))
    assertStackIs(stk1, "100")
    assertStackIs(stk2, "123")
    
    stk2.push(constantPoly(8))
    assertStackIs(stk1, "100")
    assertStackIs(stk2, "8123")
  }
  
  ///////////////////////////////////////////////////////////////////////////////////////
  ////  Pop
  ///////////////////////////////////////////////////////////////////////////////////////
  
  public func testPop() -> Void {
    let stk1 = stack("123")
    
    var poly = stk1.pop()
    XCTAssertTrue(poly == constantPoly(1))
    assertStackIs(stk1, "23")
    
    poly = stk1.pop()
    XCTAssertTrue(poly == constantPoly(2))
    assertStackIs(stk1, "3")
    
    poly = stk1.pop()
    assertStackIs(stk1, "")
  }
  
  
  
  ///////////////////////////////////////////////////////////////////////////////////////
  ////  Duplicate
  ///////////////////////////////////////////////////////////////////////////////////////
  
  public func testDupWithOneVal() -> Void {
    var stk1 = stack("3")
    stk1.dup()
    assertStackIs(stk1, "33")
    stk1 = stack("123")
    stk1.dup()
    assertStackIs(stk1, "1123")
  }
  
  public func testDupWithTwoVal() -> Void {
    let stk1 = stack("23")
    stk1.dup()
    assertStackIs(stk1, "223")
    XCTAssertEqual(3, stk1.count)
    XCTAssertTrue(stk1[indexFromTop: 0] == constantPoly(2))
    XCTAssertTrue(stk1[indexFromTop: 1] == constantPoly(2))
    XCTAssertTrue(stk1[indexFromTop: 2] == constantPoly(3))
  }
  
  public func testDupWithMultVal() -> Void {
    let stk1 = stack("123")
    stk1.dup()
    assertStackIs(stk1, "1123")
  }
  
  ///////////////////////////////////////////////////////////////////////////////////////
  ////  Swap
  ///////////////////////////////////////////////////////////////////////////////////////
  
  public func testSwapWithTwoElems() -> Void {
    let stk1 = stack("23")
    stk1.swap()
    assertStackIs(stk1, "32")
  }
  
  public func testSwapWithMultElems() -> Void {
    let stk1 = stack("123")
    stk1.swap()
    assertStackIs(stk1, "213")
  }
  
  public func testSwapWitSameElems() -> Void {
    let stk1 = stack("112")
    stk1.swap()
    assertStackIs(stk1, "112")
  }
  
  ///////////////////////////////////////////////////////////////////////////////////////
  ////  Clear
  ///////////////////////////////////////////////////////////////////////////////////////
  
  public func testClear() -> Void {
    let stk1 = stack("123")
    stk1.clear()
    assertStackIs(stk1, "")
    let stk2 = stack("112")
    stk2.clear()
    assertStackIs(stk2, "")
  }
  
  ///////////////////////////////////////////////////////////////////////////////////////
  ////  Add
  ///////////////////////////////////////////////////////////////////////////////////////
  
  public func testAddTwoElems() -> Void {
    let stk1 = stack("11")
    stk1.add()
    assertStackIs(stk1, "2")
  }
  
  public func testAddMultiElems() -> Void {
    var stk1 = stack("123")
    stk1.add()
    assertStackIs(stk1, "33")
    stk1.add()
    assertStackIs(stk1, "6")
    
    stk1 = stack("112")
    stk1.add()
    assertStackIs(stk1, "22")
    stk1.add()
    assertStackIs(stk1, "4")
    stk1.push(constantPoly(5))
    assertStackIs(stk1, "54")
    stk1.add()
    assertStackIs(stk1, "9")
    
  }
  
  ///////////////////////////////////////////////////////////////////////////////////////
  ////  Subtract
  ///////////////////////////////////////////////////////////////////////////////////////
  
  public func testSubTwoElems() -> Void {
    let stk1 = stack("12")
    stk1.sub()
    assertStackIs(stk1, "1")
  }
  
  public func testSubMultiElems() -> Void {
    var stk1 = stack("123")
    stk1.sub()
    assertStackIs(stk1, "13")
    stk1.sub()
    assertStackIs(stk1, "2")
    
    stk1 = stack("5723")
    stk1.sub()
    assertStackIs(stk1, "223")
    stk1.sub()
    assertStackIs(stk1, "03")
    stk1.sub()
    assertStackIs(stk1, "3")
  }
  
  ///////////////////////////////////////////////////////////////////////////////////////
  ////  Multiplication
  ///////////////////////////////////////////////////////////////////////////////////////
  
  public func testMulTwoElems() -> Void {
    let stk1 = stack("23")
    stk1.mul()
    assertStackIs(stk1, "6")
  }
  
  public func testMulMultiElems() -> Void {
    var stk1 = stack("123")
    stk1.mul()
    assertStackIs(stk1, "23")
    stk1.mul()
    assertStackIs(stk1, "6")
    
    stk1 = stack("112")
    stk1.mul()
    assertStackIs(stk1, "12")
    stk1.mul()
    assertStackIs(stk1, "2")
    stk1.push(constantPoly(4))
    assertStackIs(stk1, "42")
    stk1.mul()
    assertStackIs(stk1, "8")
  }
  
  ///////////////////////////////////////////////////////////////////////////////////////
  ////  Division
  ///////////////////////////////////////////////////////////////////////////////////////
  
  public func testDivTwoElems() -> Void {
    let stk1 = stack("28")
    stk1.div()
    assertStackIs(stk1, "4")
  }
  
  public func testDivMultiElems() -> Void {
    let stk1 = stack("123")
    stk1.div()
    assertStackIs(stk1, "23")
  }
  
  ///////////////////////////////////////////////////////////////////////////////////////
  ////  Differentiate
  ///////////////////////////////////////////////////////////////////////////////////////
  
  public func testDifferentiate() -> Void {
    let stk1 = stack("123")
    stk1.differentiate()
    stk1.differentiate()
    stk1.differentiate()
    stk1.differentiate()
    XCTAssertEqual(3, stk1.count)
    assertStackIs(stk1, "023")
    
    let rp1 = RatPoly(c: 3, e: 5)
    let rp2 = RatPoly(c: 7, e: 0)
    let rp3 = RatPoly(c: 4, e: 1)
    stk1.push(rp1)
    stk1.push(rp2)
    stk1.push(rp3)
    
    stk1.differentiate()
    XCTAssertEqual("4", stk1.pop().description)
    stk1.differentiate()
    XCTAssertEqual("0", stk1.pop().description)
    stk1.differentiate()
    XCTAssertEqual("15*x^4", stk1.pop().description)
  }
  
  ///////////////////////////////////////////////////////////////////////////////////////
  ////  Integrate
  ///////////////////////////////////////////////////////////////////////////////////////
  
  public func testIntegrate() -> Void {
    let stk1 = stack("123")
    stk1.integrate()
    stk1.integrate()
    stk1.integrate()
    stk1.integrate()
    XCTAssertEqual(3, stk1.count)
    XCTAssertEqual("1/24*x^4", stk1.pop().description)
    let rp1 = RatPoly(c: 15, e: 4)
    let rp2 = RatPoly(c: 7, e: 0)
    let rp3 = RatPoly(c: 4, e: 0)
    stk1.push(rp1)
    stk1.push(rp2)
    stk1.push(rp3)
    
    stk1.integrate()
    XCTAssertEqual("4*x", stk1.pop().description)
    stk1.integrate()
    XCTAssertEqual("7*x", stk1.pop().description)
    stk1.integrate()
    XCTAssertEqual("3*x^5", stk1.pop().description)
  }
  
}