{-# LANGUAGE UndecidableInstances, TypeOperators, FlexibleContexts, MultiParamTypeClasses, FlexibleInstances, TypeFamilies #-}
{-# LANGUAGE CPP #-}
#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 702
{-# LANGUAGE Trustworthy #-}
#endif

-----------------------------------------------------------------------------
-- |
-- Module      :  Data.Generator.Combinators
-- Copyright   :  (c) Edward Kmett 2009
-- License     :  BSD-style
-- Maintainer  :  ekmett@gmail.com
-- Stability   :  experimental
-- Portability :  non-portable (type families, MPTCs)
--
-- Utilities for working with Monoids that conflict with names from the "Prelude",
-- "Data.Foldable", "Control.Monad" or elsewhere. Intended to be imported qualified.
--
-- > import Data.Generator.Combinators as Generator
--
-----------------------------------------------------------------------------

module Data.Generator.Combinators
    (
    -- * Monadic Reduction
      mapM_
    , forM_
    , msum
    -- * Applicative Reduction
    , traverse_
    , for_
    , asum
    -- * Logical Reduction
    , and
    , or
    , any
    , all
    -- * Monoidal Reduction
    , foldMap
    , fold
    , toList 
    -- * List-Like Reduction
    , concatMap
    , elem
    , filter
    , filterWith
    --, find
    , sum
    , product
    , notElem
    ) where

import Prelude hiding 
  ( mapM_, any, all, elem, filter, concatMap, and, or
  , sum, product, notElem, replicate, cycle, repeat
  )
import Control.Applicative
import Control.Monad (MonadPlus)
import Data.Generator
import Data.Monoid (Monoid(..))
import Data.Semigroup (Sum(..), Product(..), All(..), Any(..), WrappedMonoid(..))
import Data.Semigroup.Applicative (Traversal(..))
import Data.Semigroup.Alternative (Alternate(..))
import Data.Semigroup.Monad (Action(..))
import Data.Semigroup.MonadPlus (MonadSum(..))
import Data.Semigroup.Reducer (Reducer(..))

-- | Efficiently 'mapReduce' a 'Generator' using the 'Traversal' monoid. A specialized version of its namesake from "Data.Foldable"
--
-- @
--     'mapReduce' 'getTraversal'
-- @
traverse_ :: (Generator c, Applicative f) => (Elem c -> f b) -> c -> f ()
traverse_ = mapReduceWith getTraversal
{-# INLINE traverse_ #-}
    
-- | Convenience function as found in "Data.Foldable"
--
-- @
--     'flip' 'traverse_'
-- @
for_ :: (Generator c, Applicative f) => c -> (Elem c -> f b) -> f ()
for_ = flip traverse_
{-# INLINE for_ #-}

-- | The sum of a collection of actions, generalizing 'concat'
--
-- @
--    'reduceWith' 'getAlt'
-- @ 
asum :: (Generator c, Alternative f, f a ~ Elem c) => c -> f a
asum = reduceWith getAlternate
{-# INLINE asum #-}

-- | Efficiently 'mapReduce' a 'Generator' using the 'Action' monoid. A specialized version of its namesake from "Data.Foldable" and "Control.Monad"
-- 
-- @
--    'mapReduceWith' 'getAction'
-- @ 
mapM_ :: (Generator c, Monad m) => (Elem c -> m b) -> c -> m ()
mapM_ = mapReduceWith getAction
{-# INLINE mapM_ #-}

-- | Convenience function as found in "Data.Foldable" and "Control.Monad"
--
-- @
--     'flip' 'mapM_'
-- @
forM_ :: (Generator c, Monad m) => c -> (Elem c -> m b) -> m ()
forM_ = flip mapM_
{-# INLINE forM_ #-}

-- | The sum of a collection of actions, generalizing 'concat'
--
-- @
--     'reduceWith' 'getMonadSum'
-- @
msum :: (Generator c, MonadPlus m, m a ~ Elem c) => c -> m a
msum = reduceWith getMonadSum
{-# INLINE msum #-}

-- | Efficiently 'mapReduce' a 'Generator' using the 'WrappedMonoid' monoid. A specialized version of its namesake from "Data.Foldable"
--
-- @
--     'mapReduceWith' 'unwrapMonoid'
-- @
foldMap :: (Monoid m, Generator c) => (Elem c -> m) -> c -> m
foldMap = mapReduceWith unwrapMonoid
{-# INLINE foldMap #-}

-- | Type specialization of "foldMap" above
concatMap :: Generator c => (Elem c -> [b]) -> c -> [b]
concatMap = foldMap
{-# INLINE concatMap #-}

-- | Efficiently 'reduce' a 'Generator' using the 'WrappedMonoid' monoid. A specialized version of its namesake from "Data.Foldable"
--
-- @
--     'reduceWith' 'unwrapMonoid'
-- @
fold :: (Monoid m, Generator c, Elem c ~ m) => c -> m
fold = reduceWith unwrapMonoid
{-# INLINE fold #-}

-- | Convert any 'Generator' to a list of its contents. Specialization of 'reduce'
toList :: Generator c => c -> [Elem c]
toList = reduce
{-# INLINE toList #-}

-- | Efficiently 'reduce' a 'Generator' that contains values of type 'Bool'
--
-- @
--     'reduceWith' 'getAll'
-- @
and :: (Generator c, Elem c ~ Bool) => c -> Bool
and = reduceWith getAll
{-# INLINE and #-}

-- | Efficiently 'reduce' a 'Generator' that contains values of type 'Bool'
--
-- @
--     'reduceWith' 'getAny'
-- @
or :: (Generator c, Elem c ~ Bool) => c -> Bool
or = reduceWith getAny
{-# INLINE or #-}

-- | Efficiently 'mapReduce' any 'Generator' checking to see if any of its values match the supplied predicate
--
-- @
--     'mapReduceWith' 'getAny'
-- @
any :: Generator c => (Elem c -> Bool) -> c -> Bool
any = mapReduceWith getAny
{-# INLINE any #-}

-- | Efficiently 'mapReduce' any 'Generator' checking to see if all of its values match the supplied predicate
--
-- @
--     'mapReduceWith' 'getAll'
-- @
all :: Generator c => (Elem c -> Bool) -> c -> Bool
all = mapReduceWith getAll
{-# INLINE all #-}

-- | Efficiently sum over the members of any 'Generator'
--
-- @
--     'reduceWith' 'getSum'
-- @
sum :: (Generator c, Num (Elem c)) => c -> Elem c
sum = reduceWith getSum
{-# INLINE sum #-}

-- | Efficiently take the product of every member of a 'Generator'
--
-- @
--     'reduceWith' 'getProduct'
-- @
product :: (Generator c, Num (Elem c)) => c -> Elem c
product = reduceWith getProduct
{-# INLINE product #-}

-- | Check to see if 'any' member of the 'Generator' matches the supplied value
elem :: (Generator c, Eq (Elem c)) => Elem c -> c -> Bool
elem = any . (==)
{-# INLINE elem #-}

-- | Check to make sure that the supplied value is not a member of the 'Generator'
notElem :: (Generator c, Eq (Elem c)) => Elem c -> c -> Bool
notElem x = not . elem x
{-# INLINE notElem #-}

-- | Efficiently 'mapReduce' a subset of the elements in a 'Generator'
filter :: (Generator c, Reducer (Elem c) m, Monoid m) => (Elem c -> Bool) -> c -> m
filter p = foldMap f where
    f x | p x = unit x
        | otherwise = mempty
{-# INLINE filter #-}

-- | Allows idiomatic specialization of filter by proving a function that will be used to transform the output
filterWith :: (Generator c, Reducer (Elem c) m, Monoid m) => (m -> n) -> (Elem c -> Bool) -> c -> n 
filterWith f p = f . filter p
{-# INLINE filterWith #-}

{-

-- | A specialization of 'filter' using the 'First' 'Monoid', analogous to 'Data.List.find'
--
-- @
--     'filterWith' 'getFirst'
-- @
find :: Generator c => (Elem c -> Bool) -> c -> Maybe (Elem c)
find = filterWith getFirst
{-# INLINE find #-}

-}