{-# OPTIONS_HADDOCK hide #-}
-- Packet Header:   https://msdn.microsoft.com/en-us/library/dd340948.aspx

module Database.Tds.Message.Header ( packetSize
                                   , tdsVersion
                                   , Header (..)
                                   , putMessage
                                   , getMessage
                                   ) where

import Control.Applicative((<$>))

import qualified Data.ByteString.Lazy as LB
import qualified Data.ByteString.Builder as BB

import Data.Word (Word8(..),Word16(..),Word32(..),Word64(..))
import Data.Int (Int8(..),Int16(..),Int32(..),Int64(..))

import Data.Binary (Put(..),Get(..),Binary(..))
import qualified Data.Binary.Put as Put
import qualified Data.Binary.Get as Get

import Control.Monad.Writer (WriterT(..),runWriterT,tell)
import Control.Monad.Trans (lift)


type Type = Word8
type Status = Word8
type Length = Word16
type SPID = Word16
type PacketID = Word8
type Window = Word8

data Header = Header !Type !Status !Length !SPID !PacketID !Window


headerLength :: Integral a => a
headerLength = fromIntegral 8

packetSize :: Integral a => a
packetSize = fromIntegral 4096

tdsVersion :: Word32
tdsVersion = 0x71000001

-- [MEMO]
-- tds70Version = 0x70000000
-- tds71Version = 0x71000001
-- tds72Version = 0x72090002
-- tds73Version = 0x730B0003
-- tds74Version = 0x74000004



-- https://msdn.microsoft.com/en-us/library/dd340948.aspx
putHeader :: Header -> Put
putHeader (Header pt st len spid pcid win) = do
  Put.putWord8 pt      -- packet type
  Put.putWord8 st      -- packet status -- [TODO] flags
  Put.putWord16be $ fromIntegral $ len -- packet len
  Put.putWord16be spid -- SPID
  Put.putWord8 pcid    -- PacketID
  Put.putWord8 win     -- Window

-- https://msdn.microsoft.com/en-us/library/dd340948.aspx
getHeader :: Get Header
getHeader = do
  pt   <- Get.getWord8    -- packet type
  st   <- Get.getWord8    -- packet status -- [TODO] flags
  len  <- Get.getWord16be -- packet len
  spid <- Get.getWord16be -- SPID
  pcid <- Get.getWord8    -- PacketIK
  win  <- Get.getWord8    -- Window
  return $ Header pt st len spid pcid win


instance Binary Header where
  put = putHeader
  get = getHeader



putMessage :: Word8 -> LB.ByteString -> Put
putMessage pt bs = mapM_ f $ split (packetSize -headerLength) bs
  where
    f :: (Bool,LB.ByteString) -> Put
    f (isLast,bs) = do
      let
        len = (fromIntegral $ LB.length bs) + headerLength
        flg = if isLast then 0x01 else 0x00 -- last flag
      put $ Header pt flg len 0 0 0
      Put.putLazyByteString bs


    split :: Int64 -> LB.ByteString -> [(Bool,LB.ByteString)]
    split len lbs =
      let
        (lbs',rem) = LB.splitAt len lbs
      in if LB.null rem
         then [(True,lbs')]
         else (False,lbs'): split len rem




getMessage :: Get (Word8,LB.ByteString)
getMessage = (\(pt,bs) -> (pt,BB.toLazyByteString bs)) <$> runWriterT f
  where
    f :: WriterT BB.Builder Get Word8
    f = do
      (Header pt flg len _ _ _) <- lift get
      tell =<< BB.byteString <$> (lift $ Get.getByteString (fromIntegral $ len -8))
      if flg == 0x01
        then return pt
        else f