#!/usr/bin/env python
"""SimulationTrace 1.5.1
__version__ = '$Revision: 1.1.1.3.4.2 $ $Date: 2005/01/30 11:41:26 $ kgm'
LICENSE:
Copyright (C) 2002, 2005  Klaus G. Muller, Tony Vignaux
mailto: kgmuller@xs4all.nl and Tony.Vignaux@vuw.ac.nz

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
END OF LICENSE

Implements a trace capability for SimPy 1.3.
Based on generators (Python 2.2 and later)



**Change history:**
    9 May 03: SimulationTrace module based on SimPy 1.3
    
    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.

    November 03: 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.
                 
    19 May 2004: Added erroneously omitted Histogram class.
    
    5 Sep 2004: Added SimEvents synchronization constructs
    
    17 Sep 2004: Added waituntil synchronization construct
                     
    01 Dec 2004: SimPy version 1.5
                 Changes in this module: Repaired SimEvents bug re proc.eventsFired

    12 Jan 2005: SimPy version 1.5.1
                 Changes in this module: Monitor objects now have a default name
                                         'a_Monitor'

"""
from __future__ import generators
from SimPy.Lister import *
import bisect
import types
import sys
__TESTING=False
__version__="1.5.1 February 2005"
if __TESTING: print "SimPy.SimulationTrace %s" %__version__
hold=1234
passivate=5678
request=135
release=246
waitevent=4321
queueevent=8765
waituntil=999

_endtime=0
_t=0
_e=None
_stop=True
_wustep=False #controls per event stepping for waituntil construct; not for user API
True=1
False=0
condQ=[]

def initialize():
    global _e,_t,_stop,trace
    _e=__Evlist()
    _t=0
    _stop=False

def now():
    return _t

def stopSimulation():
    """Application function to stop simulation run"""
    global _stop
    _stop=True
    
def _startWUStepping():
    """Application function to start stepping through simulation for waituntil construct."""
    global _wustep
    _wustep=True

def _stopWUStepping():
    """Application function to stop stepping through simulation."""
    global _wustep
    _wustep=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     #next activation time
        self._remainService=0
        self._preempted=0
        self._priority={}
        self._terminated=0      
        self._inInterrupt= False    
        self.eventsFired=[] #events waited/queued for occurred

    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)
        trace.recordCancel(self,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 or passive process or currently interrupted process
        if victim.active():
            save=trace._comment
            trace._comment=None	
            victim.interruptCause=self  # self causes interrupt
            left=victim._nextTime-_t
            victim.interruptLeft=left   # time left in current 'hold'
            victim._inInterrupt=True    
            reactivate(victim)
            trace._comment=save
            trace.recordInterrupt(self,victim)
            return left
        else: #victim not active -- can't interrupt
            return None

    def interruptReset(self):
        """
        Application function for an interrupt victim to get out of
        'interrupted' state.
        """
        self._inInterrupt= False

def allEventNotices():
    """Returns string with eventlist as list of tuples (eventtime,action)"""
    return `[(_e.events[i][0],[inst.who.name for inst in _e.events[1]]) \
                 for i in _e.events]`        
                       
class __Evlist:
    """Defines event list and operations on it"""
    def __init__(self):
        #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) 
        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)
    trace.recordActivate(object,zeit,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:
        a=Process("SimPysystem")        
        a.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)
        trace.recordReactivate(object,zeit,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
        
    def _request(self,arg):
        """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 re-queued process
                z._nextTime=None 
                # 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 queuing process
                object._nextTime=None 
        else: # treat non-preemption case       
            if self.n == 0:
                self.waitQ.enter(object)
                # passivate queuing process
                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):
        """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)


