# Multiparadigm Programming with Python 3
# Rational Arithmetic Testing Module
# Copyright (C) 2018, H. Conrad Cunningham
#
# 34567890123456789012345678901234567890123456789012345678901234567890
#
# 2018-10-19: Original Python version (referencing Haskell versions)
# 2018-11-02: Cleanup based on flake8, pylint, & black runs

from rational_arith import Rat
from rational_rep import RatRep
from rational_core import RatCore
from rational_defer import RatDefer


def result(b: bool) -> str:
    """Convert boolean result to PASS/FAIL string"""
    if b:
        return "PASS"
    return "FAIL"


def test_RatRep(rr: Rat) -> None:
    """Test script for RatRep subclass operations"""

    print("\nBegin test RatRep subclass")
    print("    rr.makeRat(), rr.numer(), and rr.denom()")

    # Test non-error inputs on y-axis boundary
    # Test 0/1, 0/-1, 0/9, 0/-9; all become 0/1
    print("\nTesting inputs along y-axis")
    print(
        "rr.numer(rr.makeRat(0,1) == 0):            "
        + result(rr.numer(rr.makeRat(0, 1)) == 0)
    )
    print(
        "rr.denom(rr.makeRat(0,1)) == 1:            "
        + result(rr.denom(rr.makeRat(0, 1)) == 1)
    )

    print(
        "rr.numer(rr.makeRat(0,-1)) == 0):          "
        + result(rr.numer(rr.makeRat(0, -1)) == 0)
    )
    print(
        "rr.denom(rr.makeRat(0,-1)) == 1:           "
        + result(rr.denom(rr.makeRat(0, -1)) == 1)
    )

    print(
        "rr.numer(rr.makeRat(0,9)) == 0:            "
        + result(rr.numer(rr.makeRat(0, 9)) == 0)
    )
    print(
        "rr.denom(rr.makeRat(0,9)) == 1:            "
        + result(rr.denom(rr.makeRat(0, 9)) == 1)
    )

    print(
        "rr.numer(rr.makeRat(0,-9)) == 0:           "
        + result(rr.numer(rr.makeRat(0, -9)) == 0)
    )
    print(
        "rr.denom(rr.makeRat(0,-9)) == 1:           "
        + result(rr.denom(rr.makeRat(0, -9)) == 1)
    )

    # Test inputs from 1st & 3rd quadrants, same integer output
    # Test 1/1, -1/-1,  9/9, -9/-9
    print("\nTesting inputs in 1st/3rd quadrants, value 1/1")
    print(
        "rr.numer(rr.makeRat(1,1)) == 1:            "
        + result(rr.numer(rr.makeRat(1, 1)) == 1)
    )
    print(
        "rr.denom(rr.makeRat(1,1)) == 1:            "
        + result(rr.denom(rr.makeRat(1, 1)) == 1)
    )

    print(
        "rr.numer(rr.makeRat(-1,-1)) == 1:          "
        + result(rr.numer(rr.makeRat(-1, -1)) == 1)
    )
    print(
        "rr.denom(rr.makeRat(-1,-1)) == 1:          "
        + result(rr.denom(rr.makeRat(-1, -1)) == 1)
    )

    print(
        "rr.numer(rr.makeRat(9,9)) == 1:            "
        + result(rr.numer(rr.makeRat(9, 9)) == 1)
    )
    print(
        "rr.denom(rr.makeRat(9,9)) == 1:            "
        + result(rr.denom(rr.makeRat(9, 9)) == 1)
    )

    print(
        "rr.numer(rr.makeRat(-9,-9)) == 1:          "
        + result(rr.numer(rr.makeRat(-9, -9)) == 1)
    )
    print(
        "rr.denom(rr.makeRat(-9,-9)) == 1:          "
        + result(rr.denom(rr.makeRat(-9, -9)) == 1)
    )

    # Test inputs from 2nd & 4th quadrants, same integer output
    # Test -1/1, -9/9, 1/-1, 9/-9
    print("\nTesting inputs in 2nd/4th quadrants, value -1/1")
    print(
        "rr.numer(rr.makeRat(-1,1)) == -1:          "
        + result(rr.numer(rr.makeRat(-1, 1)) == -1)
    )
    print(
        "rr.denom(rr.makeRat(-1,1)) == 1:           "
        + result(rr.denom(rr.makeRat(-1, 1)) == 1)
    )

    print(
        "rr.numer(rr.makeRat(1,-1)) == -1:          "
        + result(rr.numer(rr.makeRat(1, -1)) == -1)
    )
    print(
        "rr.denom(rr.makeRat(1,-1)) == 1:           "
        + result(rr.denom(rr.makeRat(1, -1)) == 1)
    )

    print(
        "rr.numer(rr.makeRat(-9,9)) == -1:          "
        + result(rr.numer(rr.makeRat(-9, 9)) == -1)
    )
    print(
        "rr.denom(rr.makeRat(-9,9) == 1:            "
        + result(rr.denom(rr.makeRat(-9, 9)) == 1)
    )

    print(
        "rr.numer(rr.makeRat(9,-9)) == -1:          "
        + result(rr.numer(rr.makeRat(9, -9)) == -1)
    )
    print(
        "rr.denom(rr.makeRat(9,-9)) == 1:           "
        + result(rr.denom(rr.makeRat(9, -9)) == 1)
    )

    # Test more inputs, not integer result

    # 1st and 3rd quadrants
    # Test 3/2, -3/-2, 12/8, -12/-8; all become 3/2
    print("\nTesting inputs in 1st/3rd quadrants, value 3/2")
    print(
        "rr.numer(rr.makeRat(3,2)) == 3:            "
        + result(rr.numer(rr.makeRat(3, 2)) == 3)
    )
    print(
        "rr.denom(rr.makeRat(3,2) == 2:             "
        + result(rr.denom(rr.makeRat(3, 2)) == 2)
    )

    print(
        "rr.numer(rr.makeRat(-3,-2)) == 3:          "
        + result(rr.numer(rr.makeRat(-3, -2)) == 3)
    )
    print(
        "rr.denom(rr.makeRat(-3,-2)) == 2:          "
        + result(rr.denom(rr.makeRat(-3, -2)) == 2)
    )

    print(
        "rr.numer(rr.makeRat(12 8) == 3:            "
        + result(rr.numer(rr.makeRat(12, 8)) == 3)
    )
    print(
        "rr.denom(rr.makeRat(12,8)) == 2:           "
        + result(rr.denom(rr.makeRat(12, 8)) == 2)
    )

    print(
        "rr.numer(rr.makeRat(-12,-8)) == 3:         "
        + result(rr.numer(rr.makeRat(-12, -8)) == 3)
    )
    print(
        "rr.denom(rr.makeRat(-12,-8)) == 2:         "
        + result(rr.denom(rr.makeRat(-12, -8)) == 2)
    )

    # 2nd and 4th quadrants
    # Test -3/2, 3/-2, -12/8, 12/-8; all becomes -3/2
    print("\nTesting inputs in 2nd/4th quadrants, value -3/2")
    print(
        "rr.numer(rr.makeRat(-3,2)( == -3:          "
        + result(rr.numer(rr.makeRat(-3, 2)) == -3)
    )
    print(
        "rr.denom(rr.makeRat(-3,2)) == 2:           "
        + result(rr.denom(rr.makeRat(-3, 2)) == 2)
    )

    print(
        "rr.numer(rr.makeRat(3,-2)) == -3:          "
        + result(rr.numer(rr.makeRat(3, -2)) == -3)
    )
    print(
        "rr.denom(rr.makeRat(3,-2)) == 2:           "
        + result(rr.denom(rr.makeRat(3, -2)) == 2)
    )

    print(
        "rr.numer(rr.makeRat(-12,8)) == -3:         "
        + result(rr.numer(rr.makeRat(-12, 8)) == -3)
    )
    print(
        "rr.denom(rr.makeRat(-12,8)) == 2:          "
        + result(rr.denom(rr.makeRat(-12, 8)) == 2)
    )

    print(
        "rr.numer(rr.makeRat(12,-8)) == -3:         "
        + result(rr.numer(rr.makeRat(12, -8)) == -3)
    )
    print(
        "rr.denom(rr.makeRat(12,-8)) == 2:          "
        + result(rr.denom(rr.makeRat(12, -8)) == 2)
    )

    print("\nEnd test RatRep core:")
    print("    rr.makeRat(), rr.numer(), and rr.denom()")


