#!/usr/bin/env python
"""SimulationStep 1.4.2
__version__ = '$Revision: 1.2 $ $Date: 2004-05-20 14:08:05+02 $ kgm'
LICENSE:
Copyright (C) 2002  Klaus G. Muller, Tony Vignaux
mailto: kgmuller@xs4all.nl and Tony.Vignaux@vuw.ac.nz

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
END OF LICENSE

Implements SimPy Processes, resources, and the backbone simulation scheduling
by coroutine calls.
Based on generators (Python 2.2 and later)



**Change history:**

    Started out as SiPy 0.9
    
    5/9/2002: SiPy 0.9.1
    
        - Addition of '_cancel' method in class Process and supporting '_unpost' method in 
      class __Evlist.
        
        - Removal of redundant 'Action' method in class Process.
        
    12/9/2002:
    
        - Addition of resource class
        
        - Addition of "_request" and "_release" coroutine calls
        
    15/9/2002: moved into SimPy package
    
    16/9/2002:
        - Resource attributes fully implemented (resources can now have more
          than 1 shareable resource units)
        
    17/9/2002:
    
        - corrected removal from waitQ (Vignaux)
        
    17/9/2002:
    
        - added test for queue discipline in "test_demo()". Must be FIFO
        
    26/9/02: Version 0.2.0
    
        - cleaned up code; more consistent naming
        
        - prefixed all Simulation-private variable names with "_".
        
        - prefixed all class-private variable names with "__".
        
        - made normal exit quiet (but return message from scheduler()
        
    28/9/02:
    
        - included stopSimulation()
        
    15/10/02: Simulation version 0.3
    
        - Version printout now only if __TESTING
        
        - "_stop" initialized to True by module load, and set to False in 
      initialize()
        
        - Introduced 'simulate(until=0)' instead of 'scheduler(till=0)'. 
      Left 'scheduler()' in for backward compatibility, but marked
      as deprecated.
        
        - Added attribute "name" to class Process; default=="a_process"
        
        - Changed Resource constructor to 
      'def __init__(self,capacity=1,name="a_resource",unitName="units"'.
        
    13/11/02: Simulation version 0.6
    
        - Major changes to class Resource:
        
            - Added two queue types for resources, FIFO (default) and PriorityQ
            
            - Changed constructor to allow selection of queue type.
            
            - Introduced preemption of resources (to be used with PriorityQ
              queue type)
            
            - Changed constructor of class Resource to allow selection of preemption
            
            - Changes to class Process to support preemption of service
            
            - Cleaned up 'simulate' by replacing series of if-statements by dispatch table.

    19/11/02: Simulation version 0.6.1
        - Changed priority schemes so that higher values of Process 
       attribute "priority" represent higher priority.

    20/11/02: Simulation version 0.7
        - Major change of priority approach:

            - Priority set by "yield request,self,res,priority"

            - Priority of a Process instance associated with a specific 
          resource

    25/11/02: Simulation version 0.7.1

        - Code cleanup and optimization

        - Made process attributes remainService and preempted private 
      (_remainService and _preempted)

    11/12/2002: First process interrupt implementation

        - Addition of user methods 'interrupt' and 'resume'

        - Significant code cleanup to maintain process state

    20/12/2002: Changes to "interrupt"; addition of boolean methods to show
                     process states

    16/3/2003: Changed hold (allowing posting events past _endtime)
    
    18/3/2003: Changed _nextev to prevent _t going past _endtime

    23/3/2003: Introduced new interrupt construct; deleted 'resume' method
    
    25/3/2003: Expanded interrupt construct:
    
        - Made 'interrupt' a method  of Process

        - Added 'interruptCause' as an attribute of an interrupted process

        - Changed definition of 'active' to 
      'self._nextTime <> None and not self._inInterrupt'

        - Cleaned up test_interrupt function

    30/3/2003: Modification of 'simulate':

        - error message if 'initialize' not called (fatal)

        - error message if no process scheduled (warning)

        - Ensured that upon exit from 'simulate', now() == _endtime is 
      always valid

    2/04/2003:

        - Modification of 'simulate': leave _endtime alone (undid change
          of 30 Mar 03)

        - faster '_unpost'

    3/04/2003: Made 'priority' private ('_priority')

    4/04/2003: Catch activation of non-generator error

    5/04/2003: Added 'interruptReset()' function to Process.

    7/04/2003: Changed '_unpost' to ensure that process has
                   _nextTime == None (is passive) afterwards.

    8/04/2003: Changed _hold to allow for 'yield hold,self' 
                   (equiv to 'yield hold,self,0')

    10/04/2003: Changed 'cancel' syntax to 'Process().cancel(victim)'

    12/5/2003: Changed eventlist handling from dictionary to bisect
    
    9/6/2003: - Changed eventlist handling from pure dictionary to bisect-
                sorted "timestamps" list of keys, resulting in greatly 
        improved performance for models with large
                numbers of event notices with differing event times.
                =========================================================
        This great change was suggested by Prof. Simon Frost. 
        Thank you, Simon! This version 1.3 is dedicated to you!
        =========================================================
                - Added import of Lister which supports well-structured 
          printing of all attributes of Process and Resource instances.

    9 Nov 2003: Added capability to step through simulation event by event.

    November 2003: Brought up to Simulation 1.4alpha

    13 Dec 2003: Merged in Monitor and Histogram

    27 Feb 2004: Repaired bug in activeQ monitor of class Resource. Now actMon
                 correctly records departures from activeQ.
		 
    May 19, 2004: Added erroneously omitted Histogram class.
"""


