{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------
-- |
-- Module        : Data.Minecraft.Types
-- Copyright     : (c) 2016 Michael Carpenter
-- License       : BSD3
-- Maintainer    : Michael Carpenter <oldmanmike.dev@gmail.com>
-- Stability     : experimental
-- Portability   : portable
--
--------------------------------------------------------------------
module Data.Minecraft.Types
  ( Array
  , Buffer
  , mkBuffer
  , Container
  , CompressedNBT
  , Count (..)
  , EntityMetadata
  , Position
  , PositionIBI (..)
  , PositionIII (..)
  , PositionISI (..)
  , RestBuffer
  , Slot
  , VarInt
  , putVarInt
  , getVarInt
  , putWord16
  , getWord16
  , putInt8
  , putInt16
  , putInt32
  , putInt64
  , getInt16
  , getInt32
  , getInt64
  , putFloat
  , getFloat
  , putDouble
  , getDouble
  , putBool
  , getBool
  , putText
  , getText
  , putByteString
  , getByteString
  , putPosition
  , getPosition
  , putUUID
  , getUUID
  , putSlot
  , getSlot
  , putArray
  , getArray
  , putBuffer
  , getBuffer
  , putRestBuffer
  , getRestBuffer
  , putEntityMetadata
  , getEntityMetadata
  , putNBT
  , getNBT
  ) where

import            Data.Bits
import qualified  Data.ByteString as B
import qualified  Data.ByteString.Char8 as BC
import            Data.Int
import            Data.NBT
import qualified  Data.Serialize as S
import qualified  Data.Text as T
import            Data.Word
import            Data.UUID

type Array = B.ByteString -- Undefined

data Buffer a = Buf a B.ByteString
  deriving (Show,Eq)

mkBuffer :: Enum a => B.ByteString -> Buffer a
mkBuffer b = Buf (toEnum . B.length $ b) b

type Container = B.ByteString -- Undefined

type CompressedNBT = B.ByteString -- Undefined

data Count a = Count a
  deriving (Show,Eq)

type EntityMetadata = B.ByteString -- Undefined

type Position = Word64 -- Undefined

data PositionIBI = PositionIBI
  { ibi_x :: Int32
  , ibi_y :: Word8
  , ibi_z :: Int32
  } deriving (Show,Eq)


data PositionIII = PositionIII
  { iii_x :: Int32
  , iii_y :: Int32
  , iii_z :: Int32
  } deriving (Show,Eq)


data PositionISI = PositionISI
  { isi_x :: Int32
  , isi_y :: Int16
  , isi_z :: Int32
  } deriving (Show,Eq)

type RestBuffer = B.ByteString -- Undefined

type Slot = NBT

type VarInt = Int


-- Adapted from the protocol-buffers library, but only for Serialize and Ints

putVarInt :: VarInt -> S.Put
putVarInt i | i < 0x80 = S.putWord8 (fromIntegral i)
            | otherwise = S.putWord8 (fromIntegral (i .&. 0x7F) .|. 0x80) >> putVarInt (i `shiftR` 7)
{-# INLINE putVarInt #-}

getVarInt :: S.Get VarInt
getVarInt = do
    w <- S.getWord8
    if testBit w 7 then go 7 (fromIntegral (w .&. 0x7F))
      else return (fromIntegral w)
  where
    go n val = do
      w' <- S.getWord8
      if testBit w' 7 then go (n+7) (val .|. ((fromIntegral (w' .&. 0x7F)) `shiftL` n))
        else return (val .|. ((fromIntegral w') `shiftL` n))
{-# INLINE getVarInt #-}

-------------------------------------------------------------------------------

putWord16 :: Word16 -> S.PutM ()
putWord16 w = undefined

getWord16 :: S.Get Word16
getWord16 = undefined

putInt8 :: Int8 -> S.PutM ()
putInt8 i = S.put i
{-# INLINE putInt8 #-}


putInt16 :: Int16 -> S.PutM ()
putInt16 i = S.put i
{-# INLINE putInt16 #-}


putInt32 :: Int32 -> S.PutM ()
putInt32 i = S.put i
{-# INLINE putInt32 #-}

putInt64 :: Int64 -> S.PutM ()
putInt64 i = S.put i
{-# INLINE putInt64 #-}


getInt16 :: S.Get Int16
getInt16 = S.getInt16be
{-# INLINE getInt16 #-}

getInt32 :: S.Get Int32
getInt32 = S.getInt32be
{-# INLINE getInt32 #-}


getInt64 :: S.Get Int64
getInt64 = S.getInt64be
{-# INLINE getInt64 #-}

putFloat :: Float -> S.PutM ()
putFloat f = undefined

getFloat :: S.Get Float
getFloat = undefined

putDouble :: Double -> S.PutM ()
putDouble d = undefined

getDouble :: S.Get Double
getDouble = undefined

putBool :: Bool -> S.PutM ()
putBool b = undefined

getBool :: S.Get Bool
getBool = undefined

putText :: T.Text -> S.PutM ()
putText t = do
  putVarInt . T.length $ t
  S.putByteString . BC.pack . T.unpack $ t
{-# INLINE putText #-}


getText :: S.Get T.Text
getText = do
  len <- getVarInt
  if len /= 0
    then fmap (T.pack . BC.unpack) (S.getByteString len)
    else return ""
{-# INLINE getText #-}

putByteString :: B.ByteString -> S.PutM ()
putByteString b = do
  putVarInt . B.length $ b
  S.putByteString b
{-# INLINE putByteString #-}

getByteString :: S.Get B.ByteString
getByteString = getVarInt >>= S.getByteString
{-# INLINE getByteString #-}

putPosition :: Position -> S.PutM ()
putPosition p = undefined

getPosition :: S.Get Position
getPosition = undefined

putUUID :: UUID -> S.PutM ()
putUUID u = undefined

getUUID :: S.Get UUID
getUUID = undefined

putSlot :: Slot -> S.PutM ()
putSlot s = undefined

getSlot :: S.Get Slot
getSlot = undefined

putArray :: Array -> S.PutM ()
putArray a = undefined

getArray :: S.Get Array
getArray = undefined

putBuffer :: Buffer a -> S.PutM ()
putBuffer = undefined

getBuffer :: S.Get (Buffer a)
getBuffer = undefined

putRestBuffer :: RestBuffer -> S.PutM ()
putRestBuffer r = undefined

getRestBuffer :: S.Get RestBuffer
getRestBuffer = undefined

putEntityMetadata :: EntityMetadata -> S.PutM ()
putEntityMetadata e = undefined

getEntityMetadata :: S.Get EntityMetadata
getEntityMetadata = undefined

putNBT :: NBT -> S.PutM ()
putNBT n = undefined

getNBT :: S.Get NBT
getNBT = undefined
