# !/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Oct 31 16:23:13 2018

@author: samschott
"""
import sys
import os
import os.path as osp
import platform
import re
import pkg_resources
from packaging.version import Version

from PyQt5 import QtWidgets, QtCore, QtGui

_icon_provider = QtWidgets.QFileIconProvider()
_tmp_file_for_ext = dict()


def resource_path(name):
    folder = getattr(sys, '_MEIPASS', pkg_resources.resource_filename('maestral_qt', 'resources'))
    return osp.join(folder, name)


def _get_gnome_version():
    # this may not work in restricted environments such as snap, docker, etc

    gnome3_config_path = '/usr/share/gnome/gnome-version.xml'
    gnome2_config_path = '/usr/share/gnome-about/gnome-version.xml'

    xml = None

    for path in (gnome2_config_path, gnome3_config_path):
        try:
            with open(path) as f:
                xml = f.read()
        except OSError:
            pass

    if xml:
        p = re.compile(r'<platform>(?P<maj>\d+)</platform>\s+<minor>'
                       r'(?P<min>\d+)</minor>\s+<micro>(?P<mic>\d+)</micro>')
        m = p.search(xml)
        version = '{0}.{1}.{2}'.format(m.group('maj'), m.group('min'), m.group('mic'))

        return version
    else:
        return '0.0.0'


APP_ICON_PATH = resource_path('maestral.png')
TRAY_ICON_DIR_SVG = resource_path('tray-icons-svg')
TRAY_ICON_DIR_PNG = resource_path('tray-icons-png')
TRAY_ICON_PATH_SVG = osp.join(TRAY_ICON_DIR_SVG, 'maestral-icon-{0}-{1}.svg')
TRAY_ICON_PATH_PNG = osp.join(TRAY_ICON_DIR_PNG, 'maestral-icon-{0}-{1}.png')

THEME_ICON_NAME = 'maestral-icon-{}'
THEME_ICON_NAME_SYMBOLIC = 'maestral-icon-{}-symbolic'

FACEHOLDER_PATH = resource_path('faceholder.png')

FOLDERS_DIALOG_PATH = resource_path('folders_dialog.ui')
SETUP_DIALOG_PATH = resource_path('setup_dialog.ui')
SETTINGS_WINDOW_PATH = resource_path('settings_window.ui')
UNLINK_DIALOG_PATH = resource_path('unlink_dialog.ui')
RELINK_DIALOG_PATH = resource_path('relink_dialog.ui')
SYNC_ISSUES_WINDOW_PATH = resource_path('sync_issues_window.ui')
SYNC_ISSUE_WIDGET_PATH = resource_path('sync_issue_widget.ui')
SYNC_EVENT_WIDGET_PATH = resource_path('sync_event_widget.ui')

THEME_DARK = 'dark'
THEME_LIGHT = 'light'

GNOME_VERSION = _get_gnome_version()


def _get_desktop():
    """
    Determines the current desktop environment. This is used for instance to decide
    which keyring backend is preferred to store the auth token.

    :returns: 'gnome', 'kde', 'xfce', 'cocoa', '' or any other string if the desktop
        $XDG_CURRENT_DESKTOP if the desktop environment is not known to us.
    :rtype: str
    """

    if platform.system() == 'Linux':
        current_desktop = os.environ.get('XDG_CURRENT_DESKTOP', '').lower()
        desktop_session = os.environ.get('GDMSESSION', '').lower()

        for desktop in ('gnome', 'kde', 'xfce', 'mate'):
            if desktop in current_desktop or desktop in desktop_session:
                return desktop

        return 'unknown'

    elif platform.system() == 'Darwin':
        return 'cocoa'


DESKTOP = _get_desktop()


def native_item_icon(item_path):
    """Returns the system icon for the given file or folder. If there is no item at the
    given path, the systems default file icon will be returned.

    :param str item_path: Path to local item.
    """
    while not osp.exists(item_path):
        # We create a temporary file with the given extension to get the correct icon
        # this is necessary because QFileIconProvider has no API to get an icon for a
        # file extension.
        _, extension = osp.splitext(item_path)
        try:
            item_path = _tmp_file_for_ext[extension]
        except KeyError:
            tmp_file = QtCore.QTemporaryFile(osp.join(QtCore.QDir.tempPath(),
                                                      f'XXXXXX{extension}'))
            tmp_file.setAutoRemove(False)
            # open and close to create
            tmp_file.open()
            tmp_file.close()

            item_path = tmp_file.fileName()
            _tmp_file_for_ext[extension] = item_path

    return _icon_provider.icon(QtCore.QFileInfo(item_path))


def native_folder_icon():
    """Returns the system's default folder icon."""
    # use a real folder here because Qt may otherwise
    # return the wrong folder icon in some cases
    return _icon_provider.icon(QtCore.QFileInfo('/usr'))


def native_file_icon():
    """Returns the system's default file icon."""
    return _icon_provider.icon(_icon_provider.File)


