-- | A strict version of the semi-direct product. If a monoid m acts
--   on s then this version of the semi-direct product is strict in
--   the m-portion of the semi-direct product.

{-# LANGUAGE CPP                   #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TupleSections         #-}

module Data.Monoid.SemiDirectProduct.Strict
       ( Semi, unSemi, tag, inject, untag, embed, quotient
       ) where

#if !MIN_VERSION_base(4,8,0)
import           Data.Monoid
#endif

import           Data.Monoid.Action

-- | The semi-direct product of monoids @s@ and @m@, which is a monoid
--   when @m@ acts on @s@. Structurally, the semi-direct product is
--   just a pair @(s,m)@.  However, the monoid instance is different.
--   In particular, we have
--
-- > (s1,m1) <> (s2,m2) = (s1 <> (m1 `act` s2), m1 <> m2)
--
--   We call the monoid @m@ the quotient monoid and the monoid @s@ the
--   sub-monoid of the semi-direct product. The semi-direct product
--   @Semi s m@ is an extension of the monoid @s@ with @m@ being the
--   quotient.
data Semi s m = Semi s !m

unSemi :: Semi s m -> (s,m)
unSemi (Semi s m) = (s,m)

instance (Monoid m, Monoid s, Action m s) => Monoid (Semi s m) where
  mempty                            = Semi mempty mempty
  {-# INLINE mempty #-}
  mappend (Semi xs xm) (Semi ys ym) = Semi (xs `mappend` (xm `act` ys)) (xm `mappend` ym)
  {-# INLINE mappend #-}
  mconcat                           = foldr mappend mempty
  {-# INLINE mconcat #-}

-- | Tag an @s@ value with an @m@ value to create an element of the
--   semi-direct product.
tag :: s -> m -> Semi s m
tag = Semi

-- | The injection map, *i.e.* give an @s@ value a trivial tag.
inject :: Monoid m => s -> Semi s m
inject = flip Semi mempty

-- | Forget the monoidal tag.  Of course, @untag . inject = id@, and
--   @untag (tag s m) = s@.
untag :: Semi s m -> s
untag (Semi s _) = s

-- | Embed a "tag" value as a value of type @Semi s m@.  Note that
--
--   @inject s <> embed m = tag s m@
--
--   and
--
--   @embed m <> inject s@ = tag (act m s) m@.
--
--   The semi-direct product gives a split extension of @s@ by
--   @m@. This allows us to embed @m@ into the semi-direct
--   product. This is the embedding map. The quotient and embed maps
--   should satisfy the equation @quotient . embed = id@.
embed :: Monoid s => m -> Semi s m
embed = Semi mempty

-- | The quotient map, *i.e.* retrieve the monoidal tag value.
quotient :: Semi s m -> m
quotient (Semi _ m) = m