{-# LANGUAGE CPP           #-}
{-# LANGUAGE DeriveFunctor #-}
{- |
    Monoid and [group actions](https://en.wikipedia.org/wiki/Group_action) (M-Sets and G-Sets).
    The category of @MSet@s (and @GSet@s) is monadic (unlike the category of @SSet@s).
 -}
module Data.Monoid.MSet
    ( MSet (..)
    , SSet (..)
    , Endo (..)
    , rep
    , fact
#if __GLASGOW_HASKELL__ < 804
    , fmact
#endif
    , FreeMSet (..)
    , hoistFreeMSet
    , foldrMSet
    , S (..)
    ) where

import           Control.Monad (ap)
import           Data.Functor.Const (Const (..))
import           Data.Functor.Identity (Identity (..))
import qualified Data.Functor.Product as Functor (Product)
import qualified Data.Functor.Sum as Functor (Sum)
import           Data.List.NonEmpty (NonEmpty)
#if __GLASGOW_HASKELL__ < 804
import qualified Data.List.NonEmpty as NE
#endif
import           Data.Monoid (Monoid, Endo (..), Sum (..), Product (..))
import           Data.Natural (Natural)
import           Data.Ord (Down (..))
import           Data.Semigroup (Semigroup (..))
import           Data.Set (Set)
#if __GLASGOW_HASKELL__ < 804
import qualified Data.Set as Set
#endif

import           Data.Semigroup.SSet (SSet (..), S (..), fact, rep)
import           Data.Algebra.Free
    ( AlgebraType
    , AlgebraType0
    , FreeAlgebra (..)
    , proof
    , bindFree
    , foldrFree
    )

-- |
-- Lawful instance should satisfy:
--
-- prop> act mempty = id
-- prop> g `act` h `act` a = g <> h `act` a
--
-- This is the same as to say that `act` is a monoid homomorphism from @m@ to
-- the monoid of endomorphisms of @a@ (i.e. maps from @a@ to @a@).
--
-- Note that if @g@ is a @'Group'@ then an @MSet@ is simply a @GSet@, this
-- is because monoids and groups share the same morphisms (a monoid homomorphis
-- between groups necessarily preserves inverses).
#if __GLASGOW_HASKELL__ >= 804
class (Monoid m, SSet m a) => MSet m a where
    mact :: m -> a -> a
    mact = act
#else
class Monoid m => MSet m a where
    mact :: m -> a -> a
#endif

instance {-# OVERLAPPABLE #-} Monoid m => MSet m m where
#if __GLASGOW_HASKELL__ < 804
   mact = mappend
#endif

instance (MSet m a, MSet m b) => MSet m (a, b) where
#if __GLASGOW_HASKELL__ < 804
    mact m (a, b) = (mact m a, mact m b)
#endif

instance (MSet m a, MSet m b, MSet m c) => MSet m (a, b, c) where
#if __GLASGOW_HASKELL__ < 804
    mact m (a, b, c) = (mact m a, mact m b, mact m c)
#endif

instance (MSet m a, MSet m b, MSet m c, MSet m d) => MSet m (a, b, c, d) where
#if __GLASGOW_HASKELL__ < 804
    mact m (a, b, c, d) = (mact m a, mact m b, mact m c, mact m d)
#endif

instance (MSet m a, MSet m b, MSet m c, MSet m d, MSet m e) => MSet m (a, b, c, d, e) where
#if __GLASGOW_HASKELL__ < 804
    mact m (a, b, c, d, e) = (mact m a, mact m b, mact m c, mact m d, mact m e)
#endif

instance (MSet m a, MSet m b, MSet m c, MSet m d, MSet m e, MSet m f) => MSet m (a, b, c, d, e, f) where
#if __GLASGOW_HASKELL__ < 804
    mact m (a, b, c, d, e, f) = (mact m a, mact m b, mact m c, mact m d, mact m e, mact m f)
#endif

instance (MSet m a, MSet m b, MSet m c, MSet m d, MSet m e, MSet m f, MSet m h) => MSet m (a, b, c, d, e, f, h) where
#if __GLASGOW_HASKELL__ < 804
    mact m (a, b, c, d, e, f, h) = (mact m a, mact m b, mact m c, mact m d, mact m e, mact m f, mact m h)
#endif

instance (MSet m a, MSet m b, MSet m c, MSet m d, MSet m e, MSet m f, MSet m h, MSet m i) => MSet m (a, b, c, d, e, f, h, i) where
#if __GLASGOW_HASKELL__ < 804
    mact m (a, b, c, d, e, f, h, i) = (mact m a, mact m b, mact m c, mact m d, mact m e, mact m f, mact m h, mact m i)
#endif

instance MSet m a => MSet m [a] where
#if __GLASGOW_HASKELL__ < 804
    mact m = map (mact m)
#endif

instance MSet m a => MSet m (NonEmpty a) where
#if __GLASGOW_HASKELL__ < 804
    mact m = NE.map (mact m)
#endif

instance (MSet m a, Ord a) => MSet m (Set a) where
#if __GLASGOW_HASKELL__ < 804
    mact m as = Set.map (mact m) as
#endif

#if __GLASGOW_HASKELL__ < 804
fmact :: (Functor f, MSet s a) => s -> f a -> f a
fmact s = fmap (mact s)
#endif

instance MSet m a => MSet m (Identity a) where
#if __GLASGOW_HASKELL__ < 804
    mact = fmact
#endif

instance MSet m a => MSet (Identity m) a where
#if __GLASGOW_HASKELL__ < 804
    mact (Identity f) a = f `mact` a
#endif

instance MSet m a => MSet m (Maybe a) where
#if __GLASGOW_HASKELL__ < 804
    mact = fmact
#endif

instance MSet m b => MSet m (Either a b) where
#if __GLASGOW_HASKELL__ < 804
    mact = fmact
#endif

instance MSet m a => MSet m (Down a) where
#if __GLASGOW_HASKELL__ < 804
    mact m (Down a) =  Down (mact m a)
#endif

instance MSet m a => MSet m (IO a) where
#if __GLASGOW_HASKELL__ < 804
    mact = fmact
#endif

instance MSet m b => MSet m (a -> b) where
#if __GLASGOW_HASKELL__ < 804
    mact = fmact
#endif

instance MSet (Endo a) a where
#if __GLASGOW_HASKELL__ < 804
    mact = appEndo
#endif

instance MSet m b => MSet (S m) (Endo b) where
#if __GLASGOW_HASKELL__ < 804
    mact (S m) (Endo f) = Endo $ mact m . f
#endif

instance Monoid m => MSet (Sum Natural) m where
#if __GLASGOW_HASKELL__ < 804
    mact (Sum 0) _ = mempty
    mact (Sum n) s = s `mappend` mact (Sum (n - 1)) s
#endif

instance MSet m a => MSet m (Const a b) where
#if __GLASGOW_HASKELL__ < 804
    mact s (Const a) = Const $ s `mact` a
#endif

instance (Functor f, Functor h, MSet m a) => MSet m (Functor.Product f h a) where
#if __GLASGOW_HASKELL__ < 804
    mact = fmact
#endif

instance (Functor f, Functor h, MSet m a) => MSet m (Functor.Sum f h a) where
#if __GLASGOW_HASKELL__ < 804
    mact = fmact
#endif

newtype FreeMSet m a = FreeMSet { runFreeMSet :: (m, a) }
    deriving (Show, Ord, Eq, Functor)

hoistFreeMSet
    :: (m -> n)       -- ^ monoid homomorphism
    -> FreeMSet m a
    -> FreeMSet n a
hoistFreeMSet f (FreeMSet (m, a)) = FreeMSet (f m, a)

instance Monoid m => Applicative (FreeMSet m) where
    pure  = returnFree
    (<*>) = ap

instance ( Monoid m
         ) => Monad (FreeMSet m) where
    return = returnFree
    (>>=)  = bindFree

instance Semigroup m => SSet m (FreeMSet m a) where
    act m (FreeMSet (h, a)) = FreeMSet (m <> h, a)

instance Monoid m => MSet m (FreeMSet m a) where
#if __GLASGOW_HASKELL__ < 804
    mact m (FreeMSet (h, a)) = FreeMSet (m `mappend` h, a)
#endif

instance Num s => MSet (Sum s) s where
#if __GLASGOW_HASKELL__ < 804
    mact (Sum n) s = n + s
#endif

instance Num s => MSet (Product s) s where
#if __GLASGOW_HASKELL__ < 804
    mact (Product n) s = n * s
#endif

-- |
-- @'foldrFree'@ for @'FreeMSet'@
foldrMSet :: forall m a b . MSet m b => (a -> b -> b) -> b -> (m, a) -> b
foldrMSet f b (m, a) = foldrFree f b (FreeMSet (S m, a))

type instance AlgebraType0 (FreeMSet m) a = ()
type instance AlgebraType  (FreeMSet m) a = MSet m a
instance ( Monoid m
         ) => FreeAlgebra (FreeMSet m) where
    returnFree a = FreeMSet (mempty, a)
    foldMapFree f (FreeMSet (m, a)) = mact m (f a)
    codom  = proof
    forget = proof