#!{{ python_executable }}

import json
import os
import subprocess
import sys
import sqlite3
import time
import threading
from optparse import OptionParser, make_option


FAILURE = "FAILURE"
SUCCESS = "SUCCESS"

db_path = "{{ monitor_parameter['db-path'] }}"
instance_path = "{{ directory['home'] }}"
monitor_dir = "{{ directory['monitor-custom-scripts'] }}"
pid_dir = "{{ directory['run'] }}"
promise_dir = "{{ directory['promise'] }}"

monitoring_file_json = "{{ monitoring_file_json }}"

option_list = [
  make_option("-a", "--all", action="store_true", dest="all",
              help="test everything : promises, services, customs"),
  make_option("-n", "--no-write", action="store_true", dest="only_stdout",
              help="just show the json output on stdout"),
  make_option("-m", "--monitors", action="store_true", dest="monitor",
              help="add the custom monitoring file to the files to monitor"),
  make_option("-p", "--promises", action="store_true", dest="promise",
              help="add the promises\'file to the files to monitor"),
  make_option("-s", "--services", action="store_true", dest="service",
              help="add the file containing services\'pid to the files to monitor")
]

class Popen(subprocess.Popen):

  def set_timeout(self, timeout):
    self.set_timeout = None # assert we're not called twice
    event = threading.Event()
    event.__killed = False # we just need a mutable
    def t():
      # do not call wait() or poll() because they're not thread-safe
      if not event.wait(timeout) and self.returncode is None:
        # race condition if waitpid completes just before the signal sent ?
        self.terminate()
        event.__killed = True
        if event.wait(5):
          return
        if self.returncode is None:
          self.kill() # same race as for terminate ?
    t = threading.Thread(target=t)
    t.daemon = True
    t.start()
    def killed():
      event.set()
      t.join()
      return event.__killed
    return killed

def init_db(db):
  db.executescript("""
CREATE TABLE IF NOT EXISTS status (
  timestamp INTEGER UNIQUE,
  status VARCHAR(255));
CREATE TABLE IF NOT EXISTS individual_status (
  timestamp INTEGER,
  status VARCHAR(255),
  element VARCHAR(255),
  output TEXT);
""")

def getListOfScripts(directory):
  """
  Get the list of script inside of a directory (not recursive)
  """
  scripts = []
  if os.path.exists(directory) and os.path.isdir(directory):
    for file_name in os.listdir(directory):
      file = os.path.join(directory, file_name)
      if os.access(file, os.X_OK) and not os.path.isdir(file):
        scripts.append(file)
  else:
    exit("There is a problem in your directories" \
          "of monitoring. Please check them")
  return scripts


def runServices(directory):
  services = getListOfScripts(directory)
  result = {}
  for service in services:
    service_path = os.path.join(pid_dir, service)
    service_name = os.path.basename(service_path)
    try:
      pid = int(open(service_path).read())
    ### because apache (or others) can write sockets
    ### We also ignore not readable pid files
    except (IOError, ValueError):
      continue
    try:
      os.kill(pid, 0)
      result[service_name] = ''
    except OSError:
      result[service_name] = "This service is not running anymore"
  return result


def runScripts(directory):
  # XXX script_timeout could be passed as parameters
  script_timeout = 60 # in seconds
  result = {}
  with open(os.devnull, 'r+') as f:
    for script in getListOfScripts(directory):
      command = os.path.join(promise_dir, script),
      script = os.path.basename(script)
      result[script] = ''

      p = Popen(command, cwd=instance_path,
                env=None if sys.platform == 'cygwin' else {},
                stdin=f, stdout=f, stderr=subprocess.PIPE)
      killed = p.set_timeout(script_timeout)
      stderr = p.communicate()[1]
      if killed():
        result[script] = "Time Out"
      elif p.returncode:
        result[script] = stderr.strip()
  return result


def writeFiles(monitors):
  timestamp = int(time.time())
  db = sqlite3.connect(db_path)
  init_db(db)
  status = SUCCESS
  for key, value in monitors.iteritems():
    if value:
      element_status = status = FAILURE
    else:
      element_status = SUCCESS
    db.execute("insert into individual_status(timestamp, element, output, status) values (?, ?, ?, ?)", (timestamp, key, value, element_status))
  db.execute("insert into status(timestamp, status) values (?, ?)", (timestamp, status))
  db.commit()
  db.close()
  monitors['datetime'] = time.ctime(timestamp)
  json.dump(monitors, open(monitoring_file_json, "w+"))

def main():
  parser = OptionParser(option_list=option_list)
  monitors = {}
  (options, args) = parser.parse_args()

  if not (options.monitor or options.promise
         or options.service or options.all):
    exit("Please provide at list one arg in : -a, -m, -p, -s")

  if options.monitor or options.all:
    monitors.update(runScripts(monitor_dir))
  if options.promise or options.all:
    monitors.update(runScripts(promise_dir))
  if options.service or options.all:
    monitors.update(runServices(pid_dir))

  if options.only_stdout:
    print json.dumps(monitors)
  else:
    writeFiles(monitors)


if __name__ == "__main__":
  main()
