module Control.Concurrent.CachedIO (
    cachedIO
    ) where

import Control.Concurrent.STM (atomically, newTVar, readTVar, writeTVar)
import Control.Monad (join)
import Data.Time.Clock (NominalDiffTime, addUTCTime, getCurrentTime)

-- | Cache an IO action, producing a version of this IO action that is cached
-- for 'interval' seconds. Immediately initialize the cache with the given IO
-- action.
--
-- The outer IO is responsible for setting up the cache. Use the inner one to
-- either get the cached value or refresh, if the cache is older than 'interval'
-- seconds.
cachedIO :: NominalDiffTime -> IO a -> IO (IO a)
cachedIO interval io = do
  initValue <- io
  initTime <- getCurrentTime
  cachedT <- atomically (newTVar (initTime, initValue))
  return $ do
    now <- getCurrentTime
    join . atomically $ do
      cached <- readTVar cachedT
      case cached of
        (lastUpdated, value) | addUTCTime interval lastUpdated > now ->
          return (return value)
        (_, value) -> do
          writeTVar cachedT (now, value)
          return $ do
            newValue <- io
            atomically (writeTVar cachedT (now, newValue))
            return newValue