#!/usr/bin/env python3

# Libervia CLI
# Copyright (C) 2009-2026 Jérôme Poisson (goffi@goffi.org)

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import sys
import asyncio
import logging as log
from libervia.backend.core.exceptions import InternalError
from libervia.backend.core.i18n import _
from libervia.cli.constants import Const as C

log.basicConfig(level=log.WARNING, format="[%(name)s] %(message)s")

USER_INTER_MSG = _("User interruption: good bye")


class QuitException(BaseException):
    """Quitting is requested

    This is used to stop execution when host.quit() is called
    """


class LiberviaCLILoop:
    """Libervia CLI event loop.

    This class must  be created before bridge initialization, and "install_hooks" must be
    called after.
    """

    def __init__(self):
        """Initialize the loop.

        The actual event loop backend (dbus/GLib or Twisted) is set up
        later via install_hooks() after bridge initialization.
        """
        # we always install asyncioreactor because it must be done before any asyncio
        # loop is used. If we end up using dbus, this shouldn't cause trouble.
        from twisted.internet import asyncioreactor

        asyncioreactor.install()
        from twisted.internet import reactor

        self.loop = asyncio.get_event_loop()
        self.reactor = reactor
        self._hooks_installed = False

        # exit code must be set when using quit, so if it's not set something got wrong
        # and we must report it
        self._exit_code = C.EXIT_INTERNAL_ERROR
        self._is_asyncio = True

    def install_hooks(self, bridge_name):
        """Install signal handlers and event loop hooks.

        This must be called after bridge initialization and before run().

        @param bridge_name: Name of the bridge.
        """
        if self._hooks_installed:
            return
        self._hooks_installed = True

        import signal

        signal.signal(signal.SIGINT, self._on_sigint)

    def run(self, libervia_cli):
        """Run the event loop.

        @param libervia_cli: The LiberviaCli instance
        """
        if not self._hooks_installed:
            raise InternalError(
                "LiberviaCLILoop.run() called before install_hooks(). Ensure"
                " install_hooks(bridge_name) is called after bridge initialization."
            )

        self._run_asyncio()

    def _run_asyncio(self):
        """Run the dbus-based loop."""
        loop = self.loop
        loop.run_forever()

    def quit(self, exit_code):
        """Stop the loop and exit.

        @param exit_code: Exit code to return
        """
        self._exit_code = exit_code
        loop = asyncio.get_event_loop()
        loop.stop()
        sys.exit(exit_code)

    def call_later(self, delay, callback, *args):
        """Call a callback after a delay.

        @param delay: Delay in seconds
        @param callback: Method to call
        @param args: Arguments to pass to callback
        """
        loop = asyncio.get_event_loop()
        loop.call_later(delay, callback, *args)

    def _on_sigint(self, sig_number, stack_frame):
        """Called on keyboard interruption

        Print user interruption message, set exit code and stop loop.
        """
        print("\r" + USER_INTER_MSG)
        self._exit_code = C.EXIT_USER_CANCELLED
        self.quit(self._exit_code)