class SimEvent(Lister):
    """Supports one-shot signalling between processes. All processes waiting for an event to occur
    get activated when its occurrence is signalled. From the processes queuing for an event, only
    the first gets activated.
    """
    def __init__(self,name="a_SimEvent"):
        self.name=name
        self.waits=[]
        self.queues=[]
        self.occurred=False
        self.signalparam=None

    def signal(self,param=None):
        """Produces a signal to self;
        Fires this event (makes it occur).
        Reactivates ALL processes waiting for this event. (Cleanup waits lists
        of other events if wait was for an event-group (OR).)
        Reactivates the first process for which all events they are queuing for
        have fired. (Cleanup queues of other events if wait was for an event-group (OR).)
        """
        self.signalparam=param
        
        trace.recordSignal(self)
        if not self.waits and not self.queues:
            self.occurred=True
        else:
            #reactivate all waiting processes
            for p in self.waits:
                p[0].eventsFired.append(self)
                reactivate(p[0])
                #delete waits entries for this process in other events
                for ev in p[1]:
                    if ev!=self:
                        if ev.occurred:
                            p[0].eventsFired.append(ev)
                        for iev in ev.waits:
                            if iev[0]==p[0]:
                                ev.waits.remove(iev)
                                break
            self.waits=[]
            if self.queues:
                proc=self.queues.pop(0)[0]
                proc.eventsFired.append(self)
                reactivate(proc)

    def _wait(self,par):
        """Consumes a signal if it has occurred, otherwise process 'proc'
        waits for this event.
        """
        proc=par[0][1] #the process issuing the yield waitevent command
        proc.eventsFired=[]
        if not self.occurred:
            self.waits.append([proc,[self]])
            proc._nextTime=None #passivate calling process
        else:
            proc.eventsFired.append(self)
            self.occurred=False
            _e._post(_Action(who=proc,generator=proc._nextpoint),
                at=_t,prior=1)

    def _waitOR(self,par):
        """Handles waiting for an OR of events in a tuple/list.
        """
        proc=par[0][1]
        evlist=par[0][2]
        proc.eventsFired=[]
        anyoccur=False
        for ev in evlist:
            if ev.occurred:
                anyoccur=True
                proc.eventsFired.append(ev)
                ev.occurred=False
        if anyoccur: #at least one event has fired; continue process
            _e._post(_Action(who=proc,generator=proc._nextpoint),
                at=_t,prior=1)      
       
        else: #no event in list has fired, enter process in all 'waits' lists
            proc.eventsFired=[]
            proc._nextTime=None #passivate calling process
            for ev in evlist:
                ev.waits.append([proc,evlist])

    def _queue(self,par):
        """Consumes a signal if it has occurred, otherwise process 'proc'
        queues for this event.
        """
        proc=par[0][1] #the process issuing the yield queueevent command
        proc.eventsFired=[]
        if not self.occurred:
            self.queues.append([proc,[self]])
            proc._nextTime=None #passivate calling process
        else:
            proc.eventsFired.append(self)
            self.occurred=False
            _e._post(_Action(who=proc,generator=proc._nextpoint),
                at=_t,prior=1)

    def _queueOR(self,par):
        """Handles queueing for an OR of events in a tuple/list.
        """
        proc=par[0][1]
        evlist=par[0][2]
        proc.eventsFired=[]
        anyoccur=False
        for ev in evlist:
            if ev.occurred:
                anyoccur=True
                proc.eventsFired.append(ev)
                ev.occurred=False
        if anyoccur: #at least one event has fired; continue process
            _e._post(_Action(who=proc,generator=proc._nextpoint),
                at=_t,prior=1)      
       
        else: #no event in list has fired, enter process in all 'waits' lists
            proc.eventsFired=[]
            proc._nextTime=None #passivate calling process
            for ev in evlist:
                ev.queues.append([proc,evlist])
            
## begin waituntil functionality
def _test():
    """
    Gets called by simulate after every event, as long as there are processes
    waiting in condQ for a condition to be satisfied.
    Tests the conditions for all waiting processes. Where condition satisfied,
    reactivates that process immediately and removes it from queue.
    """    
    global condQ
    rList=[]
    for el in condQ:
        if el.cond():
            rList.append(el)
            reactivate(el)
    for i in rList:
        condQ.remove(i)
    
    if not condQ:
        _stopWUStepping()
    
def _waitUntilFunc(proc,cond):
    global condQ
    """
    Puts a process 'proc' waiting for a condition into a waiting queue.
    'cond' is a predicate function which returns True if the condition is
    satisfied.
    """    
    if not cond():
        condQ.append(proc)
        proc.cond=cond
        _startWUStepping()         #signal 'simulate' that a process is waiting
        # passivate calling process
        proc._nextTime=None
    else:
        #schedule continuation of calling process
        _e._post(_Action(who=proc,generator=proc._nextpoint),
                at=_t,prior=1)


