-- | A collection of common left folds. Note that all of these are
-- strict and do not short circuit. These are useful for operations
-- that require inspecting the entire list to calculate the final
-- state.
module Data.Fold.Common.L' where
import Data.Fold
import Data.Fold.Internal
import Data.Monoid
import qualified Data.Set as S

-- | Sum of the inputs
--
-- >>> run [1 .. 10] sum
-- 55
sum :: Num a => L' a a
sum = L' id (+) 0

-- | Product of the input
--
-- >>> run [1 .. 10] product
-- 3628800
product :: Num a => L' a a
product = L' id (*) 1

-- | Count the number of elements fed to a fold
--
-- >>> run [1 .. 10] count
-- 10
--
-- Note: GHCi will default @Enum e@ to @()@. If you see
--
-- > *** Exception: Prelude.Enum.().succ: bad argument
--
-- You've been bitten by this.
count :: Enum e => L' a e
count = L' id (\c _ -> succ c) (toEnum 0)

-- | 'mappend' all the elements of a sequence together.
--
-- >>> run [[1, 2, 3, 4], [5, 6, 7, 8]] mconcat
-- [1, 2, 3, 4, 5, 6, 7, 8]
--
-- >>> run (map Sum [1, 2, 3, 4]) mconcat
-- Sum {getSum = 10}
mconcat :: Monoid m => L' m m
mconcat = L' id mappend mempty

-- | Minimum of all inputs. If no inputs are supplied this returns
-- 'Nothing'.
--
-- >>> run [1, 2, 3] minimum
-- 1
-- >>> run [1 ..] minimum
-- ... diverges ...
minimum :: Ord a => L' a (Maybe a)
minimum = L' id comp Nothing
  where comp Nothing a  = Just a
        comp (Just b) a = Just (min a b)

-- | Maximum of all inputs. If no inputs are supplied this returns
-- 'Nothing'.
--
-- >>> run [1, 2, 3] maximum
-- 3
--
-- >>> run [1 ..] maximum
-- ... diverges ...
maximum :: Ord a => L' a (Maybe a)
maximum = L' id comp Nothing
  where comp Nothing a  = Just a
        comp (Just b) a = Just (max a b)

-- | De-duplicate all the inputs while preserving order. @O(n log(n))@
--
-- >>> run (replicate 10 1 ++ replicate 10 2) nub
-- [1, 2]
--
-- >>> run [1, 2, 1] nub
-- [1, 2]
nub :: Ord a => L' a [a]
nub = L' (\(Pair' _ l) -> reverse l) step (Pair' S.empty [])
  where step st@(Pair' s as) a | S.member a s = st
                               | otherwise = Pair' (S.insert a s) (a : as)

-- | De-duplicate all the inputs while preserving
-- order. @O(n^2)@. This should be equivalent (but slower) then 'nub'
-- for 'Ord' types.
--
-- >>> run (replicate 10 1 ++ replicate 10 2) slowNub
-- [1, 2]
--
-- >>> run [1, 2, 1] slowNub
-- [1, 2]
slowNub :: Eq a => L' a [a]
slowNub = L' id step []
  where step as a | a `elem` as = as
                  | otherwise = a : as

-- | Collect all members into a @Set@.
--
-- >>> run [1 .. 10] intoSet
-- fromList [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
intoSet :: Ord a => L' a (S.Set a)
intoSet = L' id (flip S.insert) S.empty

-- | Grab the last element inputted
--
-- >>> run [1 .. 10] last
-- Just 10
--
-- >>> run [] last
-- Nothing
last :: L' a (Maybe a)
last = L' id step Nothing
  where step _ = Just

-- | Grab the nth element inputted.
--
-- >>> run [1 .. 10] (nth 5)
-- Just 6
--
-- >>> run [1 .. 10] (nth 20)
-- Nothing
nth :: (Eq b, Num b) => b -> L' a (Maybe a)
nth b = L' (\(Pair' e _) -> maybe' Nothing Just e) step (Pair' Nothing' b)
  where step st@(Pair' (Just' _) _) _ = st
        step (Pair' _ 0) a = Pair' (Just' a) 0
        step (Pair' _ n) _ = Pair' Nothing' (n - 1)