{-# LANGUAGE Trustworthy #-}

module Data.Hash.SL2.Mutable
  ( valid
  , eq, cmp
  , unit
  , concat
  , append, prepend
  , foldAppend, foldPrepend
  , serialize, unserialize
  , withUnit, withCopy
  ) where

import Prelude hiding (concat)

import Foreign
import Foreign.C.String

import Data.ByteString (ByteString)
import Data.ByteString.Unsafe

import Data.Foldable (foldlM, foldrM)

import Data.Hash.SL2.Internal (Hash, hashLen)
import qualified Data.Hash.SL2.Internal as Internal
import Data.Hash.SL2.Unsafe

-- | /O(1)/ Check a hash for bit-level validity.
valid :: Ptr Hash -> IO Bool
{-# INLINE valid #-}
valid h = fmap toBool $ Internal.valid h

-- | /O(1)/ Compare the two hashes for equality.
eq :: Ptr Hash -> Ptr Hash -> IO Bool
{-# INLINE eq #-}
eq a b = fmap toBool $ Internal.eq a b

-- | /O(1)/ Compare two hashes.
cmp :: Ptr Hash -> Ptr Hash -> IO Ordering
{-# INLINE cmp #-}
cmp a b = fmap (compare 0) $ Internal.cmp a b

-- | /O(1)/ Set the 'Hash' to the empty value.
unit :: Ptr Hash -> IO ()
{-# INLINE unit #-}
unit h = Internal.unit h

-- | /O(1)/ Concatenate the second and third 'Hash', store the result in the first.
concat :: Ptr Hash -> Ptr Hash -> Ptr Hash -> IO ()
{-# INLINE concat #-}
concat c a b = Internal.concat c a b

-- | /O(n)/ Append the hash of the 'ByteString' to the existing 'Hash'.
append :: ByteString -> Ptr Hash -> IO ()
{-# INLINE append #-}
append s p = unsafeUseAsCStringLen s $ \(s', len) -> Internal.append p s' (fromIntegral len)

-- | /O(n)/ Prepend the hash of the 'ByteString' to the existing 'Hash'.
prepend :: ByteString -> Ptr Hash -> IO ()
{-# INLINE prepend #-}
prepend s p = unsafeUseAsCStringLen s $ \(s', len) -> Internal.prepend p s' (fromIntegral len)

-- | /O(n)/ Append the hash of every 'ByteString' to the existing 'Hash', from left to right.
foldAppend :: Foldable t => t ByteString -> Ptr Hash -> IO ()
{-# INLINE foldAppend #-}
foldAppend ss p = foldlM (const $ flip append p) () ss

-- | /O(n)/ Prepend the hash of every 'ByteString' to the existing 'Hash', from right to left.
foldPrepend :: Foldable t => t ByteString -> Ptr Hash -> IO ()
{-# INLINE foldPrepend #-}
foldPrepend ss p = foldrM (const . flip prepend p) () ss

-- | /O(1)/ Serialize the hash into a url-safe base64 representation.
serialize :: Ptr Hash -> IO String
{-# INLINE serialize #-}
serialize h = allocaBytes hashLen $ \p -> Internal.serialize h p >> peekCStringLen (p, hashLen)

-- | /O(1)/ Unserialize the hash from the representation generated by 'serialize'.
unserialize :: String -> Ptr Hash -> IO (Maybe ())
{-# INLINE unserialize #-}
unserialize s p = withCAStringLen s $ \(s', len) ->
  if len == hashLen then Just `fmap` Internal.unserialize p s' else return Nothing

withUnit :: (Ptr Hash -> IO a) -> IO (Hash, a)
{-# INLINE withUnit #-}
withUnit f = unsafeWithNew $ \p -> unit p >> f p

withCopy :: Hash -> (Ptr Hash -> IO a) -> IO (Hash, a)
{-# INLINE withCopy #-}
withCopy h f = unsafeWithNew $ \p -> poke p h >> f p