"""
Utility to support asynchronously signaling the current process.
"""

from __future__ import absolute_import

import atexit
import os
import signal
import sys
import threading
import time
import traceback

_is_windows = (sys.platform == "win32")
if _is_windows:
    import win32api
    import win32event

from . import reportfile
from . import testing


def register(logger, suites, start_time):
    """
    On Windows, set up an event object to wait for signal, otherwise, register a signal handler
    for the SIGUSR1 signal.
    """

    def _handle_sigusr1(signum, frame):
        """
        Signal handler that will dump the stacks of all threads and
        then write out the report file and log suite summaries.
        """

        header_msg = "Dumping stacks due to SIGUSR1 signal"

        _dump_and_log(header_msg)

    def _handle_set_event(event_handle):
        """
        Windows event object handler that will dump the stacks of all threads and then write out
        the report file and log suite summaries.
        """

        while True:
            try:
                # Wait for task time out to dump stacks.
                ret = win32event.WaitForSingleObject(event_handle, win32event.INFINITE)
                if ret != win32event.WAIT_OBJECT_0:
                    logger.error("_handle_set_event WaitForSingleObject failed: %d" % ret)
                    return
            except win32event.error as err:
                logger.error("Exception from win32event.WaitForSingleObject with error: %s" % err)
            else:
                header_msg = "Dumping stacks due to signal from win32event.SetEvent"

                _dump_and_log(header_msg)

    def _dump_and_log(header_msg):
        """
        Dumps the stacks of all threads, writes the report file, and logs the suite summaries.
        """
        _dump_stacks(logger, header_msg)
        reportfile.write(suites)

        testing.suite.Suite.log_summaries(logger, suites, time.time() - start_time)


    # On Windows spawn a thread to wait on an event object for signal to dump stacks. For Cygwin
    # platforms, we use a signal handler since it supports POSIX signals.
    if _is_windows:
        # Create unique event_name.
        event_name = "Global\\Mongo_Python_" + str(os.getpid())

        try:
            security_attributes = None
            manual_reset = False
            initial_state = False
            task_timeout_handle = win32event.CreateEvent(security_attributes,
                                                         manual_reset,
                                                         initial_state,
                                                         event_name)
        except win32event.error as err:
            logger.error("Exception from win32event.CreateEvent with error: %s" % err)
            return

        # Register to close event object handle on exit.
        atexit.register(win32api.CloseHandle, task_timeout_handle)

        # Create thread.
        event_handler_thread = threading.Thread(target=_handle_set_event,
                                                kwargs={"event_handle": task_timeout_handle},
                                                name="windows_event_handler_thread")
        event_handler_thread.daemon = True
        event_handler_thread.start()
    else:
        # Otherwise register a signal handler
        signal.signal(signal.SIGUSR1, _handle_sigusr1)


def _dump_stacks(logger, header_msg):
    """
    Signal handler that will dump the stacks of all threads.
    """

    sb = []
    sb.append(header_msg)

    frames = sys._current_frames()
    sb.append("Total threads: %d" % (len(frames)))
    sb.append("")

    for thread_id in frames:
        stack = frames[thread_id]
        sb.append("Thread %d:" % (thread_id))
        sb.append("".join(traceback.format_stack(stack)))

    logger.info("\n".join(sb))
