{-# LANGUAGE TupleSections #-}
module FoldableHelp
    ( safeMinimumOn
    , safeMinimumOnMaybe
    , safeMinimumOnMaybeF
    , safeMinimumOnMaybeL
    , safeMinMaybeL
    , module Data.Foldable
    )
    where
import Data.Foldable

import qualified Control.Foldl as L
-- TODO check up whether and how it is necessary to specialise this
-- | A version of 'minimum' where the comparison is done on some extracted value.
--   Returns Nothing if the list is empty.
--
-- > safeMinimumOn id [] == Nothing
-- > safeMinimumOn length ["test","extra","a"] == Just "a"
safeMinimumOn :: (Foldable t,Ord b) => (a -> b) -> t a -> Maybe a
{-# INLINE safeMinimumOn #-}
safeMinimumOn f = foldl' g Nothing
  where
    g (Just x) y 
        | f x < f y = Just x
        | otherwise = Just y
    g Nothing y = Just y
safeMinimumOnMaybeF :: (Ord b) => (a -> Maybe b) -> [a] -> Maybe a
safeMinimumOnMaybeF f = L.fold $ L.Fold step initial extract
  where
    initial = Nothing
    step Nothing      x  = (x ,) <$> f x
    step (Just (a,x)) b = case f b of
        Just y | y < x  -> Just (b,y)
        _               -> Just (a,x)
    extract = fmap fst
safeMinMaybeL :: (Ord b) => (a -> Maybe b) -> L.Fold a (Maybe b)
safeMinMaybeL f = L.Fold step initial id
  where
    initial = Nothing
    step Nothing x  = f x
    step (Just x) b = case f b of
        Just y | y < x  -> Just y
        _               -> Just x

safeMinimumOnMaybeL :: (Ord b) => (a -> Maybe b) -> L.Fold a (Maybe a)
safeMinimumOnMaybeL f = L.Fold step initial extract
  where
    initial = Nothing
    step Nothing      x  = (x ,) <$> f x
    step (Just (a,x)) b = case f b of
        Just y | y < x  -> Just (b,y)
        _               -> Just (a,x)
    extract = fmap fst

safeMinimumOnMaybe :: (Ord b) => (a -> Maybe b) -> [a] -> Maybe a
safeMinimumOnMaybe f ys = fst <$> go Nothing ys
  where
    go Nothing (x:xs) = go ((x ,) <$> f x) xs
    go (Just (y,cmin)) (x:xs) = case f x of
        Just v | v < cmin -> go (Just (x,v)) xs
        _                 -> go (Just (y,cmin)) xs
    go m _ = m


--{- | Partial. -}
--minimumOn :: Ord b => (a -> b) -> [a] -> a
--minimumOn f [] = error "tried to take minimumOn of empty list"
--minimumOn f (x:xs) = go x xs
--  where
--    go x (y:xs) | f x < f y = go x xs
--                | otherwise = go y xs
--    go x [] = x
