-- ==================================================================================
--  WHAT CONCURRENT_HELLO_6 DOES
--
--  This program has one 'writer' process type that is instantiated several times, and one 'output.collector' process (invoked just once).
--  The writers share a single channel of communication between them, so a client end of the channel is defined as shared, and passed to all
--  the writers, which must claim it before they can send their data. Writers send a series of messages to output.collector, containing
--  their IDs (which are now passed to them upon instantiation), a message sequence number, and a random number.
--
--  Random.num.generator is a process that creates random numbers within a designated range (starting from zero) through a client-server
--  architecture, using shared client end of a channel, and a normal server end which the random.num.generator process possesses. Random.num.generator
--  receives a signal to terminate from the termination manager (see below). 
--
--  Msg.seq.tracker is a process that maintains message sequence numbers. It has a case protocol implemented which handles the client-server
--  interaction between itself and writers. During this exchange, a channel is passed from msg.seq.tracker, down which the message sequence number
--  is transmitted. Upon receipt of the msg seq num, writers will return this channel to the msg.seq.tracker server. 
--
--  Output.collector receives data from writers along the shared channel, and prints that data to the terminal. It receives a signal to terminate
--  from the termination manager. 
--
--  The mechanism for termination of all processes (severs and clients (which are writers)) is improved, there is no bespoke process to handle
--  syncroniseation from writers and write down a channel to servers, now servers themselves enroll on the termination barrier and transmit
--  the signal to terminate to themselves.    
--
--  This program should be compiled with "kroc -d -di concurrent_hello_6.occ"
--  and then run with ./concurrent_hello_6
-- ==================================================================================

-- ==================================================================================
-- FUNCTIONALITY/FEATURES INTRODUCED
--  1) Improved termination barrier pattern to remove the termination manager. All processes now terminate on barrier without a third party interviening
--  2) Enroll any number of processes on barrier (within a PAR loop), along with some select processes.
--  3) Made use of case protocols, some of which pass only literals. Also uses sequential messages (sending several things at once)
--  4) Mobile channels used to full effect, data passed down a channel which itself is passed between servers and clients. specifiers can be tricky here, so this is a good example of how its done 
-- ==================================================================================

-- ==================================================================================
-- ITEMS STILL TODO
--  *) is it possible to send null data? for RANDOM.RETREIVER req channel.
--  *) use forking
-- ==================================================================================


