    -- |
    -- Module      : Foundation.Check
    -- License     : BSD-style
    -- Maintainer  : Foundation maintainers
    --
    -- An implementation of a test framework
    -- and property expression & testing
    --
    {-# LANGUAGE ScopedTypeVariables #-}
    {-# LANGUAGE ExistentialQuantification #-}
    {-# LANGUAGE Rank2Types #-}
    {-# LANGUAGE GADTs #-}
    module Foundation.Check
        ( Gen
        , Arbitrary(..)
        , oneof
        , elements
        , frequency
        , between
        -- test
        , Test(..)
        , testName
        -- * Property
        , PropertyCheck
        , Property(..)
        , IsProperty(..)
        , (===)
        , propertyCompare
        , propertyCompareWith
        , propertyAnd
        , propertyFail
        , forAll
        -- * Check Plan
        , Check
        , validate
        , pick
        , iterateProperty
        ) where

    import           Basement.Imports
    import           Basement.Cast (cast)
    import           Basement.IntegralConv
    import           Basement.Types.OffsetSize
    import           Foundation.Check.Gen
    import           Foundation.Check.Arbitrary
    import           Foundation.Check.Property
    import           Foundation.Check.Types
    import           Foundation.Check.Print
    import           Foundation.Monad
    import           Foundation.Monad.State
    import           Foundation.Numerical
    import           Control.Exception (evaluate, SomeException)

    validate :: IsProperty prop => String -> prop -> Check ()
    validate propertyName prop = Check $ do
        (genrng, params) <- withState $ \st -> ( (planRng st, planParams st)
                                               , st { planValidations = planValidations st + 1 }
                                               )
        (r,nb) <- liftIO $ iterateProperty 100 params genrng (property prop)
        case r of
            PropertySuccess        -> return ()
            PropertyFailed failMsg -> do
                withState $ \st -> ((), st { planFailures = PropertyResult propertyName nb (PropertyFailed failMsg) : planFailures st })
                return ()

    pick :: String -> IO a -> Check a
    pick _ io = Check $ do
        -- TODO catch most exception to report failures
        r <- liftIO io
        pure r

    iterateProperty :: CountOf TestResult ->  GenParams -> (Word64 -> GenRng) -> Property -> IO (PropertyResult, CountOf TestResult)
    iterateProperty limit genParams genRngIter prop = iterProp 1
      where
        iterProp !iter
          | iter == limit = return (PropertySuccess, iter)
          | otherwise  = do
              r <- liftIO toResult
              case r of
                  (PropertyFailed e, _)               -> return (PropertyFailed e, iter)
                  (PropertySuccess, cont) | cont      -> iterProp (iter+1)
                                          | otherwise -> return (PropertySuccess, iter)
            where
              iterW64 :: Word64
              iterW64 = let (CountOf iter') = iter in cast (integralUpsize iter' :: Int64)

              -- TODO revisit to let through timeout and other exception like ctrl-c or thread killing.
              toResult :: IO (PropertyResult, Bool)
              toResult = (propertyToResult <$> evaluate (runGen (unProp prop) (genRngIter iterW64) genParams))
                `catch` (\(e :: SomeException) -> return (PropertyFailed (show e), False))