def test_Rat(rr: Rat) -> None:
    """Test script for rational arithmetic operations (Rat)"""

    print("\nBegin test of Rat arithmetic methods")
    print("\nCreate constants for testing with rr.makeRat()")

    # Named constants used in testing
    zero: RatRep = rr.zeroRat               # 0
    one: RatRep = rr.makeRat(1, 1)          # 1
    negone: RatRep = rr.makeRat(-1, 1)      # -1
    two: RatRep = rr.makeRat(2, 1)          # 2
    negtwo: RatRep = rr.makeRat(-2, 1)      # -2
    half: RatRep = rr.makeRat(1, 2)         # 1/2
    quart1: RatRep = rr.makeRat(1, 4)       # 1/4
    quart3: RatRep = rr.makeRat(3, 4)       # 3/4
    r_32_27: RatRep = rr.makeRat(32, 27)    # 32/27
    nr_32_27: RatRep = rr.makeRat(-32, 27)  # -32/77

    # Display the constants
    print("Constants are as follows")
    print("zero                             =>       " + str(zero))
    print("one      = rr.makeRat(1,1)       =>       " + str(one))
    print("negone   = rr.makeRat(-1,1)      =>       " + str(negone))
    print("two      = rr.makeRat(2,1)       =>       " + str(two))
    print("negtwo   = rr.makeRat(-2,1)      =>       " + str(negtwo))
    print("half     = rr.makeRat(1,2)       =>       " + str(half))
    print("quart1   = rr.makeRat(1,4)       =>       " + str(quart1))
    print("quart3   = rr.makeRat(3,4)       =>       " + str(quart3))
    print("r_32_27  = rr.makeRat(32,27)     =>       " + str(r_32_27))
    print(
        "nr_32_27 = rr.makeRat(-32,27)    =>       " + str(nr_32_27)
    )

    # Test rr.eqRat()
    # Boundary values: zero, one, negone
    # Representative values: quart1, quart3
    # Equality properties: refexivity, symmetry, transitivity
    # Interface invariant
    print("\nTesting rr.eqRat()")
    print(
        "rr.eqRat(zero,zero) is True:                           "
        + result(rr.eqRat(zero, zero) is True)
    )
    print(
        "rr.eqRat(zero,one) is False:                           "
        + result(rr.eqRat(zero, one) is False)
    )
    print(
        "rr.eqRat(one,zero) is False:                           "
        + result(rr.eqRat(one, zero) is False)
    )
    print(
        "rr.eqRat(negone,negone) is True:                       "
        + result(rr.eqRat(negone, negone) is True)
    )
    print(
        "rr.eqRat(one,negone) is False:                         "
        + result(rr.eqRat(one, negone) is False)
    )
    print(
        "rr.eqRat(negone,one) is False:                         "
        + result(rr.eqRat(negone, one) is False)
    )
    print(
        "rr.eqRat(quart1,quart3) is False:                      "
        + result(rr.eqRat(quart1, quart3) is False)
    )
    print(
        "rr.eqRat(quart3,quart1) is False:                      "
        + result(rr.eqRat(quart3, quart1) is False)
    )

    # Test rr.negRat()
    # Boundary values: zero, one, negone
    # Representative values: r_32_27, nr_32_27
    # Properties: idempotence (double negation)
    # Interface invariant
    print("\nTesting rr.negRat()")
    print(
        "rr.eqRat(rr.negRat(zero),zero) is True:                "
        + result(rr.eqRat(rr.negRat(zero), zero) is True)
    )
    print(
        "rr.eqRat(rr.negRat(one),negone) is True:               "
        + result(rr.eqRat(rr.negRat(one), negone) is True)
    )
    print(
        "rr.eqRat(rr.negRat(negone),one) is True:               "
        + result(rr.eqRat(rr.negRat(negone), one) is True)
    )
    print(
        "rr.eqRat(rr.negRat(two) negtwo is True:                "
        + result(rr.eqRat(rr.negRat(two), negtwo) is True)
    )
    print(
        "rr.eqRat(rr.negRat(negtwo) two is True:                "
        + result(rr.eqRat(rr.negRat(negtwo), two) is True)
    )
    print(
        "rr.eqRat(rr.negRat(r_32_27),nr_32_27) is True:         "
        + result(rr.eqRat(rr.negRat(r_32_27), nr_32_27) is True)
    )
    print(
        "rr.eqRat(rr.negRat(nr_32_27) r_32_27 is True:          "
        + result(rr.eqRat(rr.negRat(nr_32_27), r_32_27) is True)
    )

    # Test rr.addRat()
    # Boundary values: zero, one, negone, maxInt, minInt
    # Representative values: quart1, half, quart3, two
    # Properties: associativity, symmetry/commutativity,
    #             identity, inverse
    # Interface invariant
    print("\nTesting rr.addRat()")
    print(
        "rr.eqRat(rr.addRat(zero,zero),zero) is True:           "
        + result(rr.eqRat(rr.addRat(zero, zero), zero) is True)
    )
    print(
        "rr.eqRat(rr.addRat(zero,one),one) is True:             "
        + result(rr.eqRat(rr.addRat(zero, one), one) is True)
    )
    print(
        "rr.eqRat(rr.addRat(one,zero),one) is True:             "
        + result(rr.eqRat(rr.addRat(one, zero), one) is True)
    )
    print(
        "rr.eqRat(rr.addRat(one,one),two) is True:              "
        + result(rr.eqRat(rr.addRat(one, one), two) is True)
    )
    print(
        "rr.eqRat(rr.addRat(one,negone),zero) is True:          "
        + result(rr.eqRat(rr.addRat(one, negone), zero) is True)
    )
    print(
        "rr.eqRat(rr.addRat(negone,one),zero) is True:          "
        + result(rr.eqRat(rr.addRat(negone, one), zero) is True)
    )
    print(
        "rr.eqRat(rr.addRat(quart1,quart1),half) is True:       "
        + result(rr.eqRat(rr.addRat(quart1, quart1), half) is True)
    )
    print(
        "rr.eqRat(rr.addRat(half,quart1),quart3) is True:       "
        + result(rr.eqRat(rr.addRat(half, quart1), quart3) is True)
    )
    print(
        "rr.eqRat(rr.addRat(quart1,half),quart3) is True:       "
        + result(rr.eqRat(rr.addRat(quart1, half), quart3) is True)
    )

    # Test rr.subRat()
    # Boundary values: zero, one, negone
    # Representative values: quart1, half, quart3, two
    # Properties: right identity, right inverse
    # Interface invariant
    print("\nTesting rr.subRat()")
    print(
        "rr.eqRat(rr.subRat(one,zero),one) is True:             "
        + result(rr.eqRat(rr.subRat(one, zero), one) is True)
    )
    print(
        "rr.eqRat(rr.subRat(one,one),zero) is True:             "
        + result(rr.eqRat(rr.subRat(one, one), zero) is True)
    )
    print(
        "rr.eqRat(rr.subRat(zero,one),negone )is True:          "
        + result(rr.eqRat(rr.subRat(zero, one), negone) is True)
    )
    print(
        "rr.eqRat(rr.subRat(one,negone),two) is True:           "
        + result(rr.eqRat(rr.subRat(one, negone), two) is True)
    )
    print(
        "rr.eqRat(rr.subRat(one,two),negone) is True:           "
        + result(rr.eqRat(rr.subRat(one, two), negone) is True)
    )
    print(
        "rr.eqRat(rr.subRat(quart3,half),quart1) is True:       "
        + result(rr.eqRat(rr.subRat(quart3, half), quart1) is True)
    )
    print(
        "rr.eqRat(rr.subRat(quart3,quart1),half) is True:       "
        + result(rr.eqRat(rr.subRat(quart3, quart1), half) is True)
    )

    # Test rr.mulRat()
    # Boundary values: zero, one, negone
    # Representative values: quart1, half, quart3, two
    # Properties: associativity, symmetry/commutativity,
    #             identity, inverse, zero element
    # Interface invariant
    print("\nTesting rr.mulRat()")
    print(
        "rr.eqRat(rr.mulRat(zero,zero),zero) is True:           "
        + result(rr.eqRat(rr.mulRat(zero, zero), zero) is True)
    )
    print(
        "rr.eqRat(rr.mulRat(zero,one),zero) is True:            "
        + result(rr.eqRat(rr.mulRat(zero, one), zero) is True)
    )
    print(
        "rr.eqRat(rr.mulRat(one zero) zero is True:             "
        + result(rr.eqRat(rr.mulRat(one, zero), zero) is True)
    )
    print(
        "rr.eqRat(rr.mulRat(one one) one is True:               "
        + result(rr.eqRat(rr.mulRat(one, one), one) is True)
    )
    print(
        "rr.eqRat(rr.mulRat(one,negone),negone) is True:        "
        + result(rr.eqRat(rr.mulRat(one, negone), negone) is True)
    )
    print(
        "rr.eqRat(rr.mulRat(negone,one),negone) is True:        "
        + result(rr.eqRat(rr.mulRat(negone, one), negone) is True)
    )
    print(
        "rr.eqRat(rr.mulRat(one,two),two) is True:              "
        + result(rr.eqRat(rr.mulRat(one, two), two) is True)
    )
    print(
        "rr.eqRat(rr.mulRat(two,one),two) is True:              "
        + result(rr.eqRat(rr.mulRat(two, one), two) is True)
    )
    print(
        "rr.eqRat(rr.mulRat(half,two),one) is True:             "
        + result(rr.eqRat(rr.mulRat(half, two), one) is True)
    )
    print(
        "rr.eqRat(rr.mulRat(two,half),one) is True:             "
        + result(rr.eqRat(rr.mulRat(two, half), one) is True)
    )
    print(
        "rr.eqRat(rr.mulRat(two,quart1),half) = True:           "
        + result(rr.eqRat(rr.mulRat(two, quart1), half) is True)
    )
    print(
        "rr.eqRat(rr.mulRat(quart1 two) half is True:           "
        + result(rr.eqRat(rr.mulRat(quart1, two), half) is True)
    )

    # Test divRat :: Rat -> Rat -> Rat
    # Division by zero is an error!
    # Boundary values: zero, one, negone
    # Representative values: half, two, negtwo
    # Properties: left zero, right identity, inverse
    # Interface invariant
    print("\nTesting rr.divRat()")
    print(
        "rr.eqRat(rr.divRat(zero,one),zero) is True:            "
        + result(rr.eqRat(rr.divRat(zero, one), zero) is True)
    )
    print(
        "rr.eqRat(rr.divRat(one,one),one) is True:              "
        + result(rr.eqRat(rr.divRat(one, one), one) is True)
    )
    print(
        "rr.eqRat(rr.divRat(one,negone),negone) is True:        "
        + result(rr.eqRat(rr.divRat(one, negone), negone) is True)
    )
    print(
        "rr.eqRat(rr.divRat(negone,one),negone) is True:        "
        + result(rr.eqRat(rr.divRat(negone, one), negone) is True)
    )
    print(
        "rr.eqRat(rr.divRat(one,two),half) = True:              "
        + result(rr.eqRat(rr.divRat(one, two), half) is True)
    )
    print(
        "rr.eqRat(rr.divRat(one,half),two) is True:             "
        + result(rr.eqRat(rr.divRat(one, half), two) is True)
    )
    print(
        "rr.eqRat(rr.divRat(half,half),one) is True:            "
        + result(rr.eqRat(rr.divRat(half, half), one) is True)
    )

    print("\nEnd test of Rat arithmetic methods")


if __name__ == "__main__":
    rr_test: Rat

    # Test Rat object with RatCore implementation of RatRep
    rr_test = Rat(RatCore)
    print("\nBEGIN testing rr_test = Rat(RatCore)")
    test_RatRep(rr_test)
    test_Rat(rr_test)
    print("\nEND testing rr_test1 = Rat(RatCore)")

    # Test Rat object with RatDefer implementation of RatRep
    rr_test = Rat(RatDefer)
    print("\nBEGIN testing rr_test = Rat(RatDefer)")
    test_RatRep(rr_test)
    test_Rat(rr_test)
    print("\nEND testing rr_test = Rat(RatDefer)")

    print("\nEND OF TESTING")
