module Rattletrap.Encode.CompressedWord
  ( putCompressedWord
  )
where

import Rattletrap.Type.CompressedWord

import qualified Data.Binary.Bits.Put as BinaryBits
import qualified Data.Bits as Bits

putCompressedWord :: CompressedWord -> BinaryBits.BitPut ()
putCompressedWord compressedWord =
  let
    limit = compressedWordLimit compressedWord
    value = compressedWordValue compressedWord
    maxBits = getMaxBits limit
  in putCompressedWordStep limit value maxBits 0 0

putCompressedWordStep
  :: Word -> Word -> Int -> Int -> Word -> BinaryBits.BitPut ()
putCompressedWordStep limit value maxBits position soFar =
  if position < maxBits
    then do
      let x = Bits.shiftL 1 position :: Word
      if maxBits > 1 && position == maxBits - 1 && soFar + x > limit
        then pure ()
        else do
          let bit = Bits.testBit value position
          BinaryBits.putBool bit
          let delta = if bit then x else 0
          putCompressedWordStep
            limit
            value
            maxBits
            (position + 1)
            (soFar + delta)
    else pure ()

getMaxBits :: Word -> Int
getMaxBits x =
  let
    n :: Int
    n = max 1 (ceiling (logBase (2 :: Double) (fromIntegral (max 1 x))))
  in if x < 1024 && x == 2 ^ n then n + 1 else n