module Rattletrap.Primitive.CompressedWord where

import qualified Data.Binary.Bits.Get as BinaryBit
import qualified Data.Binary.Bits.Put as BinaryBit
import qualified Data.Bits as Bits

data CompressedWord = CompressedWord
  { compressedWordLimit :: Word
  , compressedWordValue :: Word
  } deriving (Eq, Ord, Show)

getCompressedWord :: Word -> BinaryBit.BitGet CompressedWord
getCompressedWord limit = do
  value <- getCompressedWordStep limit (getMaxBits limit) 0 0
  pure (CompressedWord limit value)

putCompressedWord :: CompressedWord -> BinaryBit.BitPut ()
putCompressedWord compressedWord = do
  let limit = compressedWordLimit compressedWord
  let value = compressedWordValue compressedWord
  let maxBits = getMaxBits limit
  let go position soFar =
        if position < maxBits
          then do
            let x = Bits.shiftL 1 position
            if maxBits > 1 && position == maxBits - 1 && soFar + x > limit
              then pure ()
              else do
                let bit = Bits.testBit value position
                BinaryBit.putBool bit
                let delta =
                      if bit
                        then x
                        else 0
                go (position + 1) (soFar + delta)
          else pure ()
  go 0 0

getMaxBits
  :: (Integral a, Integral b)
  => a -> b
getMaxBits x = do
  let n = max 1 (ceiling (logBase (2 :: Double) (fromIntegral (max 1 x))))
  if x < 1024 && x == 2 ^ n
    then n + 1
    else n

getCompressedWordStep :: Word -> Word -> Word -> Word -> BinaryBit.BitGet Word
getCompressedWordStep limit maxBits position value = do
  let x = Bits.shiftL 1 (fromIntegral position)
  if position < maxBits && value + x <= limit
    then do
      bit <- BinaryBit.getBool
      let newValue =
            if bit
              then value + x
              else value
      getCompressedWordStep limit maxBits (position + 1) newValue
    else pure value