-- | Base 16 or hexadecimal encoding of objects.
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE CPP                        #-}
module Raaz.Core.Encode.Base16
       ( Base16
       , fromBase16, showBase16
       ) where

import Data.Char
import Data.Bits
import Data.String

import Data.ByteString as B
import Data.ByteString.Char8 as C8
import Data.ByteString.Internal (c2w)

import Data.ByteString.Unsafe(unsafeIndex)
import Data.Monoid
import Data.Word

import Prelude

import Raaz.Core.Encode.Internal

-- | The type corresponding to base-16 or hexadecimal encoding. The
-- `Base16` encoding has a special place in this library: most
-- cryptographic types use `Base16` encoding for their `Show` and
-- `IsString` instance. The combinators `fromBase16` and `showBase16`
-- are exposed mainly to make these definitions easy.
--

newtype Base16 = Base16 {unBase16 :: ByteString}
#if MIN_VERSION_base(4,11,0)
                 deriving (Eq, Semigroup, Monoid)
#else
                 deriving (Eq, Monoid)
#endif

-- Developers note: Internally base16 just stores the bytestring as
-- is. The conversion happens when we do an encode and decode of
-- actual base16.
--


instance Encodable Base16 where
  toByteString          = hex . unBase16

  fromByteString bs
    | B.length bs `mod` 2 /= 0 = Nothing
    | validInput bs            = Just $ Base16 $ unsafeFromHex bs
    | otherwise                = Nothing
    where validInput  = C8.all isHexDigit
  unsafeFromByteString bs
    | B.length bs `mod` 2 /= 0 = error "base16 encoding is always of even size"
    | otherwise                = Base16 $ unsafeFromHex bs

instance Show Base16 where
  show = C8.unpack . toByteString

-- | Ignores spaces and ':' (colon).
instance IsString Base16 where
  fromString = unsafeFromByteString . C8.filter (not . useless) . fromString
    where useless c = isSpace c || c == ':'

instance Format Base16 where
  encodeByteString = Base16
  {-# INLINE encodeByteString #-}

  decodeFormat     = unBase16
  {-# INLINE decodeFormat #-}

-- Since the encoding to base16 is usually used for user interaction
-- we can afford to be slower here.
--
-- TODO (Liquid Haskell)
--
{--@ hex :: inp:ByteString -> { bs : ByteString | bslen bs = 2 * bslen inp } @-}
--
hex :: ByteString -> ByteString
hex  bs = fst $ B.unfoldrN (2 * B.length bs) gen 0
    where gen i | rm == 0   = Just (hexDigit $ top4 w, i+1)
                | otherwise = Just (hexDigit $ bot4 w, i+1)
            where (idx, rm) = quotRem i 2
                  w         = unsafeIndex bs idx





hexDigit :: Word8 -> Word8
hexDigit x | x < 10    = c2w '0' + x
           | otherwise = c2w 'a' + (x - 10)

top4 :: Word8 -> Word8; top4 x  = x `shiftR` 4
bot4 :: Word8 -> Word8; bot4 x  = x  .&. 0x0F

{-@ unsafeFromHex :: {bs : ByteString | (bslen bs) mod 2 == 0 } -> ByteString @-}
unsafeFromHex :: ByteString -> ByteString
unsafeFromHex bs = fst $ B.unfoldrN len gen 0
  where len   = B.length bs `quot` 2
        gen i = Just (shiftL w0 4 .|. w1, i + 1)
          where w0 = fromHexWord $ unsafeIndex bs (2 * i)
                w1 = fromHexWord $ unsafeIndex bs (2 * i + 1)
        fromHexWord x
          | c2w '0' <= x && x <= c2w '9' = x - c2w '0'
          | c2w 'a' <= x && x <= c2w 'f' = 10 + (x - c2w 'a')
          | c2w 'A' <= x && x <= c2w 'F' = 10 + (x - c2w 'A')
          | otherwise                    = error "bad base16 character"


-- | Base16 variant of `fromString`. Useful in definition of
-- `IsString` instances as well as in cases where the default
-- `IsString` instance does not parse from a base16 encoding.
fromBase16 :: Encodable a => String -> a
fromBase16 = unsafeFromByteString . unBase16 . fromString

-- | Base16 variant of `show`.
showBase16 :: Encodable a => a -> String
showBase16 = show . Base16 . toByteString
