# Multiparadigm Programming with Python 3
# Rational Number; Black-box unit test using Pytest for RatDefer
# Copyright (C) 2018, H. Conrad Cunningham
#
# 34567890123456789012345678901234567890123456789012345678901234567890
#
# 2018-11-01: Created from RatCore test

from typing import List, Tuple
import pytest

from rational_arith import Rat
from rational_rep import RatRep

# from rational_core import RatCore
from rational_defer import RatDefer


rn: Rat = Rat(RatDefer)


# Test illegal 0 denominator
@pytest.mark.xfail()
def test_RatRep_denom0():
    """Test zero denominator for makeRat; raises exception"""
    rr: RatRep = rn.makeRat(1, 0)
    assert rr.numer() == 1
    assert rr.denom() == 0


# Test 0/1, 0/-1, 0/9, 0/-9; all become 0/1
test_pairs: List[Tuple[int, int]]
tests_r: List[RatRep]
test_ids: List[str]

test_pairs = [(0, 1), (0, -1), (0, 9), (0, -9)]
tests_r = [rn.makeRat(x, y) for (x, y) in test_pairs]
test_ids = [f"{t}" for t in test_pairs]


@pytest.mark.parametrize("rr", tests_r, ids=test_ids)
def test_y_axis(rr):
    """Test RatRep non-error inputs on y-axis boundary"""
    assert rr.numer() == 0
    assert rr.denom() == 1


# Test 1/1, -1/-1,  9/9, -9/-9
test_pairs = [(1, 1), (-1, -1), (9, 9), (-9, -9)]
tests_r = [rn.makeRat(x, y) for (x, y) in test_pairs]
test_ids = [f"{t}" for t in test_pairs]


@pytest.mark.parametrize("rr", tests_r, ids=test_ids)
def test_quad_1_3(rr):
    """Test RatRep 1st & 3rd quadrants, same integer output"""
    assert rr.numer() == 1
    assert rr.denom() == 1


# Test -1/1, -9/9, 1/-1, 9/-9
test_pairs = [(-1, 1), (1, -1), (-9, 9), (9, -9)]
tests_r = [rn.makeRat(x, y) for (x, y) in test_pairs]
test_ids = [f"{t}" for t in test_pairs]


@pytest.mark.parametrize("rr", tests_r, ids=test_ids)
def test_quad_2_4(rr):
    """Test RatRep 2nd & 4th quadrants, same integer output"""
    assert rr.numer() == -1
    assert rr.denom() == 1


# Test 3/2, -3/-2, 12/8, -12/-8; all become 3/2
test_pairs = [(3, 2), (-3, -2), (12, 8), (-12, -8)]
tests_r = [rn.makeRat(x, y) for (x, y) in test_pairs]
test_ids = [f"{t}" for t in test_pairs]


@pytest.mark.parametrize("rr", tests_r, ids=test_ids)
def test_quad_1_3_notintrr(rr):
    """Test RatRep 1st and 3rd quadrants, non-integer"""
    assert rr.numer() == 3
    assert rr.denom() == 2


# Test -3/2, 3/-2, -12/8, 12/-8; all becomes -3/2
test_pairs = [(-3, 2), (3, -2), (-12, 8), (12, -8)]
tests_r = [rn.makeRat(x, y) for (x, y) in test_pairs]
test_ids = [f"{t}" for t in test_pairs]


@pytest.mark.parametrize("rr", tests_r, ids=test_ids)
def test_quad_2_4_notint(rr):
    """Test RatRep 2nd and 4th quadrants, non-integer"""
    assert rr.numer() == -3
    assert rr.denom() == 2


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


# Test eqRat
# Boundary values: zero, one, negone
# Representative values: quart1, quart3
# Equality properties: refexivity, symmetry, transitivity
# Interface invariant

tests_rrb: List[Tuple[RatRep, RatRep, bool]]

tests_rrb = [
    (zero, zero, True),
    (zero, one, False),
    (one, zero, False),
    (negone, negone, True),
    (one, negone, False),
    (negone, one, False),
    (quart1, quart3, False),
    (quart3, quart1, False)
]
test_ids = [f"{t}" for t in tests_rrb]


