{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | Module supporting the caching of a service.
module Glue.Caching(
    cacheWithBasic
  , cacheWithMulti
) where

import Data.Hashable
import Glue.Types
import qualified Data.HashSet as S
import qualified Data.HashMap.Strict as M

-- | Caching of a `BasicService` instance, that defers to external functions for the actual caching.
-- | Note: Values within m will be lost for calls that hit the cache.
cacheWithBasic :: (Monad m) 
               => (a -> m (Maybe b))  -- ^ Cache lookup function, used before potentially invoking the fallback service.
               -> (a -> b -> m ())    -- ^ Cache write function, used after invoking the fallback service to populate the cache.
               -> BasicService m a b  -- ^ The service to cache.
               -> BasicService m a b
cacheWithBasic lookupWith insertWith service = 
  let fallback request = do
                            result <- service request
                            insertWith request result
                            return result 
      cachedService request = do
                                fromCache <- lookupWith request
                                maybe (fallback request) return fromCache
  in cachedService

-- | Caching of a `MultiGetService` instance, that defers to external functions for the actual caching.
-- | Partial responses will result in partial fallback calls that get just the missing keys.
-- | Values within m will be lost for calls that hit the cache.
cacheWithMulti :: (Monad m, Functor m, Eq a, Hashable a) 
               => ((MultiGetRequest a) -> m (MultiGetResponse a b)) -- ^ Cache lookup function, used before potentially invoking the fallback service.
               -> ((MultiGetResponse a b) -> m ())                  -- ^ Cache write function, used after invoking the fallback service to populate the cache.
               -> MultiGetService m a b                             -- ^ The service to cache.
               -> MultiGetService m a b
cacheWithMulti lookupWith insertWith service = 
  let fallback request = do
                            result <- service request
                            insertWith result
                            return result 
      cachedService request = do
                                fromCache <- lookupWith request
                                let uncachedKeys = S.difference request (S.fromList $ M.keys fromCache)
                                if (S.null uncachedKeys) then (return fromCache) else (fmap (M.union fromCache) $ fallback uncachedKeys)
  in cachedService