# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018  Rickard Lindberg, Roger Lindberg
#
# This file is part of Timeline.
#
# Timeline is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Timeline 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Timeline.  If not, see <http://www.gnu.org/licenses/>.


"""
All data needed for configuration of shortcuts are collected in _menu_items_info
objects wich are of type
:class:`~timelinelib.config.menuiteminfo.MenuItemInfo` class.
These objects are created in the menu class to which they belong.
(:class:`~timelinelib.wxgui.frames.mainframe.menus.filemenu.FileMenu`,
:class:`~timelinelib.wxgui.frames.mainframe.menus.editmenu.EditMenu`,
:class:`~timelinelib.wxgui.frames.mainframe.menus.viewmenu.ViewMenu`,
:class:`~timelinelib.wxgui.frames.mainframe.menus.timelinemenu.TimelineMenu`,
:class:`~timelinelib.wxgui.frames.mainframe.menus.navigatemenu.NavigateMenu`,
:class:`~timelinelib.wxgui.frames.mainframe.menus.helpmenu.HelpMenu`)

The text in a menu item to the right of the menu label (or tab character) is called shortcut.
Examples of shortcuts: Ctrl+N, PgUp, Shift+Ctrl+X.

The shortcut, if it exists, consists of optional modifiers and a shortcut
key. So the format of a shortcut is: [modifier] [+ modifier]* shortcut_key.
"""