from __future__ import generators
from SimPy.Lister import *
import bisect
import types
__TESTING=True
__version__="1.4.2 19 May 2004"
if __TESTING: print "SimPy.SimulationStep %s" %__version__
hold=1234
passivate=5678
request=135
release=246
_endtime=0
_t=0
_e=None
_stop=1
True=1
False=0
paused=False
_step=False


def initialize():
    global _e,_t,_stop
    _e=__Evlist()
    _t=0
    _stop=0

def now():
    return _t

def stopSimulation():
    """Application function to stop simulation run"""
    global _stop
    _stop=1

def startStepping():
    """Application function to start stepping through simulation."""
    global _step
    _step=True

def stopStepping():
    """Application function to stop stepping through simulation."""
    global _step
    _step=False
    
class Simerror(Exception):
    def __init__(self,value):
        self.value=value

    def __str__(self):
        return `self.value`
    
class Process(Lister):
    """Superclass of classes which may use generator functions"""
    def __init__(self,name="a_process"):
        #the reference to this Process instances single process (==generator)
        self._nextpoint=None 
        self.name=name
        self._nextTime=None 
        self._remainService=0
        self._preempted=0
        self._priority={}
        self._terminated=0      
        self._inInterrupt= False    

    def active(self):
        return self._nextTime <> None and not self._inInterrupt 

    def passive(self):
        return self._nextTime == None and not self._terminated

    def terminated(self):
        return self._terminated

    def interrupted(self):
        return self._inInterrupt and not self._terminated

    def queuing(self,resource):
        return self in resource.waitQ
           
    def cancel(self,victim): 
        """Application function to cancel all event notices for this Process
           instance;(should be all event notices for the _generator_)."""
        
        _e._unpost(whom=victim)
        
    def _hold(self,a):
        if len(a[0]) == 3: 
            delay=abs(a[0][2])
        else:
            delay=0
        who=a[1]
        self.interruptLeft=delay 
        self._inInterrupt=False
        self.interruptCause=None
        _e._post(what=who,at=_t+delay)

    def _passivate(self,a):
        a[0][1]._nextTime=None

    def interrupt(self,victim):
        """Application function to interrupt active processes""" 
        # can't interrupt terminated/passive/interrupted process
        if victim.active():
            victim.interruptCause=self  # self causes interrupt
            left=victim._nextTime-_t
            victim.interruptLeft=left   # time left in current 'hold'
            victim._inInterrupt=True    
            reactivate(victim)
            return left
        else:
            return None

    def interruptReset(self):
        """
        Application function for an interrupt victim to get out of
        'interrupted' state.
        """
        self._inInterrupt= False

def showEvents():
    """Returns string with eventlist as list of tuples (eventtime,action)"""
    return `[(_e.events[i],_e.aclist[i].who.name) \
                 for i in range(len(_e.events))]`        
                       
