    {-# LANGUAGE CPP #-}
    {-# LANGUAGE BangPatterns #-}
    #if __GLASGOW_HASKELL__
    {-# LANGUAGE DeriveDataTypeable, StandaloneDeriving #-}
    #endif
    #if !defined(TESTING) && defined(__GLASGOW_HASKELL__)
    {-# LANGUAGE Trustworthy #-}
    #endif
    #if __GLASGOW_HASKELL__ >= 708
    {-# LANGUAGE RoleAnnotations #-}
    {-# LANGUAGE TypeFamilies #-}
    #define USE_MAGIC_PROXY 1
    #endif

    #if USE_MAGIC_PROXY
    {-# LANGUAGE MagicHash #-}
    #endif

    #include "containers.h"

    -----------------------------------------------------------------------------
    -- |
    -- Module      :  Data.IntMap.Merge.Strict
    -- Copyright   :  (c) wren romano 2016
    -- License     :  BSD-style
    -- Maintainer  :  libraries@haskell.org
    -- Portability :  portable
    --
    -- This module defines an API for writing functions that merge two
    -- maps. The key functions are 'merge' and 'mergeA'.
    -- Each of these can be used with several different \"merge tactics\".
    --
    -- The 'merge' and 'mergeA' functions are shared by
    -- the lazy and strict modules. Only the choice of merge tactics
    -- determines strictness. If you use 'Data.Map.Merge.Strict.mapMissing'
    -- from this module then the results will be forced before they are
    -- inserted. If you use 'Data.Map.Merge.Lazy.mapMissing' from
    -- "Data.Map.Merge.Lazy" then they will not.
    --
    -- == Efficiency note
    --
    -- The 'Control.Category.Category', 'Applicative', and 'Monad' instances for
    -- 'WhenMissing' tactics are included because they are valid. However, they are
    -- inefficient in many cases and should usually be avoided. The instances
    -- for 'WhenMatched' tactics should not pose any major efficiency problems.
    --
    -- @since 0.5.9

    module Data.IntMap.Merge.Strict (
        -- ** Simple merge tactic types
          SimpleWhenMissing
        , SimpleWhenMatched

        -- ** General combining function
        , merge

        -- *** @WhenMatched@ tactics
        , zipWithMaybeMatched
        , zipWithMatched

        -- *** @WhenMissing@ tactics
        , mapMaybeMissing
        , dropMissing
        , preserveMissing
        , mapMissing
        , filterMissing

        -- ** Applicative merge tactic types
        , WhenMissing
        , WhenMatched

        -- ** Applicative general combining function
        , mergeA

        -- *** @WhenMatched@ tactics
        -- | The tactics described for 'merge' work for
        -- 'mergeA' as well. Furthermore, the following
        -- are available.
        , zipWithMaybeAMatched
        , zipWithAMatched

        -- *** @WhenMissing@ tactics
        -- | The tactics described for 'merge' work for
        -- 'mergeA' as well. Furthermore, the following
        -- are available.
        , traverseMaybeMissing
        , traverseMissing
        , filterAMissing

        -- ** Covariant maps for tactics
        , mapWhenMissing
        , mapWhenMatched

        -- ** Miscellaneous functions on tactics

        , runWhenMatched
        , runWhenMissing
        ) where

    import Data.IntMap.Internal
      ( SimpleWhenMissing
      , SimpleWhenMatched
      , merge
      , dropMissing
      , preserveMissing
      , filterMissing
      , WhenMissing (..)
      , WhenMatched (..)
      , mergeA
      , filterAMissing
      , runWhenMatched
      , runWhenMissing
      )
    import Data.IntMap.Strict.Internal
    #if !MIN_VERSION_base(4,8,0)
    import Control.Applicative (Applicative (..), (<$>))
    #endif
    import Prelude hiding (filter, map, foldl, foldr)

    -- | Map covariantly over a @'WhenMissing' f k x@.
    mapWhenMissing :: Functor f => (a -> b) -> WhenMissing f x a -> WhenMissing f x b
    mapWhenMissing f q = WhenMissing
      { missingSubtree = fmap (map f) . missingSubtree q
      , missingKey = \k x -> fmap (forceMaybe . fmap f) $ missingKey q k x}

    -- | Map covariantly over a @'WhenMatched' f k x y@.
    mapWhenMatched :: Functor f => (a -> b) -> WhenMatched f x y a -> WhenMatched f x y b
    mapWhenMatched f q = WhenMatched
      { matchedKey = \k x y -> fmap (forceMaybe . fmap f) $ runWhenMatched q k x y }

    -- | When a key is found in both maps, apply a function to the
    -- key and values and maybe use the result in the merged map.
    --
    -- @
    -- zipWithMaybeMatched :: (k -> x -> y -> Maybe z)
    --                     -> SimpleWhenMatched k x y z
    -- @
    zipWithMaybeMatched :: Applicative f
                        => (Key -> x -> y -> Maybe z)
                        -> WhenMatched f x y z
    zipWithMaybeMatched f = WhenMatched $
      \k x y -> pure $! forceMaybe $! f k x y
    {-# INLINE zipWithMaybeMatched #-}

    -- | When a key is found in both maps, apply a function to the
    -- key and values, perform the resulting action, and maybe use
    -- the result in the merged map.
    --
    -- This is the fundamental 'WhenMatched' tactic.
    zipWithMaybeAMatched :: Applicative f
                         => (Key -> x -> y -> f (Maybe z))
                         -> WhenMatched f x y z
    zipWithMaybeAMatched f = WhenMatched $
      \ k x y -> forceMaybe <$> f k x y
    {-# INLINE zipWithMaybeAMatched #-}

    -- | When a key is found in both maps, apply a function to the
    -- key and values to produce an action and use its result in the merged map.
    zipWithAMatched :: Applicative f
                    => (Key -> x -> y -> f z)
                    -> WhenMatched f x y z
    zipWithAMatched f = WhenMatched $
      \ k x y -> (Just $!) <$> f k x y
    {-# INLINE zipWithAMatched #-}

    -- | When a key is found in both maps, apply a function to the
    -- key and values and use the result in the merged map.
    --
    -- @
    -- zipWithMatched :: (k -> x -> y -> z)
    --                -> SimpleWhenMatched k x y z
    -- @
    zipWithMatched :: Applicative f
                   => (Key -> x -> y -> z) -> WhenMatched f x y z
    zipWithMatched f = WhenMatched $
      \k x y -> pure $! Just $! f k x y
    {-# INLINE zipWithMatched #-}

    -- | Map over the entries whose keys are missing from the other map,
    -- optionally removing some. This is the most powerful 'SimpleWhenMissing'
    -- tactic, but others are usually more efficient.
    --
    -- @
    -- mapMaybeMissing :: (k -> x -> Maybe y) -> SimpleWhenMissing k x y
    -- @
    --
    -- prop> mapMaybeMissing f = traverseMaybeMissing (\k x -> pure (f k x))
    --
    -- but @mapMaybeMissing@ uses fewer unnecessary 'Applicative' operations.
    mapMaybeMissing :: Applicative f => (Key -> x -> Maybe y) -> WhenMissing f x y
    mapMaybeMissing f = WhenMissing
      { missingSubtree = \m -> pure $! mapMaybeWithKey f m
      , missingKey = \k x -> pure $! forceMaybe $! f k x }
    {-# INLINE mapMaybeMissing #-}

    -- | Map over the entries whose keys are missing from the other map.
    --
    -- @
    -- mapMissing :: (k -> x -> y) -> SimpleWhenMissing k x y
    -- @
    --
    -- prop> mapMissing f = mapMaybeMissing (\k x -> Just $ f k x)
    --
    -- but @mapMissing@ is somewhat faster.
    mapMissing :: Applicative f => (Key -> x -> y) -> WhenMissing f x y
    mapMissing f = WhenMissing
      { missingSubtree = \m -> pure $! mapWithKey f m
      , missingKey = \k x -> pure $! Just $! f k x }
    {-# INLINE mapMissing #-}

    -- | Traverse over the entries whose keys are missing from the other map,
    -- optionally producing values to put in the result.
    -- This is the most powerful 'WhenMissing' tactic, but others are usually
    -- more efficient.
    traverseMaybeMissing :: Applicative f
                         => (Key -> x -> f (Maybe y)) -> WhenMissing f x y
    traverseMaybeMissing f = WhenMissing
      { missingSubtree = traverseMaybeWithKey f
      , missingKey = \k x -> forceMaybe <$> f k x }
    {-# INLINE traverseMaybeMissing #-}

    -- | Traverse over the entries whose keys are missing from the other map.
    traverseMissing :: Applicative f
                         => (Key -> x -> f y) -> WhenMissing f x y
    traverseMissing f = WhenMissing
      { missingSubtree = traverseWithKey f
      , missingKey = \k x -> (Just $!) <$> f k x }
    {-# INLINE traverseMissing #-}

    forceMaybe :: Maybe a -> Maybe a
    forceMaybe Nothing = Nothing
    forceMaybe m@(Just !_) = m
    {-# INLINE forceMaybe #-}