{-# OPTIONS_GHC -fno-warn-tabs #-}
{-|
Module      : Test.QuickCheck.Variant
Description : Variant class
Copyright   : (c) Jorge Santiago Alvarez Cuadros, 2015
License     : GPL-3
Maintainer  : sanjorgek@ciencias.unam.mx
Stability   : experimental
Portability : portable

To get random "invalid" and "valid" data
-}
module Test.QuickCheck.Variant where
import Test.QuickCheck.Arbitrary
import Test.QuickCheck.Exception
import Test.QuickCheck.Gen
import Test.QuickCheck.Property
import Test.QuickCheck.State
import Test.QuickCheck.Text

{-|
You can define

>>> instance (Variant a) => Arbitrary a where {arbitrary = oneof [valid, invalid]}
-}
class Variant a where
  -- |Get a generator of valid random data type
  valid   :: Gen a
  -- |Get a generator of invalid random data type
  invalid :: Gen a

{-|
The class of things wich can be tested with invalid or valid input.
-}
class VarTestable prop where
  -- |Property for valid input
  propertyValid::prop -> Property
  -- |Property for invalid input
  propertyInvalid::prop -> Property

{-|
Same as Testeable
-}
instance VarTestable Bool where
  propertyValid = property
  propertyInvalid = property

mapTotalResultValid :: VarTestable prop => (Result -> Result) -> prop -> Property
mapTotalResultValid f = mapRoseResultValid (fmap f)

-- f here mustn't throw an exception (rose tree invariant).
mapRoseResultValid :: VarTestable prop => (Rose Result -> Rose Result) -> prop -> Property
mapRoseResultValid f = mapPropValid (\(MkProp t) -> MkProp (f t))

mapPropValid :: VarTestable prop => (Prop -> Prop) -> prop -> Property
mapPropValid f = MkProperty . fmap f . unProperty . propertyValid

-- | Adds a callback
callbackValid :: VarTestable prop => Callback -> prop -> Property
callbackValid cb = mapTotalResultValid (\res -> res{ callbacks = cb : callbacks res })

-- | Adds the given string to the counterexample if the property fails.
counterexampleValid :: VarTestable prop => String -> prop -> Property
counterexampleValid s =
  mapTotalResult (\res -> res{ testCase = s:testCase res }) .
  callbackValid (PostFinalFailure Counterexample $ \st _res -> do
    s <- showCounterexampleValid s
    putLine (terminal st) s)

showCounterexampleValid :: String -> IO String
showCounterexampleValid s = do
  let force [] = return ()
      force (x:xs) = x `seq` force xs
  res <- tryEvaluateIO (force s)
  return $
    case res of
      Left err ->
        formatException "Exception thrown while showing test case" err
      Right () ->
        s

-- | Like 'forAll', but tries to shrink the argument for failing test cases.
forAllShrinkValid :: (Show a, VarTestable prop)
             => Gen a -> (a -> [a]) -> (a -> prop) -> Property
forAllShrinkValid gen shrinker pf =
  again $
  MkProperty $
  gen >>= \x ->
    unProperty $
    shrinking shrinker x $ \x' ->
      counterexampleValid (show x') (pf x')

mapTotalResultInvalid :: VarTestable prop => (Result -> Result) -> prop -> Property
mapTotalResultInvalid f = mapRoseResultInvalid (fmap f)

-- f here mustn't throw an exception (rose tree invariant).
mapRoseResultInvalid :: VarTestable prop => (Rose Result -> Rose Result) -> prop -> Property
mapRoseResultInvalid f = mapPropInvalid (\(MkProp t) -> MkProp (f t))

mapPropInvalid :: VarTestable prop => (Prop -> Prop) -> prop -> Property
mapPropInvalid f = MkProperty . fmap f . unProperty . propertyInvalid

-- | Adds a callback
callbackInvalid :: VarTestable prop => Callback -> prop -> Property
callbackInvalid cb = mapTotalResultInvalid (\res -> res{ callbacks = cb : callbacks res })

-- | Adds the given string to the counterexample if the property fails.
counterexampleInvalid :: VarTestable prop => String -> prop -> Property
counterexampleInvalid s =
  mapTotalResult (\res -> res{ testCase = s:testCase res }) .
  callbackInvalid (PostFinalFailure Counterexample $ \st _res -> do
    s <- showCounterexampleInvalid s
    putLine (terminal st) s)

showCounterexampleInvalid :: String -> IO String
showCounterexampleInvalid s = do
  let force [] = return ()
      force (x:xs) = x `seq` force xs
  res <- tryEvaluateIO (force s)
  return $
    case res of
      Left err ->
        formatException "Exception thrown while showing test case" err
      Right () ->
        s

-- | Like 'forAll', but tries to shrink the argument for failing test cases.
forAllShrinkInvalid :: (Show a, VarTestable prop)
             => Gen a -> (a -> [a]) -> (a -> prop) -> Property
forAllShrinkInvalid gen shrinker pf =
  again $
  MkProperty $
  gen >>= \x ->
    unProperty $
    shrinking shrinker x $ \x' ->
      counterexampleInvalid (show x') (pf x')

{-|
Instead of variant we use valid or invalid generators
-}
instance (Arbitrary a, Variant a, Show a, VarTestable prop) => VarTestable (a->prop) where
  propertyValid = forAllShrinkValid valid shrink
  propertyInvalid = forAllShrinkInvalid invalid shrink