class __Evlist:
    """Defines event list and operations on it"""
    def __init__(self):
        #Event list has the structure {<time1>:(ev notice1, ev notice2, . . ),
        #                              <time2>:(ev notice 3,...),..}
        self.events={}
        #always sorted list of event times (keys for self.events)	
        self.timestamps=[]

    def _post(self,what,at,prior=False):
        """Post an event notice for process what for time at"""
        # event notices are _Action instances 
        if at < _t:
            raise SimError("Attempt to schedule event in the past")
        if at in self.events:
            if prior:
            #before all other event notices at this time
                self.events[at][0:0]= [what] 
            else:
                self.events[at].append(what)
        else: # first event notice at this time
            self.events[at]=[what]
            bisect.insort(self.timestamps,at) # Added SDWF
        what.who._nextTime=at

    def _unpost(self,whom):
        """
        Search through all event notices at whom's event time and remove whom's
        event notice if whom is a suspended process
        """
        thistime=whom._nextTime
        if thistime == None: #check if whom was actually active
            return
        else:
            thislist=self.events[thistime]
            for n in thislist:
                if n.who==whom:
                    self.events[thistime].remove(n)
                    whom._nextTime = None 
                    if not self.events[thistime]:
                    # no other ev notices for thistime
                        del(self.events[thistime])
                        item_delete_point = bisect.bisect(self.timestamps,
                                              thistime)
                        del self.timestamps[item_delete_point-1]
                                              
    def _nextev(self):
        """Retrieve next event from event list"""
        global _t, _stop
        if not self.events: raise Simerror("No more events at time %s" %now())
        earliest=self.timestamps[0]        
        _t=max(earliest,_t)
        temp=self.events[earliest] #list of actions for this time _t
        tempev=temp[0] #first action        
        del(self.events[earliest][0])
        if self.events[earliest]==[]: 
            del(self.events[earliest]) #delete empty list of actions
            del(self.timestamps[0])   
        if _t > _endtime:
            _t = _endtime
            _stop = True
            return (None,)
        try:
            tt=tempev.who._nextpoint.next()
        except StopIteration:
            tempev.who._nextpoint=None
            tempev.who._terminated=True 
            tempev.who._nextTime=None
            tt=None
            tempev=tempev.who
        return tt,tempev

class _Action:
    """Structure (who=process owner, generator=process)"""
    def __init__(self,who,generator):
        self.who=who
        self.process=generator

    def __str__(self):
        return "Action%s" %((self.who,self.process))

def activate(object,process,at="undefined",delay="undefined",prior=False):
    """Application function to activate passive process.""" 
    if not (type(process) == types.GeneratorType):
        raise Simerror("Fatal SimPy error: activating function which"+
                       " is not a generator (contains no 'yield')")
    if not object._terminated and not object._nextTime: 
        #store generator reference in object; needed for reactivation
        object._nextpoint=process       
        if at=="undefined":
            at=_t
        if delay=="undefined":
            zeit=max(_t,at)
        else:
            zeit=max(_t,_t+delay)
        _e._post(_Action(who=object,generator=process),at=zeit,prior=prior)


def reactivate(object,at="undefined",delay="undefined",prior=False):
    """Application function to reactivate a process which is active,
    suspended or passive.""" 
    # Object may be active, suspended or passive
    if not object._terminated: 
        Process().cancel(object)
        # object now passive
        if at=="undefined":
            at=_t
        if delay=="undefined":
            zeit=max(_t,at)
        else:
            zeit=max(_t,_t+delay)
        _e._post(_Action(who=object,generator=object._nextpoint),at=zeit,
             prior=prior)


    
class Queue(list):
    def __init__(self,res,moni):
        if moni==[]:
            self.monit=True # True if Monitor
        else:
            self.monit=False
        self.moni=moni # The Monitor
        self.resource=res # the resource this queue belongs to

    def enter(self,object):
        pass

    def leave(self):
        pass

class FIFO(Queue):
    def __init__(self,res,moni):
        Queue.__init__(self,res,moni)

    def enter(self,object):
        self.append(object)
        if self.monit:
            self.moni.observe(len(self),t=now())

    def leave(self):
        a= self.pop(0)
        if self.monit:
            self.moni.observe(len(self),t=now())
        return a

