{-|
Description : Container utilities
-}
module Language.Haskell.Formatter.Toolkit.Visit
       (findJust, orderByKey, compose, halfZipWith,
        mapAccumulateLeftWithCreation)
       where
import qualified Data.Foldable as Foldable
import qualified Data.List as List
import qualified Data.Maybe as Maybe
import qualified Data.Monoid as Monoid
import qualified Data.Ord as Ord
import qualified Data.Traversable as Traversable

{-| @findJust f c@ returns the first non-'Nothing' value of @c@ mapped with @f@,
    or 'Nothing' if there is none. -}
findJust :: (Functor t, Foldable.Foldable t) => (a -> Maybe b) -> t a -> Maybe b
findJust function = Foldable.asum . fmap function

{-| @compose f@ returns the function composition of the elements of @f@. -}
compose :: Foldable.Foldable t => t (a -> a) -> a -> a
compose = Monoid.appEndo . Foldable.foldMap Monoid.Endo

{-| @orderByKey k l@ orders @l@ by the sort keys generated by @k@. -}
orderByKey :: Ord b => (a -> b) -> [a] -> [a]
orderByKey = List.sortBy . Ord.comparing

{-| @halfZipWith m b e@ zips the elements of @b@ and @e@ with @m@, using the
    structure of @b@. 'Nothing' is returned if and only if @b@ does not have
    enough elements. -}
halfZipWith ::
              (Traversable.Traversable t, Foldable.Foldable f) =>
              (a -> b -> c) -> t a -> f b -> Maybe (t c)
halfZipWith merge base extension = Traversable.sequenceA zippedMaybe
  where (_, zippedMaybe) = Traversable.mapAccumL process extensionList base
        process [] _ = ([], Nothing)
        process (extensionElement : list) baseElement
          = (list, Just $ merge baseElement extensionElement)
        extensionList = Foldable.toList extension

{-| Like 'Traversable.mapAccumL', but with a function to create the base. -}
mapAccumulateLeftWithCreation ::
                                Traversable.Traversable t =>
                                (a -> b -> (a, c)) ->
                                  (b -> a) -> t b -> (Maybe a, t c)
mapAccumulateLeftWithCreation process createBase
  = Traversable.mapAccumL processMaybe Nothing
  where processMaybe maybeBefore element = (Just after, element')
          where (after, element') = process before element
                before = Maybe.fromMaybe (createBase element) maybeBefore