-- |The ROS connection header contains important metadata about a
-- connection being established, including typing information and
-- routing information. How it is exchanged depends on the ROS
-- transport being used.
module Ros.Node.ConnectionHeader (genHeader, ConnHeader(..), parseHeader) where
import Control.Applicative ((<$>))
import Control.Arrow (second, (***))
import Data.Binary.Get (getWord32le, Get, getByteString, runGetIncremental)
import qualified Data.Binary.Get as G
import Data.Binary.Put (runPut, putWord32le)
import Data.ByteString.Char8 (ByteString, pack, unpack)
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy as BL

-- |Wrapper for the Connection Header type that is a list of key-value
-- pairs.
newtype ConnHeader = ConnHeader [(String,String)]

-- |Serialize a list of key-value pairs using the ROS Connection
-- Header protocol.
genHeader :: [(String,String)] -> ByteString
genHeader = tagLength . 
            B.concat . 
            map (tagLength . uncurry B.append . 
                 second (B.cons '=') . (pack *** pack))

-- Prefix a ByteString with its length encoded as a 4-byte little
-- endian integer.
tagLength :: ByteString -> ByteString
tagLength x = let len = runPut $ putWord32le (fromIntegral (B.length x))
              in B.append (BL.toStrict len) x

getInt :: Get Int
getInt = fromIntegral <$> getWord32le

-- Each entry in the header is a 4-byte little endian integer denoting
-- the length of the entry, the field name string, an equals sign, and
-- the field value string.
parsePair :: Get (String,String)
parsePair = do len <- fromIntegral <$> getInt
               field <- getByteString len
               case B.elemIndex '=' field of
                 Just i -> return . (unpack *** unpack . B.tail) . B.splitAt i $
                           field
                 Nothing -> error "Didn't find '=' in connection header field"

-- Keep parsing header entries until we run out of bytes.
parseHeader :: ByteString -> [(String, String)]
parseHeader bs | B.null bs = []
               | otherwise = let G.Partial k = runGetIncremental parsePair
                                 G.Done rst _ p = k (Just bs)
                             in p : parseHeader rst