    -- |
    -- Module      : Foundation.Check.Types
    -- License     : BSD-style
    -- Maintainer  : Foundation maintainers
    --
    -- A implementation of a test framework
    -- and property expression & testing
    --
    {-# LANGUAGE ScopedTypeVariables #-}
    {-# LANGUAGE ExistentialQuantification #-}
    {-# LANGUAGE Rank2Types #-}
    {-# LANGUAGE GADTs #-}
    {-# LANGUAGE OverloadedStrings #-}
    {-# LANGUAGE GeneralizedNewtypeDeriving #-}
    module Foundation.Check.Types
        ( Test(..)
        , testName
        , fqTestName
        , groupHasSubGroup
        , Check(..)
        , PlanState(..)
        , PropertyResult(..)
        , TestResult(..)
        , HasFailures
        ) where

    import           Basement.Imports
    import           Foundation.Collection
    import           Foundation.Monad.State
    import           Foundation.Check.Property
    import           Foundation.Check.Gen

    -- | Result of a property run
    data PropertyResult =
          PropertySuccess
        | PropertyFailed  String
        deriving (Show,Eq)

    -- | Name of a test Followed
    data TestResult =
          PropertyResult String HasTests       PropertyResult
        | GroupResult    String HasFailures HasTests [TestResult]
        deriving (Show)

    -- | number of tests and failures
    type HasTests    = CountOf TestResult
    type HasFailures = CountOf TestResult

    data PlanState = PlanState
        { planRng         :: Word64 -> GenRng
        , planValidations :: CountOf TestResult
        , planParams      :: GenParams
        , planFailures    :: [TestResult]
        }

    newtype Check a = Check { runCheck :: StateT PlanState IO a }
        deriving (Functor, Applicative, Monad)
    instance MonadState Check where
        type State Check = PlanState
        withState f = Check (withState f)

    -- | different type of tests supported
    data Test where
        -- Unit test
        Unit      :: String -> IO () -> Test
        -- Property test
        Property  :: IsProperty prop => String -> prop -> Test
        -- Multiples tests grouped together
        Group     :: String -> [Test] -> Test
        -- Check plan
        CheckPlan :: String -> Check () -> Test

    -- | Name of a test
    testName :: Test -> String
    testName (Unit s _)     = s
    testName (Property s _) = s
    testName (Group s _)    = s
    testName (CheckPlan s _) = s

    fqTestName :: [String] -> String
    fqTestName = intercalate "/" . reverse

    groupHasSubGroup :: [Test] -> Bool
    groupHasSubGroup [] = False
    groupHasSubGroup (Group{}:_) = True
    groupHasSubGroup (_:xs) = groupHasSubGroup xs