# LANGUAGE Trustworthy #

-- |
-- Module     : Data.Hash.SL2
  License     : MIT
  Maintainer :   <  >
--
-- An algebraic hash function, inspired by the paper /Hashing with SL2/ by
   and  .
--
-- The hash function is based on matrix multiplication in the special linear group
  of degree 2 , over a  field of order 2 ^ 127 ,   with all computations modulo
  the polynomial x^127 + x^63 + 1 .
--
-- This construction gives some nice properties, which traditional bit-scambling
-- hash functions don't possess, including it being composable. It holds:
--
-- prop> hash (m1 <> m2) == hash m1 <> hash m2
--
-- Following that, the hash function is also parallelisable. If a message @m@ can be divided
-- into a list of chunks @cs@, the hash of the message can be calculated in parallel:
--
  prop   (  rpar hash cs ) = = hash m
--
  All operations in this package are implemented in a very efficient manner using SSE instructions .
--

module Data.Hash.SL2
  ( Hash
  -- ** Hashing
  , hash
  , append, prepend
  , foldAppend, foldPrepend
  -- ** Composition
  , unit, concat, concatAll
  -- ** Parsing
  , parse
  -- ** Validation
  , valid, validate
  -- ** Packing
  , pack8, pack16, pack32, pack64
  -- ** Unpacking
  , unpack8, unpack16, unpack32, unpack64
  ) where

import Prelude hiding (concat)

import Data.Hash.SL2.Internal (Hash)
import Data.Hash.SL2.Unsafe
import qualified Data.Hash.SL2.Mutable as Mutable

import System.IO.Unsafe

import Data.ByteString (ByteString)

import Data.Word
import Data.Foldable (foldl', foldr')

instance Show Hash where
  show h = unsafePerformIO $ unsafeUseAsPtr h Mutable.serialize

instance Eq Hash where
  a == b = unsafePerformIO $ unsafeUseAsPtr2 a b Mutable.eq

instance Ord Hash where
  compare a b = unsafePerformIO $ unsafeUseAsPtr2 a b Mutable.cmp

instance Monoid Hash where
  mempty = unit
  mappend = concat
  mconcat = concatAll

  | /O(n)/ Calculate the hash of the ' ByteString ' .  for @('append ' ' unit')@.
hash :: ByteString -> Hash
hash = append unit

-- | /O(n)/ Append the hash of the 'ByteString' to the existing 'Hash'.
-- A significantly faster equivalent of @((. 'hash') . 'concat')@.
append :: Hash -> ByteString -> Hash
append h s = fst $ unsafePerformIO $ Mutable.withCopy h $ Mutable.append s
# RULES " hash / concat " forall h s .   ( hash s ) = append h s #

-- | /O(n)/ Prepend the hash of the 'ByteString' to the existing 'Hash'.
  A significantly faster equivalent of @('concat ' . ' hash')@.
prepend :: ByteString -> Hash -> Hash
prepend s h = fst $ unsafePerformIO $ Mutable.withCopy h $ Mutable.prepend s
# RULES " concat / hash " forall s h .  ( hash s ) h = prepend s h #

-- | /O(n)/ Append the hash of every 'ByteString' to the existing 'Hash', from left to right.
  A significantly faster equivalent of @('foldl ' ' append')@.
