module Botan.Hash.Class
( Hash(..)
, Digest(..)
, hashProxy
, hashFile
, IncrementalHash(..)
, hashFileLazy
-- , MutableHash(..)
-- , MutableCtx(..)
) where

import Botan.Prelude

import Data.Proxy (Proxy(..))

import qualified Data.ByteString as ByteString
import qualified Data.ByteString.Lazy as Lazy

data family Digest hash

class (Eq (Digest hash), Ord (Digest hash)) => Hash hash where
    hash :: ByteString -> Digest hash
    default hash :: (IncrementalHash hash) => ByteString -> Digest hash
    hash = ByteString -> Digest hash
forall hash. IncrementalHash hash => ByteString -> Digest hash
hashLazy (ByteString -> Digest hash)
-> (ByteString -> ByteString) -> ByteString -> Digest hash
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString
ByteString.fromStrict

    -- verifyHash :: ByteString -> Digest hash -> Bool
    -- verifyHash bs d = hash bs == d

hashProxy :: (Hash hash) => Proxy hash -> ByteString -> Digest hash
hashProxy :: forall hash. Hash hash => Proxy hash -> ByteString -> Digest hash
hashProxy Proxy hash
_ = ByteString -> Digest hash
forall hash. Hash hash => ByteString -> Digest hash
hash

hashFile :: (Hash hash, MonadIO m) => FilePath -> m (Digest hash)
hashFile :: forall hash (m :: * -> *).
(Hash hash, MonadIO m) =>
FilePath -> m (Digest hash)
hashFile FilePath
fp = ByteString -> Digest hash
forall hash. Hash hash => ByteString -> Digest hash
hash (ByteString -> Digest hash) -> m ByteString -> m (Digest hash)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO ByteString -> m ByteString
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (FilePath -> IO ByteString
ByteString.readFile FilePath
fp)

class (Hash hash) => IncrementalHash hash where
    hashLazy :: Lazy.ByteString -> Digest hash

hashFileLazy :: (IncrementalHash hash, MonadIO m) => FilePath -> m (Digest hash)
hashFileLazy :: forall hash (m :: * -> *).
(IncrementalHash hash, MonadIO m) =>
FilePath -> m (Digest hash)
hashFileLazy FilePath
fp = do
    ByteString
bs <- IO ByteString -> m ByteString
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO ByteString -> m ByteString) -> IO ByteString -> m ByteString
forall a b. (a -> b) -> a -> b
$ FilePath -> IO ByteString
Lazy.readFile FilePath
fp
    -- Seq is probably unnecessary
    let d :: Digest hash
d = ByteString -> Digest hash
forall hash. IncrementalHash hash => ByteString -> Digest hash
hashLazy ByteString
bs
        in Digest hash
d Digest hash -> m (Digest hash) -> m (Digest hash)
forall a b. a -> b -> b
`seq` Digest hash -> m (Digest hash)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Digest hash
d

-- Experimental below


-- TODO: Mutable hashes

-- TODO: Rename Mutable?
data family MutableCtx hash

class (IncrementalHash hash, MonadIO m) => MutableHash hash m where
    hashInit     :: m (MutableCtx hash)
    hashUpdate   :: MutableCtx hash -> ByteString -> m ()
    hashUpdates  :: MutableCtx hash -> [ByteString] -> m ()
    hashFinalize :: MutableCtx hash -> m (Digest hash)


mutableHashLazy :: MutableHash hash m => Lazy.ByteString -> m (Digest hash)
mutableHashLazy :: forall hash (m :: * -> *).
MutableHash hash m =>
ByteString -> m (Digest hash)
mutableHashLazy ByteString
lbs = do
    MutableCtx hash
ctx <- m (MutableCtx hash)
forall hash (m :: * -> *).
MutableHash hash m =>
m (MutableCtx hash)
hashInit
    MutableCtx hash -> [ByteString] -> m ()
forall hash (m :: * -> *).
MutableHash hash m =>
MutableCtx hash -> [ByteString] -> m ()
hashUpdates MutableCtx hash
ctx ([ByteString] -> m ()) -> [ByteString] -> m ()
forall a b. (a -> b) -> a -> b
$ ByteString -> [ByteString]
Lazy.toChunks ByteString
lbs
    MutableCtx hash -> m (Digest hash)
forall hash (m :: * -> *).
MutableHash hash m =>
MutableCtx hash -> m (Digest hash)
hashFinalize MutableCtx hash
ctx



-- TODO: Something like `Salted "salt" SHA3_512` for static salts?
{-

data Salted (salt :: Symbol) h

-- NOTE: Needs proper encodable to get rid of coerce
instance (KnownSymbol salt, Hash h) => Hash (Salted salt h) where
    hash bs = SaltedDigest $ unsafeCoerce $ hash @h $ bs <> Char8.pack (symbolVal (Proxy @salt))

newtype instance Digest (Salted salt h) = SaltedDigest
    { getSaltedByteString :: ByteString {- ByteVector n -} }
    deriving newtype (Eq, Ord)

instance (KnownSymbol salt, Hash h) => Show (Digest (Salted salt h)) where
    show :: Digest (Salted salt h) -> String
    show (SaltedDigest bytes) = Text.unpack $ Botan.hexEncode bytes Botan.Lower

-}