{-# language CPP #-}
{-# language GeneralizedNewtypeDeriving #-}

#if !MIN_VERSION_containers(0,5,9)
module Data.Map.Annihilate
  (
  ) where
#else

module Data.Map.Annihilate
  ( Map
  , singleton
  , lookup
  ) where

import Prelude hiding (lookup)

import Data.Semigroup (Semigroup((<>)))
import Data.Monoid (Monoid(mempty))
import Data.Monoid.Monus (Monus(monus))
import Data.Maybe (fromMaybe)

import qualified Data.Map.Strict as M
import qualified Data.Map.Merge.Strict as MM

-- | A 'Map' that supports annihilation, i.e. it is a 'Monus',
--   where for 'Monus' values, matching keys will be subtracted,
--   and keys not shared by both 'Map's will be discarded.
newtype Map k v = Map (M.Map k v)
  deriving (Eq,Ord,Functor,Foldable,Show)
--todo: manually write Show instance

-- | Create a singleton 'Map'.
singleton :: (Monoid v, Eq v) => k -> v -> Map k v
singleton k v = if v == mempty
  then Map M.empty
  else Map (M.singleton k v)

-- | Lookup a value in a 'Map'. If no value is found, this
--   returns 'mempty'.
lookup :: (Ord k, Monoid v) => k -> Map k v -> v
lookup k (Map m) = fromMaybe mempty (M.lookup k m)

instance (Ord k, Monoid v, Eq v) => Semigroup (Map k v) where
  Map x <> Map y = Map
    ( MM.merge
      MM.dropMissing
      MM.dropMissing
      ( MM.zipWithMaybeMatched
        (\_ a b ->
          let c = a `mappend` b
           in if c == mempty then Nothing else Just c
        )
      )
      x
      y
    )

instance (Ord k, Monoid v, Eq v) => Monoid (Map k v) where
  mempty = Map M.empty
#if !MIN_VERSION_base(4,11,0)
  mappend x y = x <> y
#endif

instance (Ord k, Monus v, Eq v) => Monus (Map k v) where
  monus (Map x) (Map y) = Map
    ( MM.merge
      MM.dropMissing
      MM.dropMissing
      ( MM.zipWithMaybeMatched
        (\_ a b ->
          let c = monus a b
           in if c == mempty then Nothing else Just c
        )
      )
      x
      y
    )

#endif