-- |
-- 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
    , 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))