# Standard library imports.
import ctypes
import os
import time
from thread import interrupt_main
from threading import Thread

# Local imports.
from IPython.utils.io import raw_print


class ParentPollerUnix(Thread):
    """ A Unix-specific daemon thread that terminates the program immediately 
    when the parent process no longer exists.
    """

    def __init__(self):
        super(ParentPollerUnix, self).__init__()
        self.daemon = True
    
    def run(self):
        # We cannot use os.waitpid because it works only for child processes.
        from errno import EINTR
        while True:
            try:
                if os.getppid() == 1:
                    raw_print('Killed by parent poller!')
                    os._exit(1)
                time.sleep(1.0)
            except OSError, e:
                if e.errno == EINTR:
                    continue
                raise


class ParentPollerWindows(Thread):
    """ A Windows-specific daemon thread that listens for a special event that
    signals an interrupt and, optionally, terminates the program immediately
    when the parent process no longer exists.
    """
    
    def __init__(self, interrupt_handle=None, parent_handle=None):
        """ Create the poller. At least one of the optional parameters must be
        provided.

        Parameters:
        -----------
        interrupt_handle : HANDLE (int), optional
            If provided, the program will generate a Ctrl+C event when this
            handle is signaled.

        parent_handle : HANDLE (int), optional
            If provided, the program will terminate immediately when this 
            handle is signaled.
        """
        assert(interrupt_handle or parent_handle)
        super(ParentPollerWindows, self).__init__()
        self.daemon = True
        self.interrupt_handle = interrupt_handle
        self.parent_handle = parent_handle

    @staticmethod
    def create_interrupt_event():
        """ Create an interrupt event handle.

        The parent process should use this static method for creating the
        interrupt event that is passed to the child process. It should store
        this handle and use it with ``send_interrupt`` to interrupt the child
        process.
        """
        # Create a security attributes struct that permits inheritance of the
        # handle by new processes.
        # FIXME: We can clean up this mess by requiring pywin32 for IPython.
        class SECURITY_ATTRIBUTES(ctypes.Structure):
            _fields_ = [ ("nLength", ctypes.c_int), 
                         ("lpSecurityDescriptor", ctypes.c_void_p), 
                         ("bInheritHandle", ctypes.c_int) ]
        sa = SECURITY_ATTRIBUTES()
        sa_p = ctypes.pointer(sa)
        sa.nLength = ctypes.sizeof(SECURITY_ATTRIBUTES)
        sa.lpSecurityDescriptor = 0
        sa.bInheritHandle = 1

        return ctypes.windll.kernel32.CreateEventA(
            sa_p,  # lpEventAttributes
            False, # bManualReset
            False, # bInitialState
            '')    # lpName

    @staticmethod
    def send_interrupt(interrupt_handle):
        """ Sends an interrupt event using the specified handle.
        """
        ctypes.windll.kernel32.SetEvent(interrupt_handle)

    def run(self):
        """ Run the poll loop. This method never returns.
        """
        from _subprocess import WAIT_OBJECT_0, INFINITE

        # Build the list of handle to listen on.
        handles = []
        if self.interrupt_handle:
            handles.append(self.interrupt_handle)
        if self.parent_handle:
            handles.append(self.parent_handle)

        # Listen forever.
        while True:
            result = ctypes.windll.kernel32.WaitForMultipleObjects(
                len(handles),                            # nCount
                (ctypes.c_int * len(handles))(*handles), # lpHandles
                False,                                   # bWaitAll
                INFINITE)                                # dwMilliseconds

            if WAIT_OBJECT_0 <= result < len(handles):
                handle = handles[result - WAIT_OBJECT_0]

                if handle == self.interrupt_handle:
                    raw_print('Interrupted by parent poller!')
                    interrupt_main()

                elif handle == self.parent_handle:
                    raw_print('Killed by parent poller!')
                    os._exit(1)