#!/usr/bin/env python3
# This file is part of Xpra.
# Copyright (C) 2010 Nathaniel Smith <njs@pobox.com>
# Copyright (C) 2012-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.

import os
import sys
import binascii
from typing import Any
from collections.abc import Callable

from xpra.common import noop
from xpra.platform import platform_import
from xpra.util.str_fn import bytestostr
from xpra.log import Logger

_init_done = False


def init() -> None:
    # warning: we currently call init() from multiple places to try
    # to ensure we run it as early as possible..
    global _init_done
    if not _init_done:
        _init_done = True
        do_init()


def do_init() -> None:
    """ some platforms override this """


_ready_done = False


def ready() -> None:
    global _ready_done
    if not _ready_done:
        _ready_done = True
        do_ready()


def do_ready() -> None:
    """ some platforms override this """


_default_icon = "xpra.png"


def set_default_icon(icon_filename: str) -> None:
    global _default_icon
    _default_icon = icon_filename


def get_default_icon():
    global _default_icon
    return _default_icon


def force_focus(duration=2000) -> None:
    # only implemented on macos
    assert isinstance(duration, int)


def use_stdin() -> bool:
    stdin = sys.stdin
    return bool(stdin) and stdin.isatty()


def get_clipboard_native_class() -> str:
    return ""


# defaults:
def get_native_tray_menu_helper_class() -> type | None:
    # classes that generate menus for xpra's system tray
    # let the toolkit classes use their own
    return None


def get_native_tray_classes(*_args) -> list[type]:
    # the classes we can use for our system tray:
    # let the toolkit classes use their own
    return []


def get_native_system_tray_classes(*_args) -> list[type]:
    # the classes we can use for application system tray forwarding:
    # let the toolkit classes use their own
    return []


def system_bell(*_args) -> bool:
    # let the toolkit classes use their own
    return False


def get_native_notifier_classes() -> list[type]:
    return []


def get_session_type() -> str:
    return ""


def get_xdpi() -> int:
    return -1


def get_ydpi() -> int:
    return -1


def get_monitors_info(xscale=1, yscale=1) -> dict[int, Any]:
    from xpra.gtk.info import get_monitors_info
    return get_monitors_info(xscale, yscale)


def get_icon_size() -> int:
    xdpi = get_xdpi()
    ydpi = get_ydpi()
    if xdpi > 0 and ydpi > 0:
        dpi = round((xdpi + ydpi) / 2)
    else:
        dpi = 96
    if dpi > 144:
        return 48
    if dpi > 120:
        return 32
    if dpi > 96:
        return 24
    return 16


def get_antialias_info() -> dict[str, Any]:
    return {}


def get_display_icc_info() -> dict[str, Any]:
    # per display info
    return {}


def get_icc_info() -> dict[str, Any]:
    return default_get_icc_info()


def default_get_icc_info() -> dict[str, Any]:
    ENV_ICC_DATA = os.environ.get("XPRA_ICC_DATA")
    if ENV_ICC_DATA:
        return {
            "source": "environment-override",
            "data": binascii.unhexlify(ENV_ICC_DATA),
        }
    return get_pillow_icc_info()


def get_pillow_icc_info() -> dict[str, Any]:
    screenlog = Logger("screen")
    info = {}
    try:
        from PIL import ImageCms
        from PIL.ImageCms import get_display_profile
        INTENT_STR: dict[Any, str] = {}
        for x in ("PERCEPTUAL", "RELATIVE_COLORIMETRIC", "SATURATION", "ABSOLUTE_COLORIMETRIC"):
            intent = getattr(ImageCms, "Intent", None)
            if intent:
                v = getattr(intent, x, None)
            else:
                v = getattr(ImageCms, "INTENT_%s" % x, None)
            if v:
                INTENT_STR[v] = x.lower().replace("_", "-")
        screenlog("get_icc_info() intents=%s", INTENT_STR)
        p = get_display_profile()  # NOSONAR @SuppressWarnings("python:S5727")
        screenlog("get_icc_info() display_profile=%s", p)
        if p:

            def getDefaultIntentStr(v) -> str:
                return INTENT_STR.get(v, "unknown")

            def getData(v) -> bytes:
                return v.tobytes()

            for (k, fn, conv) in (
                    ("name", "getProfileName", None),
                    ("info", "getProfileInfo", None),
                    ("copyright", "getProfileCopyright", None),
                    ("manufacturer", "getProfileManufacturer", None),
                    ("model", "getProfileModel", None),
                    ("description", "getProfileDescription", None),
                    ("default-intent", "getDefaultIntent", getDefaultIntentStr),
                    ("data", "getData", getData),
            ):
                m = getattr(ImageCms, fn, None)
                if m is None:
                    screenlog("%s lacks %s", ImageCms, fn)
                    continue
                try:
                    v = m(p)
                    if conv:
                        v = conv(v)
                    info[k] = bytestostr(v).rstrip("\n\r")
                except Exception as e:
                    screenlog("get_icc_info()", exc_info=True)
                    screenlog("ICC profile error on %s using %s: %s", k, fn, e)
    except Exception as e:
        screenlog("get_icc_info()", exc_info=True)
        screenlog.warn("Warning: cannot query ICC profiles:")
        screenlog.warn(" %s", e)
    return info


# global workarea for all screens
def get_workarea():
    return None


# per monitor workareas (assuming a single screen)
def get_workareas():
    return ()


