-- | Handles the connections between a memcache client and the various servers
-- that make up the cluster.
module Database.Memcache.Server (
        Connection(..), newMemcacheClient, send, sendRecv, recv
    ) where

import Control.Concurrent.MVar
import Database.Memcache.Types
import Database.Memcache.Wire

import Blaze.ByteString.Builder
import Control.Exception
import qualified Data.ByteString.Lazy as L

import Network.BSD (getProtocolNumber, getHostByName, hostAddress)
import Network.Socket hiding (send, recv)
import qualified Network.Socket.ByteString as N

-- | A Memcache connection handle.
-- XXX: Should make abstract
data Connection = Conn {
        conn :: MVar Socket
    }

-- | Establish a new connection to a memcache backend.
newMemcacheClient :: HostName -> PortNumber -> IO Connection
newMemcacheClient h p = do
    s <- connectTo h p
    m <- newMVar s
    setSocketOption s KeepAlive 1
    setSocketOption s NoDelay 1
    return (Conn m)

-- | Connect to a host. (Internal, socket version of connectTo).
connectTo :: HostName -> PortNumber -> IO Socket
connectTo host port = do
    proto <- getProtocolNumber "tcp"
    bracketOnError
        (socket AF_INET Stream proto)
        (close)
        (\sock -> do
            h <- getHostByName host
            connect sock (SockAddrInet port (hostAddress h))
            return sock
        )

-- | Send a request to the memcache cluster.
send :: Socket -> Request -> IO ()
-- XXX: catch errors and rethrow as MemcacheErrors?
send s m = N.sendAll s (toByteString $ szRequest m)

-- | Send a receieve a single request/response pair to the memcache cluster.
sendRecv :: Connection -> Request -> IO Response
sendRecv c m = withMVar (conn c) $ \s -> do
    send s m
    recv s

-- | Retrieve a single response from the memcache cluster.
recv :: Socket -> IO Response
recv s = do
    -- XXX: recv may return less.
    header <- N.recv s mEMCACHE_HEADER_SIZE
    let h = dzHeader' (L.fromChunks [header])
    if (bodyLen h > 0) then do
      body <- N.recv s (fromIntegral $ bodyLen h)
      return $ dzBody' h (L.fromChunks [body])
      else
        return $ dzBody' h L.empty