{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE DeriveFunctor         #-}
{-# LANGUAGE GADTs                 #-}
{-# LANGUAGE KindSignatures        #-}
{-# LANGUAGE PolyKinds             #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE RankNTypes            #-}
{-# LANGUAGE StandaloneDeriving    #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE TypeInType            #-}


-- | This module defines the core of the typed protocol framework.
--

module Network.TypedProtocol.Core
  ( -- * Introduction
    -- $intro
    -- * Defining protocols
    -- $defining
    Protocol (..)
    -- $lemmas
    -- * Engaging in protocols
    -- $using
  , PeerRole (..)
  , TokPeerRole (..)
  , FlipAgency
  , PeerHasAgency (..)
  , WeHaveAgency
  , TheyHaveAgency
  , Peer (..)
  ) where

import           Data.Void (Void)

-- $intro
-- A typed protocol between two peers is defined via a state machine: a
-- collection of protocol states and protocol messages which are transitions
-- between those states.
--
-- Start from the idea that a protocol is some language of messages sent
-- between two peers. To specify a protocol is to describe what possible
-- sequences of messages are valid. One simple but still relatively expressive
-- way to do this is via a state machine: starting from some initial state,
-- all possible paths through the state machine gives the set of valid protocol
-- traces. This then dictates what a peer participating in a protocol may
-- produce and what it must accept.
--
-- In this style we have a fixed number of states and in each state there is
-- some number of valid messages that move us on to the next state. This can be
-- illustrated as a graph, which can be a helpful form of documentation.
--
-- We further constrain this idea by saying that the two peers will use the
-- same state machine and change states in lock-step by sending\/receiving
-- messages. In this approach, for each protocol state, the description
-- dictates which peer has the agency to choose to send a message, while
-- correspondingly the other must be prepared to receive the message.
--
-- The views of the two peers are dual. In each state one peer can send any
-- message that is valid for the current protocol state while the other
-- must be prepared to receive any valid message for current protocol state.
--
-- We can also have terminal protocol states in which neither peer has agency.
--
-- So part of the protocol description is to label each protocol state with
-- the peer that has the agency in that state, or none for terminal states.
-- We use the labels \"client\" and \"server\" for the two peers, but they are
-- in fact symmetric.


-- $defining
--
-- The 'Protocol' type class bundles up all the requirements for a typed
-- protocol, which are in fact all type level constructs. Defining a new
-- protocol and making it an instance of the 'Protocol' class requires the
-- following language extensions:
--
-- > {-# LANGUAGE GADTs, TypeFamilies, DataKinds #-}
--
-- The type class itself is indexed on a protocol \"tag\" type. This type
-- does double duty as the /kind/ of the /types/ of the protocol states.

-- We will use as a running example a simple \"ping\/pong\" protocol. (You can
-- see the example in full in "Network.TypedProtocol.PingPong.Type".) In this
-- example protocol the client sends a ping message and the serve must respond
-- with a pong message. The client can also terminate the protocol. So modelled
-- as a state machine this protocol has three states, the one in which the
-- client can send a ping or terminate message, the one in which the server
-- must send a pong, and the terminal state where neither can send anything.
-- We somewhat arbitrarily choose label these protocol states as \"idle\"
-- \"busy\" and \"done\".
--
-- For this ping pong example the protocol tag and the protocol state types
-- would be defined (via promoted data kinds) as:
--
-- > data PingPong where
-- >   StIdle :: PingPong
-- >   StBusy :: PingPong
-- >   StDone :: PingPong
--
-- We use @DataKinds@ promotion here so @StIdle@, @StBusy@ and @StDone@ are
-- /types/ (of /kind/ @PingPong@) representing the three states in this
-- protocol's state machine. @PingPong@ itself is both the kind of these types
-- and is also the tag for protocol. We only ever use these as types, via the
-- @DataKinds@ promotion, never as value level data constructors.
--
-- Having defined our protocol tag and states we can instantiate the 'Protocol'
-- type class and fill out the other details.
--
-- The protocol must define what its messages are. These form the state
-- transitions in the protocol state machine. Each transition specifies a
-- \"from\" and \"to\" state as type parameters. This of course determines in
-- which protocol states each message can appear.
--
-- In the \"ping\/pong\" protocol example, the messages are of course ping and
-- pong, which transition between the two main states. There is also a done
-- message that moves the system into a terminal state.
--
-- > instance Protocol PingPong where
-- >   data Message PingPong from to where
-- >     MsgPing :: Message PingPong StIdle StBusy
-- >     MsgPong :: Message PingPong StBusy StIdle
-- >     MsgDone :: Message PingPong StIdle StDone
--
-- This says that in the idle state a ping message takes us to the busy state,
-- while a pong message takes us back to idle. Also in the idle state a done
-- message takes us to the done state.
--
-- It is not required that protocols have any terminal states or corresponding
-- transitions, as in this example, but it is often useful and it aids testing
-- to have protocols that terminate cleanly as it allows them to return a
-- result.
--
-- As described above, this style of protocol gives agency to only one peer at
-- once. That is, in each protocol state, one peer has agency (the ability to
-- send) and the other does not (it must only receive). The three associated
-- data families ('ClientHasAgency', 'ServerHasAgency' and 'NobodyHasAgency')
-- define which peer has agency for each state.
--
-- In the \"ping\/pong\" protocol example, the idle state is the one in which
-- the client can send a message, and the busy state is the one in which the
-- server must respond. Finally in the done state, neither peer can send any
-- further messages. This arrangement is defined as so:
--
-- >    -- still within the instance Protocol PingPong
-- >    data ClientHasAgency st where
-- >      TokIdle :: ClientHasAgency StIdle
-- >
-- >    data ServerHasAgency st where
-- >      TokBusy :: ServerHasAgency StBusy
-- >
-- >    data NobodyHasAgency st where
-- >      TokDone :: NobodyHasAgency StDone
--
-- In this simple protocol there is exactly one state in each category, but in
-- general for non-trivial protocols there may be several protocol states in
-- each category.
--

-- $tests
-- The mechanism for labelling each protocol state with the agency does not
-- automatically prevent mislabelling, ie giving conflicting labels to a
-- single state. It does in fact prevent forgetting to label states in the
-- sense that it would not be possible to write protocol peers that make
-- progress having entered these unlabelled states.
--
-- This partition property is however crucial for the framework's guarantees.
-- The "Network.TypedProtocol.Proofs" module provides a way to guarantee for
-- each protocol that this property is not violated. It also provides utilities
-- helpful for testing protocols.

-- $lemmas
--
-- The 'connect' and 'connectPipelined' proofs rely on lemmas about the
-- protocol. Specifically they rely on the property that each protocol state
-- is labelled with the agency of one peer or the other, or neither, but never
-- both. Or to put it another way, the protocol states should be partitioned
-- into those with agency for one peer, or the other or neither.
--
-- The way the labelling is encoded does not automatically enforce this
-- property. It is technically possible to set up the labelling for a protocol
-- so that one state is labelled as having both peers with agency, or declaring
-- simultaneously that one peer has agency and that neither peer has agency
-- in a particular state.
--
-- So the overall proofs rely on lemmas that say that the labelling has been
-- done correctly. This type bundles up those three lemmas.
--
-- Specifically proofs that it is impossible for a protocol state to have:
--
-- * client having agency and server having agency
-- * client having agency and nobody having agency
-- * server having agency and nobody having agency
--
-- These lemmas are structured as proofs by contradiction, e.g. stating
-- \"if the client and the server have agency for this state then it leads to
-- contradiction\". Contradiction is represented as the 'Void' type that has
-- no values except ⊥.
--
-- For example for the ping\/pong protocol, it has three states, and if we set
-- up the labelling correctly we have:
--
-- > data PingPong where
-- >   StIdle :: PingPong
-- >   StBusy :: PingPong
-- >   StDone :: PingPong
-- >
-- > instance Protocol PingPong where
-- >
-- >   data ClientHasAgency st where
-- >     TokIdle :: ClientHasAgency StIdle
-- >
-- >   data ServerHasAgency st where
-- >     TokBusy :: ServerHasAgency StBusy
-- >
-- >   data NobodyHasAgency st where
-- >     TokDone :: NobodyHasAgency StDone
--
-- So now we can prove that if the client has agency for a state then there
-- are no cases in which the server has agency.
--
-- >   exclusionLemma_ClientAndServerHaveAgency TokIdle tok = case tok of {}
--
-- For this protocol there is only one state in which the client has agency,
-- the idle state. By pattern matching on the state token for the server
-- agency we can list all the cases in which the server also has agency for
-- the idle state. There are of course none of these so we give the empty
-- set of patterns. GHC can check that we are indeed correct about this.
-- This also requires the @EmptyCase@ language extension.
--
-- To get this completeness checking it is important to compile modules
-- containing these lemmas with @-Wincomplete-patterns@, which is implied by
-- @-Wall@.
--
-- All three lemmas follow the same pattern.
--

-- | The protocol type class bundles up all the requirements for a typed
-- protocol.
--
-- Each protocol consists of three things:
--
-- * The protocol itself, which is also expected to be the kind of the types
--   of the protocol states. The class is indexed on the protocol itself.
-- * The protocol messages.
-- * The partition of the protocol states into those in which the client has
--   agency, or the server has agency, or neither have agency.
--
-- The labelling of each protocol state with the peer that has agency in that
-- state is done by giving a definition to the data families
-- 'ClientHasAgency', 'ServerHasAgency' and 'NobodyHasAgency'. These
-- definitions are expected to be singleton-style GADTs with one constructor
-- per protocol state.
--
-- Each protocol state must be assigned to only one label. See
-- "Network.TypedProtocol.Proofs" for more details on this point.
--
class Protocol ps where

  -- | The messages for this protocol. It is expected to be a GADT that is
  -- indexed by the @from@ and @to@ protocol states. That is the protocol state
  -- the message transitions from, and the protocol state it transitions into.
  -- These are the edges of the protocol state transition system.
  --
  data Message ps (st :: ps) (st' :: ps)

  -- | Tokens for those protocol states in which the client has agency.
  --
  data ClientHasAgency (st :: ps)

  -- | Tokens for those protocol states in which the server has agency.
  --
  data ServerHasAgency (st :: ps)

  -- | Tokens for terminal protocol states in which neither the client nor
  -- server has agency.
  --
  data NobodyHasAgency (st :: ps)

  -- | Lemma that if the client has agency for a state, there are no
  -- cases in which the server has agency for the same state.
  --
  exclusionLemma_ClientAndServerHaveAgency
    :: forall (st :: ps).
       ClientHasAgency st
    -> ServerHasAgency st
    -> Void

  -- | Lemma that if the nobody has agency for a state, there are no
  -- cases in which the client has agency for the same state.
  --
  exclusionLemma_NobodyAndClientHaveAgency
    :: forall (st :: ps).
       NobodyHasAgency st
    -> ClientHasAgency st
    -> Void

  -- | Lemma that if the nobody has agency for a state, there are no
  -- cases in which the server has agency for the same state.
  --
  exclusionLemma_NobodyAndServerHaveAgency
    :: forall (st :: ps).
       NobodyHasAgency st
    -> ServerHasAgency st
    -> Void

-- | Types for client and server peer roles. As protocol can be viewed from
-- either client or server side.
--
-- Note that technically \"client\" and \"server\" are arbitrary labels. The
-- framework is completely symmetric between the two peers.
--
-- This definition is only used as promoted types and kinds, never as values.
--
data PeerRole = AsClient | AsServer

-- | Singletons for the promoted 'PeerRole' types.  Not directly used by the
-- framework, however some times useful when writing code that is shared between
-- client and server.
--
data TokPeerRole (peerRole :: PeerRole) where
    TokAsClient :: TokPeerRole AsClient
    TokAsServer :: TokPeerRole AsServer

-- | This data type is used to hold state tokens for states with either client
-- or server agency. This GADT shows up when writing protocol peers, when
-- 'Yield'ing or 'Await'ing, and when writing message encoders\/decoders.
--
data PeerHasAgency (pr :: PeerRole) (st :: ps) where
  ClientAgency :: !(ClientHasAgency st) -> PeerHasAgency AsClient st
  ServerAgency :: !(ServerHasAgency st) -> PeerHasAgency AsServer st

instance ( forall (st' :: ps). Show (ClientHasAgency st')
         , forall (st' :: ps). Show (ServerHasAgency st')
         ) => Show (PeerHasAgency pr (st :: ps)) where
    show :: PeerHasAgency pr st -> String
show (ClientAgency ClientHasAgency st
stok) = String
"ClientAgency " String -> ShowS
forall a. [a] -> [a] -> [a]
++ ClientHasAgency st -> String
forall a. Show a => a -> String
show ClientHasAgency st
stok
    show (ServerAgency ServerHasAgency st
stok) = String
"ServerAgency " String -> ShowS
forall a. [a] -> [a] -> [a]
++ ServerHasAgency st -> String
forall a. Show a => a -> String
show ServerHasAgency st
stok

-- | A synonym for an state token in which \"our\" peer has agency. This is
-- parametrised over the client or server roles. In either case the peer in
-- question has agency.
--
-- This shows up when we are sending messages, or dealing with encoding
-- outgoing messages.
--
type WeHaveAgency   (pr :: PeerRole) st = PeerHasAgency             pr  st

-- | A synonym for an state token in which the other peer has agency. This is
-- parametrised over the client or server roles. In either case the other peer
-- has agency.
--
-- This shows up when we are receiving messages, or dealing with decoding
-- incoming messages.
--
type TheyHaveAgency (pr :: PeerRole) st = PeerHasAgency (FlipAgency pr) st

-- | A type function to flip the client and server roles.
--
type family FlipAgency (pr :: PeerRole) where
  FlipAgency AsClient = AsServer
  FlipAgency AsServer = AsClient


-- | A description of a peer that engages in a protocol.
--
-- The protocol describes what messages peers /may/ send or /must/ accept.
-- A particular peer implementation decides what to actually do within the
-- constraints of the protocol.
--
-- Peers engage in a protocol in either the client or server role. Of course
-- the client role can only interact with the serve role for the same protocol
-- and vice versa.
--
-- 'Peer' has several type arguments:
--
-- * the protocol itself;
-- * the client\/server role;
-- *.the current protocol state;
-- * the monad in which the peer operates; and
-- * the type of any final result once the peer terminates.
--
-- For example:
--
-- > pingPongClientExample :: Int -> Peer PingPong AsClient StIdle m ()
-- > pingPongServerExample ::        Peer PingPong AsServer StIdle m Int
--
-- The actions that a peer can take are:
--
-- * to perform local monadic effects
-- * to terminate with a result (but only in a terminal protocol state)
-- * to send a message (but only in a protocol state in which we have agency)
-- * to wait to receive a message (but only in a protocol state in which the
--   other peer has agency)
--
-- In the 'Done', 'Yield' and 'Await' cases we must provide evidence of both
-- the protocol state we are in and that the appropriate peer has agency.
-- This takes the form of 'ClientAgency' or 'ServerAgency' applied to a
-- protocol-specific state token: either a 'ClientHasAgency' or a
-- 'ServerHasAgency' token for the protocol. The 'Done' state does not need
-- the extra agency information.
--
-- While this evidence must be provided, the types guarantee that it is not
-- possible to supply incorrect evidence.
--
data Peer ps (pr :: PeerRole) (st :: ps) m a where

  -- | Perform a local monadic effect and then continue.
  --
  -- Example:
  --
  -- > Effect $ do
  -- >   ...          -- actions in the monad
  -- >   return $ ... -- another Peer value
  --
  Effect :: m (Peer ps pr st m a)
         ->    Peer ps pr st m a

  -- | Terminate with a result. A state token must be provided from the
  -- 'NobodyHasAgency' states, so show that this is a state in which we can
  -- terminate.
  --
  -- Example:
  --
  -- > Yield (ClientAgency TokIdle)
  -- >        MsgDone
  -- >       (Done TokDone result)
  --
  Done   :: !(NobodyHasAgency st)
         -> a
         -> Peer ps pr st m a

  -- | Send a message to the other peer and then continue. This takes the
  -- message and the continuation. It also requires evidence that we have
  -- agency for this protocol state and thus are allowed to send messages.
  --
  -- Example:
  --
  -- > Yield (ClientAgency TokIdle) MsgPing $ ...
  --
  Yield  :: !(WeHaveAgency pr st)
         -> Message ps st st'
         -> Peer ps pr st' m a
         -> Peer ps pr st  m a

  -- | Waits to receive a message from the other peer and then continues.
  -- This takes the the continuation that is supplied with the received
  -- message. It also requires evidence that the other peer has agency for
  -- this protocol state and thus we are expected to wait to receive messages.
  --
  -- Note that the continuation that gets supplied with the message must be
  -- prepared to deal with /any/ message that is allowed in /this/ protocol
  -- state. This is why the continuation /must/ be polymorphic in the target
  -- state of the message (the third type argument of 'Message').
  --
  -- Example:
  --
  -- > Await (ClientAgency TokIdle) $ \msg ->
  -- > case msg of
  -- >   MsgDone -> ...
  -- >   MsgPing -> ...
  --
  Await  :: !(TheyHaveAgency pr st)
         -> (forall st'. Message ps st st' -> Peer ps pr st' m a)
         -> Peer ps pr st m a


deriving instance Functor m => Functor (Peer ps (pr :: PeerRole) (st :: ps) m)