{-# language Unsafe #-}
-- |
-- Module       : Data.Group.Free.Internal
-- Copyright    : (c) 2020 Reed Mullanix, Emily Pillmore, Koji Miyazato
-- License      : BSD-style
--
-- Maintainer   : Reed Mullanix <reedmullanix@gmail.com>,
--                Emily Pillmore <emilypi@cohomolo.gy>
--
-- Stability    : stable
-- Portability  : non-portable
--
-- This module exposes internals of 'FreeAbelianGroup'.
--
module Data.Group.Free.Internal
( -- * Free abelian groups
  FreeAbelianGroup(..)
) where


import Data.Map (Map)
import qualified Data.Map.Strict as Map
import qualified Data.Map.Merge.Strict as Map

import Data.Semigroup(Semigroup(..))
import Data.Group
import Data.Group.Order


-- $setup
--
-- >>> import qualified Prelude
-- >>> import Data.Group
-- >>> import Data.Monoid
-- >>> import Data.Semigroup
-- >>> import Data.Word
-- >>> :set -XTypeApplications
-- >>> :set -XFlexibleContexts

-- | A representation of a free abelian group over an alphabet @a@.
--
-- The intuition here is group elements correspond with their positive
-- or negative multiplicities, and as such are simplified by construction.
--
-- === __Examples__:
--
-- >>> let single a = MkFreeAbelianGroup $ Map.singleton a 1
-- >>> a = single 'a'
-- >>> b = single 'b'
-- >>> a
-- FreeAbelianGroup $ fromList [('a',1)]
-- >>> a <> b
-- FreeAbelianGroup $ fromList [('a',1),('b',1)]
-- >>> a <> b == b <> a
-- True
-- >>> invert a
-- FreeAbelianGroup $ fromList [('a',-1)]
-- >>> a <> b <> invert a
-- FreeAbelianGroup $ fromList [('b',1)]
-- >>> gtimes 5 (a <> b)
-- FreeAbelianGroup $ fromList [('a',5),('b',5)]
--
newtype FreeAbelianGroup a =
  MkFreeAbelianGroup (Map a Integer)
    -- ^ Unsafe "raw" constructor, which does not do normalization work.
    -- Please use 'Data.Group.Free.mkFreeAbelianGroup' as it normalizes.
    --
  deriving (Eq, Ord)

instance Show a => Show (FreeAbelianGroup a) where
    showsPrec p (MkFreeAbelianGroup g) =
        showParen (p > 0) $ ("FreeAbelianGroup $ " ++) . shows g

instance (Ord a) => Semigroup (FreeAbelianGroup a) where
    (MkFreeAbelianGroup g) <> (MkFreeAbelianGroup g') =
      MkFreeAbelianGroup $ mergeG g g'
      where
        mergeG = Map.merge
          Map.preserveMissing
          Map.preserveMissing
          (Map.zipWithMaybeMatched $ \_ m n -> nonZero $ m + n)
        nonZero n = if n == 0 then Nothing else Just n

    stimes = flip pow

instance (Ord a) => Monoid (FreeAbelianGroup a) where
    mempty = MkFreeAbelianGroup Map.empty

instance (Ord a) => Group (FreeAbelianGroup a) where
    invert (MkFreeAbelianGroup g) = MkFreeAbelianGroup $ Map.map negate g

    pow _ 0 = mempty
    pow (MkFreeAbelianGroup g) n
      | n == 0    = mempty
      | otherwise = MkFreeAbelianGroup $ Map.map (toInteger n *) g

instance (Ord a) => Abelian (FreeAbelianGroup a)

instance (Ord a) => GroupOrder (FreeAbelianGroup a) where
    order g | g == mempty = Finite 1
            | otherwise   = Infinite