import sys
import os.path

from seesaw.event import Event
from seesaw.item import Item
from seesaw.config import realize

from tornado import ioloop

class Runner(object):
  def __init__(self, stop_file=None, concurrent_items=1, max_items=None):
    self.pipeline = None
    self.concurrent_items = concurrent_items
    self.max_items = max_items

    self.item_count = 0
    self.active_items = set()
    self.stop_flag = False
    self.stop_file = stop_file
    self.initial_stop_file_mtime = self.stop_file_mtime()

    self.on_status = Event()
    self.on_create_item = Event()
    self.on_pipeline_start_item = Event()
    self.on_pipeline_finish_item = Event()
    self.on_finish = Event()

    if stop_file:
      ioloop.PeriodicCallback(self.check_stop_file, 5000).start()

  def set_current_pipeline(self, pipeline):
    old_pipeline = self.pipeline

    if pipeline:
      pipeline.on_start_item += self._item_starting
      pipeline.on_finish_item += self._item_finished

    self.pipeline = pipeline

    if old_pipeline:
      # stop any cancellable items in the previous pipeline
      old_pipeline.cancel_items()

  def is_active(self):
    return len(self.active_items) > 0

  def start(self):
    self.add_items()

  def stop_gracefully(self):
    print "Stopping when current tasks are completed..."
    self.stop_flag = True
    self.pipeline.cancel_items()
    self.initial_stop_file_mtime = self.stop_file_mtime()
    self.on_status(self, "stopping")

  def keep_running(self):
    print "Keep running..."
    self.stop_flag = False
    self.initial_stop_file_mtime = self.stop_file_mtime()
    self.on_status(self, "running")

  def check_stop_file(self):
    if self.stop_file_changed():
      self.stop_gracefully()

  def should_stop(self):
    return self.stop_flag or self.stop_file_changed()

  def stop_file_changed(self):
    current_stop_file_mtime = self.stop_file_mtime()
    if current_stop_file_mtime:
      return self.initial_stop_file_mtime == None or self.initial_stop_file_mtime < current_stop_file_mtime
    else:
      return False

  def stop_file_mtime(self):
    if self.stop_file and os.path.exists(self.stop_file):
      return os.path.getmtime(self.stop_file)
    else:
      return None

  def add_items(self):
    if self.pipeline:
      if self.max_items and self.max_items <= self.item_count:
        return

      items_required = int(realize(self.concurrent_items))
      while len(self.active_items) < items_required:
        self.item_count += 1
        item_id = "%d-%d" % (id(self), self.item_count)
        item = Item(pipeline=self.pipeline, item_id=item_id, item_number=self.item_count)
        self.on_create_item(self, item)
        self.active_items.add(item)
        self.pipeline.enqueue(item)

  def _item_starting(self, pipeline, item):
    self.on_pipeline_start_item(self, pipeline, item)

  def _item_finished(self, pipeline, item):
    self.on_pipeline_finish_item(self, pipeline, item)
    self.active_items.remove(item)
    if not self.should_stop():
      self.add_items()
    if len(self.active_items) == 0:
      self.on_finish(self)

class SimpleRunner(Runner):
  def __init__(self, pipeline, stop_file=None, concurrent_items=1, max_items=None):
    Runner.__init__(self, stop_file=stop_file, concurrent_items=concurrent_items, max_items=max_items)

    self.set_current_pipeline(pipeline)
    self.on_create_item += self._handle_create_item
    self.on_finish += self._stop_ioloop

  def start(self):
    Runner.start(self)
    ioloop.IOLoop.instance().start()

  def _stop_ioloop(self, ignored):
    ioloop.IOLoop.instance().stop()

  def _handle_create_item(self, ignored, item):
    item.on_output += self._handle_item_output

  def _handle_item_output(self, item, data):
    sys.stdout.write(data)

