module Data.Cache.Eviction.LFU (
    LFU,
    newLFU,
    -- | For testing
    LFUContentsOnlyEq(..)
) where

import Data.Cache.Eviction
import Data.Word (Word64)
import Data.Hashable
import qualified Data.HashPSQ as PSQ

-- | Evict the least frequently used element from the cache. This means as an element is accessed, its "score" increases
-- and the element is more likely to survive eviction once the cache fills up.
newtype LFU k = LFU {
    queue :: PSQ.HashPSQ k Word64 ()
    } deriving (Eq, Show)

newtype LFUContentsOnlyEq k = LFUContentsOnlyEq (LFU k)
    deriving Show
instance ( Hashable k, Ord k ) => Eq (LFUContentsOnlyEq k) where
    (==) (LFUContentsOnlyEq lfu)
         (LFUContentsOnlyEq lfu') = queue lfu == queue lfu'

newLFU :: LFU k
newLFU = LFU PSQ.empty

instance EvictionStrategy LFU where
    recordLookup key (LFU queue) =
        LFU . snd $ PSQ.alter repsert key queue
        where
            repsert Nothing = ((), Just (1, ()) )
            repsert (Just (count, x)) = ((), Just (count + 1, x))

    evict (LFU queue) =
        case PSQ.findMin queue of
            Just (evicted, _, _) -> (LFU queue', Just evicted)
            Nothing -> (LFU queue, Nothing)
        where
            queue' = PSQ.deleteMin queue