#!/usr/bin/env python

# ###########################################################################
#
# This file is part of Taurus
#
# http://taurus-scada.org
#
# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
#
# Taurus is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Taurus is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Taurus.  If not, see <http://www.gnu.org/licenses/>.
#
# ###########################################################################

"""This module provides taurus-specific functions related to icons
"""

import os
import re
import sys
import json

from taurus.external.qt import Qt

from taurus.core.taurusbasetypes import TaurusElementType, TaurusDevState
from taurus.core.util.log import Logger

__docformat__ = "restructuredtext"


__LOGGER = Logger(__name__)

__INITIALIZED = False

# Default width, height and QSize constants
__DW = 70
__DH = 24

__DQS = Qt.QSize(__DW, __DH)
__1DQS = __DQS
__2DQS = Qt.QSize(2 * __DW, __DH)
__3DQS = Qt.QSize(3 * __DW, __DH)

# Indexes for the map below
__IDX_ELEM_TYPE_ICON, __IDX_ELEM_TYPE_SIZE, __IDX_ELEM_TYPE_TOOLTIP = list(
    range(3)
)

# New default role map
# Elements are: icon theme, preferred size, description/tooltip
_ELEM_TYPE_MAP = {
    TaurusElementType.Name: ("folder", __3DQS, None),
    TaurusElementType.Device: (
        "applications-system",
        Qt.QSize(210, __DH),
        "Tango device name",
    ),
    TaurusElementType.DeviceAlias: (
        "applications-system",
        Qt.QSize(140, __DH),
        "Tango device alias",
    ),
    TaurusElementType.Domain: (
        "folder",
        Qt.QSize(80, __DH),
        "Tango device domain",
    ),
    TaurusElementType.Family: (
        "folder",
        Qt.QSize(80, __DH),
        "Tango device family",
    ),
    TaurusElementType.Member: (
        "applications-system",
        Qt.QSize(80, __DH),
        "Tango device member",
    ),
    TaurusElementType.Server: (
        "application-x-executable",
        Qt.QSize(190, __DH),
        "Tango server",
    ),
    TaurusElementType.ServerName: (
        "application-x-executable",
        Qt.QSize(80, __DH),
        "Tango server name",
    ),
    TaurusElementType.ServerInstance: (
        "application-x-executable",
        Qt.QSize(80, __DH),
        "Tango server instance",
    ),
    TaurusElementType.DeviceClass: (
        "text-x-script",
        Qt.QSize(140, __DH),
        "Tango class name",
    ),
    TaurusElementType.Exported: (
        "start-here",
        Qt.QSize(60, __DH),
        "Alive/not alive",
    ),
    TaurusElementType.Host: (
        "network-server",
        Qt.QSize(100, __DH),
        "Host machine were last ran",
    ),
    TaurusElementType.Attribute: (
        ":/actions/format-text-bold.svg",
        Qt.QSize(100, __DH),
        "Attribute name",
    ),
}

# Indexes for the map below
__IDX_STATE_ICON, __IDX_STATE_TOOLTIP = list(range(2))

_STATE_MAP = {
    TaurusDevState.Ready: ("status:available.svg", "Element ready"),
    TaurusDevState.NotReady: ("status:not-available.svg", "Element not ready"),
    TaurusDevState.Undefined: (
        "status:not-known.svg",
        "Element state undefined",
    ),
}

# set of registered prefixes (updated by registerPathFiles() )
REGISTERED_PREFIXES = set()


def sanitizePrefix(prefix):
    """strips any leading '/' and substitutes non alphanumeric characters by
    '_'
    """
    prefix = prefix.lstrip("/")
    return re.sub("[^0-9a-zA-Z]+", "_", prefix)


def registerPathFiles(pathfilenames):
    """
    Use given .path files to update Qt's search path
    Each path file contains a json-encoded list of (prefix,path) tuples.
    This function will call Qt.QDir.addSearchPath with each of the tuples
    from the path files (prefix values will be sanitized first, and relative
    path values will be made relative to the dir containing the .path file)

    :param pathfilenames: list of .path file names
    :type pathfilenames: list<str>
    """
    for filename in pathfilenames:
        try:
            with open(filename) as f:
                pathmap = json.load(f)
        except Exception as e:
            __LOGGER.error('Error registering "%s": %r', filename, e)
            pathmap = []

        base_dir = os.path.abspath(os.path.dirname(filename))
        for prefix, path in pathmap:
            prefix = sanitizePrefix(prefix)
            path = os.path.join(
                base_dir, path
            )  # no effect if path is absolute
            Qt.QDir.addSearchPath(prefix, path)
            REGISTERED_PREFIXES.add(prefix)


