# This file is part of Xpra.
# Copyright (C) 2010-2024 Antoine Martin <antoine@xpra.org>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

from time import monotonic
from typing import Any
from collections.abc import Callable

from xpra.util.objects import typedict
from xpra.util.env import envint
from xpra.common import NotificationID, ConnectionMessage
from xpra.server.source.stub_source_mixin import StubSourceMixin
from xpra.log import Logger

log = Logger("timeout")

GRACE_PERCENT = envint("XPRA_GRACE_PERCENT", 90)


class IdleMixin(StubSourceMixin):

    @classmethod
    def is_needed(cls, caps: typedict) -> bool:
        return caps.boolget("keyboard") or caps.boolget("mouse") or caps.boolget("windows")

    def __init__(self) -> None:
        self.idle_timeout = 0
        # duplicated from clientconnection:
        self.notification_callbacks: dict[int, Callable] = {}
        self.send_notifications = False
        self.send_notifications_actions = False

    def init_from(self, _protocol, server) -> None:
        self.idle_timeout = server.idle_timeout

    def init_state(self) -> None:
        self.last_user_event = monotonic()
        # grace duration is at least 10 seconds:
        self.idle_grace_duration = max(10, int(self.idle_timeout * (100 - GRACE_PERCENT) // 100))
        self.idle = False
        self.idle_timer = 0
        self.idle_grace_timer = 0

    def cleanup(self) -> None:
        self.cancel_idle_grace_timeout()
        self.cancel_idle_timeout()

    def get_info(self) -> dict[str, Any]:
        return {
            "idle_time": int(monotonic() - self.last_user_event),
            "idle": self.idle,
        }

    def parse_client_caps(self, _c: typedict) -> None:
        # start the timer
        self.schedule_idle_grace_timeout()
        self.schedule_idle_timeout()

    def user_event(self) -> None:
        log("user_event()")
        self.last_user_event = monotonic()
        self.cancel_idle_grace_timeout()
        self.schedule_idle_grace_timeout()
        self.cancel_idle_timeout()
        self.schedule_idle_timeout()
        if self.idle:
            self.no_idle()
        if self.notification_callbacks.pop(NotificationID.IDLE, None):
            self.notify_close(NotificationID.IDLE)

    def cancel_idle_timeout(self) -> None:
        it = self.idle_timer
        if it:
            self.idle_timer = 0
            self.source_remove(it)

    def schedule_idle_timeout(self) -> None:
        log("schedule_idle_timeout() idle_timer=%s, idle_timeout=%s", self.idle_timer, self.idle_timeout)
        if self.idle_timeout > 0:
            self.idle_timer = self.timeout_add(self.idle_timeout * 1000, self.idle_timedout)

    def cancel_idle_grace_timeout(self) -> None:
        igt = self.idle_grace_timer
        if igt:
            self.idle_grace_timer = 0
            self.source_remove(igt)

    def schedule_idle_grace_timeout(self) -> None:
        log("schedule_idle_grace_timeout() grace timer=%s, idle_timeout=%s", self.idle_grace_timer, self.idle_timeout)
        if self.idle_timeout > 0 and not self.is_closed():
            grace = self.idle_timeout - self.idle_grace_duration
            self.idle_grace_timer = self.timeout_add(max(0, int(grace * 1000)), self.idle_grace_timedout)
            log("schedule_idle_grace_timeout() timer=%s due in %i seconds", self.idle_grace_timer, grace)

    def idle_grace_timedout(self) -> None:
        self.idle_grace_timer = 0
        log("idle_grace_timedout()")
        if not self.send_notifications:
            # not much we can do!
            return
        # notify the user, giving him a chance to cancel the timeout:
        nid = NotificationID.IDLE
        if nid in self.notification_callbacks:
            return
        actions: tuple[str, ...] = ()
        if self.send_notifications_actions:
            actions = ("cancel", "Cancel Timeout")
        if self.session_name != "Xpra":
            summary = f"The Xpra session {self.session_name}"
        else:
            summary = "Xpra session"
        summary += " is about to timeout"
        body = "Unless this session sees some activity,\n" + \
               "it will be terminated soon."
        self.may_notify(nid, summary, body,
                        actions, {}, expire_timeout=10 * 1000,
                        icon_name="timer", user_callback=self.idle_notification_action)
        self.go_idle()

    def idle_notification_action(self, nid: int, action_id) -> None:
        log("idle_notification_action(%i, %s)", nid, action_id)
        if action_id == "cancel":
            self.user_event()

    def idle_timedout(self) -> None:
        self.idle_timer = 0
        p = self.protocol
        log("idle_timedout() protocol=%s", p)
        if p:
            self.disconnect(ConnectionMessage.IDLE_TIMEOUT)
        if not self.is_closed():
            self.schedule_idle_timeout()
