#!/usr/bin/env python3
# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*-
#
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: Michael Terry

from subprocess import check_output

import pyatspi
from dogtail import rawinput, tree
from dogtail.config import config
from dogtail.predicate import GenericPredicate
from dogtail.tree import Node, SearchError


class Gtk4Node:
    """Methods to work around the current gtk4 support in dogtail"""

    def __init__(self, node, coords=None):
        super().__init__()
        self.node = node
        self.coords = coords

    def __str__(self):
        return str(self.node)

    def _wrapShowingMethod(self, func, *args, showingOnly=None, **kwargs):
        """Handle a lack of STATE_SHOWING in gtk4"""
        try:
            c = func(*args, **kwargs, showingOnly=False)
        except SearchError:
            tree.root.dump()
            self.node.dump()  # a little debugging help
            raise
        if c is None:
            return None
        c = Gtk4Node(c, self.coords)
        if showingOnly is None:
            showingOnly = config.searchShowingOnly
        if showingOnly is True:
            if kwargs.get("requireResult", True):
                assert c.showing
            elif not c.showing:
                return None
        return c

    def button(self, name, *args, **kwargs):
        # Buttons with mnemonics seem to show up with their mnemonic and normal
        # labels jammed together. So look for the label instead, not name.
        return self._wrapShowingMethod(
            self.node.child, label=name, roleName="button", *args, **kwargs
        )

    def child(self, *args, **kwargs):
        return self._wrapShowingMethod(self.node.child, *args, **kwargs)

    def childNamed(self, *args, **kwargs):
        return self._wrapShowingMethod(self.node.childNamed, *args, **kwargs)

    def findChild(self, *args, **kwargs):
        return self._wrapShowingMethod(self.node.findChild, *args, **kwargs)

    def findChildren(self, *args, showingOnly=None, **kwargs):
        children = self.node.findChildren(*args, **kwargs, showingOnly=False)
        if showingOnly is None:
            showingOnly = config.searchShowingOnly

        allowed = []
        for c in children:
            c = Gtk4Node(c, self.coords)
            if showingOnly is True and not c.showing:
                continue
            allowed.append(c)
        return allowed

    def window(self, *args, **kwargs):
        return self._wrapShowingMethod(self.node.window, *args, **kwargs)

    def _ensure_coords(self):
        if self.coords:
            return

        top = self.findAncestor(GenericPredicate(roleName="frame"))
        if not top:
            top = self.findAncestor(GenericPredicate(roleName="dialog"))
        if not top:
            maybe_top = self.findAncestor(GenericPredicate(roleName="filler"))
            while maybe_top:
                top = maybe_top
                maybe_top = top.findAncestor(GenericPredicate(roleName="filler"))
        assert top

        # Fix coords to take into account client side decorations.
        # Hardcoding this is super gross, I'd love a better way.
        self.coords = (top.node.position[0] + 30, top.node.position[1] + 30)

    def _get_position(self) -> tuple[int, int]:
        # Dogtail's default position uses DESKTOP_COORDS, which modern GTK
        # provides as (0, 0) always.
        return self.node.queryComponent().getPosition(pyatspi.WINDOW_COORDS)

    def click(self, button=1):
        """Click with adjusted screen-global coordinates"""
        # If we can avoid the vagaries of coordinates, let's do that. This is
        # useful in particular with popup menu items, whose coordinates need
        # some further adjustment than I've made here...
        for action in ["click", "toggle"]:
            if action in self.node.actions:
                self.node.doActionNamed(action)
                return
        if len(self.node.children) == 1:  # try to find click action
            return Gtk4Node(self.node.children[0], self.coords).click(button)

        self._ensure_coords()
        position = self._get_position()
        clickX = self.coords[0] + position[0] + self.node.size[0] / 2
        clickY = self.coords[1] + position[1] + self.node.size[1] / 2 + 5

        rawinput.click(clickX, clickY, button)

    def doubleClick(self, button=1):
        """Double click with adjusted screen-global coordinates"""
        self._ensure_coords()
        position = self._get_position()
        clickX = self.coords[0] + position[0] + self.node.size[0] / 2
        clickY = self.coords[1] + position[1] + self.node.size[1] / 2
        rawinput.doubleClick(clickX, clickY, button)

    @property
    def pressed(self):
        return self.node.getState().contains(pyatspi.STATE_PRESSED)

    @property
    def checked(self):
        return self.node.checked

    @property
    def children(self):
        return self.node.children

    @property
    def dead(self):
        return self.node.dead

    def dump(self):
        self.node.dump()

    def findAncestor(self, *args, **kwargs):
        return self._wrapShowingMethod(self.node.findAncestor, *args, **kwargs)

    @property
    def focused(self):
        return self.node.focused

    @property
    def labeler(self):
        return self.node.labeler and Gtk4Node(self.node.labeler, self.coords)

    @property
    def name(self):
        return self.node.name

    @property
    def parent(self):
        return self.node.parent and Gtk4Node(self.node.parent, self.coords)

    @property
    def roleName(self):
        return self.node.roleName

    @property
    def selected(self):
        return self.node.getState().contains(pyatspi.STATE_SELECTED)

    @property
    def sensitive(self):
        return self.node.sensitive

    @property
    def showing(self):
        return self.node.visible

    @property
    def text(self):
        return self.node.text

    @text.setter
    def text(self, text):
        self.node.text = text

    def typeText(self, text):
        return self.node.typeText(text)

    @property
    def value(self):
        return self.node.value
