{-| __Internal module__: This module does not make any stability guarantees, and
    may not adhere to the PVP.

    This module implements the rose-tree data structure used by StrictCheck to
    monomorphize inputs to functions. We decouple the consumption of input from
    the production of output by converting any input to an @Input@: a lazily
    constructed rose tree with nodes each containing a @(Gen a -> Gen a)@ which
    captures a random perturbation associated with the shape of the value
    consumed. The tree-shape of an @Input@ matches that of the entire consumed
    value, and evaluating any subpart of it forces the evaluation of the
    corresponding part of the original value.
-}
module Test.StrictCheck.Internal.Inputs
  ( Variant(..)
  , Input(..)
  , Inputs(..)
  , draw
  , destruct
  ) where

import Test.QuickCheck (Gen)
import Data.Semigroup


--------------------------------------------------
-- The core user-facing types: Input and Inputs --
--------------------------------------------------

-- | A variant which can be applied to any generator--kept in a newtype to get
-- around lack of impredicativity.
newtype Variant
  = Variant { vary :: forall a. Gen a -> Gen a }

instance Semigroup Variant where
  v <> w = Variant (vary v . vary w)

instance Monoid Variant where
  mappend = (<>)
  mempty = Variant id

-- | A tree representing all possible destruction sequences for a value
-- Unfolding the contained lists forces a particular random control path
-- for destructing the datatype.
data Input
  = Input Variant [Input]  -- ^ Not exposed in safe API

-- | A list of inputs given to a function, in abstract form. This lazy structure
-- is evaluated piecewise during the course of producing a function, thus
-- triggering the partial evaluation of the original input to the function.
newtype Inputs
  = Inputs [Input]  -- ^ Not exposed in safe API

-- | Extract the list of @Input@s from an @Inputs@
destruct :: Inputs -> [Input]
destruct (Inputs is) = is

-- | Extract the entropy and subfield-@Input@s from a given @Input@
draw :: Input -> (Variant, [Input])
draw (Input v is) = (v, is)