-- !!! Testing Int and Word
module Main(main) where
import Data.Int
import Data.Word
import Data.Bits
import Data.Ix -- added SOF
import Control.Exception
import Control.Monad
import Numeric.Natural

main :: IO ()
main = test

test :: IO ()
test = do
   testIntlike "Int"    (0::Int)
   testIntlike "Int8"   (0::Int8)
   testIntlike "Int16"  (0::Int16)
   testIntlike "Int32"  (0::Int32)
   testIntlike "Int64"  (0::Int64)
   testIntlike "Word8"  (0::Word8)
   testIntlike "Word16" (0::Word16)
   testIntlike "Word32" (0::Word32)
   testIntlike "Word64" (0::Word64)
   testInteger
   testNatural

testIntlike :: (Bounded a, Integral a, Ix a, Show a, Read a, Bits a) => String -> a -> IO ()
testIntlike name zero = do
  putStrLn $ "--------------------------------"
  putStrLn $ "--Testing " ++ name
  putStrLn $ "--------------------------------"
  testBounded  zero
  testEnum     zero
  testReadShow zero
  testEq       zero
  testOrd      zero
  testNum      zero
  testReal     zero
  testIntegral zero
  testConversions zero
  testBits     zero True

testInteger  = do
  let zero = 0 :: Integer
  putStrLn $ "--------------------------------"
  putStrLn $ "--Testing Integer"
  putStrLn $ "--------------------------------"
  testEnum     zero
  testReadShow zero
  testEq       zero
  testOrd      zero
  testNum      zero
  testReal     zero
  testIntegral zero
  testBits     zero False

testNatural  = do
  let zero = 0 :: Natural
  putStrLn $ "--------------------------------"
  putStrLn $ "--Testing Natural"
  putStrLn $ "--------------------------------"
  testEnum     zero
  testReadShow zero
  testEq       zero
  testOrd      zero
  testNum      zero
  testReal     zero
  testIntegral zero
  testBits     zero False

-- In all these tests, zero is a dummy element used to get
-- the overloading to work

testBounded zero = do
  putStrLn "testBounded"
  print $ (minBound-1, minBound, minBound+1) `asTypeOf` (zero,zero,zero)
  print $ (maxBound-1, maxBound, maxBound+1) `asTypeOf` (zero,zero,zero)

testEnum zero = do
  putStrLn "testEnum"
  print $ take 10 [zero .. ]           -- enumFrom
  print $ take 10 [zero, toEnum 2 .. ] -- enumFromThen
  print [zero .. toEnum 20]            -- enumFromTo
  print [zero, toEnum 2 .. toEnum 20]  -- enumFromThenTo

testConversions zero = do
  putStrLn "testConversions"
  putStr "Integer : " >> print (map fromIntegral numbers :: [Integer])
  putStr "Int     : " >> print (map fromIntegral numbers :: [Int])
  putStr "Int8    : " >> print (map fromIntegral numbers :: [Int8])
  putStr "Int16   : " >> print (map fromIntegral numbers :: [Int16])
  putStr "Int32   : " >> print (map fromIntegral numbers :: [Int32])
  putStr "Int64   : " >> print (map fromIntegral numbers :: [Int64])
  putStr "Word8   : " >> print (map fromIntegral numbers :: [Word8])
  putStr "Word16  : " >> print (map fromIntegral numbers :: [Word16])
  putStr "Word32  : " >> print (map fromIntegral numbers :: [Word32])
  putStr "Word64  : " >> print (map fromIntegral numbers :: [Word64])
  where numbers = [minBound, 0, maxBound] `asTypeOf` [zero]

isNatural :: (Bits n) => n -> Bool
isNatural zero = not (isSigned zero) && bitSizeMaybe zero == Nothing

samples :: (Bits a, Num a) => a -> [a]
samples zero
  | isNatural zero = map fromInteger [0 .. 3]
  | otherwise      = map fromInteger ([-3 .. -1]++[0 .. 3])

table1 :: (Show a, Show b) => String -> (a -> b) -> [a] -> IO ()
table1 nm f xs = do
  sequence [ f' x | x <- xs ]
  putStrLn "#"
 where
  f' x = putStrLn (nm ++ " " ++ show x ++ " = " ++ show (f x))

table2 :: (Show a, Show b, Show c) => String -> (a -> b -> c) -> [a] -> [b] -> IO ()
table2 nm op xs ys = do
  sequence [ sequence [ op' x y | y <- ys ] >> putStrLn " "
           | x <- xs
           ]
  putStrLn "#"
 where
  op' x y = do s <- Control.Exception.catch
                        (evaluate (show (op x y)))
                        (\e -> return (show (e :: SomeException)))
               putStrLn (show x ++ " " ++ nm ++ " " ++ show y ++ " = " ++ s)

testReadShow zero = do
  putStrLn "testReadShow"
  print xs
  print (map read_show xs)
 where
  xs = samples zero
  read_show x = (read (show x) `asTypeOf` zero)

testEq zero = do
  putStrLn "testEq"
  table2 "==" (==) xs xs
  table2 "/=" (/=) xs xs
 where
  xs = samples zero

testOrd zero = do
  putStrLn "testOrd"
  table2 "<="       (<=)    xs xs
  table2 "< "       (<)     xs xs
  table2 "> "       (>)     xs xs
  table2 ">="       (>=)    xs xs
  table2 "`compare`" compare xs xs
 where
  xs = samples zero

testNum zero = do
  putStrLn "testNum"
  table2 "+"      (+)    xs xs
  table2 "-"      (-)    xs xs
  table2 "*"      (*)    xs xs
  if (isNatural zero)
  then table1 "negate" negate [0 `asTypeOf` zero]
  else table1 "negate" negate xs
 where
  xs = samples zero

testReal zero = do
  putStrLn "testReal"
  table1 "toRational" toRational xs
 where
  xs = samples zero

testIntegral zero = do
  putStrLn "testIntegral"
  table2 "`divMod` " divMod  xs xs
  table2 "`div`    " div     xs xs
  table2 "`mod`    " mod     xs xs
  table2 "`quotRem`" quotRem xs xs
  table2 "`quot`   " quot    xs xs
  table2 "`rem`    " rem     xs xs
 where
  xs = samples zero

testBits zero do_bitsize = do
  putStrLn "testBits"
  table2 ".&.  "            (.&.)         xs xs
  table2 ".|.  "            (.|.)         xs xs
  table2 "`xor`"            xor           xs xs
  unless (isNatural zero) $
    table1 "complement"       complement    xs
  table2 "`shiftL`"         shiftL        xs ([0..3] ++ [32,64])
  table2 "`shiftR`"         shiftR        xs ([0..3] ++ [32,64])
  table2 "`rotate`"         rotate        xs ([-3..3] ++ [-64,-32,32,64])
  table1 "bit"              (\ x -> (bit x) `asTypeOf` zero)   [(0::Int)..3]
  table2 "`setBit`"         setBit        xs ([0..3] ++ [32,64])
  table2 "`clearBit`"       clearBit      xs ([0..3] ++ [32,64])
  table2 "`complementBit`"  complementBit xs ([0..3] ++ [32,64])
  table2 "`testBit`"        testBit       xs ([0..3] ++ [32,64])
  if do_bitsize then table1 "bitSize" bitSize xs else return ()
  table1 "isSigned"         isSigned      xs
 where
  xs = samples zero