class PriorityQ(FIFO):
    """Queue is always ordered according to priority.
    Higher value of priority attribute == higher priority.
    """
    def __init__(self,res,moni):
        FIFO.__init__(self,res,moni)

    def enter(self,object):
        if len(self):
            ix=self.resource
            if self[-1]._priority[ix] >= object._priority[ix]:
                self.append(object)
            else:
                z=0
                while self[z]._priority[ix] >= object._priority[ix]:
                    z += 1
                self.insert(z,object)
        else:
            self.append(object)
        if self.monit:
            self.moni.observe(len(self),t=now())

class Resource(Lister):
    """Models shared, limited capacity resources with queuing;
    FIFO is default queuing discipline.
    """
    
    def __init__(self,capacity=1,name="a_resource",unitName="units",
                 qType=FIFO,preemptable=0,monitored=False): 
        self.name=name          # resource name
        self.capacity=capacity  # resource units in this resource
        self.unitName=unitName  # type name of resource units
        self.n=capacity         # uncommitted resource units
        self.monitored=monitored

        if self.monitored:           # Monitor waitQ, activeQ
            self.actMon=Monitor(name="Active Queue Monitor %s"%self.name,
                                 ylab="nr in queue",tlab="time")
            monact=self.actMon
            self.waitMon=Monitor(name="Wait Queue Monitor %s"%self.name,
                                 ylab="nr in queue",tlab="time")
            monwait=self.waitMon
        else:
            monwait=None
            monact=None            
        self.waitQ=qType(self,monwait)
        self.preemptable=preemptable
        self.activeQ=qType(self,monact)
        self.priority_default=0
        self.count=0
        
    def _request(self,arg):
        self.count+=1
        """Process request event for this resource"""
        object=arg[1].who
        if len(arg[0]) == 4:        # yield request,self,resource,priority
            object._priority[self]=arg[0][3]
        else:                       # yield request,self,resource 
            object._priority[self]=self.priority_default
        if self.preemptable and self.n == 0: # No free resource
            # test for preemption condition
            preempt=object._priority[self] > self.activeQ[-1]._priority[self]
            # If yes:
            if preempt:
                z=self.activeQ[-1]
                # suspend lowest priority process being served
                suspended = z
                # record remaining service time
                z._remainService = z._nextTime - _t 
                Process().cancel(z)
                # remove from activeQ
                self.activeQ.remove(z)
                # put into front of waitQ
                self.waitQ.insert(0,z)
                # record that it has been preempted
                z._preempted = 1
                # passivate it
                z._nextTime=None ##11 Dec
                # assign resource unit to preemptor
                self.activeQ.enter(object)                    
                # post event notice for preempting process
                _e._post(_Action(who=object,generator=object._nextpoint),
                         at=_t,prior=1)
            else:
                self.waitQ.enter(object)
                # passivate
                object._nextTime=None 
        else: # treat non-preemption case       
            if self.n == 0:
                self.waitQ.enter(object)
                # passivate
                object._nextTime=None
            else:
                self.n -= 1
                self.activeQ.enter(object)
                _e._post(_Action(who=object,generator=object._nextpoint),
                at=_t,prior=1)

    def _release(self,arg):
        self.count-=1
        """Process release request for this resource"""
        self.n += 1
        self.activeQ.remove(arg[1].who)
        if self.monitored:
            self.actMon.observe(len(self.activeQ),t=now())
        #reactivate first waiting requestor if any; assign Resource to it
        if self.waitQ:
            object=self.waitQ.leave()
            self.n -= 1             #assign 1 resource unit to object
            self.activeQ.enter(object)
            # if resource preemptable:
            if self.preemptable:
                # if object had been preempted:
                if object._preempted:
                    object._preempted = 0
                    # reactivate object delay= remaining service time
                    reactivate(object,delay=object._remainService)
                # else reactivate right away
                else:
                    reactivate(object,delay=0,prior=1)
            # else:
            else:
                reactivate(object,delay=0,prior=1)
        _e._post(_Action(who=arg[1].who,generator=arg[1].who._nextpoint),
                at=_t,prior=1)

