{-# LANGUAGE BangPatterns               #-}
{-# LANGUAGE CPP                        #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE NoImplicitPrelude          #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE StandaloneDeriving         #-}
{-# LANGUAGE TypeOperators              #-}

#if __GLASGOW_HASKELL__ >=702
{-# LANGUAGE Trustworthy                #-}
#endif

{-# OPTIONS_GHC -Wall -Werror #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  Data.Semifoldable
-- Copyright   :  Edward Kmett
-- License     :  BSD-style (see the LICENSE file in the distribution)
--
-- Maintainer  :  libraries@haskell.org
-- Stability   :  experimental
-- Portability :  portable
--
-- class of non-empty data structures that can be folded to a summary value.
--
-----------------------------------------------------------------------------

module Data.Semifoldable (
    Semifoldable(..),
    intercalate1,
    foldrM1,
    foldlM1,
    semimaximumBy,
    semiminimumBy,
    ) where

import Data.Foldable      (Foldable, foldlM, foldr)
import Data.List          (foldl, foldl')
import Data.List.NonEmpty (NonEmpty (..))
import Data.Semigroup
       (Dual (..), First (..), Last (..), Max (..), Min (..), Product (..),
       Semigroup (..), Sum (..))
import Prelude
       (Maybe (..), Monad (..), Ord, Ordering (..), id, map, seq, ($), ($!),
       (.), (=<<))

import qualified Data.List.NonEmpty as NE

#if MIN_VERSION_base(4,4,0)
import Data.Complex (Complex (..))
import GHC.Generics
       ((:*:) (..), (:+:) (..), (:.:) (..), M1 (..), Par1 (..), Rec1 (..), V1)
import Prelude      (error)
#endif

#if MIN_VERSION_base(4,6,0)
import Data.Ord (Down (..))
#endif

#if MIN_VERSION_base(4,8,0)
import qualified Data.Monoid as Mon
#endif

#if !MIN_VERSION_base(4,12,0)
import Data.Orphans ()
#endif

#ifdef MIN_VERSION_tagged
import Data.Tagged (Tagged (..))
#endif

-- Instances
import Control.Applicative.Backwards (Backwards (..))
import Control.Applicative.Lift      (Lift (..))
import Control.Monad.Trans.Identity  (IdentityT (..))
import Data.Functor.Compose          (Compose (..))
import Data.Functor.Identity         (Identity (..))
import Data.Functor.Reverse          (Reverse (..))
import Data.Tree                     (Tree (..))

import qualified Data.Functor.Product as Functor
import qualified Data.Functor.Sum     as Functor

-- coerce
#if __GLASGOW_HASKELL__ <708
import Unsafe.Coerce (unsafeCoerce)
#else
import Data.Coerce (Coercible, coerce)
#endif

#if __GLASGOW_HASKELL__ <708
coerce :: a -> b
coerce = unsafeCoerce

(#.) :: (b -> c) -> (a -> b) -> a -> c
(#.) _f = coerce
#else
(#.) :: Coercible b c => (b -> c) -> (a -> b) -> a -> c
(#.) _f = coerce
#endif

-- | Non-empty data structures that can be folded.
class Foldable t => Semifoldable t where
#if __GLASGOW_HASKELL__ >= 708
    {-# MINIMAL semifoldMap | semifoldrMap #-}
#endif

    -- At some point during design it was possible to define this class using
    -- only 'toNonEmpty'. But it seems a bad idea in general.
    --
    -- So currently we require either semifoldMap or semifoldrMap
    --
    -- * semifoldMap defined using semifoldrMap
    -- * semifoldrMap defined using semifoldMap
    --
    -- One can alsays define instance using following pattern:
    --
    --     toNonEmpty = ...
    --     foldMap f     = foldMap f     . toNonEmpty
    --     semifoldrMap f g = semifoldrMap f g . toNonEmpty

    -- | Combine the elements of a structure using a semigroup.
    semifold :: Semigroup m => t m -> m
    semifold = semifoldMap id

    -- | Map each element of the structure to a semigroup,
    -- and combine the results.
    --
    -- >>> semifoldMap Sum (1 :| [2, 3, 4])
    -- Sum {getSum = 10}
    --
    semifoldMap :: Semigroup m => (a -> m) -> t a -> m
    semifoldMap f = semifoldrMap f (<>)

    -- | A variant of 'semifoldMap' that is strict in the accumulator.
    --
    -- >>> semifoldMap' Sum (1 :| [2, 3, 4])
    -- Sum {getSum = 10}
    --
    semifoldMap' :: Semigroup m => (a -> m) -> t a -> m
    semifoldMap' f = semifoldl'Map f (<>)

    -- | Right-associative fold of a structure.
    --
    -- In the case of lists, 'semifoldr', when applied to a binary operator,
    -- and a list, reduces the list using the binary operator,
    -- from right to left:
    --
    -- > semifoldr f [x1, x2, ..., xn] == x1 `f` (x2 `f` ... (xn1 `f` xn )...)
    --
    -- Note that, since the head of the resulting expression is produced by
    -- an application of the operator to the first element of the list,
    -- 'semifoldr' can produce a terminating expression from an infinite list.
    --
    -- For a general 'Semifoldable' structure this should be semantically identical
    -- to,
    --
    -- @semifoldr f = semifoldr f . 'toNonEmpty'@
    --
    semifoldr :: (a -> a -> a) -> t a -> a
    semifoldr = semifoldrMap id

    -- | Right-associative fold of a structure, but with strict application of
    -- the operator.
    --
    semifoldr' :: (a -> a -> a) -> t a -> a
    semifoldr' = semifoldr'Map id

    -- | Left-associative fold of a structure.
    --
    -- In the case of lists, 'semifoldl', when applied to a binary
    -- operator, and a list, reduces the list using the binary operator,
    -- from left to right:
    --
    -- > semifoldl f [x1, x2, ..., xn] == (...((x1 `f` x2) `f`...) `f` xn
    --
    -- Note that to produce the outermost application of the operator the
    -- entire input list must be traversed. This means that 'semifoldl' will
    -- diverge if given an infinite list.
    --
    -- Also note that if you want an efficient left-fold, you probably want to
    -- use 'semifoldl'' instead of 'semifoldl'. The reason for this is that latter does
    -- not force the "inner" results (e.g. @x1 \`f\` x2@ in the above example)
    -- before applying them to the operator (e.g. to @(\`f\` x3)@). This results
    -- in a thunk chain \(\mathcal{O}(n)\) elements long, which then must be
    -- evaluated from the outside-in.
    --
    -- For a general 'Semifoldable' structure this should be semantically identical
    -- to,
    --
    -- @semifoldl f z = semifoldl f . 'toNonEmpty'@
    --
    semifoldl :: (a -> a -> a) -> t a -> a
    semifoldl = semifoldlMap id

    -- | Left-associative fold of a structure but with strict application of
    -- the operator.
    --
    -- This ensures that each step of the fold is forced to weak head normal
    -- form before being applied, avoiding the collection of thunks that would
    -- otherwise occur. This is often what you want to strictly reduce a finite
    -- list to a single, monolithic result (e.g. 'length').
    --
    -- For a general 'Semifoldable' structure this should be semantically identical
    -- to,
    --
    -- @semifoldl' f z = semifoldl f . 'toNonEmpty'@
    --
    semifoldl' :: (a -> a -> a) -> t a -> a
    semifoldl' = semifoldl'Map id

    -- | List of elements of a structure, from left to right.
    --
    -- >>> toNonEmpty (Identity 2)
    -- 2 :| []
    --
    toNonEmpty :: t a -> NonEmpty a
    toNonEmpty = runNonEmptyDList . semifoldMap singleton

    -- | The largest element of a non-empty structure.
    --
    -- >>> semimaximum (32 :| [64, 8, 128, 16])
    -- 128
    --
    semimaximum :: forall a . Ord a => t a -> a
    semimaximum = getMax . semifoldMap Max

    -- | The least element of a non-empty structure.
    --
    -- >>> semiminimum (32 :| [64, 8, 128, 16])
    -- 8
    --
    semiminimum :: forall a . Ord a => t a -> a
    semiminimum = getMin . semifoldMap Min

    -- | The first element of a non-empty structure.
    --
    -- >>> semihead (1 :| [2, 3, 4])
    -- 1
    --
    semihead :: t a -> a
    semihead = getFirst . semifoldMap First

    -- | The last element of a non-empty structure.
    --
    -- >>> semilast (1 :| [2, 3, 4])
    -- 4
    --
    semilast :: t a -> a
    semilast = getLast . semifoldMap Last

    -- | For 'Functor's, @'semifoldrMap' f g = semifoldr g . 'fmap' g@.
    semifoldrMap :: (a -> b) -> (b -> b -> b) -> t a -> b
    semifoldrMap f g xs =
        -- semifoldrMap f g . toNonEmpty
        appFromMaybe (semifoldMap (FromMaybe #. h) xs) Nothing
      where
        h a Nothing  = f a
        h a (Just b) = g (f a) b

    -- | For 'Functor's, @'semifoldl'Map' f g = semifoldl' g . 'fmap' g@.
    semifoldl'Map :: (a -> b) -> (b -> b -> b) -> t a -> b
    semifoldl'Map f g xs = semifoldrMap f' (\x y z -> y $! SJust (x z)) xs SNothing
      where
        f' a SNothing  = f a
        f' a (SJust b) = g b (f a)

    -- | For 'Functor's, @'semifoldlMap' f g = semifoldl g . 'fmap' g@.
    semifoldlMap :: (a -> b) -> (b -> b -> b) -> t a -> b
    semifoldlMap f g xs =
        -- semifoldlMap f g . toNonEmpty
        appFromMaybe (getDual (semifoldMap ((Dual . FromMaybe) #. h) xs)) Nothing
      where
        h a Nothing  = f a
        h a (Just b) = g b (f a)

    -- | For 'Functor's, @'semifoldr'Map' f g = semifoldr' g . 'fmap' g@.
    semifoldr'Map :: (a -> b) -> (b -> b -> b) -> t a -> b
    semifoldr'Map f g xs = semifoldlMap f' (\x y z -> x $! SJust (y z)) xs SNothing
      where
        f' a SNothing  = f a
        f' a (SJust b) = g (f a) b

-- Newtypes for semifoldrMap and semifoldlMap definitions.
-- c.f. Endo
newtype FromMaybe b = FromMaybe { appFromMaybe :: Maybe b -> b }

instance Semigroup (FromMaybe b) where
    FromMaybe f <> FromMaybe g = FromMaybe (f . Just . g)

-- Strict maybe, used to implement default semifoldl'Map etc.
data SMaybe a = SNothing | SJust !a

-- instances for Prelude types

instance Semifoldable NonEmpty where
    semifoldMap f (x :| xs) = go (f x) xs where
        go y [] = y
        go y (z : zs) = y <> go (f z) zs

    semifoldMap' f (x :| xs) = foldl' (\m y -> m <> f y) (f x) xs

    toNonEmpty = id

    semifoldr f (x :| xs) = go x xs where
        go y [] = y
        go y (z : zs) = f y (go z zs)

    semifoldrMap g f (x :| xs) = go x xs where
        go y [] = g y
        go y (z : zs) = f (g y) (go z zs)

    semifoldl f (x :| xs) = foldl f x xs
    semifoldlMap g f (x :| xs) = foldl f (g x) (map g xs)

    semifoldl' f (x :| xs) = foldl' f x xs
    semifoldl'Map g f (x :| xs) = foldl' f (g x) (map g xs)

    semihead = NE.head
    semilast = NE.last

instance Semifoldable ((,) a) where
    semifoldMap f (_, y) = f y
    semifoldr _ (_, y) = y
    toNonEmpty (_, x) = x :| []
    semiminimum (_, x) = x
    semimaximum (_, x) = x
    semihead (_, x) = x
    semilast (_, x) = x

instance Semifoldable Dual where
    semifoldMap = coerce

instance Semifoldable Sum where
    semifoldMap = coerce

instance Semifoldable Product where
    semifoldMap = coerce

instance Semifoldable Min where
    semifoldMap = coerce

instance Semifoldable Max where
    semifoldMap = coerce

instance Semifoldable First where
    semifoldMap = coerce

instance Semifoldable Last where
    semifoldMap = coerce

#if MIN_VERSION_base(4,6,0)
instance Semifoldable Down where
    semifoldMap = coerce
#endif

#if MIN_VERSION_base(4,8,0)
deriving instance (Semifoldable f) => Semifoldable (Mon.Alt f)
#endif

#if MIN_VERSION_base(4,12,0)
deriving instance (Semifoldable f) => Semifoldable (Mon.Ap f)
#endif

#if MIN_VERSION_base(4,4,0)
instance Semifoldable Complex where
    semifoldMap f (x :+ y) = f x <> f y

    toNonEmpty (x :+ y) = x :| y : []
#endif

-- Instances for GHC.Generics

#if MIN_VERSION_base(4,4,0)
instance Semifoldable V1 where
    semifoldMap _ x = x `seq` error "semifoldMap @V1"

instance Semifoldable Par1 where
    semifoldMap = coerce

deriving instance Semifoldable f => Semifoldable (Rec1 f)

deriving instance Semifoldable f => Semifoldable (M1 i c f)

instance (Semifoldable f, Semifoldable g) => Semifoldable (f :+: g) where
    semifoldMap f (L1 x) = semifoldMap f x
    semifoldMap f (R1 y) = semifoldMap f y

instance (Semifoldable f, Semifoldable g) => Semifoldable (f :*: g) where
    semifoldMap f (x :*: y) = semifoldMap f x <> semifoldMap f y

instance (Semifoldable f, Semifoldable g) => Semifoldable (f :.: g) where
    semifoldMap f = semifoldMap (semifoldMap f) . unComp1
#endif

-- | Insert an @m@ between each pair of @t m@.
--
-- >>> intercalate1 ", " $ "hello" :| ["how", "are", "you"]
-- "hello, how, are, you"
--
-- >>> intercalate1 ", " $ "hello" :| []
-- "hello"
--
-- >>> intercalate1 mempty $ "I" :| ["Am", "Fine", "You?"]
-- "IAmFineYou?"
--
intercalate1 :: (Semifoldable t, Semigroup m) => m -> t m -> m
intercalate1 sep = semifoldr (\a b -> a <> sep <> b)

-- | Monadic fold over the elements of a non-empty structure,
-- associating to the right, i.e. from right to left.
foldrM1 :: (Semifoldable t, Monad m) => (a -> a -> m a) -> t a -> m a
foldrM1 f = go . toNonEmpty
  where
    go (e:|es) =
      case es of
        []   -> return e
        x:xs -> f e =<< go (x:|xs)

-- | Monadic fold over the elements of a non-empty structure,
-- associating to the left, i.e. from left to right.
foldlM1 :: (Semifoldable t, Monad m) => (a -> a -> m a) -> t a -> m a
foldlM1 f t = foldlM f x xs
  where x:|xs = toNonEmpty t

-- | The largest element of a non-empty structure with respect to the
-- given comparison function.

-- See Note [maximumBy/minimumBy space usage]
semimaximumBy :: Semifoldable t => (a -> a -> Ordering) -> t a -> a
semimaximumBy cmp = semifoldl max'
  where max' x y = case cmp x y of
                        GT -> x
                        _  -> y

-- | The least element of a non-empty structure with respect to the
-- given comparison function.

-- See Note [maximumBy/minimumBy space usage]
semiminimumBy :: Semifoldable t => (a -> a -> Ordering) -> t a -> a
semiminimumBy cmp = semifoldl min'
  where min' x y = case cmp x y of
                        GT -> y
                        _  -> x

-- NonEmptyDList

newtype NonEmptyDList a = NEDL { unNEDL :: [a] -> NonEmpty a }

instance Semigroup (NonEmptyDList a) where
  xs <> ys = NEDL (unNEDL xs . NE.toList . unNEDL ys)
  {-# INLINE (<>) #-}

-- | Create dlist with a single element
singleton :: a -> NonEmptyDList a
singleton = NEDL #. (:|)

-- | Convert a dlist to a non-empty list
runNonEmptyDList :: NonEmptyDList a -> NonEmpty a
runNonEmptyDList = ($[]) . unNEDL
{-# INLINE runNonEmptyDList #-}

-------------------------------------------------------------------------------
-- Extra instances
-------------------------------------------------------------------------------

instance Semifoldable Identity where
    semifoldMap                = coerce

    semifoldr _                = coerce
    semifoldrMap g _           = coerce g
    semifoldl _                = coerce
    semifoldlMap g _           = coerce g

    toNonEmpty (Identity x) = x :| []

    semilast    = coerce
    semihead    = coerce
    semiminimum = coerce
    semimaximum = coerce

instance (Semifoldable f, Semifoldable g) => Semifoldable (Functor.Product f g) where
    semifoldMap f (Functor.Pair x y)    = semifoldMap f x <> semifoldMap f y
    semifoldrMap g f (Functor.Pair x y) = foldr (f . g) (semifoldrMap g f y) x

    semihead (Functor.Pair x _) = semihead x
    semilast (Functor.Pair _ y) = semilast y

instance (Semifoldable f, Semifoldable g) => Semifoldable (Functor.Sum f g) where
    semifoldMap f (Functor.InL x) = semifoldMap f x
    semifoldMap f (Functor.InR y) = semifoldMap f y

    semifoldr f (Functor.InL x) = semifoldr f x
    semifoldr f (Functor.InR y) = semifoldr f y

    semifoldrMap g f (Functor.InL x) = semifoldrMap g f x
    semifoldrMap g f (Functor.InR y) = semifoldrMap g f y

    toNonEmpty (Functor.InL x) = toNonEmpty x
    toNonEmpty (Functor.InR y) = toNonEmpty y

    semihead (Functor.InL x) = semihead x
    semihead (Functor.InR y) = semihead y
    semilast (Functor.InL x) = semilast x
    semilast (Functor.InR y) = semilast y

    semiminimum (Functor.InL x) = semiminimum x
    semiminimum (Functor.InR y) = semiminimum y
    semimaximum (Functor.InL x) = semimaximum x
    semimaximum (Functor.InR y) = semimaximum y

instance (Semifoldable f, Semifoldable g) => Semifoldable (Compose f g) where
    semifoldMap f = semifoldMap (semifoldMap f) . getCompose
    -- This is incorrect definition!
    -- semifoldrMap f g = semifoldrMap (semifoldrMap f g) g . getCompose

    semihead = semihead . semihead . getCompose
    semilast = semilast . semilast . getCompose

-------------------------------------------------------------------------------
-- containers
-------------------------------------------------------------------------------

instance Semifoldable Tree where
    semifoldMap f (Node x [])       = f x
    semifoldMap f (Node x (y : ys)) = f x <> semifoldMap (semifoldMap f) (y :| ys)

    semifoldMap' f = go where
        go (Node x ys) =
            foldl' (\m zs -> let gozs = go zs in gozs `seq` m <> gozs) (f x) ys

    -- This is incorrect definition!
    -- semifoldrMap f _ (Node x [])     = f x
    -- semifoldrMap f g (Node x (y:ys)) =
    --     g (f x) (semifoldrMap (semifoldrMap f g) g (y :| ys))

    semifoldlMap f g (Node x xs) = goForest (f x) xs where
        goForest = foldl' go
        go y (Node z zs) = goForest (g y (f z)) zs

    semifoldl'Map f g (Node x xs) = goForest (f x) xs where
        goForest !y = foldl' go y
        go !y (Node z zs) = goForest (g y (f z)) zs

    semihead (Node x _) = x

-------------------------------------------------------------------------------
-- transformers
-------------------------------------------------------------------------------

instance Semifoldable f => Semifoldable (Reverse f) where
    semifoldMap f = getDual . semifoldMap (Dual . f) . getReverse

    -- TODO:
    -- head1 = last1 . getReverse
    -- last1 = head1 . getReverse

deriving instance Semifoldable f => Semifoldable (IdentityT f)

instance Semifoldable f => Semifoldable (Backwards f) where
    semifoldMap f = semifoldMap f . forwards

instance Semifoldable f => Semifoldable (Lift f) where
    semifoldMap f (Pure x)  = f x
    semifoldMap f (Other y) = semifoldMap f y

-------------------------------------------------------------------------------
-- tagged
-------------------------------------------------------------------------------

#ifdef MIN_VERSION_tagged
instance Semifoldable (Tagged b) where
    semifoldMap                = coerce

    semifoldr _                = coerce
    semifoldrMap g _           = coerce g
    semifoldl _                = coerce
    semifoldlMap g _           = coerce g

    toNonEmpty (Tagged x) = x :| []

    semilast    = coerce
    semihead    = coerce
    semiminimum = coerce
    semimaximum = coerce
#endif

-- $setup
-- >>> import Prelude
