{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
module FromIntegerSpec where
import           Control.Monad
import           Data.Int
import           Data.Proxy
import           Data.Word
import           Numeric.Rounded.Hardware.Internal
import           Test.Hspec
import           Test.Hspec.QuickCheck (prop)
import           Test.QuickCheck
import           Util

prop_fromInteger_nearest_stock :: forall a. (RealFloat a, RoundedRing a) => Proxy a -> Integer -> Property
prop_fromInteger_nearest_stock _proxy x
  = (roundedFromInteger ToNearest x :: a)
    `sameFloatP` (fromInteger x :: a)

prop_roundedFromInteger_check :: forall a. (RealFloat a, RoundedRing a) => Proxy a -> RoundingMode -> Integer -> Property
prop_roundedFromInteger_check _proxy r x
  = (roundedFromInteger r x :: a)
    `sameFloatP` (roundedFromInteger_default r x :: a)

prop_roundedFromInt64_check :: forall a. (RealFloat a, RoundedRing a) => Proxy a -> RoundingMode -> Int64 -> Property
prop_roundedFromInt64_check _proxy r x
  = (roundedFromInteger r (fromIntegral x) :: a)
    `sameFloatP` (roundedFromInteger_default r (fromIntegral x) :: a)

prop_roundedFromWord64_check :: forall a. (RealFloat a, RoundedRing a) => Proxy a -> RoundingMode -> Word64 -> Property
prop_roundedFromWord64_check _proxy r x
  = (roundedFromInteger r (fromIntegral x) :: a)
    `sameFloatP` (roundedFromInteger_default r (fromIntegral x) :: a)

prop_roundedFromInteger_order :: forall a. (RealFloat a, RoundedRing a) => Proxy a -> Integer -> Property
prop_roundedFromInteger_order _proxy x
  = let ne   = roundedFromInteger ToNearest    x :: a
        ze   = roundedFromInteger TowardZero   x :: a
        inf  = roundedFromInteger TowardInf    x :: a
        ninf = roundedFromInteger TowardNegInf x :: a
    in ninf <= inf
       .&&. (ne == ninf || ne == inf)
       .&&. (if x < 0 then ze == inf else ze == ninf)

prop_roundedFromInteger_exact :: forall a. (RealFloat a, RoundedRing a) => Proxy a -> Integer -> Property
prop_roundedFromInteger_exact _proxy x
  = let inf  = roundedFromInteger TowardInf    x :: a
        ninf = roundedFromInteger TowardNegInf x :: a
    in if ninf == inf
       then not (isInfinite inf) .&&. toRational inf === fromInteger x
       else if isInfinite inf
            then inf > 0
                 .&&. not (isInfinite ninf)
                 .&&. toRational ninf =/= fromInteger x
            else if isInfinite ninf
                 then ninf < 0
                      .&&. not (isInfinite inf)
                      .&&. toRational inf =/= fromInteger x
                 else toRational inf =/= fromInteger x
                      .&&. toRational ninf =/= fromInteger x

specT :: forall a. (RealFloat a, RoundedRing a) => Proxy a -> Bool -> Spec
specT proxy checkAgainstStock = do
  when checkAgainstStock $ do
    prop "fromInteger (nearest) coincides with stock fromInteger" $
      -- fromInteger for Double/Float do not necessarily round to nearest.
      forAllShrink variousIntegers shrinkIntegral (prop_fromInteger_nearest_stock proxy)
  prop "roundedFromInteger coincides with the standard implementation" $ \r ->
    forAllShrink variousIntegers shrinkIntegral (prop_roundedFromInteger_check proxy r)
  prop "roundedFromInteger/Int64" $ \r ->
    prop_roundedFromInt64_check proxy r
  prop "roundedFromInteger/Word64" $ \r ->
    prop_roundedFromWord64_check proxy r
  prop "order" $
    forAllShrink variousIntegers shrinkIntegral (prop_roundedFromInteger_order proxy)
  prop "exactness" $
    forAllShrink variousIntegers shrinkIntegral (prop_roundedFromInteger_exact proxy)

spec :: Spec
spec = do
  describe "Double" $ specT (Proxy :: Proxy Double) False
  describe "Float" $ specT (Proxy :: Proxy Float) False
