{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Eta reduce" #-}
module Reader
( tests
, gen0
, genN
, test
) where

import qualified Control.Carrier.Reader as ReaderC
import           Control.Effect.Reader
import qualified Control.Monad.Trans.Reader as ReaderT
import qualified Control.Monad.Trans.RWS.Lazy as LazyRWST
import qualified Control.Monad.Trans.RWS.Strict as StrictRWST
import           Data.Function ((&))
import           Gen
import           GHC.Generics ((:.:)(..))
import qualified Monad
import qualified MonadFix
import           Test.Tasty
import           Test.Tasty.Hedgehog

tests :: TestTree
tests = testGroup "Reader"
  [ testGroup "ReaderC" $
    [ testMonad
    , testMonadFix
    , testReader
    ] >>= ($ runR (uncurry ReaderC.runReader . lower))
  , testGroup "(->)"          $ testReader (runR (uncurry (fmap pure . (&))            . lower))
  , testGroup "ReaderT"       $ testReader (runR (uncurry (flip ReaderT.runReaderT)    . lower))
  , testGroup "RWST (Lazy)"   $ testReader (runR (uncurry (runRWST LazyRWST.runRWST)   . lower))
  , testGroup "RWST (Strict)" $ testReader (runR (uncurry (runRWST StrictRWST.runRWST) . lower))
  ] where
  testMonad    run = Monad.test    (m (gen0 r) (genN r)) a b c (Comp1 <$> (identity <*> (pair <*> r <*> unit))) run
  testMonadFix run = MonadFix.test (m (gen0 r) (genN r)) a b   (Comp1 <$> (identity <*> (pair <*> r <*> unit))) run
  testReader   run = Reader.test r (m (gen0 r) (genN r)) a                (identity <*>                 unit)   run
  runRWST f r m = (\ (a, _, ()) -> a) <$> f m r r
  lower = runIdentity . unComp1


gen0
  :: forall r m a sig
  .  (Has (Reader r) sig m, Arg r, Show r, Vary r)
  => GenTerm r
  -> GenTerm a
  -> [GenTerm (m a)]
gen0 _ a = [ label "asks" (asks @r) <*> fn a ]

genN
  :: (Has (Reader r) sig m, Arg r, Show r, Vary r)
  => GenTerm r
  -> GenM m
  -> GenTerm a
  -> [GenTerm (m a)]
genN r m a = [ subtermM (m a) (label "local" local <*> fn r <*>) ]


test
  :: (Has (Reader r) sig m, Arg r, Eq a, Show a, Show r, Vary r, Functor f)
  => GenTerm r
  -> GenM m
  -> GenTerm a
  -> GenTerm (f ())
  -> Run (f :.: (,) r) Identity m
  -> [TestTree]
test r m a i (Run runReader) =
  [ testProperty "ask returns the environment variable" . forall (i :. r :. fn (m a) :. Nil) $
    \ i r k -> runReader (Comp1 ((r, ask >>= k) <$ i)) === runReader (Comp1 ((r, k r) <$ i))
  , testProperty "local modifies the environment variable" . forall (i :. r :. fn r :. m a :. Nil) $
    \ i r f m -> runReader (Comp1 ((r, local f m) <$ i)) === runReader (Comp1 ((f r, m) <$ i))
  ]