def registerTheme(name="Tango", path="", force=False):
    """Use bundled them if OS does not define a theme (non-X11 systems)

    :param name: icon theme name (default=Tango)
    :type name: str
    :param path: path to dir containing the theme (absolute or relative to dir
        of taurus.qt.qtgui.icon). Default = ''
    :type path: str
    :param force: Force to set path even if a theme is already set
    :type force: bool
    """
    if force or not sys.platform.startswith("linux"):
        base_dir = os.path.abspath(os.path.dirname(__file__))
        path = os.path.join(base_dir, path)
        Qt.QIcon.setThemeSearchPaths([path])
        Qt.QIcon.setThemeName(name)
        __LOGGER.info("Setting %s icon theme (from %s)", name, path)


def getCachedPixmap(key, size=None):
    """Returns a PyQt5.Qt.QPixmap object for the given key and size.
    The key argument supports QDir's searchPath prefixes (see
    :meth:`QDir.setSearchPaths`).

    :param key: the pixmap key., e.g.: 'status:folder-open.svg'
    :type key: str
    :param size: the pixmap size in pixels (will get a square pixmap). If None
        is passed it will return the original size
    :type size: int
    :return:
    :rtype: PyQt5.Qt.QPixmap
    """

    name = key
    if size is not None:
        key += "_%sx%s" % (size, size)
    pm = Qt.QPixmapCache.find(key)
    if pm is None:
        pm = Qt.QPixmap(name)
        if size is not None:
            pm = pm.scaled(
                size, size, Qt.Qt.KeepAspectRatio, Qt.Qt.SmoothTransformation
            )
        Qt.QPixmapCache.insert(key, pm)
    return Qt.QPixmap(pm)


def getStandardIcon(key, widget=None):
    """Returns a PyQt5.Qt.QIcon object for the given key. Key should be a
    QStyle.StandardPixmap enumeration member. The widget argument is optional
    and can also be used to aid the determination of the icon.

    :param key: a standard pixmap which can follow some existing GUI style or
        guideline
    :type key: QStyle.StandardPixmap
    :param widget: the widget argument (optional) can also be used to aid the
        determination of the icon.
    :type widget: Qt.QWidget
    :return:
    :rtype: PyQt5.Qt.QIcon
    """
    styleOption = None
    if widget is not None:
        styleOption = Qt.QStyleOption()
        styleOption.initFrom(widget)
    style = Qt.QApplication.instance().style()
    return style.standardIcon(key, styleOption, widget)


def getElementTypeToolTip(elemType):
    data = _ELEM_TYPE_MAP.get(elemType)
    if data is None:
        return
    return data[__IDX_ELEM_TYPE_TOOLTIP]


def getElementTypeSize(elemType):
    data = _ELEM_TYPE_MAP.get(elemType)
    if data is None:
        return
    return data[__IDX_ELEM_TYPE_SIZE]


def getElementTypeIconName(elemType):
    """Gets an icon name string for the given
    :class:`taurus.core.taurusbasetypes.TaurusElementType`.

    If an icon name cannot be found for elemType, None is returned.

    :param elemType: the taurus element type
    :type elemType: TaurusElementType
    :return: a string representing the icon name for the given
        :class:`taurus.core.taurusbasetypes.TaurusElementType`
    :rtype: str
    """
    if elemType is None:
        return
    data = _ELEM_TYPE_MAP.get(elemType)
    if data is None:
        return
    return data[__IDX_ELEM_TYPE_ICON]


