{-# LANGUAGE NegativeLiterals #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Data.InstanceSpec
  ( spec,
  )
where

import Control.Monad
import Data.Data
import Data.Fixed
import Data.GenValidity
import Data.Int
import Data.List.NonEmpty (NonEmpty)
import Data.Ratio
import Data.Word
import Numeric.Natural
import Test.Hspec
import Test.Hspec.Core.QuickCheck (modifyMaxSize, modifyMaxSuccess)
import Test.QuickCheck

spec :: Spec
spec = do
  genValidTest (Proxy :: Proxy ())
  genValidTest (Proxy :: Proxy Bool)
  genValidTest (Proxy :: Proxy Ordering)
  genValidTest (Proxy :: Proxy Char)
  genValidTest (Proxy :: Proxy Word)
  genValidTest (Proxy :: Proxy Word8)
  genValidTest (Proxy :: Proxy Word16)
  genValidTest (Proxy :: Proxy Word32)
  genValidTest (Proxy :: Proxy Word64)
  genValidTest (Proxy :: Proxy Int)
  genValidTest (Proxy :: Proxy Int8)
  genValidTest (Proxy :: Proxy Int16)
  genValidTest (Proxy :: Proxy Int32)
  genValidTest (Proxy :: Proxy Int64)
  genValidTest (Proxy :: Proxy Integer)
  genValidTest (Proxy :: Proxy Float)
  tupleTest (Proxy :: Proxy Float)
  -- Regression tests
  describe "shrinkValid Float" $ do
    let sf :: Float -> Spec
        sf f = it (unwords ["Does not shrink", show f, "to itself"]) $ f `shouldNotSatisfy` (`elem` shrinkValid f)

    sf (-2.1393704e20)
    sf 1.2223988e-12
    sf 2.7896812e10
  describe "shrinkValid Double" $ do
    let sd :: Double -> Spec
        sd d = it (unwords ["Does not shrink", show d, "to itself"]) $ d `shouldNotSatisfy` (`elem` shrinkValid d)
    sd (-1.032730679986007e18)
  genValidTest (Proxy :: Proxy Double)
  tupleTest (Proxy :: Proxy Double)
  genValidTest (Proxy :: Proxy (Ratio Int))
  modifyMaxSuccess (`quot` 2) $
    modifyMaxSize (`quot` 2) $ genValidTest (Proxy :: Proxy (Either Bool Ordering))
  genValidTest (Proxy :: Proxy (Maybe Ordering))
  genValidTest (Proxy :: Proxy (Maybe (Maybe (Ordering))))
  genValidTest (Proxy :: Proxy (Ratio Integer))
  -- threeTupleTests (Proxy :: Proxy (Ratio Integer))
  genValidTest (Proxy :: Proxy (Ratio Int))
  -- threeTupleTests (Proxy :: Proxy (Ratio Int))
  genValidTest (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
  genValidTest (Proxy :: Proxy Uni)
  tupleTest (Proxy :: Proxy Uni)
  genValidTest (Proxy :: Proxy Deci)
  tupleTest (Proxy :: Proxy Deci)
  genValidTest (Proxy :: Proxy Centi)
  tupleTest (Proxy :: Proxy Centi)
  genValidTest (Proxy :: Proxy Milli)
  tupleTest (Proxy :: Proxy Milli)
  genValidTest (Proxy :: Proxy Micro)
  tupleTest (Proxy :: Proxy Micro)
  genValidTest (Proxy :: Proxy Nano)
  tupleTest (Proxy :: Proxy Nano)
  genValidTest (Proxy :: Proxy Pico)
  tupleTest (Proxy :: Proxy Pico)
  genValidTest (Proxy :: Proxy Natural)
  tupleTest (Proxy :: Proxy Natural)
  genValidTest (Proxy :: Proxy (NonEmpty Ordering))

tupleTest ::
  forall a.
  (Show a, Eq a, Typeable a, GenValid a) =>
  Proxy a ->
  Spec
tupleTest proxy = do
  modifyMaxSuccess (`quot` 2) $ modifyMaxSize (`quot` 2) $ genValidTest $ (,) <$> proxy <*> proxy

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"]

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