module Test.Trial.Gen
    ( Property
    , genEither
    , genFunction
    , genFunction2
    , genInt
    , genSmallInt
    , genSmallList
    , genTrial

      -- * Trial tree
    , TrialTree (..)
    , evalTrialTree
    , genTrialTree
    ) where

import Control.Applicative (Alternative (..), liftA2)
import Data.DList (DList)
import Data.List.NonEmpty (NonEmpty (..))
import Hedgehog (Gen, PropertyT)

import Trial (Fatality, Trial (..), fiasco, fiascos, result)

import qualified Data.DList as DL
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range


-- | Helper alias for tests.
type Property = PropertyT IO ()

-- | Generate an 'Int'.
genInt :: Gen Int
genInt = Gen.enumBounded

-- | Generate a small list of the given generated elements.
genSmallList :: Gen a -> Gen [a]
genSmallList = Gen.list (Range.linear 0 6)


-- | Generate a small list of the given generated elements.
genSmallDList :: Gen a -> Gen (DList a)
genSmallDList = fmap DL.fromList . genSmallList

-- | Generate a positive 'Int' within the range of @1-6@.
genSmallInt :: Gen Int
genSmallInt = Gen.int (Range.linear 1 6)

-- | Generate a 'Validation'.
genTrial :: Gen a -> Gen (Trial Int a)
genTrial genA = Gen.choice
    [ Fiasco <$> genSmallDList (liftA2 (,) genFatality genInt)
    , Result <$> genSmallDList genInt <*> genA
    ]

genFatality :: Gen Fatality
genFatality = Gen.enumBounded

-- | Generate a simple unary function from the list.
genFunction :: Gen (Int -> Int)
genFunction = genInt >>= \n -> Gen.element
    [ id
    , (+ n)
    , (* n)
    , const n
    , (n -)
    , subtract n
    ]

-- | Generate a simple binary function from the list.
genFunction2 :: Gen (Int -> Int -> Int)
genFunction2 = Gen.element
    [ const
    , (+)
    , (*)
    , (-)
    , subtract
    ]

-- | Generate 'Either' with more frequent 'Right's.
genEither :: Gen e -> Gen a -> Gen (Either e a)
genEither genE genA = Gen.sized $ \n -> Gen.frequency
    [ (2, Left <$> genE)
    , (1 + fromIntegral n, Right <$> genA)
    ]

{- | Data type that represents 'Trial' event tree to create a value of
type @Trial Int a@.
-}
data TrialTree e a
    = SCFiasco e  -- ^ @fiasco@ smart constructor
    | SCFiascos (NonEmpty e)  -- ^ @fiascos@ smart constructor
    | SCResult e a  -- ^ @result@ smart constructor
    | Pure a  -- @pure@ from Applicative
--    | Empty  -- @empty@ from Alternative
    | Append (TrialTree e a) (TrialTree e a)  -- ^ @(<>)@ from Semigroup
    | SeqR (TrialTree e a) (TrialTree e a)  -- ^ @(*>)@ from Applicative
    | Alt (TrialTree e a) (TrialTree e a)  -- ^ @(<|>)@ from Alternative
    deriving stock (Show, Eq)

evalTrialTree :: TrialTree e a -> Trial e a
evalTrialTree = \case
    SCFiasco e -> fiasco e
    SCFiascos es -> fiascos es
    SCResult e a -> result e a
    Pure a -> pure a
--    Empty -> empty
    Append l r -> evalTrialTree l <> evalTrialTree r
    SeqR l r -> evalTrialTree l *> evalTrialTree r
    Alt l r -> evalTrialTree l <|> evalTrialTree r

genTrialTree :: forall e a . Gen e -> Gen a -> Gen (TrialTree e a)
genTrialTree genE genA = Gen.recursive
    Gen.choice
    -- non-recursive generators
    [ SCFiasco <$> genE
    , SCFiascos <$> liftA2 (:|) genE (genSmallList genE)
    , SCResult <$> genE <*> genA
    , Pure <$> genA
--    , pure Empty
    ]
    -- recursive generators
    [ Gen.subterm2 genTree genTree Append
    , Gen.subterm2 genTree genTree SeqR
    , Gen.subterm2 genTree genTree Alt
    ]
  where
    genTree :: Gen (TrialTree e a)
    genTree = genTrialTree genE genA
