module Rattletrap.Primitive.Text where

import Rattletrap.Primitive.Int32
import Rattletrap.Utility

import qualified Data.Binary as Binary
import qualified Data.Binary.Bits.Get as BinaryBit
import qualified Data.Binary.Bits.Put as BinaryBit
import qualified Data.Binary.Get as Binary
import qualified Data.Binary.Put as Binary
import qualified Data.ByteString.Lazy as ByteString
import qualified Data.Char as Char
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Encoding

newtype Text = Text
  { textValue :: Text.Text
  } deriving (Eq, Ord, Show)

getText :: Binary.Get Text
getText = do
  rawSize <- getInt32
  let decode = getTextDecoder rawSize
  let size = normalizeTextSize rawSize
  bytes <- Binary.getLazyByteString size
  let text = dropNull (decode bytes)
  pure (Text text)

putText :: Text -> Binary.Put
putText text = do
  let size = getTextSize text
  let encode = getTextEncoder size
  putInt32 size
  Binary.putLazyByteString (encode (addNull (textValue text)))

getTextBits :: BinaryBit.BitGet Text
getTextBits = do
  rawSize <- getInt32Bits
  let decode = getTextDecoder rawSize
  let size = normalizeTextSize rawSize
  bytes <- BinaryBit.getLazyByteString size
  let text = dropNull (decode (reverseBytes bytes))
  pure (Text text)

putTextBits :: Text -> BinaryBit.BitPut ()
putTextBits text = do
  let size = getTextSize text
  let encode = getTextEncoder size
  putInt32Bits size
  BinaryBit.putByteString
    (ByteString.toStrict (reverseBytes (encode (addNull (textValue text)))))

stringToText :: String -> Text
stringToText string = Text (Text.pack string)

textToString :: Text -> String
textToString text = Text.unpack (textValue text)

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

normalizeTextSize
  :: Integral a
  => Int32 -> a
normalizeTextSize size =
  case int32Value size of
    0x05000000 -> 8
    x ->
      if x < 0
        then (-2 * fromIntegral x)
        else fromIntegral x

getTextDecoder :: Int32 -> ByteString.ByteString -> Text.Text
getTextDecoder size bytes =
  let decode =
        if size < Int32 0
          then Encoding.decodeUtf16LE
          else Encoding.decodeLatin1
  in decode (ByteString.toStrict bytes)

getTextEncoder :: Int32 -> Text.Text -> ByteString.ByteString
getTextEncoder size text =
  if size < Int32 0
    then ByteString.fromStrict (Encoding.encodeUtf16LE text)
    else encodeLatin1 text

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

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