{-# LANGUAGE BangPatterns #-}

-- | This module provides methods to connect to an OpenFlow control server, 
-- and send and receive messages to the server.
module Nettle.Servers.Client
    (     
      ClientHandle      
      , connectToController
      , connectToHandles
      , closeClient
      , receiveControlMessage
      , sendMessage
      , flushClient
    ) where

import Network 
import System.IO
import qualified Data.ByteString as S
import Data.Binary.Strict.Get
import Nettle.OpenFlow hiding (PortID)
import qualified Nettle.OpenFlow.StrictPut as Strict
import Data.Word
import Foreign

-- | Abstract type representing the state of the connection to the control server.
newtype ClientHandle = ClientHandle (Handle, Handle, ForeignPtr Word8)

-- | Established a connection to the control server with the given 'Network.HostName' 
-- and 'Network.PortID' and returns its 'ClientHandle'.
connectToController :: HostName -> PortID -> IO ClientHandle
connectToController host port = 
  do h <- connectTo host port
     hSetBuffering h (BlockBuffering (Just (4 * 1024)))
     connectToHandles h h

-- | Creates a 'ClientHandle' based on a handle to read from and one to write to.
connectToHandles :: Handle -> Handle -> IO ClientHandle
connectToHandles h h' = 
  do let bufferSize = 32 * 1024
     outBufferPtr <- mallocForeignPtrBytes bufferSize :: IO (ForeignPtr Word8)
     return (ClientHandle (h,h',outBufferPtr))

-- | Close client, closing read and write handles.
closeClient :: ClientHandle -> IO ()
closeClient (ClientHandle (h,h',_)) = hClose h >> hClose h'

-- | Blocks until a new control message arrives or the connection is terminated, in which 
-- the return value is 'Nothing'.
receiveControlMessage :: ClientHandle -> IO (Maybe (TransactionID, CSMessage))
receiveControlMessage (ClientHandle (h,_,_)) 
  = do eof <- hIsEOF h
       if eof 
         then return Nothing
         else do hdrbs <- S.hGet h headerSize
                 when (headerSize /= S.length hdrbs) (error "error reading header")
                 case fst (runGet getHeader hdrbs) of
                   Left err     -> error err
                   Right header -> 
                     do let expectedBodyLen = fromIntegral (msgLength header) - S.length hdrbs
                        bodybs <- S.hGet h expectedBodyLen
                        when (expectedBodyLen /= S.length bodybs) (error "error reading body")
                        case fst (runGet (getCSMessageBody header) bodybs) of
                          Left err  -> error err
                          Right msg -> return (Just msg)
  where headerSize = 8        
{-# INLINE receiveControlMessage #-}
        
-- | Sends a message to the controller.        
sendMessage :: ClientHandle -> (TransactionID, SCMessage) -> IO ()
sendMessage (ClientHandle (_,h,fptr)) msg =
  withForeignPtr fptr $ \ptr -> 
  do bytes <- Strict.runPut ptr (putSCMessage msg)
     hPutBuf h ptr bytes
{-# INLINE sendMessage #-}    
     
flushClient :: ClientHandle -> IO ()     
flushClient (ClientHandle (_,h,_)) = hFlush h