-- ==================================================================================
--  WHAT CONCURRENT_HELLO_5 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. As before, writers send a series of messages to output.collector, containing
--  their IDs (which are now passed to them upon instantiation) 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). 
--
--  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. 
--
--  Termination of all processes is now handelled by barriers (rather than timers). Writers sync on the barrier to indicate that they are ready to
--  terminate. Unfortunately you cannot ALT on a SYNC <barrier> statement - this would be useful because output.collector could use an ALT
--  statement to either receive data from a channel, or sync on the barrier and hence terminate (there are ways around this, and this program 
--  demonstrates one such way). To get around this, another process is created, 'termination.manager', which immediately tries to sync on the 
--  same barrier as the writers. When all these processes have synced on the barrier, the writers automatically terminate, and termination.manager
--  sends a message down a channel to output.collector, informing it that it too may now terminate.    
--
--  In this architecture (and those that follow in other programms) the writers act as clients to output.collector, which is a server prividing
--  a print-to-the-terminal service.
--
--  This program should be compiled with "kroc -d -di concurrent_hello_5.occ"
--  and then run with ./concurrent_hello_5
-- ==================================================================================


-- ==================================================================================
-- FUNCTIONALITY/FEATURES INTRODUCED
--  1) Implemented random number generator 
--  2) Implemented a server/client req/resp architecture for provision of a service to many clients - note the shared channels for this.
-- ==================================================================================

-- ==================================================================================
-- ITEMS STILL TODO
--  *) introduce a protocol for channel communication
--  *) use protocol for req/resp in random number generator
--  *) case protocols
--  *) passing a channel end around between writers
--  *) is it possible to send null data? for RANDOM.RETREIVER req channel.
-- ==================================================================================


--{{{ 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           -- data type for communication between writers and output.collector
    RECORD 
      INT writer.id :
      INT writer.data :           -- writers are now writing random numbers as 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. The value passed here denotes the max for the range of random number
      CHAN INT resp! :                -- channel used to return random numbers
  :
    
  BARRIER termination.barrier :   -- when all processes have finished their work then they syncronise on this barrier and terminate 
  --}}}
  
  --{{{ 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, CHAN BOOL signal.to.terminate?)
    INT data, max :
    INITIAL INT seed IS 1 :
    INITIAL BOOL terminate IS FALSE :
    
    WHILE NOT terminate
      ALT                                       -- await either a request for random number, else signal to terminate
        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
        signal.to.terminate ? terminate         -- wait for signalt to terminate
          CLAIM out!
            out.string ("random number generator received singnal to terminate", 0, out!)             
  :     
  --}}}
  

  --{{{ PROC writer
  -- process writes a random number, 'loops' number of times (determined by random number), out of a COMMS channel end. 
  -- the various instantiations of this process may share the write.to.server channel end. 
  -- when the writer has finished it's work, it syncs on the barrier to indiciate it is ready to terminate. 
  PROC writer (VAL INT id, SHARED COMMS! write.to.server, SHARED RANDOM.REQUEST! random.comms, BARRIER termination.barrier)
    INT data, loops :

    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 write.to.server                 -- claiming necessary for shared channel
            write.to.server[data] ! [id, data] (WRITER.DATA)    -- writing an entire record at once
      SYNC termination.barrier                  -- indicate that process is ready to terminate
  :
  --}}}
  
  
  --{{{ PROC termination.manager
  -- when all writers are ready to terminate, as dicated by their synchronisations on the barrier,
  -- this process signals output.collector and random.num.generator to terminate through the dedicated channels. 
  PROC termination.manager(BARRIER termination.barrier, CHAN BOOL signal.to.terminate.1!, signal.to.terminate.2!)
    SEQ 
      SYNC termination.barrier
      signal.to.terminate.1 ! TRUE
      signal.to.terminate.2 ! TRUE
  :
  --}}}
  
  
  --{{{ 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, CHAN BOOL signal.to.terminate?)

    WRITER.DATA received :                          -- temporarily store data to be output
    INITIAL BOOL terminate IS FALSE :               -- flag used to indicate satisfaction of termination condition
    
    --{{{ start receiving and outputting data        
    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 ("  DATA: ", 0, out!)
              out.int    (received[writer.data], 0, out!)
              out.string ("*n", 0, out!)
            
        signal.to.terminate ? 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
  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 RANDOM.REQUEST! random.client :          -- server's POV, it will be writing down these channels
  RANDOM.REQUEST? random.server :                 -- server's POV, it will receive requests from a shared channel 
  
  CHAN BOOL signal.to.terminate.1:                -- channel used to signal that writers have finished inputting, shared between receivers
  CHAN BOOL signal.to.terminate.2:                -- channel used to signal that writers have finished inputting, shared between receivers  
  
  SEQ
    CLAIM out!
      out.string("starting the writers...*n", 0, out!)               
    comms.client, comms.server := MOBILE COMMS                        -- instantiating channel ends
    random.client, random.server := MOBILE RANDOM.REQUEST             -- instantiating channel ends
    
    PAR
      output.collector (comms.server, signal.to.terminate.1)            -- this process is not enrolled on the barrier
      random.num.generator (random.server, signal.to.terminate.2)       -- note that we must specify direction. Not enrolled on barrier                 
      PAR ENROLL termination.barrier                                    -- all these parallel processes are enrolled on the barrier  
        termination.manager (termination.barrier, signal.to.terminate.1, signal.to.terminate.2)
        writer (1, comms.client, random.client, termination.barrier)
        writer (2, comms.client, random.client, termination.barrier)
        writer (3, comms.client, random.client, termination.barrier)
    CLAIM out!  
      out.string("writers have finished.*n", 0, out!)
  --}}}
:
--}}}



