{-# LANGUAGE CPP #-}

import Language.C.Quote.C
import Language.C.Inline.C
import Language.Haskell.TH
import Test.Hspec
import Test.HUnit (assertBool)

import Test.PosixC


main :: IO ()
main = do
    posixCInit
#if (__APPLE__ && __MACH__)
    hspec $ do
        -- The PosixC test suite should also run on Mac, but the ObjC one
        -- needs Apple's ObjC.
        testPosixC
        testObjC
#else
    hspec
        testPosixC
#endif

#if (__APPLE__ && __MACH__)
testObjC :: Spec
testObjC =
    describe "ObjC:" $
        error "Not ObjC test suite defined yet."
#endif

-- Hack together a simple type class to represent
-- approximate equality for floating-point types.
-- It is only intended for the simple cases used here;
-- I assume there's something out there on Hackage
-- which has a more-principled approach.
--
class EqualIsh a where
    -- | Returns `True` if the two values are close
    --   enough to be considered equal.
    (=~) :: a -> a -> Bool
    (=~) a b = not $ a /=~ b

    -- | Returns `True` if the two values are not close
    --   enough to be considered equal.
    (/=~) :: a -> a -> Bool
    (/=~) a b = not $ a =~ b

instance EqualIsh a => EqualIsh [a] where
    (=~) = go
        where
          go [] [] = True
          go (a:as) (b:bs) | a =~ b    = go as bs
                           | otherwise = False
          go _ _ = False

-- | This is a very naive definition of approximate
--   eqaulity (absolute difference within 1e-15;
--   this value is just picked at random rather than
--   based on anything sensible like the ulp or a
--   fractional difference).
instance EqualIsh Double where
    a =~ b = let d = abs $ a - b
             in  d < 1e-15

-- | This is a very naive definition of approximate
--   eqaulity (absolute difference within 5e-7;
--   this value is just picked at random rather than
--   based on anything sensible like the ulp or a
--   fractional difference).
instance EqualIsh Float where
    a =~ b = let d = abs $ a - b
             in  d < 5e-7

-- |
-- @actual \`shouldBeIsh\` expected@ sets the expectation that @actual@ is equal
-- to @expected@ (as defined by the `EqualIsh` instance).
shouldBeIsh :: (Show a, EqualIsh a) => a -> a -> Expectation
actual `shouldBeIsh` expected = 
    assertBool 
    ("Value " ++ show actual ++ " not equalIsh to " ++ show expected)
    (actual =~ expected)

-- |
-- @action \`shouldReturnIsh\` expected@ sets the expectation that @action@
-- returns @expected@ (as defined by the `EqualIsh` instance).
shouldReturnIsh :: (Show a, EqualIsh a) => IO a -> a -> Expectation
action `shouldReturnIsh` expected = action >>= (`shouldBeIsh` expected)

testPosixC :: Spec
testPosixC =
    describe "PosixC:" $ do
        it "Can pass an Int through a C function." $
            mapM cPlusOne [0, 1, 2] `shouldReturn` [ 1, 2, 3 ]
        it "Can compare two strings using C strcmp." $ do
            cStringCompare "abc" "bbc" `shouldReturn` LT
            cStringCompare "bbc" "abc" `shouldReturn` GT
            cStringCompare "abc" "abc" `shouldReturn` EQ
            cStringCompare ""    ""    `shouldReturn` EQ
        it "Can retreve Strings from C code." $
            cGetString `shouldReturn` "Hello Haskell"
        it "Can pass Strings through C code.." $ do
            cPassString "abc" `shouldReturn` "abc"
            cPassString "abcde" `shouldReturn` "abcde"
            cPassString "" `shouldReturn` ""

        -- type specifiers are not required; added for
        -- documentation
        it "Can pass a Double through a C function.." $
            mapM cSin [0, pi/2.0, pi] `shouldReturnIsh` [ 0.0, 1.0, 0.0 :: Double ]
        it "Can pass a Float through a C function.." $
            mapM cSinF [0, pi/2.0, pi] `shouldReturnIsh` [ 0.0, 1.0, 0.0 :: Float ]
        it "Can pass a Double through a slightly-complicated C function.." $
            mapM cInvertSin [0.0, 1.0] `shouldReturnIsh` [0.0, 1.0 :: Double ]
        it "Can pass a Float through a slightly-complicated C function.." $
            mapM cInvertSinF [0.0, 1.0] `shouldReturnIsh` [0.0, 1.0 :: Float ]
