# coding=utf-8
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import (absolute_import, division, generators, nested_scopes, print_function,
                        unicode_literals, with_statement)

import logging
import os
import signal
import subprocess
import time
import traceback
from contextlib import contextmanager

import psutil

from pants.base.build_environment import get_buildroot
from pants.util.dirutil import safe_delete, safe_mkdir, safe_open


logger = logging.getLogger(__name__)


class ProcessGroup(object):
  """Wraps a logical group of processes and provides convenient access to ProcessManager objects."""

  def __init__(self, name):
    self._name = name

  @contextmanager
  def _swallow_psutil_exceptions(self):
    """A contextmanager that swallows standard psutil access exceptions."""
    try:
      yield
    except (psutil.AccessDenied, psutil.NoSuchProcess):
      # This masks common, but usually benign psutil process access exceptions that might be seen
      # when accessing attributes/methods on psutil.Process objects.
      pass

  def _instance_from_process(self, process):
    """Default converter from psutil.Process to process instance classes for subclassing."""
    return ProcessManager(name=process.name(), pid=process.pid, process_name=process.name())

  def iter_processes(self, proc_filter=None):
    proc_filter = proc_filter or (lambda x: True)
    with self._swallow_psutil_exceptions():
      for proc in (x for x in psutil.process_iter() if proc_filter(x)):
        yield proc

  def iter_instances(self, *args, **kwargs):
    for item in self.iter_processes(*args, **kwargs):
      yield self._instance_from_process(item)


class ProcessManager(object):
  """Subprocess/daemon management mixin/superclass. Not intended to be thread-safe."""

  class NonResponsiveProcess(Exception): pass
  class Timeout(Exception): pass

  WAIT_INTERVAL = .1
  KILL_WAIT = 1
  KILL_CHAIN = (signal.SIGTERM, signal.SIGKILL)

  def __init__(self, name, pid=None, socket=None, process_name=None, socket_type=None):
    self._name = name
    self._pid = pid
    self._socket = socket
    self._socket_type = socket_type or int
    self._process_name = process_name
    self._buildroot = get_buildroot()
    self._process = None

  @property
  def name(self):
    """The logical name/label of the process."""
    return self._name

  @property