{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE CPP #-}

module Data.InstanceSpec
  ( spec
  ) where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative ((<*>), pure)
import Data.Functor ((<$>))
#endif
import Data.Data
import Data.Int
#if MIN_VERSION_base(4,9,0)
import Data.List.NonEmpty (NonEmpty)
#endif
import Data.Fixed
import Data.Ratio
import Data.Word

import Control.Monad

import Test.Hspec
import Test.Hspec.Core.QuickCheck (modifyMaxSize, modifyMaxSuccess)
import Test.QuickCheck
#if MIN_VERSION_base(4,8,0)
import GHC.Natural
#endif
import Data.GenValidity

spec :: Spec
spec = do
  twoTests (Proxy :: Proxy ())
  twoTests (Proxy :: Proxy Bool)
  twoTests (Proxy :: Proxy Ordering)
  twoTests (Proxy :: Proxy Char)
  twoTests (Proxy :: Proxy Word)
  twoTests (Proxy :: Proxy Word8)
  twoTests (Proxy :: Proxy Word16)
  twoTests (Proxy :: Proxy Word32)
  twoTests (Proxy :: Proxy Word64)
  twoTests (Proxy :: Proxy Int)
  twoTests (Proxy :: Proxy Int8)
  twoTests (Proxy :: Proxy Int16)
  twoTests (Proxy :: Proxy Int32)
  twoTests (Proxy :: Proxy Int64)
  twoTests (Proxy :: Proxy Integer)
  twoTests (Proxy :: Proxy Float)
  twoTupleTests (Proxy :: Proxy Float)
  twoTests (Proxy :: Proxy Double)
  twoTupleTests (Proxy :: Proxy Double)
  threeTests (Proxy :: Proxy (Ratio Int))
  modifyMaxSuccess (`quot` 2) $
    modifyMaxSize (`quot` 2) $ twoTests (Proxy :: Proxy (Either Bool Ordering))
  twoTests (Proxy :: Proxy (Maybe Ordering))
  twoTests (Proxy :: Proxy (Maybe (Maybe (Ordering))))
  threeTests (Proxy :: Proxy (Ratio Integer))
    -- threeTupleTests (Proxy :: Proxy (Ratio Integer))
  threeTests (Proxy :: Proxy (Ratio Int))
    -- threeTupleTests (Proxy :: Proxy (Ratio Int))
  threeTests (Proxy :: Proxy (Ratio Int8))
  describe "shrinking (Ratio Int)" $
    it "can shrink this example" $
    let v = ((-9223372036854775808) % 9223372036854775761) :: Ratio Int
     in v `notElem` shrinkValid v
  describe "shrinking (Ratio Int8)" $
    it "can shrink this example" $
    let v = ((-128) % 113) :: Ratio Int8
     in v `notElem` shrinkValid v
  twoTests (Proxy :: Proxy Uni)
  twoTupleTests (Proxy :: Proxy Uni)
  twoTests (Proxy :: Proxy Deci)
  twoTupleTests (Proxy :: Proxy Deci)
  twoTests (Proxy :: Proxy Centi)
  twoTupleTests (Proxy :: Proxy Centi)
  twoTests (Proxy :: Proxy Milli)
  twoTupleTests (Proxy :: Proxy Milli)
  twoTests (Proxy :: Proxy Micro)
  twoTupleTests (Proxy :: Proxy Micro)
  twoTests (Proxy :: Proxy Nano)
  twoTupleTests (Proxy :: Proxy Nano)
  twoTests (Proxy :: Proxy Pico)
  twoTupleTests (Proxy :: Proxy Pico)
#if MIN_VERSION_base(4,8,0)
  twoTests (Proxy :: Proxy Natural)

  twoTupleTests (Proxy :: Proxy Natural)
#endif
#if MIN_VERSION_base(4,9,0)
  twoTests (Proxy :: Proxy (NonEmpty Ordering))
#endif
twoTupleTests ::
     forall a. (Show a, Eq a, Typeable a, GenUnchecked a, GenValid a)
  => Proxy a
  -> Spec
twoTupleTests proxy = do
  modifyMaxSuccess (`quot` 2) $ modifyMaxSize (`quot` 2) $ twoTests $ (,) <$> proxy <*> proxy

twoTests ::
     forall a. (Show a, Eq a, Typeable a, GenUnchecked a, GenValid a)
  => Proxy a
  -> Spec
twoTests proxy =
  describe (nameOf proxy) $ do
    genUncheckedTest proxy
    genValidTest proxy

threeTests ::
     forall a. (Show a, Eq a, Typeable a, GenUnchecked a, GenValid a, GenInvalid a)
  => Proxy a
  -> Spec
threeTests proxy =
  describe (nameOf proxy) $ do
    genUncheckedTest proxy
    genValidTest proxy
    genInvalidTest proxy

genUncheckedTest ::
     forall a. (Show a, Eq a, Typeable a, GenUnchecked a, GenValid a)
  => Proxy a
  -> Spec
genUncheckedTest proxy = do
  it (unwords ["genUnchecked of", nameOf proxy, "does not crash while validating"]) $
    forAll genUnchecked $ \a ->
      case prettyValidate (a :: a) of
        Right v -> seq v True
        Left err -> seq err True
  modifyMaxSuccess (`quot` 5) $
    it
      (unwords
         [ "shrinkUnchecked of"
         , nameOf proxy
         , "only produces values that do not crash while validating"
         ]) $
    forAll genUnchecked $ \a ->
      forM_ (shrinkUnchecked a) $ \v ->
        case prettyValidate (v :: a) of
          Right v_ -> seq v_ $ pure () :: IO ()
          Left err -> seq err $ pure ()
  modifyMaxSuccess (`quot` 5) $
    it (unwords ["shrinkUnchecked of", nameOf proxy, "does not shrink to itself"]) $
    forAll genValid $ \a ->
      forM_ (shrinkUnchecked a) $ \a' ->
        unless (a /= a') $
        expectationFailure $ unlines ["The value", show (a :: a), "was shrunk to itself"]

genValidTest ::
     forall a. (Show a, Eq a, Typeable a, GenValid a)
  => Proxy a
  -> Spec
genValidTest proxy = do
  it (unwords ["genValid of", nameOf proxy, "generates only valid values"]) $
    forAll genValid $ \a ->
      case prettyValidate (a :: a) of
        Right v -> seq v $ pure ()
        Left err ->
          expectationFailure $
          unlines ["'validate' reported this value to be invalid:", show a, err, ""]
  modifyMaxSuccess (`quot` 5) $
    it (unwords ["shrinkValid of", nameOf proxy, "shrinks to only valid values"]) $
    forAll genValid $ \a ->
      forM_ (shrinkValid a) $ \v ->
        case prettyValidate (v :: a) of
          Right v_ -> seq v_ $ pure ()
          Left err ->
            expectationFailure $
            unlines ["'validate' reported this value to be invalid:", show v, err, ""]
  modifyMaxSuccess (`quot` 5) $
    it
      (unwords
         ["shrinkValid of", nameOf proxy, "only produces values that do not crash while validating"]) $
    forAll genValid $ \a ->
      forM_ (shrinkValid a) $ \v ->
        case prettyValidate (v :: a) of
          Right v_ -> seq v_ $ pure () :: IO ()
          Left err -> seq err $ pure ()
  modifyMaxSuccess (`quot` 5) $
    it (unwords ["shrinkValid of", nameOf proxy, "does not shrink to itself"]) $
    forAll genValid $ \a ->
      forM_ (shrinkValid a) $ \a' ->
        unless (a /= a') $
        expectationFailure $ unlines ["The value", show (a :: a), "was shrunk to itself"]

genInvalidTest ::
     forall a. (Show a, Typeable a, GenInvalid a)
  => Proxy a
  -> Spec
genInvalidTest proxy = do
  it (unwords ["genInvalid of", nameOf proxy, "generates only invalid values"]) $
    forAll genInvalid $ \a ->
      case prettyValidate (a :: a) of
        Right _ ->
          expectationFailure $ unlines ["'validate' reported this value to be valid: ", show a]
        Left e -> seq e $ pure ()
  modifyMaxSuccess (`quot` 5) $
    it (unwords ["shrinkInvalid of", nameOf proxy, "shrinks to only invalid values"]) $
    forAll genInvalid $ \a ->
      forM_ (shrinkInvalid a) $ \v ->
        case prettyValidate (v :: a) of
          Right _ ->
            expectationFailure $ unlines ["'validate' reported this value to be valid: ", show v]
          Left e -> seq e $ pure ()
  modifyMaxSuccess (`quot` 5) $
    it
      (unwords
         [ "shrinkInvalid of"
         , nameOf proxy
         , "only produces values that do not crash while validating"
         ]) $
    forAll genInvalid $ \a ->
      forM_ (shrinkInvalid a) $ \v ->
        case prettyValidate (v :: a) of
          Right _ ->
            expectationFailure $ unlines ["'validate' reported this value to be valid: ", show v]
          Left e -> seq e $ pure ()

nameOf ::
     forall a. Typeable a
  => Proxy a
  -> String
nameOf = show . typeRep
