{-# 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
    , 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)
import qualified Data.List.NonEmpty as NE
import           Data.Monoid (Monoid, Endo (..), Sum (..))
import           Data.Natural (Natural)
import           Data.Ord (Down (..))
import           Data.Semigroup (Semigroup (..))
import           Data.Set (Set)
import qualified Data.Set as Set

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__ > 822
class (Monoid m , SSet m a) => MSet m a
  mact :: m -> a -> a
  mact = act
#else
class Monoid m => MSet m a where
  mact :: m -> a -> a
#endif

instance Monoid m => MSet m m where
#if __GLASGOW_HASKELL__ <= 822
  mact = mappend
#endif

instance (MSet m a, MSet m b) => MSet m (a, b) where
#if __GLASGOW_HASKELL__ <= 822
  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__ <= 822
    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__ <= 822
    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__ <= 822
    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__ <= 822
    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__ <= 822
    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__ <= 822
    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__ <= 822
    mact m = map (mact m)
#endif

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

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

{--
  - instance {-# OVERLAPPABLE #-} (Functor f, MSet m a) => MSet m (f a) where
  -     act m fa = fmap (act m) fa
  --}


#if __GLASGOW_HASKELL__ <= 822
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__ <= 822
    mact = fmact
#endif

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

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

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

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

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

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

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

instance {-# OVERLAPPABLE #-} MSet m a => MSet (S m) a where
#if __GLASGOW_HASKELL__ <= 822
    S m `mact` a = m `mact` a
#endif

instance {-# OVERLAPPING #-} MSet m b => MSet (S m) (Endo b) where
#if __GLASOW_HASKELL__ <= 822
    mact m (Endo f) = Endo $ mact m . f
#endif

instance Monoid m => MSet (Sum Natural) m where
#if __GLASOW_HASKELL__ <= 822
    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 __GLASOW_HASKELL__ <= 822
    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 __GLASOW_HASKELL__ <= 822
    mact = fmact
#endif

instance (Functor f, Functor h, MSet m a) => MSet m (Functor.Sum f h a) where
#if __GLASOW_HASKELL__ <= 822
    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 __GLASOW_HASKELL__ <= 822
    mact m (FreeMSet (h, a)) = FreeMSet (m `mappend` h, a)
#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