{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ExistentialQuantification #-}

-- |
-- Module: Data.ContentStore.Digest
-- Copyright: (c) 2017 Red Hat, Inc.
-- License: LGPL
--
-- Maintainer: https://github.com/weldr
-- Stability: alpha
-- Portability: portable
--
-- Functions for working with digest algorithms as used by the content store

module Data.ContentStore.Digest(DigestAlgorithm,
                                getDigestAlgorithm,
                                digestName,
                                digestSize,
                                digestInit,
                                DigestContext,
                                digestUpdate,
                                digestUpdates,
                                digestFinalize,
                                digestByteString,
                                digestLazyByteString,
                                ObjectDigest,
                                toHex,
                                fromByteString)
 where

import           Crypto.Hash
import           Data.ByteArray(Bytes, ByteArrayAccess, convert)
import           Data.ByteArray.Encoding(Base(..), convertToBase)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Text as T
import           Data.Text.Encoding(decodeUtf8)

-- cryptonite is about 50% too clever with its types, if you ask me.
-- Here's simpler ones that we can use in ContentStore.

--
-- DIGEST TYPES
--

-- | A DigestAlgorithm represents one specific hash algorithm.
data DigestAlgorithm = forall a. (HashAlgorithm a, Show a) => DigestAlgorithm a

-- | Holds the context for a given instance of a 'DigestAlgorithm'.
data DigestContext = forall a. HashAlgorithm a => DigestContext (Context a)

-- | ObjectDigest is the (binary) representation of a digest.
newtype ObjectDigest = ObjectDigest Bytes
    deriving (Eq, Ord, ByteArrayAccess)

instance Show ObjectDigest where
    show (ObjectDigest b) = show b

--
-- PRIVATE FUNCTIONS
--

fromDigest :: HashAlgorithm a => Digest a -> ObjectDigest
fromDigest = ObjectDigest . convert

hashLazyWith :: HashAlgorithm a => a -> LBS.ByteString -> Digest a
hashLazyWith _ = hashlazy

--
-- PUBLIC FUNCTIONS
--

-- | Get the name of this 'DigestAlgorithm'.
digestName :: DigestAlgorithm -> String
digestName (DigestAlgorithm a) = show a

-- | The size of the 'ObjectDigest' returned by this 'DigestAlgorithm'.
digestSize :: DigestAlgorithm -> Int
digestSize (DigestAlgorithm a) = hashDigestSize a

-- | Initialize a new 'DigestContext' for this 'DigestAlgorithm'.
digestInit :: DigestAlgorithm -> DigestContext
digestInit (DigestAlgorithm a) = DigestContext $ hashInitWith a

-- | Update the 'DigestContext' with one 'ByteString'/'Bytes'/etc item.
digestUpdate :: ByteArrayAccess ba => DigestContext -> ba -> DigestContext
digestUpdate (DigestContext ctx) = DigestContext . hashUpdate ctx

-- | Update the 'DigestContext' with many 'ByteString'/'Bytes'/etc. items.
digestUpdates :: ByteArrayAccess ba => DigestContext -> [ba] -> DigestContext
digestUpdates (DigestContext ctx) = DigestContext . hashUpdates ctx

-- | Finish the digest, returning an 'ObjectDigest'.
digestFinalize :: DigestContext -> ObjectDigest
digestFinalize (DigestContext ctx) = fromDigest $ hashFinalize ctx

-- | Hash a strict 'ByteString' into an 'ObjectDigest'.
digestByteString :: DigestAlgorithm -> BS.ByteString -> ObjectDigest
digestByteString (DigestAlgorithm a) = fromDigest . hashWith a

-- | Hash a lazy 'Data.ByteString.Lazy.ByteString' into an 'ObjectDigest'.
digestLazyByteString :: DigestAlgorithm -> LBS.ByteString -> ObjectDigest
digestLazyByteString (DigestAlgorithm a) = fromDigest . hashLazyWith a

-- | Check and convert a 'ByteString' into an 'ObjectDigest'.
fromByteString :: ByteArrayAccess ba => DigestAlgorithm -> ba -> Maybe ObjectDigest
fromByteString (DigestAlgorithm a) = fmap fromDigest . digestFromByteStringWith a
-- helper for above
digestFromByteStringWith :: (HashAlgorithm a, ByteArrayAccess ba) => a -> ba -> Maybe (Digest a)
digestFromByteStringWith _ = digestFromByteString

-- | Convert an 'ObjectDigest' to its hex representation.
-- TODO: probably more efficient if we can just coerce the converted Bytes..
toHex :: ObjectDigest -> String
toHex = T.unpack . decodeUtf8 . convertToBase Base16


-- | Given the 'Text' name of a digest algorithm, return a 'DigestAlgorithm'
-- (or Nothing if we don't recognize the DigestAlgorithm's name).
getDigestAlgorithm :: T.Text -> Maybe DigestAlgorithm
getDigestAlgorithm "SHA256"     = Just $ DigestAlgorithm SHA256
getDigestAlgorithm "SHA512"     = Just $ DigestAlgorithm SHA512
getDigestAlgorithm "BLAKE2"     = Just $ DigestAlgorithm Blake2b_512
getDigestAlgorithm "BLAKE2b"    = Just $ DigestAlgorithm Blake2b_512
getDigestAlgorithm "BLAKE2b512" = Just $ DigestAlgorithm Blake2b_512
getDigestAlgorithm "BLAKE2b256" = Just $ DigestAlgorithm Blake2b_256
getDigestAlgorithm "BLAKE2s"    = Just $ DigestAlgorithm Blake2s_256
getDigestAlgorithm "BLAKE2s256" = Just $ DigestAlgorithm Blake2s_256
getDigestAlgorithm _            = Nothing