import Queue
import thread

import chakana.event
import chakana.threads
from chakana.debug import *

class Monitor(chakana.threads.ManagedThread):
  """Base class for causal path monitors.  Pass the threadManager to the
  constructor.  Override the doRun method or pass a routine argument.  In
  the latter case, the routine will be called with the Monitor object as
  argument plus any extra arguments and keyword arguments specified."""
  
  def __init__(self, shepherd, routine = None, * args, ** kwArgs):
    chakana.threads.ManagedThread.__init__(self, shepherd.threadManager())
    self._shepherd = shepherd
    self._routine = routine
    self._args = args
    self._kwArgs = kwArgs
    self._ready = 0
    self._discarded = 0
    resourceAllocated = 0
    while resourceAllocated == 0:
      try:
        self._isWaitingMonitor = threading.Event()
        resourceAllocated = 1
      except thread.error:
        resourceAllocated = 0
        shepherd.threadManager().registerPIDerror()
    debug(Debug, "Monitor created")
    self._shepherd.registerMonitor(self)

  def startMonitor(self):
    self.start()
    debug(Debug, "New monitor thread started: " + repr(self))

  def discard(self):
    self._discarded = 1
    self._ready = 0
    debug(Debug, "Discarding monitor:\n" + repr(self))

    # Wake up thread with empty results
    if hasattr(self, "_resultQueue"):
      self._resultQueue.put(None)

  def waitMonitor(self, parentEventpoint = None):
    debug(Debug, "Waiting for monitor to finish: " + repr(self))

    # While monitor is registered, check for continuations
    while self in self.shepherd()._monitors:
      resourceAllocated = 0
      while resourceAllocated == 0:
        try:
          self._isWaitingMonitor.wait()
          resourceAllocated = 1
        except thread.error:
          resourceAllocated = 0
          self.shepherd().threadManager().registerPIDerror()

      self._isWaitingMonitor.clear()
      self._ready = 1

      # If all monitors ready, poll continuation
      allMonitorsReady = 1
      for monitor in self.shepherd()._monitors:
        if not monitor._ready:
          allMonitorsReady = 0
      if allMonitorsReady:
        if self in self.shepherd()._monitors:
          self.shepherd().pollContinuation()
      else:
        # A monitor may be waiting for us, tell parent we are ready
        if parentEventpoint is not None:
          parentEventpoint._isWaitingEvent.set()
    self.join()

  def doRun(self):
    debug(Debug, "Running monitor")
    try:
      if (not self._discarded):
        self.runMonitor()
    except chakana.error.Discarded:
      pass
    self.shepherd().unregisterMonitor(self)
    self._isWaitingMonitor.set()
    debug(Debug, "Monitor exiting")

  def runMonitor(self):
      self._routine(self, * self._args, ** self._kwArgs)

  def waitFor(self, eventList):
    """Waits until one of the Events given in 'eventList' have triggered and
    returns that Event."""
    if isinstance(eventList, chakana.event.Eventpoint):
      eventList = [eventList]
    debug(MinorEvent, "Waiting for events to trigger. Event list: " + 
          str(eventList))
    assert eventList != []

    # Clean up eventpoints and abort monitor if discarded
    if self._discarded:
      for ev in eventList:
        ev.discard()
      raise chakana.error.Discarded()

    self.shepherd().registerWaitingMonitor(self)

    resourceAllocated = 0
    while resourceAllocated == 0:
      try:
        self._resultQueue = Queue.Queue()
        resourceAllocated = 1
      except thread.error:
        resourceAllocated = 0
        self.shepherd().threadManager().registerPIDerror()

    for ev in eventList:
      # Start thread and wait until eventpoint is waiting
      ev.await(self._resultQueue)
      
      resourceAllocated = 0
      while resourceAllocated == 0:
        try:
          ev._isWaitingEvent.wait()
          resourceAllocated = 1
        except thread.error:
          resourceAllocated = 0
          self.shepherd().threadManager().registerPIDerror()
      
      ev._isWaitingEvent.clear()

    debug(Event, "Waiting for events: " + repr(eventList))

    assert(not self._discarded)

    self._isWaitingMonitor.set()
    resourceAllocated = 0
    while resourceAllocated == 0:
      try:
        triggeredEvent = self._resultQueue.get()
        resourceAllocated = 1
      except thread.error:
        resourceAllocated = 0
        self.shepherd().threadManager().registerPIDerror()

    self._ready = 0
    self.shepherd().unregisterWaitingMonitor(self)
    debug(Event, "Eventpoint was triggered: " + repr(eventList))
    for ev in eventList:
      if not (ev is triggeredEvent):
        ev.discard()
    
    return triggeredEvent

  def shepherd(self):
    return self._shepherd

  def readVariable(self, ** kwArgs):
    return self.shepherd().readVariable(** kwArgs)

  def readInt(self, ** kwArgs):
    return self.shepherd().readInt(** kwArgs)

  def readMemory(self, ** kwArgs):
    return self.shepherd().readMemory(** kwArgs)

class TimeoutWrapperMonitor(Monitor):
  def __init__(self, shepherd, routine, eventpoint = None, timeout = 1000):
    chakana.monitor.Monitor.__init__(self, shepherd)
    self._eventpoint = eventpoint
    self._timeout = timeout
    self._triggeredEventpoint = None

  def runMonitor(self):
    timeEventpoint = chakana.event.TimeEventpoint(self.shepherd(), self._timeout)
    self._triggeredEventpoint = self.waitFor([timeEventpoint, self._eventpoint])

  def getTriggeredEventpoint(self):
    return self._triggeredEventpoint

  def timedOut(self):
    return self.getTriggeredEventpoint() != self._eventpoint