{-# LANGUAGE BangPatterns        #-}
{-# LANGUAGE CPP                 #-}
{-# LANGUAGE MagicHash           #-}
{-# LANGUAGE PatternSynonyms     #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE Trustworthy         #-}
{-# LANGUAGE UnboxedTuples       #-}
{-# LANGUAGE ViewPatterns        #-}
module Data.Hashable.XXH3 (
    -- * One shot
    xxh3_64bit_withSeed_ptr,
    xxh3_64bit_withSeed_bs,
    xxh3_64bit_withSeed_ba,
    xxh3_64bit_withSeed_w64,
    xxh3_64bit_withSeed_w32,
    -- * Incremental
    XXH3_State,
    xxh3_64bit_createState,
    xxh3_64bit_reset_withSeed,
    xxh3_64bit_digest,
    xxh3_64bit_update_bs,
    xxh3_64bit_update_ba,
    xxh3_64bit_update_w64,
    xxh3_64bit_update_w32,
) where

import Control.Monad.ST.Unsafe  (unsafeIOToST)
import Data.Array.Byte          (ByteArray (..), MutableByteArray (..))
import Data.ByteString.Internal (ByteString (..), accursedUnutterablePerformIO)
import Data.Word                (Word32, Word64, Word8)
import Foreign                  (Ptr)
import GHC.Exts                 (Int (..), MutableByteArray#, newAlignedPinnedByteArray#)
import GHC.ST                   (ST (..))

import Data.Hashable.FFI

#if MIN_VERSION_base(4,15,0)
import GHC.ForeignPtr (unsafeWithForeignPtr)
#else
import Foreign (ForeignPtr, withForeignPtr)
#endif

#if MIN_VERSION_bytestring(0,11,0)
#else
import Foreign (ForeignPtr, plusForeignPtr)
#endif

#if !MIN_VERSION_base(4,15,0)
unsafeWithForeignPtr :: ForeignPtr a -> (Ptr a -> IO b) -> IO b
unsafeWithForeignPtr = withForeignPtr
#endif

#if MIN_VERSION_bytestring(0,11,0)
#else
pattern BS :: ForeignPtr Word8 -> Int -> ByteString
pattern BS fptr len <- (matchBS -> (fptr,len))
  where BS fptr len = PS fptr 0 len
{-# COMPLETE BS #-}

matchBS :: ByteString -> (ForeignPtr Word8, Int)
matchBS (PS fptr off len) = (plusForeignPtr fptr off, len)
#endif

-------------------------------------------------------------------------------
-- OneShot
-------------------------------------------------------------------------------

-- | Hash 'Ptr'
xxh3_64bit_withSeed_ptr :: Ptr Word8 -> Int -> Word64 -> IO Word64
xxh3_64bit_withSeed_ptr !ptr !len !salt =
    unsafe_xxh3_64bit_withSeed_ptr ptr (fromIntegral len) salt

-- | Hash 'ByteString'.
xxh3_64bit_withSeed_bs :: ByteString -> Word64 -> Word64
xxh3_64bit_withSeed_bs (BS fptr len) !salt = accursedUnutterablePerformIO $
    unsafeWithForeignPtr fptr $ \ptr ->
    unsafe_xxh3_64bit_withSeed_ptr ptr (fromIntegral len) salt

-- | Hash (part of) 'ByteArray'.
xxh3_64bit_withSeed_ba :: ByteArray -> Int -> Int -> Word64 -> Word64
xxh3_64bit_withSeed_ba (ByteArray ba) !off !len !salt =
    unsafe_xxh3_64bit_withSeed_ba ba (fromIntegral off) (fromIntegral len) salt

-- | Hash 'Word64'.
xxh3_64bit_withSeed_w64 :: Word64 -> Word64 -> Word64
xxh3_64bit_withSeed_w64 !x !salt =
    unsafe_xxh3_64bit_withSeed_u64 x salt

-- | Hash 'Word32'.
xxh3_64bit_withSeed_w32 :: Word32 -> Word64 -> Word64
xxh3_64bit_withSeed_w32 !x !salt =
    unsafe_xxh3_64bit_withSeed_u32 x salt

-------------------------------------------------------------------------------
-- Incremental
-------------------------------------------------------------------------------

-- | Mutable XXH3 state.
data XXH3_State s = XXH3 (MutableByteArray# s)

-- | Create 'XXH3_State'.
xxh3_64bit_createState :: forall s. ST s (XXH3_State s)
xxh3_64bit_createState = do
    -- aligned alloc, otherwise we get segfaults.
    -- see XXH3_createState implementation
    MutableByteArray ba <- newAlignedPinnedByteArray unsafe_xxh3_sizeof_state 64
    unsafeIOToST (unsafe_xxh3_initState ba)
    return (XXH3 ba)

-- | Reset 'XXH3_State' with a seed.
xxh3_64bit_reset_withSeed :: XXH3_State s -> Word64 -> ST s ()
xxh3_64bit_reset_withSeed (XXH3 s) seed = do
    unsafeIOToST (unsafe_xxh3_64bit_reset_withSeed s seed)

-- | Return a hash value from a 'XXH3_State'.
--
-- Doesn't mutate given state, so you can update, digest and update again.
xxh3_64bit_digest :: XXH3_State s -> ST s Word64
xxh3_64bit_digest (XXH3 s) =
    unsafeIOToST (unsafe_xxh3_64bit_digest s)

-- | Update 'XXH3_State' with 'ByteString'.
xxh3_64bit_update_bs :: XXH3_State s -> ByteString -> ST s ()
xxh3_64bit_update_bs (XXH3 s) (BS fptr len) = unsafeIOToST $
    unsafeWithForeignPtr fptr $ \ptr ->
    unsafe_xxh3_64bit_update_ptr s ptr (fromIntegral len)

-- | Update 'XXH3_State' with (part of) 'ByteArray'
xxh3_64bit_update_ba :: XXH3_State s -> ByteArray -> Int -> Int -> ST s ()
xxh3_64bit_update_ba (XXH3 s) (ByteArray ba) !off !len = unsafeIOToST $
    unsafe_xxh3_64bit_update_ba s ba (fromIntegral off) (fromIntegral len)

-- | Update 'XXH3_State' with 'Word64'.
xxh3_64bit_update_w64 :: XXH3_State s -> Word64 -> ST s ()
xxh3_64bit_update_w64 (XXH3 s) w64 = unsafeIOToST $
    unsafe_xxh3_64bit_update_u64 s w64

-- | Update 'XXH3_State' with 'Word32'.
xxh3_64bit_update_w32 :: XXH3_State s -> Word32 -> ST s ()
xxh3_64bit_update_w32 (XXH3 s) w32 = unsafeIOToST $
    unsafe_xxh3_64bit_update_u32 s w32

-------------------------------------------------------------------------------
-- mini-primitive
-------------------------------------------------------------------------------

newAlignedPinnedByteArray
    :: Int  -- ^ size
    -> Int  -- ^ alignment
    -> ST s (MutableByteArray s)
{-# INLINE newAlignedPinnedByteArray #-}
newAlignedPinnedByteArray (I# n) (I# k) =
    ST (\s -> case newAlignedPinnedByteArray# n k s of (# s', arr #) -> (# s', MutableByteArray arr #))
