{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}

module Cache.Internal where

import Prelude hiding (lookup)
import Data.Maybe
import qualified Data.HashMap.Lazy as H
import Data.Hashable
import Data.Sequence as S

data Cache k v = Cache {
      _limit :: Maybe Int,
      _size  :: Int,
      _cache :: H.HashMap k v,
      _queue :: Seq k
   } deriving (Show, Eq)
   

instance (Hashable k, Eq k) => IsCache Cache k where
   lookup k cache = H.lookup k $ _cache cache
   
   insert k v cache = (if needDump then dump 1 cache else cache) {
            _cache = H.insert k v $ _cache cache,
            _queue = (_queue cache) |> k,
            _size = 1 + _size cache
         }
      where needDump = isNothing (limit cache) || fromJust (limit cache) > size cache
      
   limit = _limit
   
   size  = _size
   
   setLimit lm cache = cache {_limit = lm}
    
   dumpOldest cache = cache {
         _cache = H.delete k _c,
         _queue = S.drop 1 _q,
         _size = size cache - 1
      } where 
         _c = _cache cache
         _q = _queue cache
         k = _queue cache `index` 0
      
   newCache lm = Cache {
      _limit = lm,
      _size = 0,
      _cache = H.empty,
      _queue = S.empty
   } 
   

class IsCache m k where   
   -- | @newCache size@. Set a max number of elements the cache will hold.
   --
   -- If the newly inserted element exceeds the size limit,
   -- the oldest element is removed from the cache.
   newCache :: Maybe Int -> m k v
   
   -- | Alias for @newCache Nothing@
   newUnlimitedCache :: m k v
   newUnlimitedCache = newCache Nothing
   
   -- | Lookup an element from the cache
   lookup :: k -> m k v -> Maybe v
   
   -- | Insert an element to the cache. 
   --
   -- If the newly inserted element exceeds the size limit,
   -- the oldest element is removed from the cache.
   insert :: k -> v -> m k v -> m k v
   
   -- | Remove the oldest key from the cache.
   dumpOldest :: m k v -> m k v
   
   -- | Return the current number of elements.
   size :: m k v -> Int
   
   -- | Return the max number of elements the cache can hold.
   limit :: m k v -> Maybe Int

   -- | Change the size limit.
   setLimit :: Maybe Int -> m k v -> m k v
   
   -- | @dump n cache@ removes n oldest elements from the cache.
   dump :: Int -> m k v -> m k v
   dump 0 cache = cache
   dump n cache = dump (n - 1) (dumpOldest cache)