-- | Operations on bits
module Haskus.Format.Binary.Bits
   (
   -- * Basic
     module Haskus.Format.Binary.Bits.Basic
   -- * Bit reversal
   , BitReversable (..)
   , reverseBitsGeneric
   , reverseLeastBits
   -- * Mask
   , makeMask
   , maskLeastBits
   -- * String conversion
   , bitsToString
   , bitsFromString
   -- * Shift
   , getBitRange
   -- * Various
   , bitOffset
   , byteOffset
   )
where

import Haskus.Utils.List (foldl')
import Haskus.Format.Binary.Bits.Basic
import Haskus.Format.Binary.Bits.Reverse
import Haskus.Format.Binary.Bits.Order
import Haskus.Format.Binary.Word

-- | makeMask 3 = 00000111
makeMask :: (FiniteBits a) => Word -> a
makeMask n = x' `shiftR` (finiteBitSize x - fromIntegral n)
   where
      x = complement zeroBits
      x' = if isSigned x 
               then error "Cannot use makeMask with a signed type"
               else x
{-# SPECIALIZE makeMask :: Word -> Int #-}
{-# SPECIALIZE makeMask :: Word -> Word #-}
{-# SPECIALIZE makeMask :: Word -> Word8 #-}
{-# SPECIALIZE makeMask :: Word -> Word16 #-}
{-# SPECIALIZE makeMask :: Word -> Word32 #-}
{-# SPECIALIZE makeMask :: Word -> Word64 #-}

-- | Keep only the n least-significant bits of the given value
maskLeastBits :: (FiniteBits a) => Word -> a -> a
{-# INLINE maskLeastBits #-}
maskLeastBits n v = v .&. makeMask n

-- | Compute bit offset (equivalent to x `mod` 8 but faster)
bitOffset :: Word -> Word
{-# INLINE bitOffset #-}
bitOffset n = makeMask 3 .&. n

-- | Compute byte offset (equivalent to x `div` 8 but faster)
byteOffset :: Word -> Word
{-# INLINE byteOffset #-}
byteOffset n = n `shiftR` 3

-- | Reverse the @n@ least important bits of the given value. The higher bits
-- are set to 0.
reverseLeastBits :: (FiniteBits a, BitReversable a) => Word -> a -> a
reverseLeastBits n value = reverseBits value `shiftR` (finiteBitSize value - fromIntegral n)

-- | Convert bits into a string composed of '0' and '1' chars
bitsToString :: FiniteBits a => a -> String
bitsToString x = fmap b [s, s-1 .. 0]
   where
      s   = finiteBitSize x - 1
      b v = if testBit x v then '1' else '0'

-- | Convert a string of '0' and '1' chars into a word
bitsFromString :: Bits a => String -> a
bitsFromString xs = foldl' b zeroBits (reverse xs `zip` [0..])
   where
      b x ('0',i) = clearBit x i
      b x ('1',i) = setBit x i
      b _ (c,_)   = error $ "Invalid character in the string: " ++ [c]


-- | Take n bits at offset o and put them in the least-significant
-- bits of the result
getBitRange :: (BitReversable b, FiniteBits b) => BitOrder -> Word -> Word -> b -> b
{-# INLINE getBitRange #-}
getBitRange bo o n c = case bo of
      BB -> maskLeastBits n $ c             `shiftR` d
      BL -> maskLeastBits n $ reverseBits c `shiftR` o'
      LB -> maskLeastBits n $ reverseBits c `shiftR` d
      LL -> maskLeastBits n $ c             `shiftR` o'
   where 
      o' = fromIntegral o
      d  = finiteBitSize c - fromIntegral n - fromIntegral o