SHIFT_CTRL_MODIFIER = "Shift+Ctrl"
CTRL_MODIFIER = "Ctrl"
ALT_MODIFIER = "Alt"
NO_MODIFIER = ""
LABEL = "%s->%%s"
LABEL_FILE = LABEL % _("File")
LABEL_EDIT = LABEL % _("Edit")
LABEL_VIEW = LABEL % _("View")
LABEL_TIMELINE = LABEL % _("Timeline")
LABEL_NAVIGATE = LABEL % _("Navigate")
LABEL_HELP = LABEL % _("Help")
NAVLABEL = "%s(%s)->%%s"
LABEL_NAVIGATE_TIME = NAVLABEL % (_("Navigate"), "tm")
LABEL_NAVIGATE_NUM = NAVLABEL % (_("Navigate"), "num")
FUNCTION_KEYS = ["Up", "Down", "PgDn", "PgUp", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"]
SHORTCUT_KEYS = ["", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N",
                 "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
                 "1", "2", "3", "4", "5", "6", "7", "8", "9",
                 "+", "-",
                 ] + FUNCTION_KEYS
NON_EMPTY_MODIFIERS = ["Ctrl", "Alt", "Shift+Ctrl", "Shift+Alt", "Alt+Ctrl", "Shift+Alt+Ctrl"]
MODIFIERS = ["", ] + NON_EMPTY_MODIFIERS


class ShortcutController:
    """
    This class handles the configuration of the shortcut keys in all menu items.
    The user can edit the shortcut keys in the
    :class:`~timelinelib.wxgui.dialogs.shortcutseditor.view.ShortcutsEditorDialog` dialog.

    The user setting of shortcuts are stored in the timeline configuration file
    (:class:`~timelinelib.config.dotfile.Config`).

    :doc:`Tests are found here <unit_config_shortcut>`.
    """
    def __init__(self, config):
        self._menu_items_info = []
        self._config = config

    #
    # API for creating the menu_items_info list
    #

    def remove_menu_item_infos(self, label_prefix):
        """
        Remove all menu_item_info objects where the config_display_name starts with
        the given prefix.
        Used by the
        :class:`~timelinelib.wxgui.frames.mainframe.menus.navigatemenu.NavigateMenu`
        when a TimeType changes.
        Typically label_prefix == 'Navigate->'
        """
        self._menu_items_info = [item for item in self._menu_items_info
                                 if not item.config_display_name.startswith(label_prefix)]

    def add_menu_item_info(self, menu_item_info):
        """
        Append the given menu_item_info to  the _menu_item_infos list and
        update the shortcut for the menu_item, described by the menu_item_info
        and stor the new information in the timeline configuration file..
        """
        self._menu_items_info.append(menu_item_info)
        # Get shortcut from menu_item_info
        if menu_item_info.shortcut_modifiers != "":
            default_shortcut = "%s+%s" % (menu_item_info.shortcut_modifiers, menu_item_info.shortcut_key)
        else:
            default_shortcut = menu_item_info.shortcut_key
        # Get shortcut from user setting
        new_shortcut = self._config.get_shortcut_key(menu_item_info.config_key, default_shortcut)
        # If the shortcut not is used by any other menu item....
        if self._valid(new_shortcut, menu_item_info.wxid):
            try:
                menu_item_info.shortcut_modifiers, menu_item_info.shortcut_key = new_shortcut.rsplit("+", 1)
            except Exception as ex:
                menu_item_info.shortcut_modifiers = ""
                menu_item_info.shortcut_key = new_shortcut
        else:
            new_shortcut = "+"
        # Save the shortcut in the configuration file
        self._config.set_shortcut_key(menu_item_info.config_key, new_shortcut)
        # Update the menu item label with the shortcut
        self._set_menuitem_label(menu_item_info.menu_item, new_shortcut)

    #
    # Shortcut editor dialog API
    #

    def get_display_names(self):
        """Used by shortcut editor dialog to fill listbox."""
        return [menu_item_info.config_display_name for menu_item_info in self._menu_items_info]

    def get_modifiers(self):
        """Used by shortcut editor dialog to fill listbox."""
        return MODIFIERS

    def get_shortcut_keys(self):
        """Used by shortcut editor dialog to fill listbox."""
        return SHORTCUT_KEYS

    def get_modifier_and_key(self, display_name):
        """Used by shortcut editor dialog to fill controls for selected function in the listbox."""
        for menu_item_info in self._menu_items_info:
            if menu_item_info.config_display_name == display_name:
                return menu_item_info.shortcut_modifiers, menu_item_info.shortcut_key

    def edit(self, display_name, new_shortcut):
        """
        Used by shortcut editor dialog to change shortcut.

        Both the menu item and the user configuration file are updated.
        """
        menu_item_info = [menu_item_info for menu_item_info in self._menu_items_info
                          if menu_item_info.config_display_name == display_name][0]
        self._edit(menu_item_info.wxid, new_shortcut, menu_item_info.menu_item)

    def is_valid(self, modifier, shortcut_key):
        """Used by shortcut editor dialog to verify the combination of modifier and shortcut key."""
        if modifier == "":
            return shortcut_key in [""] + FUNCTION_KEYS
        else:
            return modifier in MODIFIERS and shortcut_key in SHORTCUT_KEYS[1:]

    def get_display_name(self, shortcut):
        """Used to display in message when shortcut already existing."""
        return [menu_item_info.config_display_name for menu_item_info in self._menu_items_info
                if menu_item_info.shortcut == shortcut][0]

    def exists(self, shortcut, wxid=None, display_name=None):
        """
        With wxid arg:
            Used by the _valid() function.
        With display_name arg:
            Used by shortcut editor dialog to verify that the shortcut is not used for some other function.
        """
        if display_name:
            for menu_item_info in self._menu_items_info:
                if menu_item_info.shortcut == shortcut and menu_item_info.config_display_name != display_name:
                    return True
        if wxid:
            for menu_item_info in self._menu_items_info:
                if menu_item_info.shortcut == shortcut and menu_item_info.wxid != wxid:
                    return True
        return False

    def save(self):
        self._config.write()

    #
    # Internals
    #

    def _edit(self, wxid, new_shortcut, menu_item):
        if new_shortcut == "":
            new_shortcut = "+"
        if self._valid(new_shortcut, wxid):
            menu_item_info = [menu_item_info for menu_item_info in self._menu_items_info
                              if menu_item_info.wxid == wxid][0]
            self._edit_shortcut(menu_item_info, new_shortcut, menu_item)

    def _valid(self, shortcut, wxid):
        if shortcut == "+":
            return True
        return not self.exists(shortcut, wxid)

    def _edit_shortcut(self, menu_item_info, new_shortcut, menu_item):
        try:
            menu_item_info.shortcut_modifiers, menu_item_info.shortcut_key = new_shortcut.rsplit("+", 1)
        except:
            menu_item_info.shortcut_modifiers, menu_item_info.shortcut_key = ("", new_shortcut)
        self._config.set_shortcut_key(menu_item_info.config_key, new_shortcut)
        self._set_menuitem_label(menu_item, new_shortcut)

    def _set_menuitem_label(self, menu_item, new_shortcut):
        label = menu_item.GetItemLabel()
        prefix = label.split("\t")[0]
        if new_shortcut in ("", "+"):
            new_label = prefix
        else:
            new_label = "%s\t%s" % (prefix, new_shortcut)
        menu_item.SetItemLabel(new_label)


def extract_parts_from_menu_label(menu_label):
    """
    A menu label can consist of the following parts
        * The label text
        * An accelerator key
        * The shortcut modifier (optional)
        * The shortcut key (optional

    Example:
        menu_label = "E&xit\tShift+Ctrl+X"

        In this example the
            - label text = Exit
            - accelerator key = x
            - shortcut modifier = Shift+Ctrl
            - shortcut key = X

    :doc:`Tests are found here <unit_config_shortcut>`.
    """
    label = menu_label.split('\t', 1)[0]
    try:
        inx = label.index('&')
        accelerator_key = label[inx + 1]
    except ValueError:
        accelerator_key = None
    label = label.replace('&', '')

    try:
        shortcut = menu_label.split('\t', 1)[1].split('+')
        shortcut_key = shortcut[-1]
        shortcut_modifiers = shortcut[:-1]
        if len(shortcut_modifiers) == 0:
            shortcut_modifiers = None
        else:
            shortcut_modifiers = '+'.join(shortcut_modifiers)
    except IndexError:
        shortcut_key = None
        shortcut_modifiers = None
    return label, accelerator_key, shortcut_modifiers, shortcut_key