#!/usr/bin/env python3
# _*_ coding:utf-8 _*_

# SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.

# SPDX-License-Identifier: GPL-2.0-only
# pylint: disable=C0114
import re
from configparser import ConfigParser
from threading import Thread
from time import sleep

import dbus

from setting.globalconfig import GlobalConfig
from src  import logger
from src.cmdctl import CmdCtl
from src.custom_exception import ApplicationStartError
from src.custom_exception import GetWindowInformation
from src.custom_exception import NoSetReferencePoint
from src.mouse_key import MouseKey


class ButtonCenter:
    """
    根据应用程序中控件元素的相对坐标，通过配置元素的x、y、w和h来定位元素在屏幕中的位置，并返回用于鼠标和键盘操作的坐标。
    """
    # pylint: disable=too-many-arguments,too-many-locals,too-many-public-methods
    __author__ = "Mikigo <huangmingqiang@uniontech.com>, Litao <litaoa@uniontech.com>"

    def __init__(self, app_name: str, config_path: str, number: int = -1, pause: int = 1):
        """
        :param app_name: 系统应用软件包，例如，dde-file-manager
        :param config_path: ui 定位配置文件路径（绝对路径）
        :param number: 默认为 -1, 即最后一个窗口
            如果你想指定不同的窗口，你可以在实例化对象的时候显式的传入 number，第一个为 0
        """
        self.app_name = app_name
        self.number = number
        # 每个操作步骤之前暂停的时间
        self.pause = pause
        self.config_path = config_path

    def window_info(self) -> [str, dbus.Dictionary]:
        """
         窗口信息
        :return:  窗口的基本信息，左上角坐标，窗口宽高等
        """
        if GlobalConfig.IS_X11:
            try:
                # Get window_operate ID based on package name
                app_id = CmdCtl.run_cmd(
                    f"xdotool search --classname --onlyvisible {self.app_name}",
                    interrupt=False,
                    out_debug_flag=False,
                    command_log=False,
                ).split("\n")
                app_id_list = [int(_id) for _id in app_id if _id]
                app_id_list.sort()
                logger.debug(f"app_id_list: {app_id_list}")
                return CmdCtl.run_cmd(
                    f"xwininfo -id {app_id_list[self.number]}",
                    interrupt=False,
                    out_debug_flag=False,
                    command_log=False,
                )
            except Exception as exc:
                raise ApplicationStartError(f"{self.app_name, exc}") from exc

        elif GlobalConfig.IS_WAYLAND:

            def click():
                sleep(1)
                MouseKey.click()

            # 移动到当前窗口
            proxy_object = dbus.SessionBus().get_object("org.kde.KWin", "/dde")
            # 移动鼠标到目标窗口
            dbus.Interface(proxy_object, "org.kde.KWin").WindowMove()
            # 鼠标为激活状态，无法进行下一步，点击一下，释放窗口
            click()
            # KWin 获取窗口信息的接口
            proxy_object = dbus.SessionBus().get_object("org.kde.KWin", "/KWin")
            # 使用子线程进行点击
            task = Thread(target=click, args=())
            task.start()
            # 获取窗口信息的接口，鼠标为十字，阻塞状态，需要点击对应窗口才能返回窗口信息
            info = dbus.Interface(proxy_object, "org.kde.KWin").queryWindowInfo()
            sleep(0.5)
            click()
            return info
        return None

    def window_location_and_sizes(self):
        """
         获取窗口的位置及大小
        :return:
        """
        try:
            app_window_info = self.window_info()

            if GlobalConfig.IS_X11:
                re_pattern = re.compile(r"Absolute.*:\s\s(-?\d+)")
                result = re.findall(re_pattern, app_window_info)
                window_width = re.findall(r"Width.*:\s(\d+)", app_window_info)[0]
                window_height = re.findall(r"Height.*:\s(\d+)", app_window_info)[0]
                if not result:
                    sleep(1)
                    app_window_info = self.window_info()
                    result = re.findall(re_pattern, app_window_info)
                    window_width = re.findall(r"Width.*:\s(\d+)", app_window_info)[0]
                    window_height = re.findall(r"Height.*:\s(\d+)", app_window_info)[0]
                window_x, window_y = result
            # wayland
            else:
                window_width = int(app_window_info.get("width"))
                window_height = int(app_window_info.get("height"))
                window_x = int(app_window_info.get("x"))
                window_y = int(app_window_info.get("y"))
            logger.debug(
                f"窗口左上角坐标 {window_x, window_y},获取窗口大小 {window_width}*{window_height}"
            )
            return (int(window_x), int(window_y), int(window_width), int(window_height))
        except (IndexError, KeyError) as exc:
            raise GetWindowInformation(f"获取窗口大小错误 {exc}") from exc

    def window_left_top_position(self) -> tuple:
        """
         获取窗口左上角坐标
        :return:  (0, 0)
        """
        try:
            re_pattern = re.compile(r"Absolute.*:\s\s(-?\d+)")
            app_window_info = self.window_info()
            if GlobalConfig.IS_X11:
                result = re.findall(re_pattern, app_window_info)
                if not result:
                    sleep(1)
                    result = re.findall(re_pattern, self.window_info())
                window_x, window_y = result
            else:
                window_x = int(app_window_info.get("x"))
                window_y = int(app_window_info.get("y"))
            logger.debug(f"窗口左上角坐标 {window_x, window_y}")
            return int(window_x), int(window_y)
        except (ValueError, KeyError) as exc:
            raise GetWindowInformation(f"获取窗口左上角坐标错误 {exc}") from exc

    def window_sizes(self) -> tuple:
        """
         获取窗口的大小
        :return:  (宽， 高)， 例如：(400, 600)
        """
        try:
            app_window_info = self.window_info()
            if GlobalConfig.IS_X11:
                window_width = re.findall(r"Width.*:\s(\d+)", app_window_info)[0]
                window_height = re.findall(r"Height.*:\s(\d+)", app_window_info)[0]
            else:
                window_width = int(app_window_info.get("width"))
                window_height = int(app_window_info.get("height"))
            logger.debug(f"获取窗口大小 {window_width}*{window_height}")
            return int(window_width), int(window_height)
        except (IndexError, KeyError) as exc:
            raise GetWindowInformation(f"获取窗口大小错误 {exc}") from exc

    def window_left_bottom_position(self) -> tuple:
        """
         左下角的坐标
        :return:  (0, 1080)
        """
        (
            window_x,
            window_y,
            _window_width,
            window_height,
        ) = self.window_location_and_sizes()
        left_y = window_y + window_height
        logger.debug(f"窗口左下角坐标 {window_x, left_y}")
        return int(window_x), int(left_y)

    def window_right_top_position(self) -> tuple:
        """
         右上角的坐标
        :return:  (1920, 0)
        """
        (
            window_x,
            window_y,
            window_width,
            _window_height,
        ) = self.window_location_and_sizes()
        right_x = window_x + window_width
        logger.debug(f"窗口右上角坐标 {right_x, window_y}")
        return int(right_x), int(window_y)

    def window_right_bottom_position(self) -> tuple:
        """
         右下角的坐标
        :return:  (1920, 1080)
        """
        (
            window_x,
            window_y,
            window_width,
            window_height,
        ) = self.window_location_and_sizes()
        right_x = window_x + window_width
        right_y = window_y + window_height
        logger.debug(f"窗口右下角坐标 {right_x, right_y}")
        return int(right_x), int(right_y)

    def window_left_center_position(self) -> tuple:
        """
         获取窗口左边界中心坐标
        :return:  (0, 540)
        """
        (
            window_x,
            window_y,
            _window_width,
            window_height,
        ) = self.window_location_and_sizes()
        center_y = window_y + window_height / 2
        logger.debug(f"窗口左边界中心坐标 {window_x, center_y}")
        return int(window_x), int(center_y)

    def window_top_center_position(self) -> tuple:
        """
         获取窗口上边界中心坐标
        :return:  (960, 0)
        """
        (
            window_x,
            window_y,
            window_width,
            _window_height,
        ) = self.window_location_and_sizes()
        center_x = window_x + window_width / 2
        logger.debug(f"获取窗口上边界中心坐标 {center_x, window_y}")
        return int(center_x), int(window_y)

    def window_right_center_position(self) -> tuple:
        """
         获取窗口右边界中心坐标
        :return:  (1920, 540)
        """
        (
            window_x,
            window_y,
            window_width,
            window_height,
        ) = self.window_location_and_sizes()
        right_x = window_x + window_width
        center_y = window_y + window_height / 2
        logger.debug(f"获取窗口右边界中心坐标 {right_x, center_y}")
        return int(right_x), int(center_y)

    def window_bottom_center_position(self) -> tuple:
        """
         获取窗口下边界中心的坐标
        :return:  (960, 1080)
        """
        (
            window_x,
            window_y,
            window_width,
            window_height,
        ) = self.window_location_and_sizes()
        center_x = window_x + window_width / 2
        bottom_y = window_y + window_height
        logger.debug(f"获取窗口下边界中心的坐标 {center_x, bottom_y}")
        return int(center_x), int(bottom_y)

    def window_center(self) -> tuple:
        """
         获取窗口的中心点坐标
        :return:  (960, 540)
        """
        (
            window_x,
            window_y,
            window_width,
            window_height,
        ) = self.window_location_and_sizes()
        _x = window_x + window_width / 2
        _y = window_y + window_height / 2
        logger.debug(f"窗口中心坐标 {_x, _y}")
        return _x, _y

    def btn_center_by_left_top(self, button_x, button_y, button_w, button_h) -> tuple:
        """
         根据左上角的坐标按钮的中心坐标
        :param button_x: 控件左上角相对于窗口左上角的横向距离
        :param button_y: 控件左上角相对于窗口左上角的纵向距离
        :param button_w: 控件宽度
        :param button_h: 控件高度
        :return:  控件的中心坐标 （1, 1）
        """
        window_x, window_y = self.window_left_top_position()
        b_x = window_x + button_x + button_w / 2
        b_y = window_y + button_y + button_h / 2
        logger.debug(f"左上角按钮的中心坐标 {b_x, b_y}")
        return b_x, b_y

    def btn_center_by_right_top(self, button_x, button_y, button_w, button_h) -> tuple:
        """
         根据右上角的坐标按钮的中心坐标
        :param button_x: 控件右上角相对于窗口右上角的横向距离
        :param button_y: 控件右上角相对于窗口右上角的纵向距离
        :param button_w: 控件宽度
        :param button_h: 控件高度
        :return:  控件的中心坐标 （1, 1）
        """
        window_x, window_y = self.window_right_top_position()
        b_x = window_x - button_x - button_w / 2
        b_y = window_y + button_y + button_h / 2
        logger.debug(f"右上角按钮的中心坐标 {b_x, b_y}")
        return b_x, b_y

    def btn_center_by_left_bottom(
            self, button_x, button_y, button_w, button_h
    ) -> tuple:
        """
         根据左下角的坐标按钮的中心坐标
        :param button_x: 控件左下角相对于窗口左下角的横向距离
        :param button_y: 控件左下角相对于窗口左下角的纵向距离
        :param button_w: 控件宽度
        :param button_h: 控件高度
        :return:  控件的中心坐标 （1, 1）
        """
        window_x, window_y = self.window_left_bottom_position()
        b_x = window_x + button_x + button_w / 2
        b_y = window_y - button_y - button_h / 2
        logger.debug(f"左下角按钮的中心坐标 {b_x, b_y}")
        return b_x, b_y

    def btn_center_by_right_bottom(
            self, button_x, button_y, button_w, button_h
    ) -> tuple:
        """
         根据右下角的坐标按钮的中心坐标
        :param button_x: 控件右下角相对于窗口右下角的横向距离
        :param button_y: 控件右下角相对于窗口右下角的纵向距离
        :param button_w: 控件宽度
        :param button_h: 控件高度
        :return:  控件的中心坐标 （1, 1）
        """
        window_x, window_y = self.window_right_bottom_position()
        b_x = window_x - button_x - button_w / 2
        b_y = window_y - button_y - button_h / 2
        logger.debug(f"右下角按钮的中心坐标 {b_x, b_y}")
        return b_x, b_y

    def btn_pic_by_left_top(self, button_x, button_y, button_w, button_h) -> tuple:
        """
         根据左上角的坐标按钮的截图区域
        :param button_x: 控件左上角相对于窗口左上角的横向距离
        :param button_y: 控件左上角相对于窗口左上角的纵向距离
        :param button_w: 控件宽度
        :param button_h: 控件高度
        :return:  控件的中心坐标 （1, 1）
        """
        window_x, window_y = self.window_left_top_position()
        b_x = window_x + button_x
        b_y = window_y + button_y
        logger.debug(f"左上角按钮的截取区域左上角 {b_x, b_y}, 控件长宽 {button_w, button_h}")
        return b_x, b_y, button_w, button_h

    def btn_pic_by_right_top(self, button_x, button_y, button_w, button_h) -> tuple:
        """
         根据右上角的坐标按钮的截图区域
        :param button_x: 控件右上角相对于窗口右上角的横向距离
        :param button_y: 控件右上角相对于窗口右上角的纵向距离
        :param button_w: 控件宽度
        :param button_h: 控件高度
        :return:  控件的中心坐标 （1, 1）
        """
        window_x, window_y = self.window_right_top_position()
        b_x = window_x - button_x - button_w
        b_y = window_y + button_y
        logger.debug(f"右上角按钮的截取区域左上角 {b_x, b_y}, 控件长宽 {button_w, button_h}")
        return b_x, b_y, button_w, button_h

    def btn_pic_by_left_bottom(self, button_x, button_y, button_w, button_h) -> tuple:
        """
         根据左下角的坐标按钮的截图区域
        :param button_x: 控件左下角相对于窗口左下角的横向距离
        :param button_y: 控件左下角相对于窗口左下角的纵向距离
        :param button_w: 控件宽度
        :param button_h: 控件高度
        :return:  控件的中心坐标 （1, 1）
        """
        window_x, window_y = self.window_left_bottom_position()
        b_x = window_x + button_x
        b_y = window_y - button_y - button_h
        logger.debug(f"左下角按钮的截取区域左上角 {b_x, b_y}, 控件长宽 {button_w, button_h}")
        return b_x, b_y, button_w, button_h

    def btn_pic_by_right_bottom(self, button_x, button_y, button_w, button_h) -> tuple:
        """
         根据右下角的坐标按钮的截图区域
        :param button_x: 控件右下角相对于窗口右下角的横向距离
        :param button_y: 控件右下角相对于窗口右下角的纵向距离
        :param button_w: 控件宽度
        :param button_h: 控件高度
        :return:  控件的中心坐标 （1, 1）
        """
        window_x, window_y = self.window_right_bottom_position()
        b_x = window_x - button_x - button_w
        b_y = window_y - button_y - button_h
        logger.debug(f"右下角按钮的截取区域左上角 {b_x, b_y}, 控件长宽 {button_w, button_h}")
        return b_x, b_y, button_w, button_h

    def btn_center(
            self,
            btn_name,
            offset_x=None,
            multiplier_x=None,
            offset_y=None,
            multiplier_y=None,
    ) -> tuple:
        """
         获取元素的中心坐标
        :param btn_name: 控件名
        :param offset_x
            正数为右移动
            负数为左移动
        :param multiplier_x
            offset_x 移动的倍数
        :param offset_y
            正数为上移动
            负数为下移动
        :param multiplier_y
            offset_y 移动的倍数
        """
        btn_x = btn_y = ""
        sleep(self.pause)
        conf = ConfigParser()
        if isinstance(self.config_path, list):
            for config in self.config_path:
                conf.read(config)
        elif isinstance(self.config_path, str):
            conf.read(self.config_path)
        else:
            raise ValueError
        direction = conf.get(btn_name, "direction")
        position = [int(i.strip()) for i in conf.get(btn_name, "location").split(",")]
        default_point = ("left_bottom", "left_top", "right_top", "right_bottom")
        default_boundary_point = (
            "top_center",
            "bottom_center",
            "left_center",
            "right_center",
        )
        if direction in default_point:
            btn_x, btn_y = getattr(self, f"btn_center_by_{direction}")(*position)
        elif direction in default_boundary_point:
            window_x, window_y = getattr(self, f"window_{direction}_position")()
            # pylint: disable=eval-used
            btn_x = eval(
                f"{window_x} + {position[0]} {'+' if position[0] > 0 else '-'} {position[2] / 2}"
            )
            # pylint: disable=eval-used
            btn_y = eval(
                f"{window_y} + {position[1]} {'+' if position[1] > 0 else '-'} {position[3] / 2}"
            )
        elif direction == "window_size":
            btn_x, btn_y, button_w, button_y = self.window_location_and_sizes()
            btn_x = btn_x + button_w / 2
            btn_y = btn_y + button_y / 2
        if btn_x and btn_y:
            if offset_x:
                btn_x = btn_x + int(offset_x) * (
                    int(multiplier_x) if multiplier_x else 1
                )
            if offset_y:
                btn_y = btn_y + int(offset_y) * (
                    int(multiplier_y) if multiplier_y else 1
                )
            logger.debug(f"[{btn_name}] 坐标：{str(btn_x)}, {str(btn_y)})")
            return btn_x, btn_y
        raise NoSetReferencePoint(
            f"{direction}, 默认参考点 {default_point + default_boundary_point}"
        )

    def btn_size(
            self,
            btn_name: str,
            offset_x: [int, float] = None,
            multiplier_x: [int, float] = None,
            offset_y: [int, float] = None,
            multiplier_y: [int, float] = None,
    ) -> tuple:
        """
         获取元素的左上角坐标及长宽
        :param btn_name: 控件名
        :param offset_x
            正数为右移动
            负数为左移动
        :param multiplier_x
            offset_x 移动的倍数
        :param offset_y
            正数为上移动
            负数为下移动
        :param multiplier_y
            offset_y 移动的倍数
        """
        btn_x = btn_y = button_w = button_y = ""
        sleep(self.pause)
        conf = ConfigParser()
        conf.read(self.config_path)
        direction = conf.get(btn_name, "direction")
        position = [int(i.strip()) for i in conf.get(btn_name, "location").split(",")]
        default_point = ("left_bottom", "left_top", "right_top", "right_bottom")
        default_boundary_point = (
            "top_center",
            "bottom_center",
            "left_center",
            "right_center",
        )
        if direction in default_point:
            btn_x, btn_y, button_w, button_y = getattr(self, f"btn_pic_by_{direction}")(
                *position
            )
        elif direction in default_boundary_point:
            window_x, window_y = getattr(self, f"window_{direction}_position")()
            btn_x = window_x + position[0] - (0 if position[0] > 0 else position[2])
            btn_y = window_y + position[1] - (0 if position[1] > 0 else position[3])
            button_w, button_y = position[2], position[3]
        elif direction == "window_size":
            btn_x, btn_y, button_w, button_y = self.window_location_and_sizes()
        if btn_x != "" and btn_y != "":
            if offset_x:
                btn_x = btn_x + int(offset_x) * (
                    int(multiplier_x) if multiplier_x else 1
                )
            if offset_y:
                btn_y = btn_y + int(offset_y) * (
                    int(multiplier_y) if multiplier_y else 1
                )
            logger.debug(
                f"[{btn_name}] 左上角坐标：{str(btn_x)}, {str(btn_y)}), 长宽 {button_w, button_y}"
            )
            return btn_x, btn_y, button_w, button_y
        raise NoSetReferencePoint(
            f"{direction}, 默认参考点 {default_point + default_boundary_point}"
        )

    def btn_info(self, btn_name: str) -> tuple:
        """
         元素的相对位置和参考系
        :param btn_name: 控件名称
        :return: (相对坐标，参考系）
        """
        conf = ConfigParser()
        conf.read(self.config_path)
        direction = conf.get(btn_name, "direction")
        position = [int(i.strip()) for i in conf.get(btn_name, "location").split(",")]
        return position, direction

    @classmethod
    def get_windows_number(cls, name: str) -> int:
        """
         获取应用所有窗口数量
        :param name: 应用包名
        :return: int 窗口数量
        """
        cmd = f"xdotool search --classname {name}"
        app_id = CmdCtl.run_cmd(
            cmd, interrupt=False, out_debug_flag=False, command_log=False
        )
        return len([i for i in app_id.split("\n") if i])

    @classmethod
    def get_windows_id(cls, name: str) -> list:
        """
         获取活动应用窗口ID
        :param name: 应用包名
        :return: 窗口编号列表
        """
        cmd = f"xdotool search --onlyvisible --classname {name}"
        app_id = CmdCtl.run_cmd(
            cmd, interrupt=False, out_debug_flag=False, command_log=False
        )
        if app_id:
            return [i for i in app_id.split("\n") if i]
        raise ApplicationStartError(app_id)

    def focus_windows(self, app_name: str = None):
        """
         窗口置顶并聚焦
        :param app_name: 应用包名
        """
        if GlobalConfig.IS_WAYLAND:
            return
        app_id = self.get_windows_id(app_name if app_name else self.app_name)
        windows = int(app_id[self.number])
        cmd = f"xdotool windowactivate {windows}"
        CmdCtl.run_cmd(cmd, interrupt=False, out_debug_flag=False, command_log=False)
        logger.debug(f"<{app_name}> 窗口置顶并聚焦")

    @staticmethod
    def get_lastest_window_id(app_name: str) -> int:
        """
         获取应用的所有窗口编号，并返回编号最大的窗口ID
        :return: 返回最新创建的窗口编号
        """
        try:
            app_id = CmdCtl.run_cmd(
                f"xdotool search --classname --onlyvisible {app_name}",
                interrupt=False,
                out_debug_flag=False,
                command_log=False,
            ).split("\n")
            app_id_list = [int(_id) for _id in app_id if _id]  # to int
            app_id_list.sort()
            return app_id_list[-1]
        except Exception as exc:
            raise ApplicationStartError(f"{app_name, exc}") from exc