def scheduler(till=0):
    """Schedules Processes/semi-coroutines until time 'till'.
    Deprecated since version 0.5.
    """
    simulate(until=till)
    
def holdfunc(a):
    a[0][1]._hold(a)

def requestfunc(a):
    a[0][2]._request(a)

def releasefunc(a):
    a[0][2]._release(a)

def passivatefunc(a):
    a[0][1]._passivate(a)
    
def simulate(callback=lambda :None,until=0):
    """Schedules Processes/semi-coroutines until time 'until'"""
    
    """Gets called once. Afterwards, co-routines (generators) return by 
    'yield' with a cargo:
    yield hold, self, <delay>: schedules the "self" process for activation 
                               after <delay> time units.If <,delay> missing,
                               same as "yield hold,self,0"
                               
    yield passivate,self    :  makes the "self" process wait to be re-activated

    yield request,self,<Resource> : request 1 unit from <Resource>.

    yield release,self,<Resource> : release 1 unit to <Resource>    

    Event notices get posted in event-list by scheduler after "yield" or by 
    "activate"/"reactivate" functions.

    Nov 9, 2003: Added capability to step through simulation event by event if
                 step==True. 'callback' gets called after every event. It can
                 cancel stepping or end run. API and semantics backwards
                 compatible with previous versions of simulate().
    
    """
    global _endtime,_e,_stop,_t,_step
    paused=False
    _stop=False

    if _e == None:
        raise Simerror("Fatal SimPy error: Simulation not initialized")
    if _e.events == {}:
        message="SimPy: No activities scheduled"
        return message
        
    _endtime=until
    message="SimPy: Normal exit" 
    dispatch={hold:holdfunc,request:requestfunc,release:releasefunc,
              passivate:passivatefunc}
    while not _stop and _t<=_endtime:
        try:
            a=_e._nextev()        
            if not a[0]==None:
                command = a[0][0]
                dispatch[command](a)     
        except Simerror, error:
            message="SimPy: "+ error.value
            _stop = True
        if _step:
            callback()
    stopStepping()
    _e=None
    return message

def simulateStep(callback=lambda :None,until=0):
    """Schedules Processes/semi-coroutines until next event"""
    
    """Can be called repeatedly.
    Behaves like "simulate", but does execute only one event per call.
                               

    
    """
    global _endtime,_e,_stop,_t,_step

    status="resumable"

    if _e == None:
        raise Simerror("Fatal SimPy error: Simulation not initialized")
    if _e.events == {}:
        message="SimPy: No activities scheduled"
        status="notResumable"
        return message,status
        
    _endtime=until
    message="SimPy: Normal exit" 
    dispatch={hold:holdfunc,request:requestfunc,release:releasefunc,
              passivate:passivatefunc}
    if not _stop and _t<=_endtime:
        try:
            a=_e._nextev()        
            if not a[0]==None:
                command = a[0][0]
                dispatch[command](a)     
        except Simerror, error:
            message="SimPy: "+ error.value
            _stop = True
            status="notResumable"
        if _step:
            callback()
    return message,status
    
class Histogram(list):
    """ A histogram gathering and sampling class"""

    def __init__(self,name = '',low=0.0,high=100.0,nbins=10):
        list.__init__(self)
        self.name  = name
        self.low   = low
        self.high  = high
        self.nbins = nbins
        self.binsize=(self.high-self.low)/nbins
        #self[:] = [[1,2],[3,4]]
        self[:] =[[low+(i-1)*self.binsize,0] for i in range(self.nbins+2)]
        #print '__init__ :', self[0],self
       
    def addIn(self,y):
        """ add a value into the correct bin"""
        b = int((y-self.low+self.binsize)/self.binsize)
        if b < 0: b = 0
        if b > self.nbins+1: b = self.nbins+1
        assert 0 <= b <=self.nbins+1,'Histogram.addIn: b out of range: %s'%b
        self[b][1]+=1

