module Data.Generics.Strafunski.StrategyLib.Models.Deriving.StrategyPrimitives (
  Term,
  TP,         TU,
  paraTP,     paraTU,
  applyTP,    applyTU,
  adhocTP,    adhocTU,
  msubstTP,   msubstTU,
  seqTP,      seqTU,
  passTP,     passTU,
  choiceTP,   choiceTU,
  mchoicesTP, mchoicesTU,
  allTP,      allTU, allTU',
  oneTP,      oneTU,
  anyTP,      anyTU, anyTU',
  someTP,     someTU, someTU',
  injTP
) where
import Data.Generics.Strafunski.StrategyLib.Models.Deriving.TermRep
import Data.Generics
import Control.Monad
import Data.Monoid
import Data.Generics.Strafunski.StrategyLib.MonadicFunctions
import Control.Monad.Run
newtype Monad m =>
        TP m =
               MkTP (forall x. Data x => x -> m x)
newtype Monad m =>
        TU a m =
                 MkTU (forall x. Data x => x -> m a)
unTP (MkTP f)	 = f
unTU (MkTU f)	 = f
paraTP 		:: Monad m => (forall t. t -> m t) -> TP m
paraTP f	=  MkTP f
paraTU 		:: Monad m => (forall t. t -> m a) -> TU a m
paraTU f	=  MkTU f
applyTP         :: (Monad m, Data x) => TP m -> x -> m x
applyTP         =  unTP
applyTU 	:: (Monad m, Data x) => TU a m -> x -> m a
applyTU         =  unTU
adhocTP :: (Monad m, Data t) => TP m -> (t -> m t) -> TP m
adhocTP s f = MkTP (unTP s `extM` f)
adhocTU :: (Monad m, Data t) => TU a m -> (t -> m a) -> TU a m
adhocTU s f = MkTU (unTU s `extQ` f)
                                               
msubstTP	:: (Monad m, Monad m') 
		=> (forall t . m t -> m' t) -> TP m -> TP m'
msubstTP e f	=  MkTP (\x -> e ((unTP f) x))
msubstTU	:: (Monad m, Monad m') 
		=> (m a -> m' a) -> TU a m -> TU a m'
msubstTU e f	=  MkTU (\x -> e ((unTU f) x))
    
seqTP 		:: Monad m => TP m -> TP m -> TP m
seqTP f g 	=  MkTP ((unTP f) `mseq` (unTP g))
passTP 		:: Monad m => TU a m -> (a -> TP m) -> TP m
passTP f g 	=  MkTP ((unTU f) `mlet` (\y -> unTP (g y)))
    
seqTU 		:: Monad m => TP m -> TU a m -> TU a m
seqTU f g	=  MkTU ((unTP f) `mseq` (unTU g))
passTU 		:: Monad m => TU a m -> (a -> TU b m) -> TU b m
passTU f g	=  MkTU ((unTU f) `mlet` (\y -> unTU (g y))) 
    
choiceTP 	:: MonadPlus m => TP m -> TP m -> TP m
choiceTP f g	=  MkTP ((unTP f) `mchoice` (unTP g))
    
choiceTU 	:: MonadPlus m => TU a m -> TU a m -> TU a m
choiceTU f g	=  MkTU ((unTU f) `mchoice` (unTU g))
mchoicesTP fs f		=  MkTP (\a -> mchoices (map unTP fs) (unTP f) a)
mchoicesTU fs f		=  MkTU (\a -> mchoices (map unTU fs) (unTU f) a)
    
allTP 	   :: Monad m => TP m -> TP m
allTP s    =  MkTP (gmapM (applyTP s))
oneTP 	   :: MonadPlus m => TP m -> TP m
oneTP s	   =  MkTP (gmapMo (applyTP s))
anyTP      :: MonadPlus m => TP m -> TP m
anyTP s    =  allTP (s `choiceTP` paraTP return)
someTP	   :: MonadPlus m => TP m -> TP m
someTP s   =  MkTP (gmapMp (applyTP s))
injTP      :: MonadPlus m => TP m -> TP m     
injTP s    =  (MkTU (return . glength))
              `passTP`
              (\x -> if x == 1 then allTP s else paraTP (const mzero))
    
allTU 		:: Monad m => (a -> a -> a) -> a -> TU a m -> TU a m
allTU op2 u s   =  MkTU (\x -> fold (gmapQ (applyTU s) x))
  where
    fold l = foldM op2' u l
    op2' x c = c >>= \y -> return (x `op2` y)
allTU' 		:: (Monad m, Monoid a) => TU a m -> TU a m
allTU'		=  allTU mappend mempty
oneTU 		:: MonadPlus m => TU a m -> TU a m
oneTU s		=  MkTU (\x -> fold (gmapQ (applyTU s) x))
  where
    fold [] = mzero
    fold (h:t) = (h >>= \x -> return x)
                 `mplus`
                 fold t
anyTU		:: MonadPlus m => (a -> a -> a) -> a -> TU a m -> TU a m
anyTU op2 u s   =  allTU op2 u (s `choiceTU` paraTU (const (return u)))
anyTU'		:: (MonadPlus m, Monoid a) => TU a m -> TU a m
anyTU' 		=  anyTU mappend mempty
someTU	 	:: MonadPlus m => (a -> a -> a) -> a -> TU a m -> TU a m
someTU op2 u s	=  MkTU (\x -> fold False (gmapQ (applyTU s) x))
  where
    fold False [] = mzero
    fold True  [] = return u
    fold b (h:t)  = (h >>= \x -> fold True t >>= \y -> return (x `op2` y))
                    `mplus`
                    fold b t
someTU'	 	:: (Monoid a, MonadPlus m) => TU a m -> TU a m
someTU'		=  someTU mappend mempty