# Copyright 1999-2020 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

import array
import errno
import os

from portage.util.futures import asyncio
from _emerge.AsynchronousTask import AsynchronousTask


class AbstractPollTask(AsynchronousTask):

    __slots__ = ("_registered",)

    _bufsize = 4096

    def _read_array(self, f):
        """
        NOTE: array.fromfile() is used here only for testing purposes,
        because it has bugs in all known versions of Python (including
        Python 2.7 and Python 3.2). See PipeReaderArrayTestCase.

        A benchmark that copies bytes from /dev/zero to /dev/null shows
        that arrays give a 15% performance improvement for Python 2.7.14.
        However, arrays significantly *decrease* performance for Python 3.
        """
        buf = array.array("B")
        try:
            buf.fromfile(f, self._bufsize)
        except EOFError:
            pass
        except TypeError:
            # Python 3.2:
            # TypeError: read() didn't return bytes
            pass
        except OSError as e:
            # EIO happens with pty on Linux after the
            # slave end of the pty has been closed.
            if e.errno == errno.EIO:
                # EOF: return empty string of bytes
                pass
            elif e.errno == errno.EAGAIN:
                # EAGAIN: return None
                buf = None
            else:
                raise

        if buf is not None:
            try:
                # Python >=3.2
                buf = buf.tobytes()
            except AttributeError:
                buf = buf.tostring()

        return buf

    def _read_buf(self, fd):
        """
        Read self._bufsize into a string of bytes, handling EAGAIN and
        EIO. This will only call os.read() once, so the caller should
        call this method in a loop until either None or an empty string
        of bytes is returned. An empty string of bytes indicates EOF.
        None indicates EAGAIN.

        NOTE: os.read() will be called regardless of the event flags,
                since otherwise data may be lost (see bug #531724).

        @param fd: file descriptor (non-blocking mode required)
        @type fd: int
        @rtype: bytes or None
        @return: A string of bytes, or None
        """
        # NOTE: array.fromfile() is no longer used here because it has
        # bugs in all known versions of Python (including Python 2.7
        # and Python 3.2).
        buf = None
        try:
            buf = os.read(fd, self._bufsize)
        except OSError as e:
            # EIO happens with pty on Linux after the
            # slave end of the pty has been closed.
            if e.errno == errno.EIO:
                # EOF: return empty string of bytes
                buf = b""
            elif e.errno == errno.EAGAIN:
                # EAGAIN: return None
                buf = None
            else:
                raise

        return buf

    def _async_wait(self):
        self._unregister()
        super()._async_wait()

    def _unregister(self):
        self._registered = False

    def _wait_loop(self, timeout=None):
        loop = self.scheduler
        tasks = [self.async_wait()]
        if timeout is not None:
            tasks.append(
                asyncio.ensure_future(asyncio.sleep(timeout, loop=loop), loop=loop)
            )
        try:
            loop.run_until_complete(
                asyncio.ensure_future(
                    asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED, loop=loop),
                    loop=loop,
                )
            )
        finally:
            for task in tasks:
                task.cancel()
