{-  Exploring Languages with Interpreters and Functional Programming
    Chapter 7 & 12: Rational Rep Test Script (Core)
    Copyright (C) 2018, H. Conrad Cunningham

1234567890123456789012345678901234567890123456789012345678901234567890

2018-07-04: Developed test script for RationalCore
2018-07-21: Added new tests/comments to align with Chapter 12

This module is a test script for the RationalRep interface. This
variant imports implementation module RationalCore.

This module uses black-box testing methods to conduct functional-type
unit tests on the RationalRep interface.

-}

module TestRatRepCore
where

-- Select data representation module to test
import RationalCore         -- for TestRatRepCore
-- import RationalDeferGCD  -- for TestRatRepDefer

-- For catching Exceptions
import Prelude hiding (catch)
import Control.Exception

-- To display whether test passed for failed
pass :: Bool -> String
pass True  = "PASS"
pass False = "FAIL"

maxInt  = (maxBound :: Int)
minInt  = (minBound :: Int)
twocomp = minInt + 1 == -maxInt

-- IO program test script
main :: IO ()
main =
    do

        -- Display the named constants
        putStrLn "\nNamed constants used in tests"
        putStrLn ("maxInt = (maxBound::Int) = " ++ show maxInt)
        putStrLn ("minInt = (minBound::Int) = " ++ show minInt)
        putStrLn ("Likely two's complement arithmetic?       " ++
                  show twocomp)

        -- makeRat :: Int -> Int -> Rat
        -- numer, denom :: Rat -> Int
        putStrLn "\nBEGIN testing makeRat, numer, and denom"

        putStrLn "\nTesting inputs along x-axis"
        
        -- Test origin input 0/0
        -- Gives 0 denominator error
        putStrLn ("makeRat 0 0 is error:                     "
                  ++ show (makeRat 0 0))
            `catch` (\(ErrorCall msg)
                     -> putStrLn ("[Error Call] (EXPECTED)\n"
                                      ++ msg))
 
        -- Test other inputs on x-axis boundary 1/0, -1/0
        -- Gives 0 denominator error
        putStrLn ("makeRat 1 0 is error:                     "
                  ++ show (makeRat 1 0))
            `catch` (\(ErrorCall msg)
                     -> putStrLn ("[Error Call] (EXPECTED)\n"
                                      ++ msg))
          
        putStrLn ("makeRat (-1) 0 is error:                  "
                  ++ show (makeRat (-1) 0))
            `catch` (\(ErrorCall msg)
                         -> putStrLn ("[Error Call] (EXPECTED)\n"
                                      ++ msg))

        -- The code below creates each test object (e.g. "makeRat 0 1"
        -- twice. These could be created once and then used twice.
        
        -- Test other inputs on y-axis boundary
        -- Test 0/1, 0/-1, 0/9, 0/-9; all become 0/1
        putStrLn "\nTesting inputs along y-axis"
        putStrLn ("numer (makeRat 0 1) == 0:                 " ++
                  pass (numer (makeRat 0 1) == 0))
        putStrLn ("denom (makeRat 0 1) == 1:                 " ++
                  pass (denom (makeRat 0 1) == 1))
          
        putStrLn ("numer (makeRat 0 (-1)) == 0:              " ++
                  pass (numer (makeRat 0 (-1)) == 0))
        putStrLn ("denom (makeRat 0 (-1)) == 1:              " ++
                  pass (denom (makeRat 0 (-1)) == 1))
 
        putStrLn ("numer (makeRat 0 9) == 0:                 " ++
                  pass (numer (makeRat 0 9) == 0))
        putStrLn ("denom (makeRat 0 9) == 1:                 " ++
                  pass (denom (makeRat 0 9) == 1))

        putStrLn ("numer (makeRat 0 (-9)) == 0:              " ++
                  pass (numer (makeRat 0 (-9)) == 0))
        putStrLn ("denom (makeRat 0 (-9)) == 1:              " ++
                  pass (denom (makeRat 0 (-9)) == 1))
        
        -- Test inputs from 1st & 3rd quadrants, same integer output
        -- Test 1/1, -1/-1,  9/9, -9/-9, maxInt/maxInt,
        -- -maxInt/-maxInt, minInt/minInt; all become 1/1
        putStrLn "\nTesting inputs in 1st/3rd quadrants, value 1/1"
        putStrLn ("numer (makeRat 1 1) == 1:                 " ++
                  pass (numer (makeRat 1 1) == 1))
        putStrLn ("denom (makeRat 1 1) == 1:                 " ++
                  pass (denom (makeRat 1 1) == 1))

        putStrLn ("numer (makeRat (-1) (-1)) == 1:           " ++
                  pass (numer (makeRat (-1) (-1)) == 1))
        putStrLn ("denom (makeRat (-1) (-1)) == 1:           " ++
                  pass (denom (makeRat (-1) (-1)) == 1))

        putStrLn ("numer (makeRat 9 9) == 1:                 " ++
                  pass (numer (makeRat 9 9) == 1))
        putStrLn ("denom (makeRat 9 9) == 1:                 " ++
                  pass (denom (makeRat 9 9) == 1))
          
        putStrLn ("numer (makeRat (-9) (-9)) == 1:           " ++
                  pass (numer (makeRat (-9) (-9)) == 1))
        putStrLn ("denom (makeRat (-9) (-9)) == 1:           " ++
                  pass (denom (makeRat (-9) (-9)) == 1))

        putStrLn ("numer (makeRat maxInt maxInt) == 1:       " ++
                  pass (numer (makeRat maxInt maxInt) == 1))
        putStrLn ("denom (makeRat maxInt maxInt) == 1:       " ++
                  pass (denom (makeRat maxInt maxInt) == 1))

        putStrLn ("numer (makeRat (-maxInt) (-maxInt)) == 1: " ++
                  pass (numer (makeRat (-maxInt) (-maxInt)) == 1))
        putStrLn ("denom (makeRat (-maxInt) (-maxInt)) == 1: " ++
                  pass (denom (makeRat maxInt maxInt) == 1))

        -- If two's complement, then Int overflow would give
        -- expected result for wrong reason
        putStrLn "** Two's complement overflow would give "
        putStrLn "   'expected' result for 'unexpected' reason"
        putStrLn "   in next 4 tests."
        putStrLn ("Likely two's complement arithmetic?       " ++
                  show twocomp)

        putStrLn ("numer (makeRat minInt minInt) == 1:       " ++
                  pass (numer (makeRat minInt minInt) == 1))
        putStrLn ("denom (makeRat minInt minInt) == 1:       " ++
                  pass (denom (makeRat minInt minInt) == 1))

        putStrLn ("numer (makeRat (-minInt) (-minInt)) == 1: " ++
                  pass (numer (makeRat (-minInt) (-minInt)) == 1))
        putStrLn ("denom (makeRat (-minInt) (-minInt)) == 1: " ++
                  pass (denom (makeRat minInt minInt) == 1))

        -- Test inputs from 2nd & 4th quadrants, same integer output
        -- Test -1/1, -9/9, 1/-1, 9/-9, -maxInt/maxInt, maxInt/-maxInt
        --   -minInt/minInt, minInt/-minInt; all become -1/1
        putStrLn "\nTesting inputs in 2nd/4th quadrants, value -1/1"
        putStrLn ("numer (makeRat (-1) 1) == -1:             " ++
                  pass (numer (makeRat (-1) 1) == -1))
        putStrLn ("denom (makeRat (-1) 1) == 1:              " ++
                  pass (denom (makeRat (-1) 1) == 1))

        putStrLn ("numer (makeRat 1 (-1)) == -1:             " ++
                  pass (numer (makeRat 1 (-1)) == -1))
        putStrLn ("denom (makeRat 1 (-1)) == 1:              " ++
                  pass (denom (makeRat 1 (-1)) == 1))
 
        putStrLn ("numer (makeRat (-9) 9) == -1:             " ++
                  pass (numer (makeRat (-9) 9) == -1))
        putStrLn ("denom (makeRat (-9) 9) == 1:              " ++
                  pass (denom (makeRat (-9) 9) == 1))

        putStrLn ("numer (makeRat 9 (-9)) == -1:             " ++
                  pass (numer (makeRat 9 (-9)) == -1))
        putStrLn ("denom (makeRat 9 (-9)) == 1:              " ++
                  pass (denom (makeRat 9 (-9)) == 1))

        putStrLn ("numer (makeRat (-maxInt) maxInt) == -1:   " ++
                  pass (numer (makeRat (-maxInt) maxInt) == -1))
        putStrLn ("denom (makeRat (-maxInt) maxInt) == 1:    " ++
                  pass (denom (makeRat (-maxInt) maxInt) == 1))

        putStrLn ("numer (makeRat maxInt (-maxInt)) == -1:   " ++
                  pass (numer (makeRat maxInt (-maxInt)) == -1))
        putStrLn ("denom (makeRat maxInt (-maxInt)) == 1:    " ++
                  pass (denom (makeRat maxInt (-maxInt)) == 1))

        -- If two's complement, then Int overflow would give
        -- unexpected result
        putStrLn "** Two's complement overflow would give "
        putStrLn "   'unexpected' result in next 4 tests."
        putStrLn "PASSing result stated for two's complement."
        putStrLn ("Likely two's complement arithmetic?       " ++
                  show twocomp)

        -- Expect -1 and 1?
        putStrLn ("numer (makeRat minInt (-minInt)) == 1:    " ++
                  pass (numer (makeRat minInt (-minInt)) == 1))
        putStrLn ("denom (makeRat minInt (-minInt)) == 1:    " ++
                  pass (denom (makeRat minInt (-minInt)) == 1))

        -- Expect -1 and 1?
        putStrLn ("numer (makeRat (-minInt) minInt) == 1:    " ++
                  pass (numer (makeRat (-minInt) minInt) == 1))
        putStrLn ("denom (makeRat (-minInt) minInt) == 1:    " ++
                  pass (denom (makeRat (-minInt) minInt) == 1))

        -- Test more inputs, not integer result
        
        -- 1st and 3rd quadrants
        -- Test 3/2, -3/-2, 12/8, -12/-8; all become 3/2
        putStrLn "\nTesting inputs in 1st/3rd quadrants, value -3/2"
        putStrLn ("numer (makeRat 3 2) == 3:                 " ++
                  pass (numer (makeRat 3 2) == 3))
        putStrLn ("denom (makeRat 3 2) == 2:                 " ++
                  pass (denom (makeRat 3 2) == 2))

        putStrLn ("numer (makeRat (-3) (-2)) == 3:           " ++
                  pass (numer (makeRat (-3) (-2)) == 3))
        putStrLn ("denom (makeRat (-3) (-2)) == 2:           " ++
                  pass (denom (makeRat (-3) (-2)) == 2))
                  
        putStrLn ("numer (makeRat 12 8) == 3:                " ++
                  pass (numer (makeRat 12 8) == 3))
        putStrLn ("denom (makeRat 12 8) == 2:                " ++
                  pass (denom (makeRat 12 8) == 2))

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

        putStrLn ("numer (makeRat 3 (-2)) == -3:             " ++
                  pass (numer (makeRat 3 (-2)) == -3))
        putStrLn ("denom (makeRat 3 (-2)) == 2:              " ++
                  pass (denom (makeRat 3 (-2)) == 2))

        putStrLn ("numer (makeRat (-12) 8) == -3:            " ++
                  pass (numer (makeRat (-12) 8) == -3))
        putStrLn ("denom (makeRat (-12) 8) == 2:             " ++
                  pass (denom (makeRat (-12) 8) == 2))

        putStrLn ("numer (makeRat 12 (-8)) == -3:            " ++
                  pass (numer (makeRat 12 (-8)) == -3))
        putStrLn ("denom (makeRat 12 (-8)) == 2:             " ++
                  pass (denom (makeRat 12 (-8)) == 2))

        -- Test maximum Int 
        putStrLn "\nTesting maximum integer"
        putStrLn ("numer (makeRat maxInt 1) == maxInt:       " ++
                  pass (numer (makeRat maxInt 1) == maxInt))
        putStrLn ("denom (makeRat maxInt 1) == 1:            " ++
                  pass (denom (makeRat maxInt 1) == 1))

        putStrLn ("numer (makeRat maxInt (-1)) == -maxInt:   " ++
                  pass (numer (makeRat maxInt (-1)) == -maxInt))
        putStrLn ("denom (makeRat maxInt (-1)) == 1:         " ++
                  pass (denom (makeRat maxInt (-1)) == 1))

        putStrLn ("numer (makeRat (-maxInt 1)) == -maxInt:   " ++
                  pass (numer (makeRat (-maxInt) 1) == -maxInt))
        putStrLn ("denom (makeRat (-maxInt 1)) == 1:         " ++
                  pass (denom (makeRat (-maxInt) 1) == 1))

        putStrLn ("numer (makeRat maxInt (-1)) == -maxInt:   " ++
                  pass (numer (makeRat maxInt (-1)) == -maxInt))
        putStrLn ("denom (makeRat maxInt (-1)) == 1:         " ++
                  pass (denom (makeRat maxInt (-1)) == 1))

        -- Test minimum Int 
        putStrLn "\nTesting minimum integer"
        putStrLn ("numer (makeRat minInt 1) == minInt:       " ++
                  pass (numer (makeRat maxInt 1) == maxInt))
        putStrLn ("denom (makeRat minInt 1) == 1:            " ++
                  pass (denom (makeRat minInt 1) == 1))

        putStrLn "** Two's complement overflow would give "
        putStrLn "   'unexpected' result in next 2 tests."
        putStrLn "PASSing result stated for two's complement."
        putStrLn ("Likely two's complement arithmetic?       " ++
                  show twocomp)

        putStrLn ("numer (makeRat minInt (-1)) == minInt:    " ++
                  pass (numer (makeRat minInt (-1)) == minInt))
        putStrLn ("denom (makeRat minInt (-1)) == 1:         " ++
                  pass (denom (makeRat minInt (-1)) == 1))

        putStrLn "\nEND testing makeRat, numer, and denom"

        -- Test invalid input directly to numer/denom
        putStrLn "\nInvalid input directly to numer & denom"
        putStrLn "Result depends on module implementation"

        putStrLn ("numer (1,0) == 1:                         " ++
                  pass (numer (1,0) == 1))
            `catch` (\(DivideByZero) -> putStrLn "[Divide by Zero]")
        putStrLn ("denom (1,0) == 0:                         " ++
                  pass (denom (1,0) == 0))
            `catch` (\(DivideByZero) -> putStrLn "[Divide by Zero]")

        -- Test showRat
        -- showRat :: Rat -> String
        putStrLn "\nTesting showRat"
        putStrLn ("showRat (makeRat 12 (-8)) == '-12/8':     " ++
                 pass (showRat (makeRat 12 (-8)) == "-3/2"))
          

        -- Test invalid input directly to numer/denom
        putStrLn "\nInvalid input directly to numer & denom"
        putStrLn "Result depends on RationalRep module implementation"

        putStrLn ("showRat (makeRat 0 (-8)) == '0/1':        " ++
                 pass (showRat (makeRat 0 (-8)) == "0/1"))
          
        putStrLn ("showRat zeroRat == '0/1':                 " ++
                  pass ((showRat zeroRat) == "0/1"))

        -- Test invalid input directly to showRat
        putStrLn "\nInvalid input directly to showRat"
        putStrLn "Result depends on RationalRep module implementation"

        putStrLn ("showRat (1,0) == '1/0':                   " ++
                  pass ((showRat (1,0)) == "1/0"))
            `catch` (\(DivideByZero) -> putStrLn "[Divide by Zero]")

