# -*- coding: utf-8 -*-
"""
   profiling.remote.select
   ~~~~~~~~~~~~~~~~~~~~~~~

   Implements a profiling server based on `select`_.

   .. _select: https://docs.python.org/library/select.html

   :copyright: (c) 2014-2017, What! Studio
   :license: BSD, see LICENSE for more details.

"""
from __future__ import absolute_import

from errno import ECONNRESET, EINTR, ENOTCONN
import select
import socket
import time

from profiling.remote import ProfilingServer


__all__ = ['SelectProfilingServer']


class SelectProfilingServer(ProfilingServer):

    def __init__(self, listener, *args, **kwargs):
        super(SelectProfilingServer, self).__init__(*args, **kwargs)
        self.listener = listener

    def serve_forever(self):
        while True:
            self.dispatch_sockets()

    def _send(self, sock, data):
        sock.sendall(data, socket.MSG_DONTWAIT)

    def _close(self, sock):
        sock.close()

    def _addr(self, sock):
        try:
            return sock.getpeername()
        except socket.error as exc:
            if exc.errno == ENOTCONN:
                return None
            else:
                raise

    def _start_profiling(self):
        self.profile_periodically()

    def profile_periodically(self):
        for __ in self.profiling():
            self.dispatch_sockets(self.interval)

    def _start_watching(self, sock):
        pass

    def sockets(self):
        """Returns the set of the sockets."""
        if self.listener is None:
            return self.clients
        else:
            return self.clients.union([self.listener])

    def select_sockets(self, timeout=None):
        """EINTR safe version of `select`.  It focuses on just incoming
        sockets.
        """
        if timeout is not None:
            t = time.time()
        while True:
            try:
                ready, __, __ = select.select(self.sockets(), (), (), timeout)
            except ValueError:
                # there's fd=0 socket.
                pass
            except select.error as exc:
                # ignore an interrupted system call.
                if exc.args[0] != EINTR:
                    raise
            else:
                # succeeded.
                return ready
            # retry.
            if timeout is None:
                continue
            # decrease timeout.
            t2 = time.time()
            timeout -= t2 - t
            t = t2
            if timeout <= 0:
                # timed out.
                return []

    def dispatch_sockets(self, timeout=None):
        """Dispatches incoming sockets."""
        for sock in self.select_sockets(timeout=timeout):
            if sock is self.listener:
                listener = sock
                sock, addr = listener.accept()
                self.connected(sock)
            else:
                try:
                    sock.recv(1)
                except socket.error as exc:
                    if exc.errno != ECONNRESET:
                        raise
                self.disconnected(sock)