#!/usr/bin/env python

# Example of SimPy for discrete-time situations.

# Here we have a type of slotted ALOHA network.  There are n nodes, each
# one either idle or with a message to send.  Just slightly after the
# beginning of each time slot, say 0.1 slot after the beginning, if a
# node is idle, it generates a message with probability p.  Slightly
# later, say 0.2 slot after the beginning, each node with a message will
# with probability q attempt to send the message, with the transmission
# time occupying the remainder of the slot.  While a node has a message
# transmission pending, it does not generate any new messages.  A node
# which has attempted to transmit will check at 0.3 time after the
# beginning of the slot, to see whether its transmission was
# successful.

# Summary of event times:

#    Idle nodes check for new messages 0.1 time after the slot starts.
#    Busy nodes decide whether to xmit 0.2 time after the slot starts.
#    Transmitting nodes check for success 0.3 time after the slot starts.

from __future__ import generators  # delete if use Python >= 2.3 
from SimPy.Simulation import *
from random import Random,expovariate,uniform

import sys

class Node(Process):
   NextID = 0  # ID of next Node object to be created
   NAttemptXMit = 0  # number of nodes which tried to xmit in this slot
   MsgsSent = 0
   TotWait = 0.0  # total wait so far among all msgs
   def __init__(self):
      Process.__init__(self)  
   def Run(self):
      self.MyID = Node.NextID
      Node.NextID += 1
      while 1:
         ##################################
         # start idle period
         ##################################
         self.Busy = 0  # 1 means have msg to send, 0 means idle
         while 1:
            # generate new message?
            if Globs.Rnd.random() < Globs.P:
               # msg created, go to busy period
               break
            # no new message, so wait until next slot
            yield hold,self,1.0
         ##################################
         # start busy period
         ##################################
         yield hold,self,0.1  
         # now at time slot start + 0.2
         self.Busy = 1
         # record time wait started for this message
         self.StartWait = now() - 0.1
         while 1:
            # attempt to xmit?
            if Globs.Rnd.random() < Globs.Q:
               Node.NAttemptXMit += 1
               yield hold,self,0.1
               # now at time slot start + 0.3
               # save copy of number of attempted xmits in this slot, so
               # can reset the number for the next slot; might have
               # several nodes doing the resetting, but that's fine
               self.NAX = Node.NAttemptXMit
               Node.NAttemptXMit = 0
               # did my xmit succeed?
               if self.NAX == 1:
                  Node.MsgsSent += 1
                  Node.TotWait += now() - 0.1 - self.StartWait
                  # sync for next idle period
                  yield hold,self,0.8
                  # go to idle period
                  break
               else:
                  # sync for time of next possible xmit attempt
                  yield hold,self,0.9
            else:
               # didn't attempt to send, but
               # go to next slot and try again
               yield hold,self,1.0

class Globs:
   Rnd = Random(12345)
   N = int(sys.argv[1])
   P = float(sys.argv[2])
   Q = float(sys.argv[3])
   Nd = []  # array of node objects
   for I in range(N):
      Nd.append(Node())

# "main" program starts here

def main():
   initialize()  
   for I in range(Globs.N):
      # note delay of 0.1, to sync for first idle period
      activate(Globs.Nd[I],Globs.Nd[I].Run(),delay=0.1)
   MaxSimtime = 10000.0
   simulate(until=MaxSimtime)
   print "mean wait", Node.TotWait/Node.MsgsSent

if __name__ == '__main__':
   main()

