module Data.LruCache.Haxl
  ( LruHandle (..)
  , newLruHandle
  , cached'
  , cached
  , remove
  , updateLruHandle
  ) where


import           Data.Hashable          (Hashable (..))
import           Haxl.Core              (GenHaxl, env, userEnv)
import           Haxl.Core.Monad        (unsafeLiftIO)

import           Data.HashPSQ           (delete, member)
import           Data.IORef             (atomicModifyIORef')
import           Data.LruCache          (empty, insert, lookup)
import           Data.LruCache.Internal (LruCache (..))
import           Data.LruCache.IO       (LruHandle (..), newLruHandle)
import           Prelude                hiding (lookup)

-- LruCache
doLookup :: (Hashable k, Ord k) => k -> LruCache k v -> (LruCache k v, Maybe v)
doLookup k c = case lookup k c of
                 Nothing      -> (c, Nothing)
                 Just (v, c') -> (c', Just v)

doInsert :: (Hashable k, Ord k) => k -> v -> a -> LruCache k v -> (LruCache k v, a)
doInsert k v v0 c = (insert k v c, v0)

-- | Return the cached result of the action or, in the case of a cache
-- miss, execute the action and insert it in the cache.
cached :: (Hashable k, Ord k) => (u -> Maybe (LruHandle k v)) -> k -> GenHaxl u w (Maybe v) -> GenHaxl u w (Maybe v)
cached lru k io = do
  h <- lru <$> env userEnv
  go h k io

  where go :: (Hashable k, Ord k) => Maybe (LruHandle k v) -> k -> GenHaxl u w (Maybe v) -> GenHaxl u w (Maybe v)
        go Nothing _ io0 = io0
        go (Just (LruHandle ref)) k0 io0 = do
          res <- unsafeLiftIO $ atomicModifyIORef' ref $ doLookup k0
          case res of
            Just v -> return (Just v)
            Nothing -> do
              v <- io0
              case v of
                Nothing -> return Nothing
                Just v0 -> unsafeLiftIO $ atomicModifyIORef' ref $ doInsert k0 v0 v

cached' :: (Hashable k, Ord k) => (u -> Maybe (LruHandle k v)) -> k -> GenHaxl u w v -> GenHaxl u w v
cached' lru k io = do
  h <- lru <$> env userEnv
  go h k io
  where go :: (Hashable k, Ord k) => Maybe (LruHandle k v) -> k -> GenHaxl u w v -> GenHaxl u w v
        go Nothing _ io0 = io0
        go (Just (LruHandle ref)) k0 io0 = do
          res <- unsafeLiftIO $ atomicModifyIORef' ref $ doLookup k0
          case res of
            Just v -> return v
            Nothing -> do
              v <- io0
              unsafeLiftIO $ atomicModifyIORef' ref $ doInsert k0 v v

remove :: (Hashable k, Ord k) => (u -> Maybe (LruHandle k v)) -> k -> GenHaxl u w ()
remove lru k = do
  h <- lru <$> env userEnv
  case h of
    Nothing -> return ()
    Just (LruHandle ref) ->
      unsafeLiftIO $ atomicModifyIORef' ref $ \c -> do
        let queue = lruQueue c
            size  = lruSize c

        if member k queue then (c { lruSize = size - 1, lruQueue = delete k queue }, ())
                else (c, ())

updateLruHandle :: (Hashable k, Ord k) => LruHandle k v -> Int -> IO ()
updateLruHandle (LruHandle ref) size =
  atomicModifyIORef' ref $ const (empty size, ())