{-|
  Pure immutable hash whose lookup is O(1).
-}

module Data.StaticHash (
    StaticHash
  , fromList, lookup
  ) where

import Data.Array
import Data.Hashable
import Data.List (groupBy,sortBy)
import Data.Numbers.Primes
import qualified Prelude as P (lookup)
import Prelude hiding (lookup)

----------------------------------------------------------------

{-|
  Data type for immutable hashes.
-}
newtype StaticHash k v = StaticHash (Array Int (Maybe [(k,v)]))

----------------------------------------------------------------

{-| 
  Creating 'StaticHash' from a list. A prime around the length of
  the list x 2 is chosen for the size of array. This may prevent
  collisions.
-}

fromList :: (Eq k,Hashable k) => [(k,v)] -> StaticHash k v
fromList xs = StaticHash $ array (0,p-1) ls
  where
    len = length xs
    threshold = len * 2 -- hoping collision free
    p = head $ filter (>= threshold) primes
    hs = map (\xy -> (fst xy `hashBy` p, xy)) xs
    x <=> y = fst x`compare` fst y
    x === y = fst x == fst y
    gs = groupBy (===) $ sortBy (<=>) hs
    unify ys = (fst (head ys), map snd ys)
    ls = fil 0 p $ map unify gs

fil :: Int -> Int -> [(Int, v)] -> [(Int, Maybe v)]
fil i lim []
  | i < lim   = (i,Nothing) : fil (i+1) lim []
  | otherwise = []
fil i lim kvs@((k,v):rest)
  | i < k     = (i,Nothing) : fil (i+1) lim kvs
  | otherwise = (k,Just v)  : fil (k+1) lim rest

----------------------------------------------------------------

{-|
  Looking up 'StaticHash'.
-}
lookup :: (Eq k,Hashable k) => k -> StaticHash k v -> Maybe v
lookup k (StaticHash hs) = hs ! i >>= P.lookup k
  where
    (_,p') = bounds hs
    p = p' + 1
    i = k `hashBy` p

----------------------------------------------------------------

hashBy :: Hashable k => k -> Int -> Int
hashBy k p = hash k `mod` p