{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Data.GenValidity.GenericSpec
  ( spec,
  )
where

import Control.Monad
import Data.GenValidity
import Data.Proxy
import Data.Typeable
import GHC.Generics (Generic, Rep)
import Test.Hspec
import Test.QuickCheck

spec :: Spec
spec = do
  describe "genValidStructurally" $ do
    genValidstructurallySpec (Proxy :: Proxy Bool)
    genValidstructurallySpec (Proxy :: Proxy Ordering)
    genValidstructurallySpec (Proxy :: Proxy (Maybe Double))
    genValidstructurallySpec (Proxy :: Proxy (Either Double Ordering))
    genValidstructurallySpec (Proxy :: Proxy MyType)
  describe "shrinkValidStructurally" $ do
    shrinkValidstructurallySpec (Proxy :: Proxy Bool)
    shrinkValidstructurallySpec (Proxy :: Proxy Ordering)
    shrinkValidstructurallySpec (Proxy :: Proxy (Maybe Double))
    shrinkValidstructurallySpec (Proxy :: Proxy (Either Double Ordering))
    shrinkValidstructurallySpec (Proxy :: Proxy MyType)

genValidstructurallySpec ::
  forall a.
  (Validity a, Show a, Typeable a, Generic a, GGenValid (Rep a)) =>
  Proxy a ->
  Spec
genValidstructurallySpec proxy =
  it (unwords ["only generates valid", "\"" ++ nameOf proxy ++ "\"s"]) $
    forAll (genValidStructurally :: Gen a) $ \a ->
      case prettyValidate a of
        Right _ -> return ()
        Left err ->
          expectationFailure $
            unlines
              [ "'validate' reported this value to be invalid: ",
                show a,
                "with explanation",
                err,
                ""
              ]

shrinkValidstructurallySpec ::
  forall a.
  ( Show a,
    Eq a,
    Typeable a,
    Generic a,
    GenValid a,
    GValidRecursivelyShrink (Rep a),
    GValidSubterms (Rep a) a
  ) =>
  Proxy a ->
  Spec
shrinkValidstructurallySpec proxy = do
  it (unwords ["only shrinks to valid", "\"" ++ nameOf proxy ++ "\"s"]) $
    forAll (genValid :: Gen a) $ \a ->
      forM_ (shrinkValidStructurally a) $ \subA ->
        case prettyValidate subA of
          Right _ -> return ()
          Left err ->
            expectationFailure $
              unlines
                [ "'validate' reported this value to be invalid: ",
                  show subA,
                  "with explanation",
                  err,
                  "but it should have been valid from shrinking"
                ]
  it
    ( unwords
        ["never shrinks to itself for valid", "\"" ++ nameOf proxy ++ "\"s"]
    )
    $ forAll (genValid :: Gen a) $ \a ->
      forM_ (shrinkValidStructurally a) $ \subA ->
        when (subA == a) $
          expectationFailure $ unlines [show a, "was shrunk to itself."]

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

data MyType
  = MyType Double Ordering
  deriving (Show, Eq, Generic, Typeable)

instance Validity MyType

instance GenValid MyType
