{- | This is an almost direct copy of [Control.Debounce](https://hackage.haskell.org/package/auto-update/docs/Control-Debounce.html)
 from the /auto-update/ package. The salient difference is that this module allows us to debounce actions in arbitrary
 monads using 'MonadUnliftIO'.

 Debounce an action, ensuring it doesn't occur more than once for a given
 period of time.

 This is useful as an optimization, for example to ensure that logs are only
 flushed to disk at most once per second.

 Example usage:

 @
 > printString <- 'mkDebounce' 'DI.defaultDebounceSettings'
                  { 'DI.debounceAction' = putStrLn "Running action"
                  , 'DI.debounceFreq' = 5000000 -- 5 seconds
                  , 'DI.debounceEdge' = 'DI.trailingEdge' -- Trigger on the trailing edge
                  }
 > printString
 Running action
 > printString
 \<Wait five seconds>
 Running action
 @

 See the fast-logger package ("System.Log.FastLogger") for real-world usage.

 @since 0.1.0
-}
module UnliftIO.Debounce
  ( -- * Creation
    mkDebounce

    -- * Settings
  , DI.DebounceSettings
  , defaultDebounceSettings

    -- ** Accessors
  , DI.debounceFreq
  , DI.debounceAction
  , DI.debounceEdge
  , DI.debounceThreadName

    -- ** Edge types
  , DI.DebounceEdge
  , DI.leadingEdge
  , DI.leadingMuteEdge
  , DI.trailingEdge
  , DI.trailingDelayEdge
  )
where

import Data.Functor.Identity
import UnliftIO (MonadUnliftIO)
import UnliftIO.Concurrent (newMVar, threadDelay)
import qualified UnliftIO.Debounce.Internal as DI

{- | Default value for creating a 'DI.DebounceSettings'.

 @since 0.1.0
-}
defaultDebounceSettings :: DI.DebounceSettings Identity
defaultDebounceSettings :: DebounceSettings Identity
defaultDebounceSettings =
  DI.DebounceSettings
    { debounceFreq :: Int
DI.debounceFreq = Int
1000000
    , debounceAction :: Identity ()
DI.debounceAction = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    , debounceEdge :: DebounceEdge
DI.debounceEdge = DebounceEdge
DI.leadingEdge
    , debounceThreadName :: String
DI.debounceThreadName = String
"Debounce"
    }

{- | Generate an action which will trigger the debounced action to be performed.

 /N.B. The generated action will always immediately return, regardless of the 'DI.debounceFreq',/
 /as the debounced action (and the delay\/cooldown) is always performed in a separate thread./

 @since 0.1.0
-}
mkDebounce :: MonadUnliftIO m => DI.DebounceSettings m -> m (m ())
mkDebounce :: forall (m :: * -> *).
MonadUnliftIO m =>
DebounceSettings m -> m (m ())
mkDebounce DebounceSettings m
settings = do
  MVar ()
baton <- forall (m :: * -> *) a. MonadIO m => a -> m (MVar a)
newMVar ()
  forall (m :: * -> *).
MonadUnliftIO m =>
MVar () -> (Int -> m ()) -> DebounceSettings m -> m (m ())
DI.mkDebounceInternal MVar ()
baton forall (m :: * -> *). MonadIO m => Int -> m ()
threadDelay DebounceSettings m
settings