# noinspection PyCallByClass,PyArgumentList
def system_tray_icon(status, geometry=None):
    """Returns the system tray icon for the given status. The following icons
    will be used:

    1) macOS: Black SVG icons with transparent background. macOS will adapt the appearance
       as necessary. This only works reliably in Qt >= 5.15 which is not yet released.
    2) Linux: Any icons installed in the system theme. Maestral will prefer "symbolic"
       icons named "maestral-status-{status}-symbolic" over regular icons named
       "maestral-status-{status}". It will fall back to manually setting the icon in a
       color which contrasts the background color of the status bar.

    :param str status: Maestral status. Must be 'idle', 'syncing', 'paused',
        'disconnected' 'info' or 'error'.
    :param geometry: Tray icon geometry on screen. If given, this location will be used to
        to determine the system tray background color. This argument is ignored on macOS.
    """
    allowed_status = ('idle', 'syncing', 'paused', 'disconnected', 'info', 'error')
    if status not in allowed_status:
        raise ValueError(f'status must be in {allowed_status}')

    if platform.system() == 'Darwin':
        # use SVG icon with automatic color
        icon = QtGui.QIcon(TRAY_ICON_PATH_SVG.format(status, 'dark'))
        icon.setIsMask(True)

    else:

        icon_color = 'light' if is_dark_status_bar(geometry) else 'dark'

        if Version(QtCore.QT_VERSION_STR) < Version('5.13.0'):
            # use PNG icon with contrasting color (see issue #46)
            fallback_icon = QtGui.QIcon(TRAY_ICON_PATH_PNG.format(status, icon_color))
        else:
            # use SVG icon with contrasting color
            fallback_icon = QtGui.QIcon(TRAY_ICON_PATH_SVG.format(status, icon_color))

        theme_icon_name = THEME_ICON_NAME.format(status)
        theme_icon_name_symbolic = THEME_ICON_NAME_SYMBOLIC.format(status)

        # Prefer "symbolic" icons where the appearance is adapted by the platform
        # automatically. Specs for symbolic icons and their use in the system tray
        # vary between plaforms.

        if QtGui.QIcon.hasThemeIcon(theme_icon_name_symbolic):
            icon = QtGui.QIcon.fromTheme(theme_icon_name_symbolic, fallback_icon)
        elif QtGui.QIcon.hasThemeIcon(theme_icon_name):
            icon = QtGui.QIcon.fromTheme(theme_icon_name, fallback_icon)
        else:
            icon = QtGui.QIcon(fallback_icon)

    return icon


# noinspection PyArgumentList
def systray_theme(icon_geometry=None):
    """
    Returns one of THEME_LIGHT or THEME_DARK, corresponding to the current status bar
    color.

    ``icon_geometry`` provides the geometry (location and dimensions) of the tray icon.
    If not given, we try to guess the location of the system tray.
    """

    # --------------------- check for the status bar color -------------------------

    # see if we can trust returned pixel colors
    # (work around for a bug in Qt with KDE where all screenshots return black)

    c0 = _pixel_at(10, 10)
    c1 = _pixel_at(300, 400)
    c2 = _pixel_at(800, 800)

    if not c0 == c1 == c2 == (0, 0, 0):  # we can trust pixel colors from screenshots

        if not icon_geometry or icon_geometry.isEmpty():
            # guess the location of the status bar

            rec_screen = QtWidgets.QDesktopWidget().screenGeometry()
            rec_available = QtWidgets.QDesktopWidget().availableGeometry()

            # convert to QRegion for subtraction
            region_screen = QtGui.QRegion(rec_screen)
            region_available = QtGui.QRegion(rec_available)

            # subtract and convert back to QRect
            rects_diff = region_screen.subtracted(region_available).rects()
            if len(rects_diff) > 0:
                # there seems to be a task bar
                taskBarRect = rects_diff[0]
            else:
                taskBarRect = rec_screen

            px = taskBarRect.left() + 2
            py = taskBarRect.bottom() - 2

        else:  # use the given location from icon_geometry
            px = icon_geometry.left()
            py = icon_geometry.bottom()

        # get pixel luminance from icon corner or status bar
        pixel_rgb = _pixel_at(px, py)
        lum = rgb_to_luminance(*pixel_rgb)

        return THEME_LIGHT if lum >= 0.4 else THEME_DARK

    else:
        # -------------------- give up, default to dark ----------------------------------
        return THEME_DARK


def is_dark_status_bar(icon_geometry=None):
    """Detects the current status bar brightness and returns ``True`` for a dark status
    bar. ``icon_geometry`` provides the geometry (location and dimensions) of the tray
    icon. If not given, we try to guess the location of the system tray."""
    return systray_theme(icon_geometry) == THEME_DARK


def rgb_to_luminance(r, g, b, base=256):
    """
    Calculates luminance of a color, on a scale from 0 to 1, meaning that 1 is the
    highest luminance. r, g, b arguments values should be in 0..256 limits, or base
    argument should define the upper limit otherwise.
    """
    return (0.2126*r + 0.7152*g + 0.0722*b)/base


# noinspection PyArgumentList
def _pixel_at(x, y):
    """
    Returns (r, g, b) color code for a pixel with given coordinates (each value is in
    0..256 limits)
    """

    desktop_id = QtWidgets.QApplication.desktop().winId()
    screen = QtWidgets.QApplication.primaryScreen()
    color = screen.grabWindow(desktop_id, x, y, 1, 1).toImage().pixel(0, 0)

    return ((color >> 16) & 0xff), ((color >> 8) & 0xff), (color & 0xff)
