-- |
-- Module      :  Codec.Scale.Compact
-- Copyright   :  Alexander Krupenkin 2016
-- License     :  BSD3
--
-- Maintainer  :  mail@akru.me
-- Stability   :  experimental
-- Portability :  noportable
--
-- Efficient general integer codec.
--

module Codec.Scale.Compact (Compact(..)) where

import           Control.Monad      (replicateM)
import           Data.Bits          (shiftL, shiftR, (.&.), (.|.))
import           Data.List          (unfoldr)
import           Data.Serialize.Get (getWord16le, getWord32le, getWord8,
                                     lookAhead)
import           Data.Serialize.Put (putWord16le, putWord32le, putWord8)

import           Codec.Scale.Class  (Decode (..), Encode (..))

-- | A "compact" or general integer encoding is sufficient for encoding
-- large integers (up to 2**536) and is more efficient at encoding most
-- values than the fixed-width version.
newtype Compact a = Compact { unCompact :: a }
  deriving (Eq, Ord)

instance Show a => Show (Compact a) where
    show = ("Compact " ++) . show . unCompact

instance Integral a => Encode (Compact a) where
    put (Compact x)
      | n < 0 = error "negatives not supported by compact codec"
      | n < 64 = singleByteMode
      | n < 2^14 = twoByteMode
      | n < 2^30 = fourByteMode
      | n < 2^536 = bigIntegerMode
      | otherwise = error $ "unable to encode " ++ show n ++ " as compact"
      where
        n = toInteger x
        singleByteMode = putWord8 (fromIntegral x `shiftL` 2)
        twoByteMode = putWord16le (fromIntegral x `shiftL` 2 .|. 1)
        fourByteMode = putWord32le (fromIntegral x `shiftL` 2 .|. 2)
        bigIntegerMode = do
            let step 0 = Nothing
                step i = Just (fromIntegral i, i `shiftR` 8)
                unroll = unfoldr step n
            putWord8 (fromIntegral (length unroll) `shiftL` 2 .|. 3)
            mapM_ putWord8 unroll

instance Integral a => Decode (Compact a) where
    get = do
        mode <- lookAhead ((3 .&.) <$> getWord8)
        Compact <$> case mode of
          0 -> fromIntegral <$> singleByteMode
          1 -> fromIntegral <$> twoByteMode
          2 -> fromIntegral <$> fourByteMode
          3 -> bigIntegerMode
          _ -> fail "unexpected prefix decoding compact number"
      where
        singleByteMode = flip shiftR 2 <$> getWord8
        twoByteMode = flip shiftR 2 <$> getWord16le
        fourByteMode = flip shiftR 2 <$> getWord32le
        bigIntegerMode = do
            let unstep b a = a `shiftL` 8 .|. fromIntegral b
                roll = fromInteger . foldr unstep 0
            len <- flip shiftR 2 <$> getWord8
            roll <$> replicateM (fromIntegral len) getWord8