class Monitor(list):
    """ Monitored variables

    A Class for monitored variables, that is, variables that allow one
    to gather simple statistics.  A Monitor is a subclass of list and
    list operations can be performed on it. An object is established
    using m= Monitor(name = '..'). It can be given a
    unique name for use in debugging and in tracing and ylab and tlab
    strings for labelling graphs.
    """
    def __init__(self,name='',ylab='y',tlab='t'):
        list.__init__(self)
        self.startTime = 0.0
        self.name = name
        self.ylab = ylab
        self.tlab = tlab

    def observe(self,y,t=None):
        """record y and t"""
        if t is  None: t = now()
        self.append([t,y])

    def tally(self,y):
        """ deprecated: tally for backward compatibility"""
        self.observe(y,0)
                   
    def accum(self,y,t=None):
        """ deprecated:  accum for backward compatibility"""
        self.observe(y,t)

    def reset(self,t=None):
        """reset the sums and counts for the monitored variable """
        self[:]=[]
        if t is None: t = now()
        self.startTime = t
        
    def tseries(self):
        """ the series of measured times"""
        return list(zip(*self)[0])

    def yseries(self):
        """ the series of measured values"""
        return list(zip(*self)[1])

    def count(self):
        """ deprecated: the number of observations made """
        return self.__len__()
        
    def total(self):
        """ the sum of the y"""
        if self.__len__()==0:  return 0
        else:
            sum = 0.0
            for i in range(self.__len__()):
                sum += self[i][1]
            return sum # replace by sum() later

    def mean(self):
        """ the simple average of the monitored variable"""
        try: return 1.0*self.total()/self.__len__()
        except:  print 'SimPy: No observations  for mean'

    def var(self):
        """ the sample variance of the monitored variable """
        n = len(self)
        tot = self.total()
        ssq=0.0
        yy = self.yseries()
        for i in range(self.__len__()):
            ssq += self[i][1]**2 # replace by sum() eventually
        try: return (ssq - float(tot*tot)/n)/n
        except: print 'SimPy: No observations for sample variance'
        
    def timeAverage(self,t=None):
        """ the time-average of the monitored variable.

            If t is used it is assumed to be the current time,
            otherwise t =  now()
        """
        N = self.__len__()
        if N  == 0:
            print 'SimPy: No observations for timeAverage'
            return None

        if t == None: t = now()
        sum = 0.0
        tlast = self.startTime
        #print 'DEBUG: timave ',t,tlast
        ylast = 0.0
        for i in range(N):
            ti,yi = self[i]
            sum += ylast*(ti-tlast)
            tlast = ti
            ylast = yi
        sum += ylast*(t-tlast)
        T = t - self.startTime
        if T == 0:
             print 'SimPy: No elapsed time for timeAverage'
             return None
        #print 'DEBUG: timave ',sum,t,T
        return sum/float(T)

    def histogram(self,low=0.0,high=100.0,nbins=10):
        """ A histogram of the monitored y data values.
        """
        h = Histogram(name=self.name,low=low,high=high,nbins=nbins)
        ys = self.yseries()
        for y in ys: h.addIn(y)
        return h

################### end of Simulation module

################### start of test/demo programs

def askCancel():
    a=raw_input("[Time=%s] End run (e), Continue stepping (s), Run to end (r)"%now())
    if a=="e":
        stopSimulation()
    elif a=="s":
        return
    else:
        stopStepping()