@pytest.mark.parametrize("x, y, r", tests_rrb, ids=test_ids)
def test_eqRat(x, y, r):
    """Test Rat eqRat"""
    assert rn.eqRat(x, y) == r


# Test negRat
# Boundary values: zero, one, negone
# Representative values: r_32_27, nr_32_27
# Properties: idempotence (double negation)
# Interface invariant

tests_rr: List[Tuple[RatRep, RatRep]]

tests_rr = [
    (zero, zero),
    (one, negone),
    (negone, one),
    (two, negtwo),
    (negtwo, two),
    (r_32_27, nr_32_27),
    (nr_32_27, r_32_27)
]
test_ids = [f"{t}" for t in tests_rr]


@pytest.mark.parametrize("x, nx", tests_rr, ids=test_ids)
def test_negRat(x, nx):
    """Test Rat negRat"""
    assert rn.eqRat(rn.negRat(x), nx) is True


# Test addRat
# Boundary values: zero, one, negone
# Representative values: quart1, half, quart3, two
# Properties: associativity, symmetry/commutativity,
#             identity, inverse

tests_rrr: List[Tuple[RatRep, RatRep, RatRep]]

tests_rrr = [
    (zero, zero, zero),
    (zero, one, one),
    (one, zero, one),
    (one, one, two),
    (one, negone, zero),
    (negone, one, zero),
    (quart1, quart1, half),
    (half, quart1, quart3),
    (quart1, half, quart3)
]
test_ids = [f"{t}" for t in tests_rrr]


@pytest.mark.parametrize("x, y, z", tests_rrr, ids=test_ids)
def test_addRat(x, y, z):
    """Test Rat addRat"""
    assert rn.eqRat(rn.addRat(x, y), z) is True


# Test subRat
# Boundary values: zero, one, negone
# Representative values: quart1, half, quart3, two
# Properties: right identity, right inverse
# Interface invariant

tests_rrr = [
    (one, zero, one),
    (one, one, zero),
    (one, negone, two),
    (one, two, negone),
    (quart3, half, quart1),
    (quart3, quart1, half)
]
test_ids = [f"{t}" for t in tests_rrr]


@pytest.mark.parametrize("x, y, z", tests_rrr, ids=test_ids)
def test_subRat(x, y, z):
    """Test Rat subRat"""
    assert rn.eqRat(rn.subRat(x, y), z) is True


# Test mulRat
# Boundary values: zero, one, negone
# Representative values: quart1, half, quart3, two
# Properties: associativity, symmetry/commutativity,
#             identity, inverse, zero element
# Interface invariant

tests_rrr = [
    (zero, zero, zero),
    (zero, one, zero),
    (one, zero, zero),
    (one, one, one),
    (one, negone, negone),
    (negone, one, negone),
    (one, two, two),
    (two, one, two),
    (half, two, one),
    (two, half, one),
    (two, quart1, half),
    (quart1, two, half)
]
test_ids = [f"{t}" for t in tests_rrr]


@pytest.mark.parametrize("x, y, z", tests_rrr, ids=test_ids)
def test_mulRat(x, y, z):
    """Test Rat mulRat"""
    assert rn.eqRat(rn.mulRat(x, y), z) is True


# Test divRat
# Division by zero is an error!
# Boundary values: zero, one, negone
# Representative values: half, two, negtwo
# Properties: left zero, right identity, inverse
# Interface invariant

tests_rrr = [
    (zero, one, zero),
    (one, one, one),
    (one, negone, negone),
    (negone, one, negone),
    (one, two, half),
    (one, half, two),
    (half, half, one)
]
test_ids = [f"{t}" for t in tests_rrr]


@pytest.mark.parametrize("x, y, z", tests_rrr, ids=test_ids)
def test_divRat(x, y, z):
    """Test Rat divRat"""
    assert rn.eqRat(rn.divRat(x, y), z) is True


# Test division by zero
@pytest.mark.xfail()
def test_div0():
    """Test divison by zero; raises exception"""
    assert rn.divRat(one, zero).denom() == 0