##end waituntil functionality
        
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 waitevfunc(a):
    #if waiting for one event only (not a tuple or list)
    evtpar=a[0][2]
    if isinstance(evtpar,SimEvent):
        a[0][2]._wait(a)
    # else, if waiting for an OR of events (list/tuple):
    else: #it should be a list/tuple of events
        # call _waitOR for first event
        evtpar[0]._waitOR(a)

def queueevfunc(a):
    #if queueing for one event only (not a tuple or list)
    evtpar=a[0][2]
    if isinstance(evtpar,SimEvent):
        a[0][2]._queue(a)
    #else, if queueing for an OR of events (list/tuple):
    else: #it should be a list/tuple of events
        # call _queueOR for first event
        evtpar[0]._queueOR(a)
    
def waituntilfunc(par):
    _waitUntilFunc(par[0][1],par[0][2])
    
    
def simulate(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>    

    yield waitevent,self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]:
    	wait for one or more of several events
    	

    yield queueevent,self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]:
    	queue for one or more of several events
    
    yield waituntil,self,cond : wait for arbitrary condition    

    Event notices get posted in event-list by scheduler after "yield" or by 
    "activate"/"reactivate" functions.
    
    """
    global _endtime,_e,_stop,_t,_wustep
    _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,waitevent:waitevfunc,queueevent:queueevfunc,
              waituntil:waituntilfunc}
    while not _stop and _t<=_endtime:
        try:
            a=_e._nextev()        
            if not a[0]==None:
                command = a[0][0]
                dispatch[command](a)
                trace.recordEvent(command,a)
            else:
                if not a==(None,): #not at endtime!
                    trace.tterminated(a[1])
        except Simerror, error:
            message="SimPy: "+error.value
            _stop = True 
        if _wustep:
            _test()
    _stopWUStepping()
    _e=None
    if not(trace.outfile is sys.stdout):
        trace.outfile.close()
    return message
    
class Trace(Lister):
    commands={1234:"hold",5678:"passivate",135:"request",246:"release",
              4321:"waitevent",8765:"queueevent",999:"waituntil"}

    def __init__(self,start=0,end=10000000000L,toTrace=\
                 ["hold","activate","cancel","reactivate","passivate","request",
                  "release","interrupt","terminated","waitevent","queueevent","signal","waituntil"],outfile=sys.stdout):
        #global tracego
        Trace.commandsproc={1234:Trace.thold,5678:Trace.tpassivate,135:Trace.trequest,246:Trace.trelease,
                            4321:Trace.twaitevent,8765:Trace.tqueueevent,999:Trace.twaituntil}
        self.start=start
        self.end=end
        self.toTrace=toTrace
        self.tracego=True
        self.outfile=outfile
        self._comment=None

    def treset(self):
        Trace.commandsproc={1234:Trace.thold,5678:Trace.tpassivate,135:Trace.trequest,246:Trace.trelease,
                            4321:Trace.twaitevent,8765:Trace.tqueueevent,999:Trace.twaituntil}
        self.start=0
        self.end=10000000000L
        self.toTrace=["hold","activate","cancel","reactivate","passivate","request",
                  "release","interrupt","terminated","waitevent","queueevent","signal","waituntil"]
        self.tracego=True
        self.outfile=sys.stdout
        self._comment=None

    def tchange(self,**kmvar):
        for v in kmvar.keys():
            if v=="start":
                self.start=kmvar[v]
            elif v=="end":
                self.end=kmvar[v]
            elif v=="toTrace":
                self.toTrace=kmvar[v]
            elif v=="outfile":
                self.outfile=kmvar[v]                
                
    def tstart(self):
        self.tracego=True

    def tstop(self):
        self.tracego=False

    def ifTrace(self,cond):
        if self.tracego and (self.start <= now() <= self.end) and cond:
            return True

    def thold(self,par):
        try:
            return "delay: %s"%par[0][2]
        except:
            return 0
    thold=classmethod(thold)
    
    def trequest(self,par):
        res=par[0][2]
        if len(par[0])==4:
            priority=" priority: "+str(par[0][3])
        else:
            priority=" priority: default"
        wQ=[x.name for x in res.waitQ]
        aQ=[x.name for x in res.activeQ]
        return "<%s> %s \n. . .waitQ: %s \n. . .activeQ: %s"%(res.name,priority,wQ,aQ)
    trequest=classmethod(trequest)
    
    def trelease(self,par):
        res=par[0][2]
        wQ=[x.name for x in res.waitQ]
        aQ=[x.name for x in res.activeQ]
        return "<%s> \n. . .waitQ: %s \n. . .activeQ: %s"%(res.name,wQ,aQ)
    trelease=classmethod(trelease)
    
    def tpassivate(self,par):
        return ""
    tpassivate=classmethod(tpassivate)

    def tactivate(self,par):
        pass
    tactivate=classmethod(tactivate)
    
    def twaitevent(self,par):
        evt=par[0][2]
        if type(evt)==list or type(evt)==tuple:
            enames=[x.name for x in evt]
            return "waits for events <%s>"%enames
        else:
            return "waits for event <%s>"%evt.name
    twaitevent=classmethod(twaitevent)
    
    def tqueueevent(self,par):
        evt=par[0][2]
        if type(evt)==list or type(evt)==tuple:
            enames=[x.name for x in evt]
            return "queues for events <%s>"%enames          
        else:
            return "queues for event <%s>"%evt.name
    tqueueevent=classmethod(tqueueevent)
    
    def tsignal(self,evt):
        wQ=[x.name for x in evt.waits]
        qQ=[x.name for x in evt.queues]
        return "<%s> \n. . .  occurred: %s\n. . .  waiting: %s\n. . .  queueing: %s"\
                %(evt.name,evt.occurred,wQ,qQ)
        pass
    tsignal=classmethod(tsignal)
    
    def twaituntil(self,par):
        condition=par[0][2]
        return "for condition <%s>"%condition.func_name
    twaituntil=classmethod(twaituntil)
    
    def recordEvent(self,command,whole):
        if self.ifTrace(Trace.commands[command] in self.toTrace):
            print >>self.outfile, now(),Trace.commands[command],"<"+whole[0][1].name+">",\
                  Trace.commandsproc[command](whole)
            if self._comment:
                print >>self.outfile,"----",self._comment
        self._comment=None

    def recordInterrupt(self,who,victim):
        if self.ifTrace("interrupt" in self.toTrace):
            print >>self.outfile,"%s interrupt by: <%s> of: <%s>"%(now(),who.name,victim.name)
            if self._comment:
                print >>self.outfile,"----",self._comment
        self._comment=None
                
    def recordCancel(self,who,victim):
        if self.ifTrace("cancel" in self.toTrace):
            print >>self.outfile,"%s cancel by: <%s> of: <%s>"%(now(),who.name,victim.name)
            if self._comment:
                print >>self.outfile,"----",self._comment
        self._comment=None
        
    def recordActivate(self,who,when,prior):
        if self.ifTrace("activate" in self.toTrace):
            print >>self.outfile,"%s activate <%s> at time: %s prior: %s"%(now(),who.name,\
                                                                       when, prior)
            if self._comment:
                print >>self.outfile,"----",self._comment
        self._comment=None                                                                       

    def recordReactivate(self,who,when,prior):
        if self.ifTrace("reactivate" in self.toTrace):
            print >>self.outfile,"%s reactivate <%s> time: %s prior: %s"%(now(),who.name,\
                                                                        when, prior)
            if self._comment:
                print >>self.outfile,"----",self._comment
        self._comment=None
        
    def recordSignal(self,evt):
        if self.ifTrace("signal" in self.toTrace):
            print >>self.outfile,"%s event <%s> is signalled"%(now(),evt.name)
            if self._comment:
                print >>self.outfile,"----",self._comment
        self._comment=None

    def tterminated(self,who):
        if self.ifTrace("terminated" in self.toTrace):
            print >>self.outfile,"%s <%s> terminated"%(now(),who.name)
            if self._comment:
                print >>self.outfile,"----",self._comment
        self._comment=None        

    def ttext(self,par):
        self._comment=par
        
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='a_Monitor',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

############# Test/demo functions #############
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.name,"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.name,"waitQ:",len(rrr.waitQ),"activeQ:",\
                      len(rrr.activeQ)
                print now(),rrr.name,"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.name,"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 process: %s: active:%s, passive:%s, terminated: %s,interrupted:%s, queuing:%s"\
                          %(now(),i.name,i.active(),i.passive(),i.terminated(),i.interrupted(),i.queuing(res))
                print
                yield hold,self,step
            
    print"\n+++test_demo output"
    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))
    a=simulate(until=10000)
    print a
    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))
    Aa.sequIn=[]
    Aa.sequOut=[]
    a=simulate(until=10000)
    print a
    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) # breakdowns only during operation
                    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))
    print simulate(until=4000)

def testSimEvents():
    class Waiter(Process):
        def waiting(self,theSignal):
            while True:
                yield waitevent,self,theSignal
                print "%s: process '%s' continued after waiting for %s"%(now(),self.name,theSignal.name)
                yield queueevent,self,theSignal
                print "%s: process '%s' continued after queueing for %s"%(now(),self.name,theSignal.name)
                
    class ORWaiter(Process):
        def waiting(self,signals):
            while True:
                yield waitevent,self,signals
                print now(),"one of %s signals occurred"%[x.name for x in signals]
                print "\t%s (fired/param)"%[(x.name,x.signalparam) for x in self.eventsFired]
                yield hold,self,1
                
    class Caller(Process):
        def calling(self):
            while True:
                signal1.signal("wake up!")
                print "%s: signal 1 has occurred"%now()
                yield hold,self,10
                signal2.signal("and again")
                signal2.signal("sig 2 again")
                print "%s: signal1, signal2 have occurred"%now()
                yield hold,self,10
    print"\n+++testSimEvents output"
    initialize()
    signal1=SimEvent("signal 1")
    signal2=SimEvent("signal 2")
    signal1.signal("startup1")
    signal2.signal("startup2")
    w1=Waiter("waiting for signal 1")
    activate(w1,w1.waiting(signal1))
    w2=Waiter("waiting for signal 2")
    activate(w2,w2.waiting(signal2))
    w3=Waiter("also waiting for signal 2")
    activate(w3,w3.waiting(signal2))
    w4=ORWaiter("waiting for either signal 1 or signal 2")
    activate(w4,w4.waiting([signal1,signal2]),prior=True)
    c=Caller("Caller")
    activate(c,c.calling())
    print simulate(until=100)
    
def testwaituntil():
    """
    Demo of waitUntil capability.

    Scenario:
    Three workers require sets of tools to do their jobs. Tools are shared, scarce
    resources for which they compete.
    """
    import random

    class Worker(Process):
        def __init__(self,name,heNeeds=[]):
            Process.__init__(self,name)
            self.heNeeds=heNeeds
        def work(self):

            def workerNeeds():
                for item in self.heNeeds:
                    if item.n==0:
                        return False
                return True
                
            while now()<8*60:
                yield waituntil,self,workerNeeds
                for item in self.heNeeds:
                    yield request,self,item
                print "%s %s has %s and starts job" %(now(),self.name,
                    [x.name for x in self.heNeeds])
                yield hold,self,random.uniform(10,30)
                for item in self.heNeeds:
                    yield release,self,item
                yield hold,self,2 #rest
                
    print "\n+++ nwaituntil demo output"
    initialize()
    brush=Resource(capacity=1,name="brush")
    ladder=Resource(capacity=2,name="ladder")
    hammer=Resource(capacity=1,name="hammer")
    saw=Resource(capacity=1,name="saw")
    painter=Worker("painter",[brush,ladder])
    activate(painter,painter.work())
    roofer=Worker("roofer",[hammer,ladder,ladder])
    activate(roofer,roofer.work())
    treeguy=Worker("treeguy",[saw,ladder])
    activate(treeguy,treeguy.work())
    for who in (painter,roofer,treeguy):
        print "%s needs %s for his job" %(who.name,[x.name for x in who.heNeeds])
    print
    print simulate(until=9*60)

if __name__ == "__main__":
    print "SimPy.SimulationTrace %s" %__version__
    trace=Trace()
    trace=Trace(end=4000)
    test_demo()
    trace=Trace(end=2000)
    test_interrupt()
    testSimEvents()
    testwaituntil()
else:
    trace=Trace()    
