
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeFamilies #-}

module Types 
  ( Message, User
  , Update(..), Request(..), Response(..)
  , UpdateS, RequestS, EndS
  , ServerInit, ServerProtocol
  , ClientInit, ClientProtocol
  , Connection(..), mkConnection
  ) where 

import Control.Concurrent.SimpleSession.SessionTypes 
  ( Eps, Var, Rec, (:&:), (:+:), (:!:), (:?:)
  , Z )
import Control.Concurrent.SimpleSession.Implicit 
  ( Rendezvous, newRendezvous )

-- | Type of the user identifier.
type User = String

-- | Type of messages.
type Message = String

-- | Updates from the server.
data Update 
  = UserLeftChat User
  | NewMessage User Message
  | NoUpdate
  deriving ( Eq, Show )

-- | Requests from the client.
data Request
  = SendMessage Message
  | ShutdownServer
  | FetchUserList
  | NoRequest
  deriving ( Eq, Show )

-- | Responses from the server to the client 'Request's.
data Response
  = EmptyResponse
  | UserListResponse [User]
  deriving ( Eq, Show )

-- | Type of a connection between server and client (View from server-side).
data Connection = Connection { unwrapConnection :: Rendezvous (ServerInit (ServerProtocol (Var Z))) }

-- | Create a new connection.
mkConnection :: IO Connection
mkConnection = Connection <$> newRendezvous  
  
-- | The server send the most current 'Update's to the client.
type UpdateS r = [Update] :!: r

-- | The client sends a 'Request' and receives a 'Response' from the server.
type RequestS r = Request :?: (Response :!: r)

-- | The server and the client have a choice to end communication...
type EndS r = Eps :+: (Eps :&: r)

-- | Initially the client has to introduce itself by name.
type ServerInit r = User :?: (Rec r)

-- | After the introduction we repeat the steps: 
--   1. End of communication? 2. Updates from server; 3. Requests from the client;
type ServerProtocol r = EndS (UpdateS (RequestS r))

type EndC r = Eps :&: (Eps :+: r)
type RequestC r = Request :!: (Response :?: r)
type UpdateC r = [Update] :?: r

-- | The client-side version of the 'ServerInit'.
type ClientInit r = User :!: (Rec r)

-- | The client-side version of the 'ServerProtocol'.
type ClientProtocol r = EndC (UpdateC (RequestC r))




