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

{- |
  Module     : Holumbus.Network.Communication
  Copyright  : Copyright (C) 2008 Stefan Schmidt
  License    : MIT

  Maintainer : Stefan Schmidt (stefanschmidt@web.de)
  Stability  : experimental
  Portability: portable
  Version    : 0.1


  This module implements an abstract client server model. The clients register
  at the server and check from time to time if the server still exists. If not,
  the client searches for a new server.
  The server on the other hand, keeps a list with all clients and checks, if
  each client is reachable. If not, the client is deleted from the list.
  
  This abstract network model helps us to implement a basic distrubuted system
  with a central server and many attached clients which get little tasks from
  the server. Because this model alone would be very unfunctional, the user is
  able to define his own functions which will be handled by the server or the
  client. 
-}

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

{-# OPTIONS -fglasgow-exts #-}
module Holumbus.Network.Communication
{-# DEPRECATED "this module will be remove in the next release, please use the packages from Holumbus.Distribution.*" #-}
(
  StreamName       -- (reexport)
, SocketId         -- (reexport)
, PortNumber       -- (reexport)

-- time constants
, time30           -- (reexport)
, time60           -- (reexport)
, timeIndefinitely -- (reexport)

, IdType

, ClientInfo(..)

-- * server operations
, Server
, newServer
, closeServer
, ServerPort
, newServerPort
, sendRequestToServer

, getClientInfo
, getAllClientInfos

-- * client operations
, ClientClass(..)
, ClientData(..)
, Client(..)
, newClient
, closeClient
, ClientPort(..)
, sendRequestToClient 
)
where

import           Control.Concurrent
import           Data.Binary
--import           Holumbus.Common.MRBinary
import qualified Data.ByteString.Lazy as B
import qualified Data.Map as Map
import           Data.Maybe
import           Network
import           System.Log.Logger

import           Holumbus.Common.Debug
import           Holumbus.Common.Threading
import qualified Holumbus.Data.MultiMap as MMap
import           Holumbus.Network.Messages
import           Holumbus.Network.Port
import           Holumbus.Network.Site



localLogger :: String
localLogger = "Holumbus.Network.Communication"


-- ----------------------------------------------------------------------------
-- General Datatypes
-- ----------------------------------------------------------------------------

-- | The type of the client id.
type IdType = Int



-- ----------------------------------------------------------------------------
-- Server-Messages
-- ----------------------------------------------------------------------------

-- | The requests, the server can handle.
data ServerRequestMessage
  = SReqRegisterClient SiteId (Port ClientRequestMessage)
  | SReqUnregisterClient IdType
  | SReqPing IdType
  | SReqServerAction B.ByteString
  | SReqUnknown
  deriving (Show)

instance Binary (ServerRequestMessage) where
  put (SReqRegisterClient sid po) = putWord8 1 >> put sid >> put po
  put (SReqUnregisterClient i)    = putWord8 2 >> put i
  put (SReqPing i)                = putWord8 3 >> put i
  put (SReqServerAction b)        = putWord8 4 >> put b
  put (SReqUnknown)               = putWord8 0
  get
    = do
      t <- getWord8
      case t of
        1 -> get >>= \sid -> get >>= \po -> return (SReqRegisterClient sid po)
        2 -> get >>= \i -> return (SReqUnregisterClient i)
        3 -> get >>= \i -> return (SReqPing i)
        4 -> get >>= \b -> return (SReqServerAction b)
        _ -> return (SReqUnknown)


-- | The responses the server gives.
data ServerResponseMessage
  = SRspSuccess
  | SRspRegisterClient IdType
  | SRspUnregisterClient
  | SRspPing Bool
  | SRspServerAction B.ByteString
  | SRspError String
  | SRspUnknown
  deriving (Show)

instance Binary (ServerResponseMessage) where
  put (SRspSuccess)          = putWord8 1
  put (SRspRegisterClient i) = putWord8 2 >> put i
  put (SRspUnregisterClient) = putWord8 3
  put (SRspPing b)           = putWord8 4 >> put b
  put (SRspServerAction b)   = putWord8 5 >> put b
  put (SRspError e)          = putWord8 6 >> put e
  put (SRspUnknown)          = putWord8 0
  get
    = do
      t <- getWord8
      case t of
        1 -> return (SRspSuccess)
        2 -> get >>= \i -> return (SRspRegisterClient i)
        3 -> return (SRspUnregisterClient)
        4 -> get >>= \b -> return (SRspPing b)
        5 -> get >>= \b -> return (SRspServerAction b)
        6 -> get >>= \e -> return (SRspError e)
        _ -> return (SRspUnknown)
  
instance RspMsg (ServerResponseMessage) where
  isError (SRspError _) = True
  isError _ = False
  
  getErrorMsg (SRspError e) = e
  getErrorMsg _ = ""
  
  isUnknown (SRspUnknown) = True
  isUnknown _ = False
  
  mkErrorMsg e = SRspError e
  



-- ----------------------------------------------------------------------------
-- Server-TypeClass
-- ----------------------------------------------------------------------------

-- | The request-functions a server has to implement.
class ServerClass s where

  -- | Register a new client in the server database.
  registerClient :: SiteId -> Port ClientRequestMessage -> s -> IO IdType
  
  -- | Delete a client from the server database.
  unregisterClient :: IdType -> s -> IO ()
  
  -- | Check, if server is responding.
  pingServer :: IdType-> s -> IO Bool



-- ----------------------------------------------------------------------------
-- Server-Data
-- ----------------------------------------------------------------------------
  
-- | The type of the functions which will be executed by registration and
--   unregistration.
type RegistrationAction = (IdType -> ClientPort -> IO ())
  
  
-- The information of the client known by the server.
data ClientInfo = ClientInfo {
    ci_Id           :: Int
  , ci_Site         :: SiteId                -- ^ SiteId (Hostname,PID) of the client process
  , ci_Port         :: ClientPort            -- ^ the port of the client
  , ci_PingThreadId :: Thread                -- ^ the threadId of the ping-Process (needed to stop it)
  , ci_LifeValue    :: Int
  }
  
instance Show ClientInfo where
  show (ClientInfo n s p _ i) = "{Id: " ++ show n ++ 
                                 " - Site: " ++ show s ++ 
                                 " - Port: " ++ show p ++ 
                                 " - LifeValue: " ++ show i ++ "}"


-- | The data of the server needed to organise the clients.
data ServerData = ServerData {
    sd_ServerThreadId  :: Thread                        -- ^ threadId of the streamDispatcher
  , sd_OwnStream       :: Stream (ServerRequestMessage) -- ^ the stream the requestDispatcher reads from
  , sd_OwnPort         :: Port (ServerRequestMessage)   -- ^ the port the clients send messages to
  , sd_ClientMap       :: Map.Map IdType ClientInfo     -- ^ infomation of the the clients
  , sd_SiteToClientMap :: MMap.MultiMap SiteId IdType   -- ^ needed to get the closest client
  , sd_SiteMap         :: SiteMap                       -- ^ needed to get the closest site
  , sd_Register        :: RegistrationAction
  , sd_Unregister      :: RegistrationAction
  , sd_NextId          :: IdType
  }
  
  
-- | The server.
data Server = Server (MVar ServerData)
  
  
maxLifeValue :: Int
maxLifeValue = 3


-- | Creates a new server.
newServer
  :: (Binary a, Binary b)
  => StreamName -> Maybe PortNumber
  -> (a -> IO (Maybe b))             -- ^ handling own request
  -> Maybe RegistrationAction        -- ^ for registration
  -> Maybe RegistrationAction        -- ^ for unregistration 
  -> IO Server
newServer sn pn dispatch register unregister
  = do
    -- create a new server
    st    <- (newStream STGlobal (Just sn) pn::IO (Stream ServerRequestMessage))
    po    <- newPortFromStream st
    tid   <- newThread
    let reg   = maybe (\_ _ -> return ()) id register
    let unreg = maybe (\_ _ -> return ()) id unregister
    let sd = ServerData tid st po Map.empty MMap.empty Map.empty reg unreg 1
    s <- newMVar sd
    let server =  Server s
    -- start the requestDispatcher to handle requests
    startRequestDispatcher tid st (dispatchServerRequest server dispatch)
    return server


-- | Closes the server.
closeServer :: Server -> IO ()
closeServer s@(Server server)
  = do
    debugM localLogger "closeServer: start"
    (allIds,thread,stream) <- withMVar server $
      \sd ->
      do
      -- getAll ClientIds
      let allIds = Map.keys (sd_ClientMap sd)
      return (allIds, sd_ServerThreadId sd, sd_OwnStream sd)          
    debugM localLogger "closeServer: stopRequestDispatcher"
    -- shutdown the server thread
    stopRequestDispatcher thread
    -- close the stream
    debugM localLogger "closeServer: closeStream"
    closeStream stream
    debugM localLogger "closeServer: unregister clients"
    _ <- mapM (\i -> do unregisterClient i s) allIds
    debugM localLogger "closeServer: end"
    return ()


-- | Handles the requests from the client.
dispatchServerRequest
  :: (Binary a, Binary b)
  => Server
  -> (a -> IO (Maybe b))
  -> ServerRequestMessage
  -> Port (ServerResponseMessage)
  -> IO ()
dispatchServerRequest server action msg replyPort
  = do
    debugM localLogger $ "dispatchServerRequest: " ++ show msg
    case msg of
      (SReqRegisterClient s po) ->
        do
        handleRequest replyPort (registerClient s po server) (\i -> SRspRegisterClient i)
        return ()
      (SReqUnregisterClient n) ->
        do
        handleRequest replyPort (unregisterClient n server) (\_ -> SRspUnregisterClient)
        return ()
      (SReqPing i) ->
        do
        handleRequest replyPort (pingServer i server) (\b -> SRspPing b)
        return ()
      (SReqServerAction b) ->
        do
        handleRequest replyPort
          (action $ decode b) 
          (\res -> maybe (SRspUnknown) (\r -> SRspServerAction $ encode r) res)
      _ -> 
        handleRequest replyPort (return ()) (\_ -> SRspUnknown)


-- | Creates a new client id and updates the serverdata.
getNextId :: ServerData -> (IdType, ServerData)
getNextId sd 
  = (i, sd { sd_NextId = nid })
  where
    i   = sd_NextId sd
    nid = i + 1


-- | Adds a new client to the server datastructures,
--   the ping-thread will not be started.
addClientToServer
  :: IdType -> SiteId -> ClientPort -> Thread
  -> ServerData -> ServerData
addClientToServer i sid cp tid sd
  = sd { sd_ClientMap = nsm', sd_SiteToClientMap = snm', sd_SiteMap = sm' }
  where
    --update the ClientMap
    nsm = sd_ClientMap sd
    nsm' = Map.insert i (ClientInfo i sid cp tid maxLifeValue) nsm
    --update the SiteToClientMap
    snm = sd_SiteToClientMap sd
    snm' = MMap.insert sid i snm
    -- update the SiteMap
    sm = sd_SiteMap sd
    sm' = addIdToMap sid sm
    

-- | Deletes a new client from the server datastructures,
--   the ping-thread will not be closed.
deleteClientFromServer :: IdType -> ServerData -> ServerData
deleteClientFromServer i sd 
  = sd { sd_ClientMap = nsm', sd_SiteToClientMap = snm', sd_SiteMap = sm' }
  where
    --update the ClientMap
    nsm = sd_ClientMap sd
    nsm' = Map.delete i nsm
    --update the SiteToClientMap and the SiteIdMap
    info = lookupClientInfo i sd
    snm = sd_SiteToClientMap sd
    sm = sd_SiteMap sd
    (snm', sm') = deleteSiteId info
    deleteSiteId Nothing = (snm, sm)
    deleteSiteId (Just info') = (MMap.deleteElem sid i snm , deleteIdFromMap sid sm)
      where
      sid = ci_Site info'


-- | Gets the ClientPort from a ClientId (on the ServerData).
lookupClientInfo :: IdType -> ServerData -> Maybe ClientInfo
lookupClientInfo i sd = Map.lookup i (sd_ClientMap sd)


-- | Gets a list with all registered clients (on the ServerData).
lookupAllClientInfos :: ServerData -> [ClientInfo]
lookupAllClientInfos sd = Map.elems (sd_ClientMap sd)


-- | Gets the ClientPort from a ClientId (on the Server).
getClientInfo :: IdType -> Server -> IO (Maybe ClientInfo)
getClientInfo i (Server server)
  = withMVar server $ \sd -> return $ lookupClientInfo i sd 


-- | Gets a list with all registered clients (on the Server).
getAllClientInfos :: Server -> IO [ClientInfo]
getAllClientInfos (Server server)
  = withMVar server $ \sd -> return $ lookupAllClientInfos sd

-- | Sets the life value of a specific client.
setClientLife :: Int -> IdType -> Server -> IO ()
setClientLife v i (Server server)
  = modifyMVar server $
      \sd -> do
        let mbCi = lookupClientInfo i sd
        sd' <- case mbCi of
          (Just ci) -> do
            let ci'  = ci {ci_LifeValue = v}
                nsm  = sd_ClientMap sd
                nsm' = Map.insert i ci' nsm
                sd'  = sd {sd_ClientMap = nsm'}
            return sd'
          (Nothing) -> return sd
        return (sd', ())

-- | Gets the life value of a specific client.
getClientLife :: IdType -> Server -> IO (Int)
getClientLife i (Server server)
  = withMVar server $
      \sd -> do
        let mbCi = lookupClientInfo i sd
        case mbCi of
          (Just ci) -> return $ ci_LifeValue ci
          (Nothing) -> return 0


instance ServerClass Server where
  registerClient sid po s@(Server server)
    = do
      let cp = newClientPort po
      -- register the client at the server
      (ptid,i,register) <- modifyMVar server $
        \sd ->
        do
        -- create a new Id and a new Port
        let (i, sd') = getNextId sd
        let register = sd_Register sd
        -- add node to controller
        ptid <- newThread
        let sd'' = addClientToServer i sid cp ptid sd'
        return (sd'', (ptid, i, register))
      -- do general registration action
      register i cp
      -- startPingProcess for Client
      setThreadDelay 5000000 ptid
      setThreadAction (checkClient i cp s ptid) ptid
      setThreadErrorHandler (handleCheckClientError i s ptid) ptid
      startThread ptid
      return i
     
  unregisterClient i (Server server)
    = do
      debugM localLogger "unregisterClient: start"
      (mbInfo, unregister) <- modifyMVar server $
        \sd ->
        do
        let unregister = sd_Unregister sd
        let mbInfo = lookupClientInfo i sd 
        let sd' = deleteClientFromServer i sd
        return (sd', (mbInfo,unregister))
      debugM localLogger "unregisterClient: client deleted"
      case mbInfo of
        (Just info) -> 
          do
          debugM localLogger "unregisterClient: killing the ping thread"
          -- kill the ping-thread
          stopThread (ci_PingThreadId info)
          debugM localLogger "unregisterClient: ping thread killed"
          debugM localLogger "unregisterClient: executing unregister-function"
          -- execute the unregister function
          unregister i (ci_Port info)
          debugM localLogger "unregisterClient: unregister-function executed"
        (Nothing)   ->
          do
          debugM localLogger "unregisterClient: no client info found"
          return ()
      debugM localLogger "unregisterClient: end"
        
  pingServer i (Server server)
    = withMVar server $
        \sd -> return $ isJust $ lookupClientInfo i sd
        


instance Debug Server where
  printDebug (Server server)
    = withMVar server $
        \sd ->
        do
        putStrLn "printServer"
        putStrLn $ "OwnStream:       " ++ show (sd_OwnStream sd)
        putStrLn $ "OwnPort:         " ++ show (sd_OwnPort sd)
        putStrLn $ "ClientMap:       " ++ show (sd_ClientMap sd)
        putStrLn $ "SiteToClientMap: " ++ show (sd_SiteToClientMap sd)
        putStrLn $ "SiteMap:         " ++ show (sd_SiteMap sd)
        putStrLn $ "NextId:          " ++ show (sd_NextId sd)
  getDebug (Server server)
    = withMVar server $
        \sd ->
        do
        return   $ ("printServer"
          ++"\n"++ "OwnStream:       " ++ show (sd_OwnStream sd)
          ++"\n"++ "OwnPort:         " ++ show (sd_OwnPort sd)
          ++"\n"++ "ClientMap:       " ++ show (sd_ClientMap sd)
          ++"\n"++ "SiteToClientMap: " ++ show (sd_SiteToClientMap sd)
          ++"\n"++ "SiteMap:         " ++ show (sd_SiteMap sd)
          ++"\n"++ "NextId:          " ++ show (sd_NextId sd))++"\n"
  


-- ----------------------------------------------------------------------------
-- Server-Port
-- ----------------------------------------------------------------------------

-- | The ServerPort is only a wrapper for a Port-Datatype.  
data ServerPort = ServerPort (Port ServerRequestMessage)
  deriving (Show)

instance Binary ServerPort where
  put (ServerPort p) = put p
  get
    = do
      p <- get
      return (ServerPort p)
      

-- | Creates a new ServerPort.
newServerPort :: StreamName -> Maybe SocketId -> IO ServerPort
newServerPort sn soid
  = do
    p <- newPort sn soid
    return (ServerPort p)

 
instance ServerClass ServerPort where
  registerClient sid po (ServerPort p) 
    = do
      withStream $
        \s -> performPortAction p s time30 (SReqRegisterClient sid po) $
          \rsp ->
          do
          case rsp of
            (SRspRegisterClient n) -> return (Just n)
            _ -> return Nothing
  
  unregisterClient i (ServerPort p)
    = do
      withStream $
        \s -> performPortAction p s time30 (SReqUnregisterClient i) $
          \rsp ->
          do
          case rsp of
            (SRspUnregisterClient) -> return (Just ())
            _ -> return Nothing
  
  pingServer i (ServerPort p)
    = do
      withStream $
        \s -> performPortAction p s time30 (SReqPing i) $
          \rsp ->
          do
          case rsp of
            (SRspPing b) -> return (Just b)
            _ -> return Nothing 
  
  


-- ----------------------------------------------------------------------------
-- Client-Messages
-- ----------------------------------------------------------------------------
  
-- | Requests datatype, which is send to a filesystem node.
data ClientRequestMessage
  = CReqPing IdType
  | CReqClientAction B.ByteString
  | CReqClientId
  | CReqServerPort
  | CReqUnknown
  deriving (Show)

instance Binary ClientRequestMessage where
  put (CReqPing i)         = putWord8 1 >> put i
  put (CReqClientAction b) = putWord8 2 >> put b
  put (CReqClientId)       = putWord8 3
  put (CReqServerPort)     = putWord8 4
  put (CReqUnknown)        = putWord8 0
  get
    = do
      t <- getWord8
      case t of
        1 -> get >>= \i -> return (CReqPing i)
        2 -> get >>= \b -> return (CReqClientAction b)
        3 -> return (CReqClientId)
        4 -> return (CReqServerPort)
        _ -> return (CReqUnknown)


-- | Response datatype from a filesystem node.
data ClientResponseMessage
  = CRspPing Bool
  | CRspClientAction B.ByteString
  | CRspClientId (Maybe IdType)
  | CRspServerPort ServerPort
  | CRspError String
  | CRspUnknown
  deriving (Show)      

instance Binary ClientResponseMessage where
  put (CRspPing b)         = putWord8 1 >> put b
  put (CRspClientAction b) = putWord8 2 >> put b
  put (CRspClientId i)     = putWord8 3 >> put i
  put (CRspServerPort p)   = putWord8 4 >> put p
  put (CRspError e)        = putWord8 5 >> put e
  put (CRspUnknown)        = putWord8 0
  get
    = do
      t <- getWord8
      case t of
        1 -> get >>= \b -> return (CRspPing b)
        2 -> get >>= \b -> return (CRspClientAction b)
        3 -> get >>= \i -> return (CRspClientId i)
        4 -> get >>= \p -> return (CRspServerPort p)
        5 -> get >>= \e -> return (CRspError e)
        _ -> return (CRspUnknown)

instance RspMsg ClientResponseMessage where
  isError (CRspError _) = True
  isError _ = False
  
  getErrorMsg (CRspError e) = e
  getErrorMsg _ = ""
  
  isUnknown (CRspUnknown) = True
  isUnknown _ = False
  
  mkErrorMsg e = CRspError e



-- ----------------------------------------------------------------------------
-- Client-TypeClass
-- ----------------------------------------------------------------------------

-- | The request-functions a client has to implement.
class ClientClass c where

  -- | Check, if the client is responding.
  pingClient :: IdType-> c -> IO Bool
  
  -- | Get the ID of the client.
  getClientId :: c -> IO (Maybe IdType)
  
  -- | Gets the server port the client wants to connect to.
  getServerPort :: c -> IO (ServerPort)
        


  
-- ----------------------------------------------------------------------------
-- Client-Data
-- ----------------------------------------------------------------------------

  
-- | Client datatype.
data ClientData = ClientData {
    cd_ServerThreadId  :: Thread
  , cd_PingThreadId    :: Thread
  , cd_Id              :: Maybe IdType
  , cd_LifeValue       :: Int
  , cd_SiteId          :: SiteId
  , cd_OwnStream       :: Stream ClientRequestMessage
  , cd_OwnPort         :: Port ClientRequestMessage
  , cd_ServerPort      :: ServerPort
  }

  
-- | Only a wrapper around an MVar.
data Client = Client (MVar ClientData)


-- | Creates a new client, it needs the StreamName and optional 
--   the SocketId of the server.
newClient
  :: (Binary a, Binary b)
  => StreamName -> Maybe SocketId
  -> (a -> IO (Maybe b))  -- ^ the individual request dispatcher for the client -- (ReqM -> IO (RespM)
  -> IO Client
newClient sn soid action
  = do  
    sp <- newServerPort sn soid
    -- initialize values
    sid     <- getSiteId
    stid    <- newThread  
    st      <- (newLocalStream Nothing::IO (Stream ClientRequestMessage))
    po      <- newPortFromStream st
    ptid    <- newThread
    let cd = (ClientData stid ptid Nothing maxLifeValue sid st po sp)
    c <- newMVar cd
    let client = Client c 
    -- first, we start the server, because we can't handle requests without it
    startRequestDispatcher stid st (dispatchClientRequest client action)
    -- then we try to register a the server
    setThreadDelay 2000000 ptid
    setThreadAction (checkServer sp sid po client) ptid
    setThreadErrorHandler (handleCheckServerError client) ptid
    startThread ptid
    return client


-- | Closes the client.
closeClient :: Client -> IO ()
closeClient (Client client)
  = modifyMVar client $
      \cd ->
      do
      case (cd_Id cd) of
        (Just i)  -> unregisterClient i (cd_ServerPort cd)
        (Nothing) -> return ()
      stopRequestDispatcher (cd_ServerThreadId cd)
      closeStream (cd_OwnStream cd)
      stopThread (cd_PingThreadId cd)
      return (cd, ())


-- | Handles the requests from the server.
dispatchClientRequest
  :: (Binary a, Binary b)
  => Client
  -> (a -> IO (Maybe b))
  -> ClientRequestMessage 
  -> Port ClientResponseMessage
  -> IO ()
dispatchClientRequest client action msg replyPort
  = do
    debugM localLogger $ "dispatchClientRequest: " ++ show msg
    case msg of
      (CReqPing i) ->
        do
        handleRequest replyPort (pingClient i client) (\b -> CRspPing b)
        return ()
      (CReqClientAction b) ->
        do
        -- now, we have a specific client request
        handleRequest replyPort 
          (action $ decode b) 
          (\res -> maybe (CRspUnknown) (\r -> CRspClientAction $ encode r) res)
      _ -> 
        handleRequest replyPort (return ()) (\_ -> CRspUnknown)


-- | Test, if the client is registered by a server. 
isClientRegistered :: Client -> IO (Bool, Maybe IdType)
isClientRegistered (Client client)
  = withMVar client $ \cd -> return $ (isJust (cd_Id cd), (cd_Id cd))


-- | Deletes the internal clientId, the client will then be in an 
--   unregisterd state.
unsetClientId :: Client -> IO ()
unsetClientId (Client client)
  = modifyMVar client $ \cd -> return ( cd {cd_Id = Nothing}, ())

-- | Sets the life value of the server port.
setLifeValue :: Int -> Client -> IO ()
setLifeValue v (Client client)
  = modifyMVar client $ \cd -> return (cd {cd_LifeValue = v}, ())

-- | Gets the life value of the server port.
getLifeValue :: Client -> IO (Int)
getLifeValue (Client client)
  = withMVar client $ \cd -> return (cd_LifeValue cd)

-- | Assigns a new clientId to the client.
setClientId :: IdType -> Client -> IO ()
setClientId i (Client client)
      = modifyMVar client $ \cd -> return ( cd {cd_Id = Just i}, ())  


instance ClientClass Client where
  pingClient i (Client client)
    = withMVar client $
        \cd -> return $ (cd_Id cd) == (Just i)
        
  getClientId (Client client)
    = withMVar client $
        \cd -> return $ (cd_Id cd)
    
  getServerPort (Client client)
    = withMVar client $
        \cd -> return $ (cd_ServerPort cd)


instance Debug Client where
  printDebug (Client client)
    = withMVar client $
        \cd ->
        do
        putStrLn "printClient"
        putStrLn $ "Id:         " ++ show (cd_Id cd)
        putStrLn $ "LifeValue   " ++ show (cd_LifeValue cd)
        putStrLn $ "Site:       " ++ show (cd_SiteId cd)
        putStrLn $ "OwnStream:  " ++ show (cd_OwnStream cd)
        putStrLn $ "OwnPort:    " ++ show (cd_OwnPort cd)
        putStrLn $ "ServerPort: " ++ show (cd_ServerPort cd)
  getDebug (Client client)
    = withMVar client $
        \cd ->
        do
        return ( "printClient"
          ++"\n"++ "Id:         " ++ show (cd_Id cd)
          ++"\n"++ "LifeValue   " ++ show (cd_LifeValue cd)
          ++"\n"++ "Site:       " ++ show (cd_SiteId cd)
          ++"\n"++ "OwnStream:  " ++ show (cd_OwnStream cd)
          ++"\n"++ "OwnPort:    " ++ show (cd_OwnPort cd)
          ++"\n"++ "ServerPort: " ++ show (cd_ServerPort cd)++"\n")



-- ----------------------------------------------------------------------------
-- Client-Port
-- ----------------------------------------------------------------------------

-- | Just a wrapper around a port.
data ClientPort = ClientPort (Port ClientRequestMessage)
  deriving (Show,Eq)

instance Ord ClientPort where  --p_StreamName
  compare (ClientPort p1) (ClientPort p2) = compare (p_StreamName p1) (p_StreamName p2)
  (<) (ClientPort p1) (ClientPort p2)     = (<)     (p_StreamName p1) (p_StreamName p2)
  (>) (ClientPort p1) (ClientPort p2)     = (>)     (p_StreamName p1) (p_StreamName p2)
  (>=) (ClientPort p1) (ClientPort p2)    = (>=)    (p_StreamName p1) (p_StreamName p2)
  (<=)  (ClientPort p1) (ClientPort p2)   = (<=)    (p_StreamName p1) (p_StreamName p2)
  max c1@(ClientPort p1) c2@(ClientPort p2) = if (max s1 s2) == s1 then c1 else c2
    where
    s1 = p_StreamName p1
    s2 = p_StreamName p2
  min c1@(ClientPort p1) c2@(ClientPort p2) = if (min s1 s2) == s1 then c1 else c2
    where
    s1 = p_StreamName p1
    s2 = p_StreamName p2

instance Binary ClientPort where
  put (ClientPort p) = put p
  get
    = do
      p <- get
      return (ClientPort p)


-- | Creates a new ClientPort.
newClientPort :: Port ClientRequestMessage -> ClientPort
newClientPort po = ClientPort po



instance ClientClass ClientPort where
  pingClient i (ClientPort p)
    = do
      withStream $
        \s -> performPortAction p s time30 (CReqPing i) $
          \rsp ->
          do
          case rsp of
            (CRspPing b) -> return (Just b)
            _ -> return Nothing

  getClientId (ClientPort p)
    = do
      withStream $
        \s -> performPortAction p s time30 (CReqClientId) $
          \rsp ->
          do
          case rsp of
            (CRspClientId i) -> return (Just i)
            _ -> return Nothing
    
  getServerPort (ClientPort p)
    = do
      withStream $
        \s -> performPortAction p s time30 (CReqServerPort) $
          \rsp ->
          do
          case rsp of
            (CRspServerPort sp) -> return (Just sp)
            _ -> return Nothing




-- ----------------------------------------------------------------------------
-- Communication-Functions
-- ----------------------------------------------------------------------------

-- | Sends a request from the server to the client an handles the response or
--   invokes a user-defined handler.
sendRequestToClient 
  :: (Show a, Binary a, Binary b)
  => ClientPort -> Int
  -> a
  -> (b -> IO (Maybe c))  -- ^ response handler
  -> IO c
sendRequestToClient (ClientPort p) timeout a handler
  = do
    debugM localLogger $ "sending request to Client: " ++ show a
    withStream $
      \s -> performPortAction p s timeout (CReqClientAction (encode a)) $
        \rsp ->
        do
        case rsp of
          (CRspClientAction b) -> 
            do
            handler (decode b)
          _ -> return Nothing


-- | Sends a request from the client to the server an handles the response or
--   invokes a user-defined handler.
sendRequestToServer 
  :: (Show a, Binary a, Binary b)
  => ServerPort -> Int
  -> a
  -> (b -> IO (Maybe c))  -- ^ response handler
  -> IO c
sendRequestToServer (ServerPort p) timeout a handler
  = do
    debugM localLogger $ "sending message to Server: " ++ show a
    withStream $
      \s -> performPortAction p s timeout (SReqServerAction (encode a)) $
        \rsp ->
        do
        case rsp of
          (SRspServerAction b) -> 
            do
            handler (decode b)
          _ -> return Nothing

          
-- ----------------------------------------------------------------------------
-- Ping-Functions
-- ----------------------------------------------------------------------------

-- | Checks, if a client is still reachable, otherwise it will be deleted
--   from the server.
checkClient :: IdType -> ClientPort -> Server -> Thread -> IO ()
checkClient i po server thread
  = do
    debugM localLogger "pingClient"
    b <- pingClient i po
    case b of
      False -> 
        do
        warningM localLogger "pingCient: client is not reachable... delete him"
        handleCheckClientError i server thread
      True  -> 
        do
        debugM localLogger "pingCient: client is ok"
        setClientLife maxLifeValue i server
        return ()
    

-- | If a client does not respond to a ping, this function is invoked.
--   It deleted the client from the server and stops the ping thread.
handleCheckClientError :: IdType -> Server -> Thread -> IO ()
handleCheckClientError i server thread
   = do
     v <- getClientLife i server
     if (v > 0)
       then do
         setClientLife (v-1) i server
       else do
         unregisterClient i server
         stopThread thread
     return ()


-- | Checks, if a server is still reachable, otherwise it will be 
--  deleted from the client.
checkServer :: ServerPort -> SiteId -> Port ClientRequestMessage -> Client -> IO ()
checkServer sepo sid clpo c
  = do
    debugM localLogger "pingServer"
    (reg,i) <- isClientRegistered c
    if (reg)
      then do
        debugM localLogger "pingServer: client is registered, testing server" 
        b <- pingServer (fromJust i) sepo
        if (b)
          then do
            debugM localLogger "pingServer: server is ok"
            setLifeValue maxLifeValue c
            return ()              
          else do
            warningM localLogger "pingServer: server is down" 
            handleCheckServerError c
      else do 
        debugM localLogger "pingServer: trying to register client"
        i' <- registerClient sid clpo sepo
        setClientId i' c    


-- | If the server does not respond to a ping, this function is invoked.
--   It sets the server to an unregistered state, so it will reconnect again.
handleCheckServerError :: Client -> IO ()
handleCheckServerError c
  = do
    v <- getLifeValue c
    if (v > 0)
      then do setLifeValue (v-1) c
      else do unsetClientId c