-- | Gray code is a binary numeral system where two successive numbers
-- differ in only one bit.
module Codec.Binary.Gray
    (
      -- * List functions (for @[Bool]@)
      binaryToGray, grayToBinary
    , bitsToBinary, binaryToBits
    , showBinary
    ) where

import Data.Bits (Bits, testBit, shiftR, bitSize)
    
xor :: Bool -> Bool -> Bool
xor p q = (p && not q) || (not p && q)

-- | Takes a list of bits (most significant last) in binary encoding
-- and converts them to Gray code.
--
-- Algorithm:
--   Haupt, R.L. and Haupt, S.E., Practical Genetic Algorithms,
--   Second ed. (2004),  5.4. Gray Codes.
binaryToGray :: [Bool] -> [Bool]
binaryToGray (b:c:bs) = b `xor` c : binaryToGray (c:bs)
binaryToGray [b] = [b]
binaryToGray [] = []

-- | Takes a list of bits in Gray code and converts them to binary encoding
-- (most significant bit last).
--
-- Algorithm:
--   Haupt, R.L. and Haupt, S.E., Practical Genetic Algorithms,
--   Second ed. (2004),  5.4. Gray Codes.
grayToBinary :: [Bool] -> [Bool]
grayToBinary = foldr go []
  where go c [] = [c]
        go c bs@(b:_) = b `xor` c : bs

-- | Convert a number to a list of bits in usual binary encoding (most
-- significant last).
-- 
-- As 'bitSize', 'bitsToBinary' is undefined for types that do not
-- have fixed bitsize, like 'Integer'.
bitsToBinary :: (Bits b) => b -> [Bool]
bitsToBinary 0 = []
bitsToBinary i
    | signum i == (-1) =
        let b = map not . bitsToBinary $ negate i - 1
        in  b ++ (take (bitSize i - length b) $ repeat True) -- pad major bits
    | otherwise        =
        let rest = bitsToBinary $ shiftR i 1  -- works only for positive i
        in  (testBit i 0 : rest)

-- | Convert a list of bits in binary encoding to a number.
binaryToBits :: (Bits a) => [Bool] -> a
binaryToBits = sum . map fst . filter snd . zip (map (2^) [0..])

-- | Render a list of bits as a 0-1 string.
showBinary :: [Bool] -> String
showBinary [] = "0"
showBinary bs = map (\b -> if b then '1' else '0') . reverse $ bs
