# LANGUAGE LambdaCase #
-- | Additional functions for manipulating 'Map's.
module Data.Map.Misc
  (
  -- * Working with Maps
    diffMapNoEq
  , diffMap
  , applyMap
  , mapPartitionEithers
  , applyMapKeysSet
  ) where

import Data.Align
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe
import Data.Set (Set)
import qualified Data.Set as Set
import Data.These

  |Produce a @'Map ' k ( Maybe v)@ by comparing two @'Map ' k v@s , @old@ and @new@ respectively . @Just@ represents an association present in @new@ and @Nothing@
-- represents an association only present in @old@ but no longer present in @new@.
--
  Similar to ' diffMap ' but does n't require '  ' on the values , thus ca n't tell if a value has changed or not .
diffMapNoEq :: (Ord k) => Map k v -> Map k v -> Map k (Maybe v)
diffMapNoEq olds news = flip Map.mapMaybe (align olds news) $ \case
  This _ -> Just Nothing
  These _ new -> Just $ Just new
  That new -> Just $ Just new

  |Produce a @'Map ' k ( Maybe v)@ by comparing two @'Map ' k v@s , @old@ and @new respectively . @Just@ represents an association present in @new@ and either not
  present in @old@ or where the value has changed . @Nothing@ represents an association only present in @old@ but no longer present in @new@.
--
  See also ' diffMapNoEq ' for a similar but weaker version which does not require '  ' on the values but thus ca n't indicated a value not changing between
  @old@ and @new@ with @Nothing@.
diffMap :: (Ord k, Eq v) => Map k v -> Map k v -> Map k (Maybe v)
diffMap olds news = flip Map.mapMaybe (align olds news) $ \case
  This _ -> Just Nothing
  These old new
    | old == new -> Nothing
    | otherwise -> Just $ Just new
  That new -> Just $ Just new

-- |Given a @'Map' k (Maybe v)@ representing keys to insert/update (@Just@) or delete (@Nothing@), produce a new map from the given input @'Map' k v@.
--
-- See also 'Reflex.Patch.Map' and 'Reflex.Patch.MapWithMove'.
applyMap :: Ord k => Map k (Maybe v) -> Map k v -> Map k v
applyMap patch old = insertions `Map.union` (old `Map.difference` deletions)
  where (deletions, insertions) = Map.mapEither maybeToEither patch
        maybeToEither = \case
          Nothing -> Left ()
          Just r -> Right r

  |Split a @'Map ' k ( Either a b)@ into @Map k a@ and @Map k b@ , equivalent to @'Map.mapEither ' id@
# DEPRECATED mapPartitionEithers " Use '  ' instead " #
mapPartitionEithers :: Map k (Either a b) -> (Map k a, Map k b)
mapPartitionEithers = Map.mapEither id

  |Given a @'Map ' k ( Maybe v)@ representing keys to insert / update ( @Just@ ) or delete ( @Nothing@ ) , produce a new @'Set ' k@ from the given input set .
--
-- Equivalent to:
--
-- @
--     applyMapKeysSet patch ('Map.keysSet' m) == 'Map.keysSet' ('applyMap' patch m)
-- @
--
  but avoids the intervening  and needs no values .
applyMapKeysSet :: Ord k => Map k (Maybe v) -> Set k -> Set k
applyMapKeysSet patch old = Map.keysSet insertions `Set.union` (old `Set.difference` Map.keysSet deletions)
  where (insertions, deletions) = Map.partition isJust patch


 | Additional functions for manipulating 'Map's.
 * Working with Maps
 represents an association only present in @old@ but no longer present in @new@.


 |Given a @'Map' k (Maybe v)@ representing keys to insert/update (@Just@) or delete (@Nothing@), produce a new map from the given input @'Map' k v@.

 See also 'Reflex.Patch.Map' and 'Reflex.Patch.MapWithMove'.

 Equivalent to:

 @
     applyMapKeysSet patch ('Map.keysSet' m) == 'Map.keysSet' ('applyMap' patch m)
 @

# LANGUAGE LambdaCase #
module Data.Map.Misc
  (
    diffMapNoEq
  , diffMap
  , applyMap
  , mapPartitionEithers
  , applyMapKeysSet
  ) where

import Data.Align
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe
import Data.Set (Set)
import qualified Data.Set as Set
import Data.These

  |Produce a @'Map ' k ( Maybe v)@ by comparing two @'Map ' k v@s , @old@ and @new@ respectively . @Just@ represents an association present in @new@ and @Nothing@
  Similar to ' diffMap ' but does n't require '  ' on the values , thus ca n't tell if a value has changed or not .
diffMapNoEq :: (Ord k) => Map k v -> Map k v -> Map k (Maybe v)
diffMapNoEq olds news = flip Map.mapMaybe (align olds news) $ \case
  This _ -> Just Nothing
  These _ new -> Just $ Just new
  That new -> Just $ Just new

  |Produce a @'Map ' k ( Maybe v)@ by comparing two @'Map ' k v@s , @old@ and @new respectively . @Just@ represents an association present in @new@ and either not
  present in @old@ or where the value has changed . @Nothing@ represents an association only present in @old@ but no longer present in @new@.
  See also ' diffMapNoEq ' for a similar but weaker version which does not require '  ' on the values but thus ca n't indicated a value not changing between
  @old@ and @new@ with @Nothing@.
diffMap :: (Ord k, Eq v) => Map k v -> Map k v -> Map k (Maybe v)
diffMap olds news = flip Map.mapMaybe (align olds news) $ \case
  This _ -> Just Nothing
  These old new
    | old == new -> Nothing
    | otherwise -> Just $ Just new
  That new -> Just $ Just new

applyMap :: Ord k => Map k (Maybe v) -> Map k v -> Map k v
applyMap patch old = insertions `Map.union` (old `Map.difference` deletions)
  where (deletions, insertions) = Map.mapEither maybeToEither patch
        maybeToEither = \case
          Nothing -> Left ()
          Just r -> Right r

  |Split a @'Map ' k ( Either a b)@ into @Map k a@ and @Map k b@ , equivalent to @'Map.mapEither ' id@
# DEPRECATED mapPartitionEithers " Use '  ' instead " #
mapPartitionEithers :: Map k (Either a b) -> (Map k a, Map k b)
mapPartitionEithers = Map.mapEither id

  |Given a @'Map ' k ( Maybe v)@ representing keys to insert / update ( @Just@ ) or delete ( @Nothing@ ) , produce a new @'Set ' k@ from the given input set .
  but avoids the intervening  and needs no values .
applyMapKeysSet :: Ord k => Map k (Maybe v) -> Set k -> Set k
applyMapKeysSet patch old = Map.keysSet insertions `Set.union` (old `Set.difference` Map.keysSet deletions)
  where (insertions, deletions) = Map.partition isJust patch

