    -- |
    -- Module      : Crypto.Random.AESCtr.Internal
    -- License     : BSD-style
    -- Maintainer  : Vincent Hanquez <vincent@snarc.org>
    -- Stability   : stable
    -- Portability : unknown
    --
    {-# LANGUAGE CPP #-}
    module Crypto.Random.AESCtr.Internal where

    import qualified Crypto.Cipher.AES as AES

    import Data.ByteString (ByteString)
    import qualified Data.ByteString as B

    import Data.Word
    import Data.Bits (xor)

    import Foreign.Ptr
    #if __GLASGOW_HASKELL__ > 704
    import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
    #else
    import Foreign.ForeignPtr (unsafeForeignPtrToPtr)
    #endif

    import Foreign.Storable
    import qualified Data.ByteString.Internal as B

    data Word128 = Word128 {-# UNPACK #-} !Word64 {-# UNPACK #-} !Word64

    {-| An opaque object containing an AES CPRNG -}
    data RNG = RNG
        {-# UNPACK #-} !Word128 -- iv
        {-# UNPACK #-} !Word128 -- cnt
        {-# UNPACK #-} !Int     -- number of chunks generated since reseed
        {-# UNPACK #-} !AES.AES -- AES context

    getNbChunksGenerated :: RNG -> Int
    getNbChunksGenerated (RNG _ _ c _) = c

    put128 :: Word128 -> ByteString
    put128 (Word128 a b) = B.unsafeCreate 16 (write64 . castPtr)
        where write64 :: Ptr Word64 -> IO ()
              write64 ptr = poke ptr a >> poke (ptr `plusPtr` 8) b

    get128 :: ByteString -> Word128
    get128 (B.PS ps s _) = B.inlinePerformIO $ do
        let ptr = castPtr (unsafeForeignPtrToPtr ps `plusPtr` s) :: Ptr Word64
        a <- peek ptr
        b <- peek (ptr `plusPtr` 8)
        return $ Word128 a b

    xor128 :: Word128 -> Word128 -> Word128
    xor128 (Word128 a1 b1) (Word128 a2 b2) = Word128 (a1 `xor` a2) (b1 `xor` b2)

    add64 :: Word128 -> Word128
    add64 (Word128 a b) = if nb < 64 then Word128 (a+1) nb else Word128 a nb
      where nb = b + 64

    {-
    withBsPtr :: ByteString -> (Ptr Word8 -> IO a) -> IO a
    withBsPtr (B.PS ps s _) f = withForeignPtr ps $ \ptr -> f (ptr `plusPtr` s)
    -}

    makeParams :: ByteString -> (AES.AES, ByteString, ByteString)
    makeParams b = (key, cnt, iv)
        where
            key          = AES.initAES $ B.take 32 left2
            (cnt, left2) = B.splitAt 16 left1
            (iv, left1)  = B.splitAt 16 b

    chunkSize :: Int
    chunkSize = 1024

    genNextChunk :: RNG -> (ByteString, RNG)
    genNextChunk (RNG iv counter nbChunks key) = (chunk, newrng)
      where
            newrng = RNG (get128 chunk) (add64 counter) (nbChunks+1) key
            chunk  = AES.genCTR key bytes chunkSize
            bytes  = put128 (iv `xor128` counter)