{-# LANGUAGE RankNTypes #-}
-- | SYB functions
module MuCheck.Utils.Syb ( selectMany
                     , selectOne
                     , relevantOps
                     , once
                     , once') where

import Data.Generics (Data, Typeable, GenericM, gmapMo, everything, mkQ, mkMp)
import MuCheck.MuOp (mkMp', MuOp)
import MuCheck.Utils.Common (safeHead)
import Control.Monad (MonadPlus, mplus)
import Data.Maybe(fromMaybe, isJust)

-- | apply a mutating function on a piece of code one at a time
once :: MonadPlus m => GenericM m -> GenericM m
once f x = f x `mplus` gmapMo (once f) x

once' :: (forall a. Data a => a -> Maybe a) -> (forall a. Data a => a -> a)
once' f x = fromMaybe x $ once f x

-- | select all code components satisfying a certain predicate
selectMany :: (Data a, Typeable b) => (b -> Bool) -> a -> [b]
selectMany f = everything (++) ([] `mkQ` keep f)
   where keep fn x = [x | fn x]

-- | special case of selectMany, which selects the first components satisfying
-- a predicate
selectOne :: (Typeable a, Data a1) => (a -> Bool) -> a1 -> Maybe a
selectOne f p = safeHead $ selectMany f p

-- | selecting all relevant ops
relevantOps :: (Data a, Eq a) => a -> [MuOp] -> [MuOp]
relevantOps m mlst = filter (relevantOp m) mlst
  -- check if an operator can be applied to a program
  where relevantOp m op = isJust $ once (mkMp' op) m