def get_number_of_desktops() -> int:
    return 1


def get_desktop_names() -> tuple[str, ...]:
    return ()


def get_vrefresh() -> int:
    return -1


def get_mouse_config():
    return {}


def get_double_click_time() -> int:
    return -1


def get_double_click_distance() -> tuple[int, int]:
    return -1, -1


def get_fixed_cursor_size() -> tuple[int, int]:
    return -1, -1


def get_cursor_size() -> int:
    return -1


def get_window_min_size() -> tuple[int, int]:
    return 0, 0


def get_window_max_size() -> tuple[int, int]:
    return 2 ** 15 - 1, 2 ** 15 - 1


def get_window_frame_size(_x, _y, _w, _h):
    return None


def get_window_frame_sizes():
    return {}


def add_window_hooks(_window) -> None:
    """
    To add platform specific code to each window,
    called when the window is created.
    """


def remove_window_hooks(_window) -> None:
    """
    Remove the hooks,
    called when the window is destroyed
    """


def show_desktop(_show) -> None:
    """ If possible, show the desktop """


def set_fullscreen_monitors(_window, _fsm, _source_indication: int = 0) -> None:
    """ Only overridden by posix """


def set_shaded(_window, _shaded: bool) -> None:
    """
    GTK never exposed the 'shaded' window attribute,
    posix clients will hook it up here.
    """


def pointer_grab(_window):
    """
    Pointer grabs require platform specific code
    """
    return False


def pointer_ungrab(_window):
    """
    Pointer grabs require platform specific code
    """
    return False


def gl_check() -> str:
    return ""  # no problem


def get_wm_name() -> str:
    return ""


def can_access_display() -> bool:
    return True


def set_window_progress(window, pct: int) -> None:
    """ some platforms can indicate progress for a specific window """


take_screenshot: Callable = noop
ClientExtras = None


def get_info_base() -> dict[str, Any]:
    def fname(v):
        try:
            return v.__name__
        except AttributeError:
            return str(v)

    def fnames(l):
        return [fname(x) for x in l]

    return {
        "native-clipboard": get_clipboard_native_class(),
        "native_tray_menu_helper": fname(get_native_tray_menu_helper_class()),
        "native_trays": fnames(get_native_tray_classes()),
        "native_system_trays": fnames(get_native_system_tray_classes()),
        "system_bell": fname(system_bell),
        "native_notifiers": fnames(get_native_notifier_classes()),
        "wm_name": get_wm_name() or "",
        "workarea": get_workarea() or "",
        "workareas": get_workareas(),
        "monitors": get_monitors_info(),
        "desktops": get_number_of_desktops(),
        "desktop_names": get_desktop_names(),
        "session-type": get_session_type(),
        "vertical-refresh": get_vrefresh(),
        "fixed_cursor_size": get_fixed_cursor_size(),
        "cursor_size": get_cursor_size(),
        "icon_size": get_icon_size(),
        "mouse": get_mouse_config(),
        "double_click": {
            "time": get_double_click_time(),
            "distance": get_double_click_distance(),
        },
        "dpi": {
            "x": get_xdpi(),
            "y": get_ydpi(),
        },
        "antialias": get_antialias_info(),
        "icc": get_icc_info(),
        "display-icc": get_display_icc_info(),
        "window_frame": get_window_frame_sizes(),
        "can_access_display": can_access_display(),
    }


get_info = get_info_base

platform_import(globals(), "gui", False,
                "do_ready",
                "do_init",
                "force_focus",
                "gl_check",
                "use_stdin",
                "get_wm_name",
                "show_desktop", "set_fullscreen_monitors", "set_shaded",
                "pointer_grab", "pointer_ungrab",
                "ClientExtras",
                "take_screenshot",
                "get_clipboard_native_class",
                "get_native_tray_menu_helper_class",
                "get_native_tray_classes",
                "get_native_system_tray_classes",
                "get_native_notifier_classes",
                "get_session_type",
                "get_vrefresh", "get_workarea", "get_workareas",
                "get_number_of_desktops", "get_desktop_names",
                "get_antialias_info", "get_icc_info", "get_display_icc_info", "get_xdpi", "get_ydpi",
                "get_monitors_info",
                "get_icon_size",
                "get_window_min_size", "get_window_max_size",
                "get_mouse_config",
                "get_double_click_time", "get_double_click_distance",
                "get_fixed_cursor_size", "get_cursor_size", "get_window_frame_sizes",
                "add_window_hooks", "remove_window_hooks",
                "system_bell",
                "can_access_display",
                "set_window_progress",
                "get_info",
                )


def main() -> int:
    from xpra.platform import program_context
    from xpra.util.str_fn import print_nested_dict
    from xpra.os_util import OSX, POSIX
    from xpra.log import enable_color
    with program_context("GUI-Properties"):
        enable_color()
        init()
        verbose = "-v" in sys.argv or "--verbose" in sys.argv
        if verbose:
            from xpra.log import get_all_loggers
            for x in get_all_loggers():
                x.enable_debug()

        # naughty, but how else can I hook this up?
        if POSIX and not OSX:
            from xpra.x11.bindings.posix_display_source import init_posix_display_source
            init_posix_display_source()
        i = get_info()
        print_nested_dict(i, hex_keys=("data", "icc-data", "icc-profile"))
    return 0


if __name__ == "__main__":
    sys.exit(main())
