'''
Created on 2015/11/30

:author: hubo

Process events multi-threaded or multi-processed
'''
from vlcp.event.runnable import RoutineContainer
import multiprocessing
import threading
import socket
import signal
import errno
import sys
from vlcp.event.event import Event, withIndices, M_
from vlcp.event.core import POLLING_IN, PollEvent
import functools
import traceback
from contextlib import closing
if sys.version_info[0] >= 3:
    from queue import Full, Queue, Empty
else:
    from Queue import Full, Queue, Em
pty
import logging
from vlcp.event.connection import ResolveRequestEvent, ResolveResponseEvent

@withIndices('connector', 'type')
class ConnectorControlEvent(Event):
    ADDMATCHERS = 'addmatcher'
    REMOVEMATCHERS = 'removematcher'
    SETMATCHERS = 'setmatchers'
    STOPRECEIVE = 'stopreceive'
    STARTRECEIVE = 'startreceive'
import os

@withIndices()
class MoreRe
sultEvent(Event):
    pass

class _Pipe(object):
    "Make a pipe looks like a socket"
    def __init__(self, fd, canread = True):
        self.canread = canread
        self.canwrite = not canread
        self.fd = fd
    def setblocking(self, blocking):
        import fcntl
        if blocking:
            fcntl.fcntl(self.fd, fcntl.F_SETFL, 0)
        else:
            fcntl.fcntl(self.fd, fcntl.F_SETFL, os.O_NONBLOCK)
    def fileno(self):
        return self.fd
    def send(self, data):
        try:
            os.write(self.fd, data)
        except OSError as exc:
            raise socket.error(*exc.args)
    def recv(self, size):
        try:
            return os.read(self.fd, size)
        except OSError as exc:
            raise socket.error(*exc.args)
    def close(self):
        if self.fd:
            os.close(self.fd)
            self.fd = None
    def __del__(self):
        self.close()

class Connector(RoutineContainer):
    def __init__(self, worker_start, matchers = (), scheduler = None, mp = True, inputlimit = 0, allowcontrol = True):
        '''
        :param worker_start: func(queuein, queueout), queuein is the input queue, queueout is the
               output queue. For queuein, each object is (event, matcher) tuple; For queueout, each
               object is a tuple of events to send. Every object in queuein must have a response in
               queueout.
        :param matcheres: match events to be processed by connector.
        :param scheduler: bind to specified scheduler
        :param mp: use multiprocessing if possible. For windows, multi-threading is always used.
        :param inputlimit: input queue size limit. 0 = infinite.
        :param allowcontrol: if True, the connector accepts ConnectorControlEvent for connector configuration.
        '''
        RoutineContainer.__init__(self, scheduler, True)
        self.worker_start = worker_start
        self.matchers = set(matchers)
        self.mp = mp
        self.inputlimit = inputlimit
        self.allowcontrol = allowcontrol
        self.stopreceive = False
        self.jobs = 0
        self._moreresult_matcher = MoreResultEvent.createMatcher()
    @staticmethod
    def wrap_signal(func):
        def f(*args, **kwargs):
            signal.signal(signal.SIGINT, signal.SIG_IGN)
            signal.signal(signal.SIGTERM, signal.SIG_DFL)
            return func(*args, **kwargs)
        return f
    @staticmethod
    def connector_pipe(queuein, pipeout, worker_start):
        try:
            queueout = multiprocessing.Queue()
            worker_start(queuein, queueout)
            while True:
                try:
                    events = queueout.get()
                    if events is None:
                        break
                    pipeout.send(events)
                except EOFError:
                    break
                except OSError as exc:
                    if exc.args[0] == errno.EINTR:
                        continue
                    else:
                        break
        finally:
            pipeout.close()
    @staticmethod
    def connector_socket(queuein, pipeout, worker_start):
        try:
            queueout = Queue()
            worker_start(queuein, queueout)
            while True:
                try:
                    events = queueout.get()
                    if events is None:
                        break
                    pipeout[0].put(events)
                    while True:
                        try:
                            events = queueout.get(False)
                            pipeout[0].put(events)
                        except Empty:
                            break
                    pipeout[1].send(b'\x00')
                except EOFError:
                    break
                except OSError as exc:
                    if exc.args[0] == errno.EINTR:
                        continue
                    else:
                        break
                except Exception:
                    pass
        finally:
            pipeout[1].close()
    def enqueue(self, queue, event, matcher):
        queue.put((event, matcher), False)
        event.canignore = True
        self.jobs += 1
        if self.jobs == 1:
            self.scheduler.setPollingDaemon(self.pipein, False)
    def sendevents(self, events):
        moreResult = False
        for e in events:
            if self._moreresult_matcher.isMatch(e):
                moreResult = True
            else:
                self.scheduler.emergesend(e)
        if not moreResult:
            self.jobs -= 1
        if self.jobs == 0:
            self.scheduler.setPollingDaemon(self.pipein, True)
    def _createobjs(self, fork, mp):
        if mp:
            queue = multiprocessing.Queue(self.inputlimit)
        else:
            queue = Queue(self.inputlimit)
        if mp:
            pipein, pipeout = multiprocessing.Pipe()
            process = multiprocessing.Process(target=self.wrap_signal(self.connector_pipe), args=(queue, pipeout, self.worker_start))
            outqueue = None
        else:
            # Use a thread instead
            # Create a socket on localhost
            if fork:
                # Linux
                pifd, pofd = os.pipe()
                pipein = _Pipe(pifd, True)
                pipeout = _Pipe(pofd, False)
                pipein.setblocking(False)
                pipeout.setblocking(True)
            else:
                addrinfo = socket.getaddrinfo('localhost', 0, socket.AF_UNSPEC, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_ADDRCONFIG|socket.AI_PASSIVE)
                socket_s = socket.socket(*addrinfo[0][0:2])
                socket_s.bind(addrinfo[0][4])
                socket_s.listen(1)
                addr_target = socket_s.getsockname()
                pipeout = socket.socket(*addrinfo[0][0:2])
                pipeout.setblocking(False)
                pipeout.connect_ex(addr_target)
                pipein, _ = socket_s.accept()
                pipein.setblocking(False)
                pipeout.setblocking(True)
                socket_s.close()
            outqueue = Queue()
            process = threading.Thread(target=self.connector_socket, args=(queue, (outqueue, pipeout), self.worker_start))
        process.daemon = True
        self.pipein = pipein
        return (process, queue, pipein, outqueue)
    async def main(self):
        import os
        self.resolving = set()
        if hasattr(os, 'fork'):
            fork = True
        else:
            fork = False
        mp = self.mp and fork
        (process, queue, pipein, outqueue) = self._createobjs(fork, mp)
        try:
            process.start()
            self.scheduler.registerPolling(pipein, POLLING_IN, True)
            response_matcher = PollEvent.createMatcher(pipein.fileno(), PollEvent.READ_READY)
            error_matcher = PollEvent.createMatcher(pipein.fileno(), PollEvent.ERROR)
            control_matcher = ConnectorControlEvent.createMatcher(self)
            if self.allowcontrol:
                system_matchers = (response_matcher, error_matcher, control_matcher)
            else:
                system_matchers = (response_matcher, error_matcher)
            isFull = False
            isEOF = False
            while T