-- ==================================================================================
--  WHAT writing_hello_1 DOES
--
--  Incomplete, work in progress. Will eventually read and write things to the file system
--  instead of the terminal. 
-- ==================================================================================

-- ==================================================================================
-- FUNCTIONALITY/FEATURES INTRODUCED
--  1) Made use of mobile []BYTE (within mobile records, which is tricky) to implement strings. 
--  2) Some initial type casting, from int to byte (character values in ascii)
-- ==================================================================================

-- ==================================================================================
-- 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.INT.PACKET
  --  this is a data packet for  data sent between writer.int and output.collector
  DATA TYPE WRITER.INT.PACKET           
    RECORD 
      INT writer.id :
      INT msg.seq.num :
      INT writer.data :
  :  
  --}}}

  --{{{ DATA TYPE WRITER.STRING.PACKET
  --  this is a data packet for data sent between writer.string and output.collector
  DATA TYPE WRITER.STRING.PACKET
    MOBILE RECORD
      INT writer.id :
      INT msg.seq.num :
      MOBILE []BYTE writer.data :
  :
  --}}}

  --{{{ PROTOCOL WRITER.DATA
  --  protocol for communication of data packets between writer.int or writer.string, and output.collector
  PROTOCOL WRITER.DATA
    CASE
      int.writer; WRITER.INT.PACKET
      string.writer; WRITER.STRING.PACKET 
  :
  --}}}
  
  --{{{ CHAN TYPE WRITER.COMMS
  -- channel type for communciation between writers and output.collector. Is shared at the client end
  CHAN TYPE WRITER.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 packet? :      -- so, server would be receiving data.
  : 
  --}}}

  --{{{ CHAN TYPE RANDOM.REQUEST
  --  channel type for communication between random number generator, and the clients that require random numbers
  CHAN TYPE RANDOM.REQUEST
    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 type for communicating message sequence numbers during requests
  CHAN TYPE MSG.SEQ.COMMUNICATOR
    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 clients requesting message sequence numbers, and the server that provides them
  PROTOCOL MSG.SEQ.REQ.PROT
    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
  --  channel type for communication between clients and server that provides message sequence numbers
  CHAN TYPE MSG.SEQ.REQUEST
    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. 
  :
  --}}}

  --{{{ CHAN TYPE WRITER.P2P.SHUTDOWN         -- not yet implemented 
  -- intended to be used for a peer-to-peer protocol that results in the shutting down of writers in order of their ids
  CHAN TYPE WRITER.P2P.SHUTDOWN
    MOBILE RECORD
      CHAN INT req? :                         -- writers will read the next id to be shut down from this channel
      CHAN INT resp! :                        -- writers will write the next id to be shut down from this channel
  :  
  --}}}
  
  --}}}
  
  --{{{ PROC random.num.generator
  -- implements a server-client, request-response architecture to hand out random numbers to clients. 
  -- clients can specificy the range within which the random number must fall
  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 termin
        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 signal to terminate
            CLAIM out!
              out.string ("random number generator received signal to terminate.*n", 0, out!)             
  : 
  --}}}
  
  --{{{ PROC msg.seq.tracker
  --  process provides a sequential string of numbers to be used as message sequence numbers. 
  --  clients of thsi server process share the same channel
  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
      communicator.sender, communicator.receiver := MOBILE MSG.SEQ.COMMUNICATOR
      PAR
        SEQ
          SYNC termination.barrier
          termination.chan ! TRUE
        WHILE NOT terminate
          ALT
            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                          -- update message sequence number internal state
            termination.chan ? terminate
              SKIP
  :
  --}}}
  
  --{{{ PROC writer.int
  -- process writes an 'id', msg seq number, and some random data, 'loops' number of times, out of a WRITER.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.int (VAL INT id, SHARED WRITER.COMMS! write.to.server, 
                           SHARED RANDOM.REQUEST! random.comms, 
                           SHARED MSG.SEQ.REQUEST! msg.seq.comms, 
                           BARRIER termination.barrier,
                           SHARED WRITER.P2P.SHUTDOWN! writer.p2p.client,
                           SHARED WRITER.P2P.SHUTDOWN? writer.p2p.server)

    INT seq.num, data, loops :
    MSG.SEQ.COMMUNICATOR? msg.seq.communicator.receiver :

    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
      loops := loops + 1
      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                                 -- acquire a message sequence 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[packet] ! int.writer; [id, seq.num, data] (WRITER.INT.PACKET)    -- writing an entire record at once
      SYNC termination.barrier                  -- indicate that process is ready to terminate

      CLAIM out!                                -- print some termination information
        SEQ
          out.string("writer (int   ) with ID ", 0, out!)
          out.int   (id, 0, out!)
          out.string(" terminating.*n", 0, out!)
  :
  --}}}
  
  --{{{ PROC writer.string
  -- process writes an 'id', msg seq number, and some random data, 'loops' number of times, 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.string (VAL INT id,  SHARED WRITER.COMMS! write.to.server,
                                   SHARED RANDOM.REQUEST! random.comms,
                                   SHARED MSG.SEQ.REQUEST! msg.seq.comms,
                                   BARRIER termination.barrier,
                                   SHARED WRITER.P2P.SHUTDOWN! writer.p2p.client,
                                   SHARED WRITER.P2P.SHUTDOWN? writer.p2p.server)

    INT seq.num, loops :
    MSG.SEQ.COMMUNICATOR? msg.seq.communicator.receiver :
    WRITER.STRING.PACKET packet :
    INT length.of.message :
    MOBILE []BYTE message : 

    SEQ
      CLAIM random.comms
        SEQ                                                 -- acquire random number that dictates how many packets are to be sent
          random.comms[req] ! 10
          random.comms[resp] ? loops
      loops := loops + 1
      SEQ i = 0 FOR loops                                   -- write 'loops' packets
        SEQ
          CLAIM random.comms
            SEQ                                             -- retrieve length of message
              random.comms[req] ! 10
              random.comms[resp] ? length.of.message
          message := MOBILE [length.of.message]BYTE         -- assign []BYTE that will store the string
          SEQ j = 0 FOR SIZE message                        -- compose the message (with random characters)
            INT character :
            SEQ                                             -- compile the message string
              CLAIM random.comms
                SEQ                                         -- acquire a random number to be converted to a character
                  random.comms[req] ! 26
                  random.comms[resp] ? character
              character := character + 97                   -- adjust the random numbers to the correct range of ascii code
              message[j] := BYTE character                  -- type cast the int representing a character to a byte
  
          CLAIM msg.seq.comms                               -- retreive message sequence number
            SEQ                                 
              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 
          SEQ                                               -- prepare packet (need to do this explicitly here, because of lacking compiler support)
            packet[writer.id] := id
            packet[msg.seq.num] := seq.num
            packet[writer.data] := message
            CLAIM write.to.server                           -- write the packet to the channel 
              write.to.server[packet] ! string.writer; packet

      SYNC termination.barrier                              -- indicate that writing has finished, and termination can proceed
      CLAIM out! 
        SEQ
          out.string("writer (string) with ID ", 0, out!)
          out.int   (id, 0, out!)
          out.string(" terminating.*n", 0, out!)
  :
  --}}}

  --{{{ 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 (WRITER.COMMS? input.from.writers, BARRIER termination.barrier)

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

    MOBILE []BYTE string :                          -- for reasons I do not understand, moving this to a location nearer its use causes havoc. 

    --{{{ 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 monitor for termination
          input.from.writers[packet] ? CASE               -- received a packet, determine which type
            int.writer; received.int                      -- received a packet from an INT writer                          
              CLAIM out!
                SEQ                                       -- output the received packet
                  out.string ("ID: ", 0, out!)
                  out.int    (received.int[writer.id], 0, out!)
                  out.string ("  MSG SEQ: ", 0, out!)
                  out.int    (received.int[msg.seq.num], 0, out!)
                  out.string ("  DATA: ", 0, out!)
                  out.int    (received.int[writer.data], 0, out!)
                  out.string ("*n", 0, out!)
            string.writer; received.string                -- received a packet from a STRING writer
              CLAIM out!
                SEQ                                       -- print the contents of a string packet
                  string := received.string[writer.data]  -- this must be cached here explicitly, for lack of compiler support
                  out.string ("ID: ", 0, out!)
                  out.int    (received.string[writer.id], 0, out!)
                  out.string ("  MSG SEQ: ", 0, out!)
                  out.int    (received.string[msg.seq.num], 0, out!)
                  out.string ("  DATA: ", 0, out!)
                  out.string (string, 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 4 :
  
  SHARED WRITER.COMMS! comms.client :                    -- channel type client and server ends for writers. writers share the channel end between them
  WRITER.COMMS? comms.server :                           -- channel end is not shared

  SHARED MSG.SEQ.REQUEST! msg.seq.client :
  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 
  
  SHARED WRITER.P2P.SHUTDOWN! writer.p2p.client :
  SHARED WRITER.P2P.SHUTDOWN? writer.p2p.server :


  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 WRITER.COMMS                        -- instantiating channel ends
    msg.seq.client, msg.seq.server := MOBILE MSG.SEQ.REQUEST
    random.client, random.server := MOBILE RANDOM.REQUEST             -- instantiating channel ends
    writer.p2p.client, writer.p2p.server := MOBILE WRITER.P2P.SHUTDOWN  

    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 * 2 ENROLL termination.barrier
        PAR ENROLL termination.barrier
          writer.string (i + (number.of.writers * 2), comms.client, random.client, msg.seq.client, termination.barrier, writer.p2p.client, writer.p2p.server)
          writer.int    (i, comms.client, random.client, msg.seq.client, termination.barrier, writer.p2p.client, writer.p2p.server)

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

