    {-# LANGUAGE CPP #-}

    -- |
    -- Module      : Unicode.Internal.Bits
    -- Copyright   : (c) 2020 Andrew Lelechenko
    --               (c) 2020 Composewell Technologies
    -- License     : BSD-3-Clause
    -- Maintainer  : streamly@composewell.com
    -- Stability   : experimental
    -- Portability : GHC
    --
    -- Fast, static bitmap lookup utilities

    module Unicode.Internal.Bits
        ( -- * Bitmap lookup
          lookupBit,
          lookupWord8AsInt,
          lookupWord8AsInt#,
          lookupWord16AsInt,
          lookupWord16AsInt#,
          lookupWord32#,
          -- * CString
          unpackCString#
        ) where

    #include "MachDeps.h"

    import GHC.Exts
           (Addr#, Int(..), Int#, Word(..), Word#, indexWord8OffAddr#,
            indexWord16OffAddr#, indexWord32OffAddr#,
            and#, word2Int#, uncheckedShiftL#)
    #if MIN_VERSION_base(4,16,0)
    import GHC.Exts (word8ToWord#, word16ToWord#, word32ToWord#)
    #endif
    #ifdef WORDS_BIGENDIAN
    import GHC.Exts
           (narrow16Word#, narrow32Word#,
            byteSwap16#, byteSwap32#)
    #endif

    #if MIN_VERSION_base(4,15,0)
    import GHC.Exts (unpackCString#)
    #else
    import GHC.CString (unpackCString#)
    #endif

    -- TODO: remove?
    -- {- | @lookupBit addr index@ looks up the bit stored at bit index @index@ using
    -- a bitmap starting at the address @addr@. Looks up the word containing the bit
    -- and then the bit in that word. The caller must make sure that the word at the
    -- byte address @(addr + index / wfbs)@, where @wfbs@ is the finite bit size of a
    -- word, is legally accessible memory.
    -- -}
    -- lookupBit :: Addr# -> Int -> Bool
    -- lookupBit addr# (I# index#) = W# (word## `and#` bitMask##) /= 0
    --   where
    --     !fbs@(I# fbs#) = finiteBitSize (0 :: Word) - 1
    --     !(I# log2Fbs) = case fbs of
    --       31 -> 5
    --       63 -> 6
    --       _  -> popCount fbs -- this is a really weird architecture

    --     wordIndex# = index# `uncheckedIShiftRL#` log2Fbs
    -- #ifdef WORDS_BIGENDIAN
    --     word## = byteSwap# (indexWordOffAddr# addr# wordIndex#)
    -- #else
    --     word## = indexWordOffAddr# addr# wordIndex#
    -- #endif
    --     -- x % 2^n = x & (2^n - 1)
    --     bitIndex# = index# `andI#` fbs#
    --     bitMask## = 1## `uncheckedShiftL#` bitIndex#

    {- | @lookupBit addr byteIndex bitIndex@ looks up the bit stored in the byte
    at index @byteIndex@ at the bit index @bitIndex@ using a bitmap starting at the
    address @addr@. The caller must make sure that the byte at address
    @(addr + byteIndex)@ is legally accessible memory.
    -}
    lookupBit :: Addr# -> Int -> Int -> Bool
    lookupBit :: Addr# -> Int -> Int -> Bool
    lookupBit Addr#
    addr# (I# Int#
    byteIndex#) (I# Int#
    bitIndex#) =
        Word# -> Word
    W# (Word#
    word## Word# -> Word# -> Word#
    `and#` Word#
    bitMask##) Word -> Word -> Bool
    forall a. Eq a => a -> a -> Bool
    /= Word
    0
      where
    #if MIN_VERSION_base(4,16,0)
        word## :: Word#
    word## = Word8# -> Word#
    word8ToWord# (Addr# -> Int# -> Word8#
    indexWord8OffAddr# Addr#
    addr# Int#
    byteIndex#)
    #else
        word## = indexWord8OffAddr# addr# byteIndex#
    #endif
        bitMask## :: Word#
    bitMask## = Word#
    1## Word# -> Int# -> Word#
    `uncheckedShiftL#` Int#
    bitIndex#

    {-| @lookupWord8AsInt addr index@ looks up for the @index@-th @8@-bits word in
    the bitmap starting at @addr@, then convert it to an 'Int'.

    The caller must make sure that:

    * @ceiling (addr + (n * 8))@ is legally accessible 'GHC.Exts.Word8#'.

    @since 0.3.0
    -}
    lookupWord8AsInt
      :: Addr# -- ^ Bitmap address
      -> Int   -- ^ Word index
      -> Int   -- ^ Resulting word as 'Int'
    lookupWord8AsInt :: Addr# -> Int -> Int
    lookupWord8AsInt Addr#
    addr# (I# Int#
    index#) = Int# -> Int
    I# (Addr# -> Int# -> Int#
    lookupWord8AsInt# Addr#
    addr# Int#
    index#)

    lookupWord8AsInt#
      :: Addr# -- ^ Bitmap address
      -> Int#  -- ^ Word index
      -> Int#  -- ^ Resulting word as 'Int'
    lookupWord8AsInt# :: Addr# -> Int# -> Int#
    lookupWord8AsInt# Addr#
    addr# Int#
    index# = Word# -> Int#
    word2Int# Word#
    word##
      where
    #if MIN_VERSION_base(4,16,0)
        word## :: Word#
    word## = Word8# -> Word#
    word8ToWord# (Addr# -> Int# -> Word8#
    indexWord8OffAddr# Addr#
    addr# Int#
    index#)
    #else
        word## = indexWord8OffAddr# addr# index#
    #endif

    lookupWord16AsInt
      :: Addr# -- ^ Bitmap address
      -> Int   -- ^ Word index
      -> Int   -- ^ Resulting word as `Int`
    lookupWord16AsInt :: Addr# -> Int -> Int
    lookupWord16AsInt Addr#
    addr# (I# Int#
    k#) = Int# -> Int
    I# (Addr# -> Int# -> Int#
    lookupWord16AsInt# Addr#
    addr# Int#
    k#)

    lookupWord16AsInt#
      :: Addr# -- ^ Bitmap address
      -> Int#  -- ^ Word index
      -> Int#  -- ^ Resulting word as `Int`
    lookupWord16AsInt# :: Addr# -> Int# -> Int#
    lookupWord16AsInt# Addr#
    addr# Int#
    k# = Word# -> Int#
    word2Int# Word#
    word##
        where
    #ifdef WORDS_BIGENDIAN
    #if MIN_VERSION_base(4,16,0)
        word## = narrow16Word# (byteSwap16# (word16ToWord# (indexWord16OffAddr# addr# k#)))
    #else
        word## = narrow16Word# (byteSwap16# (indexWord16OffAddr# addr# k#))
    #endif
    #elif MIN_VERSION_base(4,16,0)
        word## :: Word#
    word## = Word16# -> Word#
    word16ToWord# (Addr# -> Int# -> Word16#
    indexWord16OffAddr# Addr#
    addr# Int#
    k#)
    #else
        word## = indexWord16OffAddr# addr# k#
    #endif

    {-| @lookupWord32# addr index@ looks up for the @index@-th 32-bits word in
    the bitmap starting at @addr@, then convert it to a 'Word#'.

    The caller must make sure that:

    * @ceiling (addr + (n * 32))@ is legally accessible 'GHC.Exts.Word32#'.

    @since 0.4.1
    -}
    lookupWord32#
      :: Addr# -- ^ Bitmap address
      -> Int#  -- ^ Word index
      -> Word# -- ^ Resulting word
    lookupWord32# :: Addr# -> Int# -> Word#
    lookupWord32#
    #ifdef WORDS_BIGENDIAN
    #if MIN_VERSION_base(4,16,0)
        addr# k# = narrow32Word# (byteSwap32# (word32ToWord# (indexWord32OffAddr# addr# k#)))
    #else
        addr# k# = narrow32Word# (byteSwap32# (indexWord32OffAddr# addr# k#))
    #endif
    #elif MIN_VERSION_base(4,16,0)
        Addr#
    addr# Int#
    k# = Word32# -> Word#
    word32ToWord# (Addr# -> Int# -> Word32#
    indexWord32OffAddr# Addr#
    addr# Int#
    k#)
    #else
        = indexWord32OffAddr#
    #endif