{-# LANGUAGE TypeOperators #-}

-- | Random generation of higher-order functions.
--
-- === Warning
--
-- This is an internal module: it is not subject to any versioning policy,
-- breaking changes can happen at any time.
-- It is made available only for debugging.
-- Otherwise, use "Test.Fun".
--
-- If something here seems useful, please open an issue to export it from an
-- external module.
--
-- === Fun fact
--
-- This module only uses an 'Applicative' constraint on the type of
-- generators (which is really QuickCheck's @Gen@).

module Test.Fun.Internal.CoGen where

import Control.Applicative (liftA2, liftA3)

import Test.Fun.Internal.Types

-- * Cogenerators

-- | A \"cogenerator\" of @a@ is a random generator of functions with domain @a@.
-- They are parameterized by a generator in the codomain @r@.
--
-- More generally, we can make cogenerators to generate functions of arbitrary arities;
-- @'Co' gen a r@ is only the type of unary cogenerators.
--
-- > gen r -> gen (a :-> r)         -- Co gen a r
-- > gen r -> gen (a :-> b :-> r)
-- > gen r -> gen (a :-> b :-> c :-> r)
-- > gen r -> gen (a :-> b :-> c :-> d :-> r)
-- >
-- > -- etc.
--
-- === __More details__
--
-- Cogenerators can be composed using 'id' and @('.')@ (the usual combinators
-- for functions).
-- The arity of a cogenerator @f '.' g@ is the sum of the arities of @f@ and @g@.
--
-- @
-- id  ::  forall r. gen r -> gen r  -- 0-ary cogenerator
--
-- -- (1-ary) . (1-ary) = (2-ary)
-- (.) :: (forall r. gen r -> gen (a :-> r)) ->
--        (forall r. gen r -> gen (b :-> r)) ->
--        (forall r. gen r -> gen (a :-> b :-> r))
--
-- -- (2-ary) . (1-ary) = (3-ary)
-- (.) :: (forall r. gen r -> gen (a :-> b :-> r)) ->
--        (forall r. gen r -> gen (c :-> r)) ->
--        (forall r. gen r -> gen (a :-> b :-> c :-> r))
-- @
--
-- Note: the last type parameter @r@ should really be universally quantified
-- (as in the above pseudo type signatures), but instead we use more specialized
-- types to avoid making types higher-ranked.
type Co gen a r = gen r -> gen (a :-> r)

-- | Cogenerator for a type @a@ from a cogenerator for @b@,
-- given an embedding function @(a -> b)@,
-- and a name for that function (used for pretty-printing).
--
-- === __Example__
--
-- The common usage is to construct cogenerators for newtypes.
--
-- @
-- -- Given some cogenerator of Fruit
-- cogenFruit :: 'Co' Gen Fruit r
--
-- -- Wrap Fruit in a newtype
-- newtype Apple = Apple { unApple :: Fruit }
--
-- cogenApple :: 'Co' Gen Apple r
-- cogenApple = 'cogenEmbed' \"unApple\" cogenFruit
-- @
--
-- If @cogenFruit@ generates a function that looks like:
--
-- > \y -> case y :: Fruit of { ... }
--
-- then @cogenApple@ will look like this,
-- where @y@ is replaced with @unApple x@:
--
-- > \x -> case unApple x :: Fruit of { ... }
--
cogenEmbed :: Functor gen => FunName -> (a -> b) -> Co gen b r -> Co gen a r
cogenEmbed fn f cog g = (ToShrink . Apply fn f) <$> cog g

-- | Cogenerator for an integral type.
-- The name of the type is used for pretty-printing.
--
-- === __Example__
--
-- @
-- cogenInteger :: 'Co' Gen 'Integer' r
-- cogenInteger = 'cogenIntegral' \"Integer\"
--
-- cogenInt :: 'Co' Gen 'Int' r
-- cogenInt = 'cogenIntegral' \"Int\"
--
-- cogenWord :: 'Co' Gen 'Word' r
-- cogenWord = 'cogenIntegral' \"Word\"
-- @
cogenIntegral :: (Applicative gen, Integral a) => TypeName -> Co gen a r
cogenIntegral tn = cogenIntegral' tn toInteger

-- | Variant of 'cogenIntegral' with an explicit conversion to 'Integer'.
cogenIntegral' :: Applicative gen => TypeName -> (a -> Integer) -> Co gen a r
cogenIntegral' tn f g = liftA2 (CaseInteger tn f) (genBin g) g

genBin :: Applicative gen => gen r -> gen (Bin r)
genBin g = BinToShrink <$> self where
  self = liftA3 BinAlt (Just <$> g) self self

-- | Extend a cogenerator of functions @(a -> b)@ (i.e., a generator of higher-order
-- functions @((a -> b) -> r)@), applying the function to a given value @a@
-- and inspecting the result with a cogenerator of @b@.
--
-- This is parameterized by a way to generate, shrink, and show values of
-- type @a@ or, more generally, some representation @a0@ of values of type @a@.
--
-- === __Example__
--
-- @
-- -- Assume Chips is some concrete type.
-- concreteChips :: 'Concrete' Chips
--
-- -- Assume we have a cogenerator of Fish.
-- cogenFish :: forall r. Gen r -> Gen (Fish ':->' r)
--
-- -- Then we can use cogenApply to construct this function
-- -- to transform cogenerators of functions (Chips -> Fish).
-- cogenX :: forall r.
--   Chips ->
--   Gen ((Chips -> Fish) ':->' r) ->
--   Gen ((Chips -> Fish) ':->' r)
-- cogenX = 'cogenApply' concreteChips 'id' '.' cogenFish
--
-- -- If we have some inputs...
-- chips1, chips2, chips3 :: Chips
--
-- -- ... we can construct a cogenerator of functions by iterating cogenX.
-- cogenF :: forall r. Gen r -> Gen ((Chips -> Fish) ':->' r)
-- cogenF = cogenX chips1 '.' cogenX chips2 '.' cogenX chips3 '.' 'cogenConst'
-- @
cogenApply :: Functor gen =>
  Concrete a0 {- ^ Shrink and show @a0@. -} ->
  (a0 -> a)   {- ^ Reify to value @a@ (@id@ for simple data types). -} ->
  a0          {- ^ Value to inspect.     -} ->
  gen (b :-> (a -> b) :-> r) {- ^ Cogenerator of @b@ -} ->
  gen ((a -> b) :-> r)
cogenApply w fromRepr x gr = CoApply w x fromRepr <$> gr

-- | The trivial cogenerator which generates a constant function.
cogenConst :: Functor gen => Co gen a r
cogenConst g = Const <$> g

-- | Construct a cogenerator of functions @(a -> b)@ from a cogenerator of @b@,
-- using @gen (Maybe a0)@ to generate random arguments until it returns
-- @Nothing@.
cogenFun :: Monad gen =>
  Concrete a0    {- ^ Shrink and show @a0@.      -} ->
  gen (Maybe a0) {- ^ Generate representations of argument values. -} ->
  (a0 -> a)      {- ^ Interpret a representation @a0@ into a value @a@ (@id@ for simple data types). -} ->
  Co gen b ((a -> b) :-> r) {- ^ Cogenerator of @b@. -} ->
  Co gen (a -> b) r
cogenFun w ga fromRepr cb gr = self where
  self = do
    ma <- ga
    case ma of
      Nothing -> cogenConst gr
      Just a -> cogenApply w fromRepr a (cb self)