#!/usr/bin/env python3
#-*- coding:utf-8 -*-
#!/usr/bin/python

try:
    import sys
    import os
    import signal
    import queue
    import threading
    from gspylib.inspection.common.Exception import InterruptException
except ImportError as ie:
    sys.exit("[GAUSS-52200] : Unable to import module: %s." % str(ie))


class TaskThread(threading.Thread):
    def __init__(self, queWork, queResult, iTimeout):
        threading.Thread.__init__(self)
        # timeout for fetching task
        self.m_iTimeout = iTimeout
        self.m_bRunning = True
        self.setDaemon(True)
        self.m_queWork = queWork
        self.m_queResult = queResult
        self.start()

    def run(self):
        while self.m_bRunning:
            if queue is None:
                break
            try:
                # fetch a task from the queue,
                # here timout parameter MUST be asigned,
                # otherwise get() will wait for ever
                callableFun, args = self.m_queWork.get(timeout=self.m_iTimeout)
                # run the task
                Ret = callableFun(args[0])
                self.m_queResult.put(Ret)
            # if task queue is empty
            except Exception:
                self.m_bRunning = False
                continue


class TaskPool:
    def __init__(self, iNumOfThreads, iTimeOut=1):
        self.m_queWork = queue.Queue()
        self.m_queResult = queue.Queue()
        self.m_lstThreads = []
        self.m_iTimeOut = iTimeOut
        self.__createThreadPool(iNumOfThreads)

    def __createThreadPool(self, iNumOfThreads):
        for _ in range(iNumOfThreads):
            aThread = TaskThread(self.m_queWork, self.m_queResult, self.m_iTimeOut)
            self.m_lstThreads.append(aThread)

    # add a task into the thread pool
    def addTask(self, callableFunc, *args):
        self.m_queWork.put((callableFunc, list(args)))

    # get one task executing result
    def getOneResult(self):
        try:
            # get a reult from queue,
            # get will not return until a result is got
            aItem = self.m_queResult.get()
            return aItem
        except Exception:
            return None

    # notify all theads in the thread pool to exit
    def notifyStop(self):
        for aThread in self.m_lstThreads:
            aThread.m_bRunning = False

    # Waiting for all threads in the thread pool exit
    def waitForComplete(self):
        # wait all threads terminate
        while len(self.m_lstThreads):
            aThread = self.m_lstThreads.pop()
            # wait the thread terminates
            if aThread.is_alive():
                aThread.join()


class Watcher:
    """
    this class solves two problems with multithreaded
    programs in Python, (1) a signal might be delivered
    to any thread (which is just a malfeature) and (2) if
    the thread that gets the signal is waiting, the signal
    is ignored (which is a bug).

    The watcher is a concurrent process (not thread) that
    waits for a signal and the process that contains the
    threads.
    """

    def __init__(self):
        """
        Creates a child thread, which returns.
        The parent thread waits for a KeyboardInterrupt and then kills the child thread.
        """
        self.child = os.fork()
        if self.child == 0:
            return
        else:
            self.watch()

    def watch(self):
        try:
            os.wait()
        except KeyboardInterrupt:
            # I put the capital B in KeyBoardInterrupt so I can
            # tell when the Watcher gets the SIGINT
            self.kill()
            raise InterruptException()
        sys.exit()

    def kill(self):
        try:
            os.kill(self.child, signal.SIGKILL)
        except OSError:
            pass


class CheckThread(threading.Thread):
    def __init__(self, name, func, *args):
        super(CheckThread, self).__init__(name=name, target=func, args=args)
        self._stop_event = threading.Event()
        self.setDaemon(True)
        self.exitcode = 0
        self.exception = None
        self.name = name
        self.func = func
        self.args = args
        self.start()

    def run(self):
        try:
            self.func(*self.args)
        except Exception as e:
            self.exitcode = 1
            self.exception = e

    def stop(self):
        self._stop_event.set()

    def stopped(self):
        return self._stop_event.is_set()
