# Copyright 2012-2024 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

import errno
import gzip

from portage.util import writemsg_level
from portage.util.futures._asyncio.streams import _writer

from ..SlotObject import SlotObject


class SchedulerInterface(SlotObject):
    _event_loop_attrs = (
        "add_reader",
        "add_writer",
        "call_at",
        "call_exception_handler",
        "call_later",
        "call_soon",
        "call_soon_threadsafe",
        "close",
        "create_future",
        "default_exception_handler",
        "get_debug",
        "is_closed",
        "is_running",
        "remove_reader",
        "remove_writer",
        "run_in_executor",
        "run_until_complete",
        "set_debug",
        "time",
        "_asyncio_child_watcher",
        # This attribute it used by _wrap_loop to detect if the
        # loop already has a suitable wrapper.
        "_asyncio_wrapper",
    )

    __slots__ = _event_loop_attrs + ("_event_loop", "_is_background")

    def __init__(self, event_loop, is_background=None, **kwargs):
        SlotObject.__init__(self, **kwargs)
        self._event_loop = event_loop
        if is_background is None:
            is_background = self._return_false
        self._is_background = is_background
        for k in self._event_loop_attrs:
            setattr(self, k, getattr(event_loop, k))

    @property
    def _loop(self):
        """
        Returns the real underlying asyncio loop.
        """
        return self._event_loop._loop

    @staticmethod
    def _return_false():
        return False

    async def async_output(
        self, msg, log_file=None, background=None, level=0, noiselevel=-1
    ):
        """
        Output a msg to stdio (if not in background) and to a log file
        if provided.

        @param msg: a message string, including newline if appropriate
        @type msg: str
        @param log_file: log file in binary mode
        @type log_file: file
        @param background: send messages only to log (not to stdio)
        @type background: bool
        @param level: a numeric logging level (see the logging module)
        @type level: int
        @param noiselevel: passed directly to writemsg
        @type noiselevel: int
        """
        global_background = self._is_background()
        if background is None or global_background:
            background = global_background

        if not background:
            writemsg_level(msg, level=level, noiselevel=noiselevel)

        if log_file is not None:
            await _writer(log_file, msg.encode("utf-8", "backslashreplace"))

    def output(self, msg, log_path=None, background=None, level=0, noiselevel=-1):
        """
        Output msg to stdout if not self._is_background(). If log_path
        is not None then append msg to the log (appends with
        compression if the filename extension of log_path corresponds
        to a supported compression type).
        """

        global_background = self._is_background()
        if background is None or global_background:
            # Use the global value if the task does not have a local
            # background value. For example, parallel-fetch tasks run
            # in the background while other tasks concurrently run in
            # the foreground.
            background = global_background

        msg_shown = False
        if not background:
            writemsg_level(msg, level=level, noiselevel=noiselevel)
            msg_shown = True

        if log_path is not None:
            try:
                f = open(
                    log_path,
                    mode="ab",
                )
                f_real = f
            except OSError as e:
                if e.errno not in (errno.ENOENT, errno.ESTALE):
                    raise
                if not msg_shown:
                    writemsg_level(msg, level=level, noiselevel=noiselevel)
            else:
                if log_path.endswith(".gz"):
                    # NOTE: The empty filename argument prevents us from
                    # triggering a bug in python3 which causes GzipFile
                    # to raise AttributeError if fileobj.name is bytes
                    # instead of unicode.
                    f = gzip.GzipFile(filename="", mode="ab", fileobj=f)

                f.write(msg.encode("utf-8", "backslashreplace"))
                f.close()
                if f_real is not f:
                    f_real.close()
