text: from time import sleep, time as systime
  from threading import Timer
  from heapq import heappush, heappop
  import inspect
  
  from .common import *
  
  ###############################################
  class Node:
      """
      Define a dummy node structure to keep track of arbitrary node attributes
      """
      pass
  
  ###############################################
  class GenericPlotter:
      """
      Define a generic plotter class from which actual plotters are derived
      """
      def __init__(self, params=None):
          if params is None: params = Parameters()
          self.params = params
          self.scene  = None
  
      ###################
      def setScene(self, scene):
          self.scene = scene
  
      #######################################################
      # The following methods are supposed to be overridden
      #######################################################
      def init(self,tx,ty): pass
      def setTime(self, time): pass
      def node(self,id,x,y): pass
      def nodemove(self,id,x,y): pass
      def nodehollow(self,id,flag): pass
      def nodedouble(self,id,flag): pass
      def nodecolor(self,id,r,g,b): pass
      def nodewidth(self,id,width): pass
      def nodelabel(self,id,label): pass
      def nodescale(self,id,scale): pass
      def addlink(self,src,dst,style): pass
      def dellink(self,src,dst,style): pass
      def clearlinks(self): pass
      def show(self): pass
      def circle(self,x,y,r,id,linestyle,fillstyle): pass
      def line(self,x1,y1,x2,y2,id,linestyle): pass
      def rect(self,x1,y1,x2,y2,id,linestyle,fillstyle): pass
      def delshape(self,id): pass
      def linestyle(self,id,**kwargs): pass
      def fillstyle(self,id,**kwargs): pass
      def textstyle(self,id,**kwargs): pass
  
  ###############################################
  def informPlotters(_func_):
      """
      Invoke the instance method of the same name inside each of the registered
      plotters 
      """
      def _wrap_(self, *args, **kwargs):
          _func_(self, *args, **kwargs)
          for plotter in self.plotters:
              plotter_func = getattr(plotter, _func_.__name__)
              plotter_func(*args, **kwargs)
  
      # code snippet for preserving function's name, doc, and signature
      # (from http://numericalrecipes.wordpress.com/2009/05/25/signature-preserving-function-decorators/)
      sig = list(inspect.getargspec(_func_))
      wrap_sig = list(inspect.getargspec(_wrap_))
      if not sig[2] :
          sig[2] = wrap_sig[2]
      src =  'def %s%s :\n' %(_func_.__name__, inspect.formatargspec(*sig))
      sig[3] = None # if not, all vars with defaults are set to default value
      src += '    return _wrap_%s\n' % (inspect.formatargspec(*sig))
      evaldict = {'_wrap_' : _wrap_}
      code = compile(src, '<string>', 'single')
      #exec code in evaldict  # Python2
      exec(code,evaldict)  # Python3
      ret = evaldict[_func_.__name__]
      ret.__doc__ = _func_.__doc__
      return ret
  
  ###############################################
  class Scene:
      """
      Define a scene that keeps track of every object in the model.  It also
      triggers registered plotters whenever there is a state change.
      """
  
      ###################
      def __init__(self,timescale=1,realtime=False):
          """
          Instantiate a Scene object.  The timescale parameter indicates how
          TopoVis should adjust time delay as specified with a scene scripting
          command.  When the realtime parameter is True, the timescale parameter
          is ignored and each scene scripting command will take effect
          immediately once invoked.
          """
          self.plotters = []
          self.time = 0.0
          self.initialized = False
          self.timescale = timescale
          self.realtime = realtime
          self.evq = []        # Event queue
          self.uniqueId = 0    # Counter for generating unique IDs
  
          self.dim = (0,0)     # Terrain dimension
          self.nodes = {}      # Nodes' information
          self.links = set()   # Set of links between nodes
          self.lineStyles = {} # List of defined line styles
          self.fillStyles = {} # List of defined fill styles
          self.textStyles = {} # List of defined text styles
  
          if realtime:
              self.startTime = systime()
  
      ###################
      def setTiming(self, scale=1, realtime=False):
          self.timescale = scale
          self.realtime = realtime
          if realtime:
              self.startTime = systime() - self.time
  
      ###################
      def _getUniqueId(self):
          """
          Create and return a unique integer everytime it gets called
          """
          self.uniqueId = self.uniqueId + 1
          return "_" + str(self.uniqueId)
  
      ###################
      def addPlotter(self, plotter):
          """
          Add a plotter which accepts and visualizes scene scripts
          """
          plotter.setScene(self)
          self.plotters.append(plotter)
  
      ###################
      def removePlotter(self, plotter):
          """
          Remove the specified plotter from keeping track of scene scripts
          """
          self.plotters.remove(plotter)
  
      ###################
      def execute(self, time, cmd, *args, **kwargs):
          """
          Execute the scene scripting command, cmd, with specified
          variable-length and keyword arguments
          """
          if self.realtime:
              self.setTime(systime()-self.startTime)
          else:
              # examine the event queue and execute everything prior to
              # the 'current time'
              while len(self.evq) > 0 and self.evq[0][0] < time:
                  (t,proc,a,kw) = heappop(self.evq)
                  self.setTime(t)
                  proc(*a,**kw)
              self.setTime(time)
          if type(cmd) is str:
              #exec 'self.' + cmd    # Python2
              exec('self.' + cmd)  # Python3
          else:
              cmd(*args, **kwargs)
  
      ###################
      def executeAfter(self, delay, cmd, *args, **kwargs):
          """
          (Use internally) Wait until the specified delay, then executed the given
          command
          """
          if delay is INF:
              # no need to scedule any execution at time infinity
              return
          if self.realtime:
              def execfn():
                  self.execute(0, cmd, *args, **kwargs)
              Timer(delay, execfn).start()
          else:
              heappush(self.evq, (self.time+delay, cmd, args, kwargs))
  
      ###################
      def setTime(self,time):
          """
          Set the current time being tracked by TopoVis to the specified time.
          A corresponding amount of delay will be applied unless TopoVis scene
          was instantiated to run in real-time.  This method also informs all
          registered plotters about the updated time so that a label or window
          title can be updated accordingly.
          """
          if time < self.time:
              raise Exception(
                      'Time cannot flow backward: current = %.3f, new = %.3f'
                      % (self.time, time)
                      )
          if not self.realtime:
              sleep((time-self.time)*self.timescale)
              self.time = time
          for plotter in self.plotters:
              plotter.setTime(time)
  
      ###################
      @informPlotters
      def init(self,tx,ty):
          """
          (Scene scripting command) Intialize the scene.  This command should
          be called before any other scripting commands.
          """
          if (self.initialized):
              raise Exception('init() has already been called')
          self.dim = (tx,ty)
          self.initialized = True
  
  
      #########################################################################
      # All methods below define Scene Scripting Commands.  These commands also
      # inform all registered plotters to update visualization of the current
      # scene
      #########################################################################
  
      ###################
      @informPlotters
      def node(self,id,x,y):
          """
          (Scene scripting command)
          Define a node with the specified ID and location (x,y)
          """
          self.nodes[id]        = Node()
          self.nodes[id].id     = id
          self.nodes[id].pos    = (x,y)
          self.nodes[id].scale  = 1.0
          self.nodes[id].label  = str(id)
          self.nodes[id].hollow = DEFAULT
          self.nodes[id].double = DEFAULT
          self.nodes[id].width  = DEFAULT
          self.nodes[id].color  = DEFAULT
  
      ###################
      @informPlotters
      def nodemove(self,id,x,y):
          """
          (Scene scripting command)
          Move a node whose ID is id to a new location (x,y)
          """
          self.nodes[id].pos = (x,y)
  
      ###################
      @informPlotters
      def nodecolor(self,id,r,g,b):
          """
          (Scene scripting command)
          Set color (in rgb format, 0 <= r,g,b <= 1) of the node, specified by
          id
          """
          self.nodes[id].color = (r,g,b)
  
      ###################
      @informPlotters
      def nodelabel(self,id,label):
          """
          (Scene scripting command)
          Set string label for the node, specified by id
          """
          self.nodes[id].label = label
  
      ###################
      @informPlotters
      def nodescale(self,id,scale):
          """
          (Scene scripting command)
          Set node scaling factor.  By default, nodes are visualized with
          scale=1
          """
          self.nodes[id].scale = scale
  
      ###################
      @informPlotters
      def nodehollow(self,id,flag):
          """
          (Scene scripting command)
          Set node's hollow display
          """
          self.nodes[id].hollow = flag
  
      ###################
      @informPlotters
      def nodedouble(self,id,flag):
          """
          (Scene scripting command)
          Set node's double-outline display
          """
          self.nodes[id].double = flag
  
      ###################
      @informPlotters
      def nodewidth(self,id,width):
          """
          (Scene scripting command)
          Set node's outline width
          """
          self.nodes[id].width = width
  
      ###################
      @informPlotters
      def addlink(self,src,dst,style):
          """
          (Scene scripting command)
          Add a link with the specified style, which is an instance of
          LineStyle, between a pair of nodes
          """
          self.links.add((src,dst,style))
  
      ###################
      @informPlotters
      def dellink(self,src,dst,style):
          """
          (Scene scripting command)
          Remove a link with the specified style from a pair of nodes
          """
          self.links.remove((src,dst,style))
  
      ###################
      @informPlotters
      def clearlinks(self):
          """
          (Scene scripting command)
          Delete all links previously added
          """
          self.links.clear()
  
      ###################
      @informPlotters
      def show(self):
          """
          (Scene scripting command)
          Force update of topology view
          """
          pass
  
      ###################
      def circle(self,x,y,r,id=None,line=LineStyle(),fill=FillStyle(),delay=INF):
          """
          (Scene scripting command)
          Draw/update a circle centered at (x,y) with radius r.  line and fill
          are applied to the drawn object.  The object will remain on the scene
          for the specified delay.
          """
          # resolve id and inform plotters manually
          # XXX will try to use decorator later on
          if id == None:
              id = self._getUniqueId()
          if not isinstance(line,LineStyle):
              line = self.lineStyles[line]
          if not isinstance(fill,FillStyle):
              fill = self.fillStyles[fill]
          for plotter in self.plotters:
              plotter.circle(x, y, r, id, line, fill)
          if delay != INF:
              self.executeAfter(delay, self.delshape, id)
          else:
              return id
  
      ###################
      def line(self,x1,y1,x2,y2,id=None,line=LineStyle(),delay=INF):
          """
          (Scene scripting command)
          Draw/update a line from (x1,y1) to (x2,y2).  line and fill
          are applied to the drawn object.  The object will remain on the scene
          for the specified delay.
  
          """
          # resolve id and inform plotters manually
          # XXX will try to use decorator later on
          if id == None:
              id = self._getUniqueId()
          if not isinstance(line,LineStyle):
              line = self.lineStyles[line]
          for plotter in self.plotters:
              plotter.line(x1, y1, x2, y2, id, line)
          if delay != INF:
              self.executeAfter(delay, self.delshape, id)
          else:
              return id
  
      ###################
      def rect(self,x1,y1,x2,y2,id=None,line=LineStyle(),fill=FillStyle(),delay=INF):
          """
          (Scene scripting command)
          Draw/update a rectangle from (x1,y1) to (x2,y2).  line and fill
          are applied to the drawn object.  The object will remain on the scene
          for the specified delay.
  
          """
          # resolve id and inform plotters manually
          # XXX will try to use decorator later on
          if id == None:
              id = self._getUniqueId()
          if not isinstance(line,LineStyle):
              line = self.lineStyles[line]
          if not isinstance(fill,FillStyle):
              fill = self.fillStyles[fill]
          for plotter in self.plotters:
              plotter.rect(x1, y1, x2, y2, id, line, fill)
          if delay != INF:
              self.executeAfter(delay, self.delshape, id)
          else:
              return id
  
      ###################
      @informPlotters
      def delshape(self,id):
          """
          (Scene scripting command)
          Delete an animated shape (e.g., line, circle) previously created with ID id
          """
          pass
  
      ###################
      @informPlotters
      def linestyle(self,id,**kwargs):
          """
          (Scene scripting command)
          Define or redefine a line style.
          """
          self.lineStyles[id] = LineStyle(**kwargs)
  
      ###################
      @informPlotters
      def fillstyle(self,id,**kwargs):
          """
          (Scene scripting command)
          Define or redefine a fill style
          """
          self.fillStyles[id] = FillStyle(**kwargs)
  
      ###################
      @informPlotters
      def textstyle(self,id,**kwargs):
          """
          (Scene scripting command)
          Define or redefine a text style
          """
          self.textStyles[id] = FillStyle(**kwargs)
  
  