packages feed

rattletrap-14.1.4: src/lib/Rattletrap/Type/Str.hs

module Rattletrap.Type.Str where

import qualified Data.ByteString as ByteString
import qualified Data.Char as Char
import qualified Data.Int as Int
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import qualified Data.Text.Encoding.Error as Text
import qualified Rattletrap.BitGet as BitGet
import qualified Rattletrap.BitPut as BitPut
import qualified Rattletrap.ByteGet as ByteGet
import qualified Rattletrap.BytePut as BytePut
import qualified Rattletrap.Schema as Schema
import qualified Rattletrap.Type.I32 as I32
import qualified Rattletrap.Utility.Bytes as Bytes
import qualified Rattletrap.Utility.Json as Json

newtype Str
  = Str Text.Text
  deriving (Eq, Ord, Show)

instance Json.FromJSON Str where
  parseJSON = fmap fromText . Json.parseJSON

instance Json.ToJSON Str where
  toJSON = Json.toJSON . toText

schema :: Schema.Schema
schema = Schema.named "str" $ Json.object [Json.pair "type" "string"]

fromText :: Text.Text -> Str
fromText = Str

toText :: Str -> Text.Text
toText (Str x) = x

fromString :: String -> Str
fromString = fromText . Text.pack

toString :: Str -> String
toString = Text.unpack . toText

bytePut :: Str -> BytePut.BytePut
bytePut text =
  let size = getTextSize text
      encode = getTextEncoder size
   in I32.bytePut size <> (BytePut.byteString . encode . addNull $ toText text)

bitPut :: Str -> BitPut.BitPut
bitPut = BitPut.fromBytePut . bytePut

getTextSize :: Str -> I32.I32
getTextSize text =
  let value = toText text
      scale = if Text.all Char.isLatin1 value then 1 else -1 :: Int.Int32
      rawSize =
        if Text.null value
          then 0
          else fromIntegral (Text.length value) + 1 :: Int.Int32
      size =
        if value == Text.pack "\x00\x00\x00None"
          then 0x05000000
          else scale * rawSize :: Int.Int32
   in I32.fromInt32 size

getTextEncoder :: I32.I32 -> Text.Text -> ByteString.ByteString
getTextEncoder size text =
  if I32.toInt32 size < 0
    then Text.encodeUtf16LE text
    else Bytes.encodeLatin1 text

addNull :: Text.Text -> Text.Text
addNull text = if Text.null text then text else Text.snoc text '\x00'

byteGet :: ByteGet.ByteGet Str
byteGet = ByteGet.label "Str" $ do
  size <- ByteGet.label "size" I32.byteGet
  bytes <- ByteGet.label "value" . ByteGet.byteString $ normalizeTextSize size
  pure . fromText . dropNull $ getTextDecoder size bytes

bitGet :: BitGet.BitGet Str
bitGet = do
  rawSize <- I32.bitGet
  bytes <- BitGet.byteString (normalizeTextSize rawSize)
  pure (fromText (dropNull (getTextDecoder rawSize bytes)))

normalizeTextSize :: (Integral a) => I32.I32 -> a
normalizeTextSize size = case I32.toInt32 size of
  0x05000000 -> 8
  x -> (if x < 0 then negate . (*) 2 else id) $ fromIntegral x

getTextDecoder :: I32.I32 -> ByteString.ByteString -> Text.Text
getTextDecoder size bytes =
  let decode =
        if I32.toInt32 size < 0
          then Text.decodeUtf16LEWith Text.lenientDecode
          else Text.decodeLatin1
   in decode bytes

dropNull :: Text.Text -> Text.Text
dropNull = Text.dropWhileEnd (== '\x00')