    {-# LANGUAGE AllowAmbiguousTypes #-}
    {-# LANGUAGE MultiParamTypeClasses #-}
    {-# LANGUAGE ScopedTypeVariables #-}
    {-# LANGUAGE TypeApplications #-}

    -- | 'Show' and 'Read' properties
    module Test.Validity.Show
        ( showReadSpecOnValid
        , showReadSpec
        , showReadSpecOnArbitrary
        , showReadSpecOnGen
        , showReadRoundTripOnValid
        , showReadRoundTrip
        , showReadRoundTripOnArbitrary
        , showReadRoundTripOnGen
        ) where

    import Data.GenValidity

    import Data.Data

    import Text.Read

    import Test.Hspec
    import Test.QuickCheck

    import Test.Validity.Utils

    -- | Standard test spec for properties of Show and Read instances for valid values
    --
    -- Example usage:
    --
    -- > showReadSpecOnValid @Double
    showReadSpecOnValid ::
           forall a. (Show a, Eq a, Read a, Typeable a, GenValid a)
        => Spec
    showReadSpecOnValid = showReadSpecOnGen @a genValid "valid" shrinkValid

    -- | Standard test spec for properties of Show and Read instances for unchecked values
    --
    -- Example usage:
    --
    -- > showReadSpec @Int
    showReadSpec ::
           forall a. (Show a, Eq a, Read a, Typeable a, GenUnchecked a)
        => Spec
    showReadSpec = showReadSpecOnGen @a genUnchecked "unchecked" shrinkUnchecked

    -- | Standard test spec for properties of Show and Read instances for arbitrary values
    --
    -- Example usage:
    --
    -- > showReadSpecOnArbitrary @Double
    showReadSpecOnArbitrary ::
           forall a. (Show a, Eq a, Read a, Typeable a, Arbitrary a)
        => Spec
    showReadSpecOnArbitrary = showReadSpecOnGen @a arbitrary "arbitrary" shrink

    -- | Standard test spec for properties of Show and Read instances for values generated by a custom generator
    --
    -- Example usage:
    --
    -- > showReadSpecOnGen ((* 2) <$> genValid @Int) "even" (const [])
    showReadSpecOnGen ::
           forall a. (Show a, Eq a, Read a, Typeable a)
        => Gen a
        -> String
        -> (a -> [a])
        -> Spec
    showReadSpecOnGen gen n s =
        describe (unwords ["Show", nameOf @a, "and Read", nameOf @a]) $
        it (unwords ["are implemented such that read . show == id for", n, "values"]) $
        showReadRoundTripOnGen gen s

    -- |
    --
    -- prop> showReadRoundTripOnValid @Rational
    showReadRoundTripOnValid ::
           forall a. (Show a, Eq a, Read a, GenValid a)
        => Property
    showReadRoundTripOnValid =
        showReadRoundTripOnGen (genValid :: Gen a) shrinkValid

    -- |
    --
    -- prop> showReadRoundTrip @Int
    showReadRoundTrip ::
           forall a. (Show a, Eq a, Read a, GenUnchecked a)
        => Property
    showReadRoundTrip =
        showReadRoundTripOnGen (genUnchecked :: Gen a) shrinkUnchecked

    -- |
    --
    -- prop> showReadRoundTripOnArbitrary @Double
    showReadRoundTripOnArbitrary ::
           forall a. (Show a, Eq a, Read a, Arbitrary a)
        => Property
    showReadRoundTripOnArbitrary =
        showReadRoundTripOnGen (arbitrary :: Gen a) shrink

    -- |
    --
    -- prop> showReadRoundTripOnGen (abs <$> genUnchecked :: Gen Int) (const [])
    showReadRoundTripOnGen ::
           (Show a, Eq a, Read a) => Gen a -> (a -> [a]) -> Property
    showReadRoundTripOnGen gen s =
        forAllShrink gen s $ \v -> readMaybe (show v) `shouldBe` Just v