--{{{ INCLUDES  
-- this module contains utilities for pringing useful types (like strings)
-- (like strings) to a chanel (which can be linked to the terminal screen)
#INCLUDE "course.module" 
--}}} 
     
        
--{{{ outer most procedure, with a parameter list that corresponds to that required to run in unix
PROC outer(CHAN BYTE in?, SHARED CHAN BYTE out!, CHAN BYTE err!)
  
  --{{{ declarations and types
  DATA TYPE WRITER.DATA           -- this is a data packet for  data sent between writer.int and output.collector
    RECORD 
      INT writer.id :
      INT msg.seq.num :
      INT writer.data :
  :  
  
  CHAN TYPE COMMS                 -- channel type for communication between writers and output.collector
    MOBILE RECORD                 -- the specifiers (? and !) are mandatory, and specified according to the server's point of view.
      CHAN WRITER.DATA data? :    -- so, server would be receiving data.
  : 
  
  CHAN TYPE RANDOM.REQUEST            -- channel that handles requests for random numbers. A client end is shared amonst those
                                      -- that require random numbers, random.num.generator holds on to the server end (which is not shared)
    MOBILE RECORD                     -- the specifiers (? and !) are mandatory, and are specified according ot the server's point of view
      CHAN INT req? :                 -- channel used to reqest a random number
      CHAN INT resp! :                -- channel used to return random numbers
  :
  
  CHAN TYPE MSG.SEQ.COMMUNICATOR      -- channel used to communicate message sequence numbers between the msg.seq.tracker and writers. The channel 
                                      -- of this type will be passed back and forth between writers and msg.seq.tracker
    MOBILE RECORD
      CHAN INT communicator? :        -- specified from server's POV (the writer process), which will receive data from this channel. 
  :
  
  PROTOCOL MSG.SEQ.REQ.PROT           -- protocol for communication between writers and msg.seq.tracker. Client-server architecture again:
                                      -- client claims client channel end, and sends request.seq.number
                                      -- server will pass channel of type MSG.SEQ.COMMUNICATOR through another channel (see below)
                                      -- client responds with got.chan
                                      -- server writes data down the channel of type MSG.SEQ.COMMUNICATOR
                                      -- client responds received.data
                                      -- client then returns channel fo type MSG.SEQ.COMMUNICATOR back to the server.
                                      -- at this point the transaction is complete. client releases claim
    CASE
      request.seq.number                      -- request the seq number, which will be communicated down a channel that itself will be communicated
      got.chan                                -- response to indicate that the client has received the channel, server may pass seq number
      received.data                           -- response to indicated that the client has received the data from the channel.
      return.chan; MSG.SEQ.COMMUNICATOR?      -- to return the receiving end of the communicator back to where it came
  :  

  
  CHAN TYPE MSG.SEQ.REQUEST                   -- the channel used for communication between writers and msg.seq.tracker. 
                                              -- intended to be shared at the client end (req). 
    MOBILE RECORD 
      CHAN MSG.SEQ.REQ.PROT req? :            -- receive requests from a client wanting a message seq number. 
      CHAN MSG.SEQ.COMMUNICATOR? resp! :      -- response will write the receiving end of MSG.SEQ.COMMUNICATOR down the channel resp. 
  :  
  --}}}
  
  
  --{{{ PROC random.num.generator
  --  process implements a client-server architecture between writers (clients) and itself (server) for the provision of random numbers
  --  clients send a request along the shared client end of the random.comms channel, and the value sent in the request deontes the upper
  --  bound for the range within which the random number generated will reside.
  --  this process receives a signal to terminate from termination.manager through the signal.to.terminate channel. 
  PROC random.num.generator (RANDOM.REQUEST? random.comms, BARRIER termination.barrier)
    INT data, max :
    INITIAL INT seed IS 1 :
    INITIAL BOOL terminate IS FALSE :
    CHAN BOOL termination.channel :
    
    PAR
      SEQ                                         -- this process manages termination
        SYNC termination.barrier
        termination.channel ! TRUE
      WHILE NOT terminate
        ALT
          random.comms[req] ? max                 -- wait for request on random.comms
            SEQ
              data, seed := random (max, seed)    -- retreive random number to pass on
              random.comms[resp] ! data           -- send random number to the client
          termination.channel ? terminate         -- wait for signalt to terminate
            CLAIM out!
              out.string ("random number generator received singnal to terminate", 0, out!)             
  : 
  --}}}
  
  
  --{{{ PROC msg.seq.tracker
  --  process maintains a track of the messages that have been send between writers and output.collector (I realise this is perhaps not the best
  --  way of achiving this in a real environment, it should be attached by the output.collector, but for the purposes of unnecessary 
  --  complication to learn the language, it works). Writers request msg.seq.numbers from this process, which acts as a server and 
  --  returns them. Part of the protocol for this client-server interaction has a channel passed between writers and msg.seq.tracker, it is
  --  down that channel that the msg.seq.numbers are actually transmitted. 
  PROC msg.seq.tracker (MSG.SEQ.REQUEST? msg.seq.comms, BARRIER termination.barrier)
    INITIAL INT seq.num IS 1 :
      
    MSG.SEQ.COMMUNICATOR! communicator.sender :               -- server from perspective that this 'server' process is writing the data. 
    MSG.SEQ.COMMUNICATOR? communicator.receiver :             -- client from perspective that 'client' to this server process receives
                                                              -- the data. Normally the specifiers '!' and '?' would be the other way round
    INITIAL BOOL terminate IS FALSE :
    CHAN BOOL termination.chan :

    SEQ
                                                              -- the channel that msg.seq.nums will be transmitted down. 
                                                              -- it is sent and returned between writers and this process.
      communicator.sender, communicator.receiver := MOBILE MSG.SEQ.COMMUNICATOR
      PAR
        SEQ                                                   -- monitor for termination, when received write down a channel to inform
          SYNC termination.barrier                            -- the main loop of the process
          termination.chan ! TRUE
        WHILE NOT terminate
          ALT                                                 -- either provide msg.seq.numbers, or wait for termination. 
            msg.seq.comms[req] ? CASE request.seq.number      -- client requested a sequence number
              SEQ
                msg.seq.comms[resp] ! communicator.receiver   -- send client the channel along which the seq number will be communicated
                msg.seq.comms[req] ? CASE got.chan            -- client has received the channel
                communicator.sender[communicator] ! seq.num   -- communicate msg seq num down channel
                msg.seq.comms[req] ? CASE received.data       -- client has received the data
                msg.seq.comms[req] ? CASE return.chan; communicator.receiver       -- receive channel from client, store in communicator.client
                seq.num := seq.num + 1
            termination.chan ? terminate                      -- receive signal to terminate
              SKIP
  :
  --}}}
  

  --{{{ PROC writer
  -- Process writes an 'id', msg seq number, and some random data, 'loops' number of times (randomly determined), out of a COMMS channel end. 
  -- The various instantiations of this process may share the write.to.server channel end. They also share random.comms, and msg.seq.comms.
  -- When the writer has finished it's work, it syncs on the barrier to indiciate it is ready to terminate. 
  PROC writer.int (VAL INT id, SHARED COMMS! write.to.server, 
                           SHARED RANDOM.REQUEST! random.comms, 
                           SHARED MSG.SEQ.REQUEST! msg.seq.comms, 
                           BARRIER termination.barrier)

    INT seq.num, data, loops :
    MSG.SEQ.COMMUNICATOR? msg.seq.communicator.receiver :     -- channel down which msg.seq.numbers will be received. 
                                                              -- actual channel will be passed during the exchange
    SEQ
      CLAIM random.comms                        -- request a random number for number of data items to be written
        SEQ
          random.comms[req] ! 10                -- request the random number
          random.comms[resp] ? loops            -- receive the random number
      SEQ i = 0 FOR loops                       -- write some data 'loops' times
        SEQ
          CLAIM random.comms                    -- note that we must specify direection that we wish to claim
            SEQ
              random.comms[req] ! 1000          -- request a random number
              random.comms[resp] ? data         -- receive random number
          CLAIM msg.seq.comms
            SEQ                                 -- receive the msg.seq.number
              msg.seq.comms[req] ! request.seq.number
              msg.seq.comms[resp] ? msg.seq.communicator.receiver
              msg.seq.comms[req] ! got.chan
              msg.seq.communicator.receiver[communicator] ? seq.num
              msg.seq.comms[req] ! received.data
              msg.seq.comms[req] ! return.chan; msg.seq.communicator.receiver
              
          CLAIM write.to.server                 -- claiming necessary for shared channel
            write.to.server[data] ! [id, seq.num, data] (WRITER.DATA)    -- writing an entire record at once
      SYNC termination.barrier                  -- indicate that process is ready to terminate
  :
  --}}}
  
  
  --{{{ PROC output.collector
  -- procedure receives data from the writers and outputs it to the terminal
  -- procedure attempts to sync on the barrier, and when all the writers have done so, everyone can terminate
  PROC output.collector (COMMS? input.from.writers, BARRIER termination.barrier)

    WRITER.DATA received :                          -- temporarily store data to be output
    INITIAL BOOL terminate IS FALSE :               -- flag used to indicate satisfaction of termination condition
    CHAN BOOL termination.channel :

    --{{{ start receiving and outputting data     
    PAR
      SEQ                                           -- this process manages termination
        SYNC termination.barrier
        termination.channel ! TRUE
      WHILE NOT terminate                           -- loop until termination condition met
        ALT                                         -- receive data, and run timeout in parallel
          input.from.writers[data] ? received       -- receive data from either writer                          
            CLAIM out!
              SEQ                                   -- output the received packet
                out.string ("ID: ", 0, out!)
                out.int    (received[writer.id], 0, out!)
                out.string ("  MSG SEQ : ", 0, out!)
                out.int    (received[msg.seq.num], 0, out!)
                out.string ("  DATA: ", 0, out!)
                out.int    (received[writer.data], 0, out!)
                out.string ("*n", 0, out!)
              
          termination.channel ? terminate                 -- receive signal to terminate, and terminate
            CLAIM out!
              out.string("*noutput.collector received signal to terminate.*n", 0, out!)       
    --}}}
  :
  --}}}
  
  
  --{{{ the main body of the program
  VAL INT number.of.writers IS 7 :
  
  SHARED COMMS! comms.client :                    -- channel type client and server ends for writers. writers share the channel end between them
  COMMS? comms.server :                           -- channel end is not shared

  SHARED MSG.SEQ.REQUEST! msg.seq.client :        -- channel ends for communication between writers and msg.seq.tracker
  MSG.SEQ.REQUEST? msg.seq.server :
  
  SHARED RANDOM.REQUEST! random.client :          -- server's POV, channel shared amongst clients. '!' is standard for client end
  RANDOM.REQUEST? random.server :                 -- server's POV, it will receive requests on this channel. '?' is convention for server end 
  
  BARRIER termination.barrier :                   -- barrier used to indicate when all writers have finished and hence when everything can terminate
  SEQ
    CLAIM out!
      out.string("starting the writers...*n", 0, out!)               
    comms.client, comms.server := MOBILE COMMS                        -- instantiating channel ends
    msg.seq.client, msg.seq.server := MOBILE MSG.SEQ.REQUEST
    random.client, random.server := MOBILE RANDOM.REQUEST             -- instantiating channel ends
  
    PAR ENROLL termination.barrier                                    -- all these processes are enrolled on the termination barrier
      output.collector (comms.server, termination.barrier)     
      msg.seq.tracker (msg.seq.server, termination.barrier)       
      random.num.generator (random.server, termination.barrier)
      PAR i = 0 FOR number.of.writers ENROLL termination.barrier      -- instantiate writers. These are also enrolled on termination barrier
        writer.int (i, comms.client, random.client, msg.seq.client, termination.barrier)

    CLAIM out!  
      out.string("writers have finished.*n", 0, out!)
  --}}}
:
--}}}