foldAppend :: Foldable t => Hash -> t ByteString -> Hash
foldAppend h ss = fst $ unsafePerformIO $ Mutable.withCopy h $ Mutable.foldAppend ss
# RULES " foldl / append " forall h ss . foldl append h ss = foldAppend h ss #
{-# RULES "foldl'/append" forall h ss . foldl' append h ss = foldAppend h ss #-}

-- | /O(n)/ Prepend the hash of every 'ByteString' to the existing 'Hash', from right to left.
  A significantly faster equivalent of  ' ( ' foldr ' ' prepend'))@.
foldPrepend :: Foldable t => t ByteString -> Hash -> Hash
foldPrepend ss h = fst $ unsafePerformIO $ Mutable.withCopy h $ Mutable.foldPrepend ss
# RULES " foldr / prepend " forall ss h . foldr prepend h ss = foldPrepend ss h #
{-# RULES "foldr'/prepend" forall ss h . foldr' prepend h ss = foldPrepend ss h #-}

  | /O(1)/ The unit element for concatenation .  for '  ' .
unit :: Hash
unit = fst $ unsafePerformIO $ unsafeWithNew Mutable.unit

  | /O(1)/ Concatenate two hashes .  for ' mappend ' .
concat :: Hash -> Hash -> Hash
concat a b = fst $ unsafePerformIO $ unsafeWithNew (unsafeUseAsPtr2 a b . Mutable.concat)
{-# INLINE[1] concat #-}

  | /O(n)/  a list of hashes .  for ' mconcat ' .
concatAll :: [Hash] -> Hash
concatAll [] = unit
concatAll [h] = h
concatAll (h:hs) = fst $ unsafePerformIO $ Mutable.withCopy h $ \p ->
  mapM_ (flip unsafeUseAsPtr $ Mutable.concat p p) hs

-- | /O(1)/ Parse the representation generated by 'show'.
parse :: String -> Maybe Hash
parse s = uncurry (<$) $ unsafePerformIO $ unsafeWithNew $ Mutable.unserialize s

-- | /O(1)/ Check a hash for bit-level validity.
valid :: Hash -> Bool
valid h = unsafePerformIO $ unsafeUseAsPtr h Mutable.valid

  | /O(1)/ Validate a hash on the bit - level . From @'valid ' h = = ' True'@ follows @'validate ' h = = ' Just ' h@.
validate :: Hash -> Maybe Hash
validate h | valid h = Just h
validate _ = Nothing

  | /O(1)/ Pack a list of 64 8 - bit words .
pack8 :: [Word8] -> Maybe Hash
pack8 ws | length ws == 64 = validate (unsafePack ws)
pack8 _ = Nothing

  | /O(1)/ Pack a list of 32 16 - bit words .
pack16 :: [Word16] -> Maybe Hash
pack16 ws | length ws == 32 = validate (unsafePack ws)
pack16 _ = Nothing

  | /O(1)/ Pack a list of 16 32 - bit words .
pack32 :: [Word32] -> Maybe Hash
pack32 ws | length ws == 16 = validate (unsafePack ws)
pack32 _ = Nothing

  | /O(1)/ Pack a list of 8 64 - bit words .
pack64 :: [Word64] -> Maybe Hash
pack64 ws | length ws == 8 = validate (unsafePack ws)
pack64 _ = Nothing

  | /O(1)/ Unpack into list of 64 8 - bit words .
unpack8 :: Hash -> [Word8]
unpack8 = unsafeUnpack

  | /O(1)/ Unpack into list of 32 16 - bit words .
unpack16 :: Hash -> [Word16]
unpack16 = unsafeUnpack

  | /O(1)/ Unpack into list of 16 32 - bit words .
unpack32 :: Hash -> [Word32]
unpack32 = unsafeUnpack

  | /O(1)/ Unpack into list of 8 64 - bit words .
unpack64 :: Hash -> [Word64]
unpack64 = unsafeUnpack

 |
 Module     : Data.Hash.SL2

 An algebraic hash function, inspired by the paper /Hashing with SL2/ by

 The hash function is based on matrix multiplication in the special linear group

 This construction gives some nice properties, which traditional bit-scambling
 hash functions don't possess, including it being composable. It holds:

 prop> hash (m1 <> m2) == hash m1 <> hash m2

 Following that, the hash function is also parallelisable. If a message @m@ can be divided
 into a list of chunks @cs@, the hash of the message can be calculated in parallel:



 ** Hashing
 ** Composition
 ** Parsing
 ** Validation
 ** Packing
 ** Unpacking
 | /O(n)/ Append the hash of the 'ByteString' to the existing 'Hash'.
 A significantly faster equivalent of @((. 'hash') . 'concat')@.
 | /O(n)/ Prepend the hash of the 'ByteString' to the existing 'Hash'.
 | /O(n)/ Append the hash of every 'ByteString' to the existing 'Hash', from left to right.
# RULES "foldl'/append" forall h ss . foldl' append h ss = foldAppend h ss #
 | /O(n)/ Prepend the hash of every 'ByteString' to the existing 'Hash', from right to left.
# RULES "foldr'/prepend" forall ss h . foldr' prepend h ss = foldPrepend ss h #
# INLINE[1] concat #
 | /O(1)/ Parse the representation generated by 'show'.
 | /O(1)/ Check a hash for bit-level validity.
# LANGUAGE Trustworthy #

  License     : MIT
  Maintainer :   <  >
   and  .
  of degree 2 , over a  field of order 2 ^ 127 ,   with all computations modulo
  the polynomial x^127 + x^63 + 1 .
  prop   (  rpar hash cs ) = = hash m
  All operations in this package are implemented in a very efficient manner using SSE instructions .

module Data.Hash.SL2
  ( Hash
  , hash
  , append, prepend
  , foldAppend, foldPrepend
  , unit, concat, concatAll
  , parse
  , valid, validate
  , pack8, pack16, pack32, pack64
  , unpack8, unpack16, unpack32, unpack64
  ) where

import Prelude hiding (concat)

import Data.Hash.SL2.Internal (Hash)
import Data.Hash.SL2.Unsafe
import qualified Data.Hash.SL2.Mutable as Mutable

import System.IO.Unsafe

import Data.ByteString (ByteString)

import Data.Word
import Data.Foldable (foldl', foldr')

instance Show Hash where
  show h = unsafePerformIO $ unsafeUseAsPtr h Mutable.serialize

instance Eq Hash where
  a == b = unsafePerformIO $ unsafeUseAsPtr2 a b Mutable.eq

instance Ord Hash where
  compare a b = unsafePerformIO $ unsafeUseAsPtr2 a b Mutable.cmp

instance Monoid Hash where
  mempty = unit
  mappend = concat
  mconcat = concatAll

  | /O(n)/ Calculate the hash of the ' ByteString ' .  for @('append ' ' unit')@.
hash :: ByteString -> Hash
hash = append unit

append :: Hash -> ByteString -> Hash
append h s = fst $ unsafePerformIO $ Mutable.withCopy h $ Mutable.append s
# RULES " hash / concat " forall h s .   ( hash s ) = append h s #

  A significantly faster equivalent of @('concat ' . ' hash')@.
prepend :: ByteString -> Hash -> Hash
prepend s h = fst $ unsafePerformIO $ Mutable.withCopy h $ Mutable.prepend s
# RULES " concat / hash " forall s h .  ( hash s ) h = prepend s h #

  A significantly faster equivalent of @('foldl ' ' append')@.
foldAppend :: Foldable t => Hash -> t ByteString -> Hash
foldAppend h ss = fst $ unsafePerformIO $ Mutable.withCopy h $ Mutable.foldAppend ss
# RULES " foldl / append " forall h ss . foldl append h ss = foldAppend h ss #

  A significantly faster equivalent of  ' ( ' foldr ' ' prepend'))@.
foldPrepend :: Foldable t => t ByteString -> Hash -> Hash
foldPrepend ss h = fst $ unsafePerformIO $ Mutable.withCopy h $ Mutable.foldPrepend ss
# RULES " foldr / prepend " forall ss h . foldr prepend h ss = foldPrepend ss h #

  | /O(1)/ The unit element for concatenation .  for '  ' .
unit :: Hash
unit = fst $ unsafePerformIO $ unsafeWithNew Mutable.unit

  | /O(1)/ Concatenate two hashes .  for ' mappend ' .
concat :: Hash -> Hash -> Hash
concat a b = fst $ unsafePerformIO $ unsafeWithNew (unsafeUseAsPtr2 a b . Mutable.concat)

  | /O(n)/  a list of hashes .  for ' mconcat ' .
concatAll :: [Hash] -> Hash
concatAll [] = unit
concatAll [h] = h
concatAll (h:hs) = fst $ unsafePerformIO $ Mutable.withCopy h $ \p ->
  mapM_ (flip unsafeUseAsPtr $ Mutable.concat p p) hs

parse :: String -> Maybe Hash
parse s = uncurry (<$) $ unsafePerformIO $ unsafeWithNew $ Mutable.unserialize s

valid :: Hash -> Bool
valid h = unsafePerformIO $ unsafeUseAsPtr h Mutable.valid

  | /O(1)/ Validate a hash on the bit - level . From @'valid ' h = = ' True'@ follows @'validate ' h = = ' Just ' h@.
validate :: Hash -> Maybe Hash
validate h | valid h = Just h
validate _ = Nothing

  | /O(1)/ Pack a list of 64 8 - bit words .
pack8 :: [Word8] -> Maybe Hash
pack8 ws | length ws == 64 = validate (unsafePack ws)
pack8 _ = Nothing

  | /O(1)/ Pack a list of 32 16 - bit words .
pack16 :: [Word16] -> Maybe Hash
pack16 ws | length ws == 32 = validate (unsafePack ws)
pack16 _ = Nothing

  | /O(1)/ Pack a list of 16 32 - bit words .
pack32 :: [Word32] -> Maybe Hash
pack32 ws | length ws == 16 = validate (unsafePack ws)
pack32 _ = Nothing

  | /O(1)/ Pack a list of 8 64 - bit words .
pack64 :: [Word64] -> Maybe Hash
pack64 ws | length ws == 8 = validate (unsafePack ws)
pack64 _ = Nothing

  | /O(1)/ Unpack into list of 64 8 - bit words .
unpack8 :: Hash -> [Word8]
unpack8 = unsafeUnpack

  | /O(1)/ Unpack into list of 32 16 - bit words .
unpack16 :: Hash -> [Word16]
unpack16 = unsafeUnpack

  | /O(1)/ Unpack into list of 16 32 - bit words .
unpack32 :: Hash -> [Word32]
unpack32 = unsafeUnpack

  | /O(1)/ Unpack into list of 8 64 - bit words .
unpack64 :: Hash -> [Word64]
unpack64 = unsafeUnpack
