-- | An abstract key/value store.
module Build.Store (
    -- * Hashing
    Hash, Hashable (..),

    -- * Store
    Store, getValue, putValue, getHash, getInfo, putInfo, mapInfo,
    initialise
    ) where

-- | A 'Hash' is used for efficient tracking and sharing of build results. We
-- use @newtype Hash a = Hash a@ for prototyping.
newtype Hash a = Hash a deriving (Eq, Ord,Show)

instance Functor Hash where
    fmap f (Hash a) = Hash (f a)

instance Applicative Hash where
    pure = Hash
    Hash f <*> Hash a = Hash (f a)

class Ord a => Hashable a where
    -- | Compute the hash of a given value. We typically assume cryptographic
    -- hashing, e.g. SHA256.
    hash :: a -> Hash a

instance Hashable Int where
    hash = Hash

instance Hashable Integer where
    hash = Hash

instance Hashable a => Hashable [a] where
    hash = Hash

instance Hashable a => Hashable (Hash a) where
    hash = Hash

instance (Hashable a, Hashable b) => Hashable (a, b) where
    hash = Hash

-- | An abstract datatype for a key/value store with build information of type @i@.
data Store i k v = Store { info :: i, values :: k -> v }

-- | Read the build information.
getInfo :: Store i k v -> i
getInfo = info

-- | Read the value of a key.
getValue :: k -> Store i k v -> v
getValue = flip values

-- | Read the hash of a key's value. In some cases may be implemented more
-- efficiently than @hash . getValue k@.
getHash :: Hashable v => k -> Store i k v -> Hash v
getHash k = hash . getValue k

-- | Write the build information.
putInfo :: i -> Store i k v -> Store i k v
putInfo i s = s { info = i }

-- | Modify the build information.
mapInfo :: (i -> j) -> Store i k v -> Store j k v
mapInfo f (Store i kv) = Store (f i) kv

-- | Update the value of a key.
putValue :: Eq k => k -> v -> Store i k v -> Store i k v
putValue k v s = s { values = \key -> if key == k then v else values s key }

-- | Initialise the store.
initialise :: i -> (k -> v) -> Store i k v
initialise = Store