import os
import psutil
import signal
import subprocess
import sys

SLAPRUNNER_PROCESS_LIST = []


class Popen(subprocess.Popen):
  """
  Extension of Popen to launch and kill process in a clean way
  """
  def __init__(self, *args, **kwargs):
    """
    Launch process and add object to process list for handler
    """
    self.name = kwargs.pop('name', None)
    kwargs['stdin'] = subprocess.PIPE
    kwargs['stderr'] = subprocess.STDOUT
    kwargs.setdefault('stdout', subprocess.PIPE)
    kwargs.setdefault('close_fds', True)
    subprocess.Popen.__init__(self, *args, **kwargs)
    SLAPRUNNER_PROCESS_LIST.append(self)
    self.stdin.flush()
    self.stdin.close()
    self.stdin = None

  def kill(self, sig=signal.SIGTERM, recursive=False):
    """
    Kill process and all its descendant if recursive
    """
    if self.poll() is None:
      if recursive:
        childs_pids = pidppid(self.pid)
        for pid in childs_pids:
          killNoFail(pid, sig)
      killNoFail(self.pid, sig)
      if self.poll() is not None:
        SLAPRUNNER_PROCESS_LIST.remove(self)

  def __del__(self):
    """
    Del function, try to kill process group
    and process if its group does not exist
    """
    for pid in (-self.pid, self.pid):
      try:
        os.kill(-self.pid, 15)
      except OSError:
        pass
    subprocess.Popen.__del__(self)


def pidppid(pid, recursive=True):
  """
  Return a list of all children of pid
  """
  return [p.pid for p in psutil.Process(pid).get_children(recursive=recursive)]


def killNoFail(pid, sig):
  """
  function to kill without failing. Return True if kill do not fail
  """
  try:
    os.kill(pid, sig)
    return True
  except OSError:
    return False


def isRunning(name):
  """
  Return True if a process with this name is running
  """
  for process in SLAPRUNNER_PROCESS_LIST:
    if process.name == name:
      if process.poll() is None:
        return True
  return False


def killRunningProcess(name, recursive=False):
  """
  Kill all process with name
  """
  for process in SLAPRUNNER_PROCESS_LIST:
    if process.name == name:
      process.kill(recursive=recursive)


def handler(sig, frame):
  """
  Signal handler to kill all process
  """
  pid = os.getpid()
  os.kill(-pid, sig)
  sys.exit()


def setHandler(sig_list=None):
  if sig_list is None:
    sig_list = [signal.SIGTERM]
  for sig in sig_list:
    signal.signal(sig, handler)