    -- |
    -- Module      : Crypto.Internal.Compat
    -- License     : BSD-style
    -- Maintainer  : Vincent Hanquez <vincent@snarc.org>
    -- Stability   : stable
    -- Portability : Good
    --
    -- Small and self contained array representation
    -- with limited safety for internal use.
    --
    -- the array produced should never be exposed to the user directly
    --
    {-# LANGUAGE BangPatterns #-}
    {-# LANGUAGE MagicHash #-}
    {-# LANGUAGE UnboxedTuples #-}
    module Crypto.Internal.WordArray
        ( Array8
        , Array32
        , Array64
        , MutableArray32
        , array8
        , array32
        , mutableArray32
        , array64
        , arrayRead8
        , arrayRead32
        , arrayRead64
        , mutableArrayRead32
        , mutableArrayWrite32
        , mutableArrayWriteXor32
        , mutableArray32FromAddrBE
        , mutableArray32Freeze
        ) where

    import Data.Word
    import Data.Bits (xor)
    import Crypto.Internal.Compat
    import Crypto.Internal.CompatPrim
    import GHC.Prim
    import GHC.Types
    import GHC.Word

    -- | Array of Word8
    data Array8 = Array8 Addr#

    -- | Array of Word32
    data Array32 = Array32 ByteArray#

    -- | Array of Word64
    data Array64 = Array64 ByteArray#

    -- | Array of mutable Word32
    data MutableArray32 = MutableArray32 (MutableByteArray# RealWorld)

    -- | Create an array of Word8 aliasing an Addr#
    array8 :: Addr# -> Array8
    array8 = Array8

    -- | Create an Array of Word32 of specific size from a list of Word32
    array32 :: Int -> [Word32] -> Array32
    array32 (I# n) l = unsafeDoIO $ IO $ \s ->
        case newAlignedPinnedByteArray# (n *# 4#) 4# s of
            (# s', mbarr #) -> loop 0# s' mbarr l
      where
            loop _ st mb [] = freezeArray mb st
            loop i st mb ((W32# x):xs)
                | booleanPrim (i ==# n) = freezeArray mb st
                | otherwise =
                    let !st' = writeWord32Array# mb i x st
                     in loop (i +# 1#) st' mb xs
            freezeArray mb st =
                case unsafeFreezeByteArray# mb st of
                    (# st', b #) -> (# st', Array32 b #)
    {-# NOINLINE array32 #-}

    -- | Create an Array of Word64 of specific size from a list of Word64
    array64 :: Int -> [Word64] -> Array64
    array64 (I# n) l = unsafeDoIO $ IO $ \s ->
        case newAlignedPinnedByteArray# (n *# 8#) 8# s of
            (# s', mbarr #) -> loop 0# s' mbarr l
      where
            loop _ st mb [] = freezeArray mb st
            loop i st mb ((W64# x):xs)
                | booleanPrim (i ==# n) = freezeArray mb st
                | otherwise =
                    let !st' = writeWord64Array# mb i x st
                     in loop (i +# 1#) st' mb xs
            freezeArray mb st =
                case unsafeFreezeByteArray# mb st of
                    (# st', b #) -> (# st', Array64 b #)
    {-# NOINLINE array64 #-}

    -- | Create a Mutable Array of Word32 of specific size from a list of Word32
    mutableArray32 :: Int -> [Word32] -> IO MutableArray32
    mutableArray32 (I# n) l = IO $ \s ->
        case newAlignedPinnedByteArray# (n *# 4#) 4# s of
            (# s', mbarr #) -> loop 0# s' mbarr l
      where
            loop _ st mb [] = (# st, MutableArray32 mb #)
            loop i st mb ((W32# x):xs)
                | booleanPrim (i ==# n) = (# st, MutableArray32 mb #)
                | otherwise =
                    let !st' = writeWord32Array# mb i x st
                     in loop (i +# 1#) st' mb xs

    -- | Create a Mutable Array of BE Word32 aliasing an Addr
    mutableArray32FromAddrBE :: Int -> Addr# -> IO MutableArray32
    mutableArray32FromAddrBE (I# n) a = IO $ \s ->
        case newAlignedPinnedByteArray# (n *# 4#) 4# s of
            (# s', mbarr #) -> loop 0# s' mbarr
      where
            loop i st mb
                | booleanPrim (i ==# n) = (# st, MutableArray32 mb #)
                | otherwise             =
                    let !st' = writeWord32Array# mb i (be32Prim (indexWord32OffAddr# a i)) st
                     in loop (i +# 1#) st' mb

    -- | freeze a Mutable Array of Word32 into a immutable Array of Word32
    mutableArray32Freeze :: MutableArray32 -> IO Array32
    mutableArray32Freeze (MutableArray32 mb) = IO $ \st ->
        case unsafeFreezeByteArray# mb st of
            (# st', b #) -> (# st', Array32 b #)

    -- | Read a Word8 from an Array
    arrayRead8 :: Array8 -> Int -> Word8
    arrayRead8 (Array8 a) (I# o) = W8# (indexWord8OffAddr# a o)
    {-# INLINE arrayRead8 #-}

    -- | Read a Word32 from an Array
    arrayRead32 :: Array32 -> Int -> Word32
    arrayRead32 (Array32 b) (I# o) = W32# (indexWord32Array# b o)
    {-# INLINE arrayRead32 #-}

    -- | Read a Word64 from an Array
    arrayRead64 :: Array64 -> Int -> Word64
    arrayRead64 (Array64 b) (I# o) = W64# (indexWord64Array# b o)
    {-# INLINE arrayRead64 #-}

    -- | Read a Word32 from a Mutable Array of Word32
    mutableArrayRead32 :: MutableArray32 -> Int -> IO Word32
    mutableArrayRead32 (MutableArray32 m) (I# o) = IO $ \s -> case readWord32Array# m o s of (# s', e #) -> (# s', W32# e #)
    {-# INLINE mutableArrayRead32 #-}

    -- | Write a Word32 from a Mutable Array of Word32
    mutableArrayWrite32 :: MutableArray32 -> Int -> Word32 -> IO ()
    mutableArrayWrite32 (MutableArray32 m) (I# o) (W32# w) = IO $ \s -> let !s' = writeWord32Array# m o w s in (# s', () #)
    {-# INLINE mutableArrayWrite32 #-}

    -- | Write into the Mutable Array of Word32 by combining through xor the current value and the new value.
    --
    -- > x[i] = x[i] xor value
    mutableArrayWriteXor32 :: MutableArray32 -> Int -> Word32 -> IO ()
    mutableArrayWriteXor32 m o w =
        mutableArrayRead32 m o >>= \wOld -> mutableArrayWrite32 m o (wOld `xor` w)
    {-# INLINE mutableArrayWriteXor32 #-}