def test_demo():
    class Aa(Process):
        sequIn=[]
        sequOut=[]
        def __init__(self,holdtime,name):
            Process.__init__(self,name)
            self.holdtime=holdtime

        def life(self,priority):
            for i in range(1):
                Aa.sequIn.append(self.name)
                print now(),rrr,"waitQ:",len(rrr.waitQ),"activeQ:",\
                      len(rrr.activeQ)
                print "waitQ: ",[(k.name,k._priority[rrr]) for k in rrr.waitQ]
                print "activeQ: ",[(k.name,k._priority[rrr]) \
                           for k in rrr.activeQ]
                assert rrr.n+len(rrr.activeQ)==rrr.capacity, \
                       "Inconsistent resource unit numbers"
                print now(),self.name,"requests 1 ", rrr.unitName
                yield request,self,rrr,priority
                print now(),self.name,"has 1 ",rrr.unitName
                print now(),rrr,"waitQ:",len(rrr.waitQ),"activeQ:",\
                      len(rrr.activeQ)
                print now(),rrr,"waitQ:",len(rrr.waitQ),"activeQ:",\
                      len(rrr.activeQ)
                assert rrr.n+len(rrr.activeQ)==rrr.capacity, \
                       "Inconsistent resource unit numbers"
                yield hold,self,self.holdtime
                print now(),self.name,"gives up 1",rrr.unitName
                yield release,self,rrr
                Aa.sequOut.append(self.name)
                print now(),self.name,"has released 1 ",rrr.unitName
                print "waitQ: ",[(k.name,k._priority[rrr]) for k in rrr.waitQ]
                print now(),rrr,"waitQ:",len(rrr.waitQ),"activeQ:",\
                      len(rrr.activeQ)
                assert rrr.n+len(rrr.activeQ)==rrr.capacity, \
                       "Inconsistent resource unit numbers"

    class Destroyer(Process):
        def __init__(self):
            Process.__init__(self)

        def destroy(self,whichProcesses):
            for i in whichProcesses:
                Process().cancel(i)
            yield hold,self,0

    class Observer(Process):
        def __init__(self):
            Process.__init__(self)

        def observe(self,step,processes,res):
            while now()<11:
                for i in processes:
                    print " %s %s: act:%s, pass:%s, term: %s,interr:%s, qu:%s"\
                          %(now(),i.name,i.active(),i.passive(),i.terminated()\
			    ,i.interrupted(),i.queuing(res))
                print
                yield hold,self,step

    class UserControl(Process):
        def letUserInteract(self,when):
            yield hold,self,when
            startStepping()
                    
    print "****First case == priority queue, resource service not preemptable"
    initialize()
    rrr=Resource(5,name="Parking",unitName="space(s)", qType=PriorityQ,
                 preemptable=0)
    procs=[]
    for i in range(10):
        z=Aa(holdtime=i,name="Car "+str(i))
        procs.append(z)
        activate(z,z.life(priority=i))
    o=Observer()
    activate(o,o.observe(1,procs,rrr))
    startStepping()
    a=simulate(until=10000,callback=askCancel)
    print "Input sequence: ",Aa.sequIn
    print "Output sequence: ",Aa.sequOut

    print "\n****Second case == priority queue, resource service preemptable"
    initialize()
    rrr=Resource(5,name="Parking",unitName="space(s)", qType=PriorityQ,
                 preemptable=1)
    procs=[]
    for i in range(10):
        z=Aa(holdtime=i,name="Car "+str(i))
        procs.append(z)
        activate(z,z.life(priority=i))
    o=Observer()
    activate(o,o.observe(1,procs,rrr))
    u=UserControl()
    activate(u,u.letUserInteract(4))
    Aa.sequIn=[]
    Aa.sequOut=[]
    a=simulate(askCancel,until=10000)
    print "Input sequence: ",Aa.sequIn
    print "Output sequence: ",Aa.sequOut   

def test_interrupt():
    class Bus(Process):
        def __init__(self,name):
            Process.__init__(self,name)

        def operate(self,repairduration=0):
            print now(),">> %s starts" %(self.name)
            tripleft = 1000
            while tripleft > 0:
                yield hold,self,tripleft
                if self.interrupted():
                    print "interrupted by %s" %self.interruptCause.name
                    print "%s: %s breaks down " %(now(),self.name)
                    tripleft=self.interruptLeft
                    self.interruptReset()
                    print "tripleft ",tripleft
                    reactivate(br,delay=repairduration) 
                    yield hold,self,repairduration
                    print now()," repaired"
                else:
                    break # no breakdown, ergo bus arrived
            print now(),"<< %s done" %(self.name)

    class Breakdown(Process):
        def __init__(self,myBus):
            Process.__init__(self,name="Breakdown "+myBus.name)
            self.bus=myBus

        def breakBus(self,interval):

            while True:
                yield hold,self,interval
                if self.bus.terminated(): break
                self.interrupt(self.bus)
                
    print"\n\n+++test_interrupt"
    initialize()
    b=Bus("Bus 1")
    activate(b,b.operate(repairduration=20))
    br=Breakdown(b)
    activate(br,br.breakBus(200))
    startStepping()
    simulate(until=4000)

    
if __name__ == "__main__":
    test_demo()
    test_interrupt()
    
