{-# 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