def getElementTypeIcon(elemType, fallback=None):
    """Gets a PyQt5.Qt.QIcon object for the given
    :class:`taurus.core.taurusbasetypes.TaurusElementType`.

    If an icon cannot be found for the given TaurusElementType,
    fallback is returned.

    :param elemType: the taurus element type
    :type elemType: TaurusElementType
    :param fallback: the fallback icon. Default is None.
    :type fallback: PyQt5.Qt.QIcon
    :return:
    :rtype: PyQt5.Qt.QIcon
    """

    themeIconName = getElementTypeIconName(elemType)
    icon = Qt.QIcon.fromTheme(themeIconName)
    if icon.isNull() and fallback is not None:
        icon = fallback
    return icon


def getElementTypePixmap(elemType, size=None):
    """Gets a PyQt5.Qt.QPixmap object for the given
    :class:`taurus.core.taurusbasetypes.TaurusElementType`.

    :param elemType: the taurus element type
    :type elemType: TaurusElementType
    :param size: the pixmap size in pixels (will get a square pixmap). Default
        is None meaning it will return the original size.
    :type size: int
    :return:
    :rtype: PyQt5.Qt.QPixmap or None
    """

    if elemType is None:
        return
    data = _ELEM_TYPE_MAP.get(elemType)
    if data is None:
        return
    themeKey = data[__IDX_ELEM_TYPE_ICON]
    return Qt.QIcon.fromTheme(themeKey, size).pixmap(size, size)


def getDevStateToolTip(state):
    data = _STATE_MAP.get(state)
    if data is None:
        return
    return data[__IDX_STATE_TOOLTIP]


def getDevStateIcon(state, fallback=None):
    """Gets a PyQt5.Qt.QIcon object for the given
    :class:`taurus.core.taurusbasetypes.TaurusDevState`.

    If an icon cannot be found for the given state, fallback is returned.

    :param state: the taurus device state
    :type state: TaurusDevState
    :param fallback: the fallback icon. Default is None.
    :type fallback: PyQt5.Qt.QIcon
    :return:
    :rtype: PyQt5.Qt.QIcon or None
    """
    if state is None:
        return
    data = _STATE_MAP.get(state)
    if data is None:
        return
    name = data[__IDX_STATE_ICON]

    icon = Qt.QIcon(name)
    if icon.isNull() and fallback is not None:
        icon = fallback
    return icon


def getDevStatePixmap(state, size=None):
    """Gets a PyQt5.Qt.QPixmap object for the given
    :class:`taurus.core.taurusbasetypes.TaurusDevState`.

    :param state: the taurus software device state
    :type state: TaurusDevState
    :param size: the pixmap size in pixels (will get a square pixmap). Default
        is None meaning it will return the original size.
    :type size: int
    :return:
    :rtype: PyQt5.Qt.QPixmap or None
    """
    if state is None:
        return None
    data = _STATE_MAP.get(state)
    if data is None:
        return None
    name = data[__IDX_STATE_ICON]
    return getCachedPixmap(name, size)


if __name__ == "__main__":
    from taurus.qt.qtgui.application import TaurusApplication

    app = TaurusApplication(cmd_line_parser=None)

    icons = [
        # null because of non-existent path
        Qt.QIcon("NONEXISTENT/PATH"),
        # null because of resource-style key (and no resources defined)
        Qt.QIcon(":/actions/edit-cut.svg"),
        # abs path
        Qt.QIcon(os.path.abspath("Tango/scalable/actions/edit-cut.svg")),
        # rel path
        Qt.QIcon("Tango/scalable/actions/edit-cut.svg"),
        # New-style (using prefix)
        Qt.QIcon("actions:edit-cut.svg"),
        Qt.QIcon("apps:preferences-system-session.svg"),
        Qt.QIcon("designer:devs_tree.png"),
        Qt.QIcon("actions:process-stop.svg"),  # from tango-icons/actions
        Qt.QIcon("actions:add.svg"),  # from rrze-icons/actions
        Qt.QIcon("actions:stop.svg"),  # from extra-icons/actions
        Qt.QIcon("logos:taurus.svg"),  # uses the "general",
        Qt.QIcon.fromTheme("computer"),
    ]

    w = Qt.QWidget()
    lyt = Qt.QVBoxLayout()
    w.setLayout(lyt)
    for icon in icons:
        button = Qt.QPushButton(icon, "kk")
        lyt.addWidget(button)

    w.show()

    sys.exit(app.exec_())
