# 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/>.


import unittest
from unittest.mock import Mock
import wx

from timelinelib.config.dotfile import Config
from timelinelib.config.shortcut import ShortcutController
import timelinelib.config.shortcut as sc
from timelinelib.config.menuiteminfo import MenuItemInfo
from timelinelib.config.shortcut import MODIFIERS, SHORTCUT_KEYS
from timelinelib.config.shortcut import extract_parts_from_menu_label


class describe_shortcut_controller(unittest.TestCase):

    def test_two_menu_items_cannot_have_the_same_shortcut(self):
        """
        The latest added menu item info will have it's shortcut removed if it
        is already used by some other menu item info.
        """
        # Given
        menu_item_info1 = MenuItemInfo(wxid=1,
                                       menu_item=self.a_menu_item('Foo\tCtrl+X'),
                                       config_key='info1',
                                       shortcut_modifier='Ctrl',
                                       shortcut_key='X')
        menu_item_info2 = MenuItemInfo(wxid=2,
                                       menu_item=self.a_menu_item('Bar\tCtrl+X'),
                                       config_key='info2',
                                       shortcut_modifier='Ctrl',
                                       shortcut_key='X')
        # When
        self.shortcut_controller.add_menu_item_info(menu_item_info1)
        self.shortcut_controller.add_menu_item_info(menu_item_info2)
        # Then
        menu_item_info1.menu_item.SetItemLabel.assert_called_with('Foo\tCtrl+X')
        menu_item_info2.menu_item.SetItemLabel.assert_called_with("Bar")
        self.config.set_shortcut_key.assert_any_call("info1", "Ctrl+X")
        self.config.set_shortcut_key.assert_any_call("info2", "+")

    def test_config_setting_overrides_menu_item_info(self):
        # Given
        menu_item_info1 = MenuItemInfo(wxid=1,
                                       menu_item=self.a_menu_item('Foo\tCtrl+A'),
                                       shortcut_modifier='Ctrl',
                                       shortcut_key='A')
        self.config.get_shortcut_key.return_value = 'Ctrl+X'
        #  When
        self.shortcut_controller.add_menu_item_info(menu_item_info1)
        # Then
        menu_item_info1.menu_item.SetItemLabel.assert_called_with('Foo\tCtrl+X')

    def test_can_return_display_names_list(self):
        # Given
        menu_item_info1 = MenuItemInfo(wxid=1,
                                       menu_item=self.a_menu_item('Foo\tCtrl+X'),
                                       config_key='info1',
                                       config_display_name='Info1',
                                       shortcut_modifier='Ctrl',
                                       shortcut_key='X')
        menu_item_info2 = MenuItemInfo(wxid=2,
                                       menu_item=self.a_menu_item('Bar\tCtrl+X'),
                                       config_key='info2',
                                       config_display_name='Info2',
                                       shortcut_modifier='Ctrl',
                                       shortcut_key='X')
        self.shortcut_controller.add_menu_item_info(menu_item_info1)
        self.shortcut_controller.add_menu_item_info(menu_item_info2)
        # Then
        self.assertEqual(['Info1', 'Info2'], self.shortcut_controller.get_display_names())

    def test_can_return_valid_modifiers(self):
        self.assertEqual(MODIFIERS, self.shortcut_controller.get_modifiers())

    def test_can_return_valid_shortcut_keys(self):
        self.assertEqual(SHORTCUT_KEYS, self.shortcut_controller.get_shortcut_keys())

    def test_shortcut_can_be_found_by_display_name(self):
        # Given
        menu_item_info1 = MenuItemInfo(wxid=1,
                                       menu_item=self.a_menu_item('Foo\tCtrl+X'),
                                       config_key='info1',
                                       config_display_name='Info1',
                                       shortcut_modifier='Ctrl',
                                       shortcut_key='X')
        menu_item_info2 = MenuItemInfo(wxid=2,
                                       menu_item=self.a_menu_item('Bar\tCtrl+Y'),
                                       config_key='info2',
                                       config_display_name='Info2',
                                       shortcut_modifier='Ctrl',
                                       shortcut_key='Y')
        self.shortcut_controller.add_menu_item_info(menu_item_info1)
        self.shortcut_controller.add_menu_item_info(menu_item_info2)
        # Then
        self.assertEqual(('Ctrl', 'X'), self.shortcut_controller.get_modifier_and_key('Info1'))
        self.assertEqual(('Ctrl', 'Y'), self.shortcut_controller.get_modifier_and_key('Info2'))

    def test_valid_shortcuts(self):
        self.assertTrue(self.shortcut_controller.is_valid('', ''))
        self.assertTrue(self.shortcut_controller.is_valid('', 'F1'))
        for modifier in sc.NON_EMPTY_MODIFIERS:
            self.assertTrue(self.shortcut_controller.is_valid(modifier, "N"))

    def test_invalid_shortcuts(self):
        self.assertFalse(self.shortcut_controller.is_valid('', 'A'))
        self.assertFalse(self.shortcut_controller.is_valid("", "N"))
        self.assertFalse(self.shortcut_controller.is_valid("Ctrl", ""))
        self.assertFalse(self.shortcut_controller.is_valid("Ctrl+", "N"))
        self.assertFalse(self.shortcut_controller.is_valid("+", "N"))
        self.assertFalse(self.shortcut_controller.is_valid("+", ""))

    def test_display_name_can_be_found_by_shortcut(self):
        # Given
        menu_item_info1 = MenuItemInfo(wxid=1,
                                       menu_item=self.a_menu_item('Foo\tCtrl+X'),
                                       config_key='info1',
                                       config_display_name='Info1',
                                       shortcut_modifier='Ctrl',
                                       shortcut_key='X')
        menu_item_info2 = MenuItemInfo(wxid=2,
                                       menu_item=self.a_menu_item('Bar\tCtrl+Y'),
                                       config_key='info2',
                                       config_display_name='Info2',
                                       shortcut_modifier='Ctrl',
                                       shortcut_key='Y')
        self.shortcut_controller.add_menu_item_info(menu_item_info1)
        self.shortcut_controller.add_menu_item_info(menu_item_info2)
        # Then
        self.assertEqual('Info1', self.shortcut_controller.get_display_name('Ctrl+X'))
        self.assertEqual('Info2', self.shortcut_controller.get_display_name('Ctrl+Y'))

    def test_can_edit_shortcut(self):
        # Given
        menu_item_info = MenuItemInfo(menu_item=self.a_menu_item(label="Menu Test"),
                                      config_key="shortcut-test-function",
                                      config_display_name="shortcut-test-function")
        self.shortcut_controller.add_menu_item_info(menu_item_info)
        # When
        self.shortcut_controller.edit(menu_item_info.config_display_name, "Ctrl+Alt+V")
        # Then
        self.config.set_shortcut_key.assert_called_with(menu_item_info.config_key, "Ctrl+Alt+V")
        menu_item_info.menu_item.SetItemLabel.assert_called_with(f'Menu Test\tCtrl+Alt+V')

    def test_can_edit_shortcut_without_modifiers(self):
        # Given
        menu_item_info = MenuItemInfo(menu_item=self.a_menu_item(label="Menu Test"),
                                      config_key="shortcut-test-function",
                                      config_display_name="shortcut-test-function")
        self.shortcut_controller.add_menu_item_info(menu_item_info)
        # When
        self.shortcut_controller.edit(menu_item_info.config_display_name, "F1")
        # Then
        self.config.set_shortcut_key.assert_called_with(menu_item_info.config_key, "F1")
        menu_item_info.menu_item.SetItemLabel.assert_called_with(f'Menu Test\tF1')

    def test_can_remove_shortcut(self):
        # Given
        menu_item_info = MenuItemInfo(menu_item=self.a_menu_item(),
                                      config_key="shortcut-test-function",
                                      config_display_name="shortcut-test-function")
        self.shortcut_controller.add_menu_item_info(menu_item_info)
        # When
        self.shortcut_controller.edit(menu_item_info.config_display_name, "")
        # Then
        self.config.set_shortcut_key.assert_called_with(menu_item_info.config_key, "+")
        menu_item_info.menu_item.SetItemLabel.assert_called_with(f'Menu item')

    def test_shortcut_used_by_other_function_can_be_detected(self):
        # Given
        self.config.get_shortcut_key.return_value = 'Ctrl+Alt+V'
        menu_item_info1 = MenuItemInfo(menu_item=self.a_menu_item(label="Menu Test 1"),
                                       shortcut_key="V",
                                       shortcut_modifier="Ctrl+Alt",
                                       config_display_name="display-name1")
        menu_item_info2 = MenuItemInfo(menu_item=self.a_menu_item(label="Menu Test 2"),
                                       shortcut_key="V",
                                       shortcut_modifier="Ctrl+Alt",
                                       config_display_name="display-name2")
        # When
        self.shortcut_controller.add_menu_item_info(menu_item_info1)
        self.shortcut_controller.add_menu_item_info(menu_item_info2)
        # Then
        self.assertTrue(self.shortcut_controller.exists(f'{menu_item_info1.shortcut_modifiers}+{menu_item_info1.shortcut_key}',
                                                        display_name=menu_item_info1.config_display_name))

    def setUp(self):
        self.config = Mock(Config)
        self.config.get_shortcut_key.return_value = 'Ctrl+X'
        self.shortcut_controller = ShortcutController(self.config)


    def a_menu_item(self, label="Menu item\tCtrl+N"):
        menu_item = Mock(wx.MenuItem)
        menu_item.GetItemLabel.return_value = label
        return menu_item


class describe_function_extract_parts_from_menu_label(unittest.TestCase):

    def test_only_text(self):
        self.assertEqual(('Exit', None, None, None), extract_parts_from_menu_label("Exit"))
        self.assertEqual(('File', None, None, None), extract_parts_from_menu_label("File"))

    def test_text_and_accelerator(self):
        self.assertEqual(('Exit', 'x', None, None), extract_parts_from_menu_label("E&xit"))
        self.assertEqual(('File', 'F', None, None), extract_parts_from_menu_label("&File"))

    def test_text_and_shortcut_key(self):
        self.assertEqual(('Help', None, None, 'F1'), extract_parts_from_menu_label("Help\tF1"))

    def test_text_and_shortcut_modifier_and_key(self):
        self.assertEqual(('New', None, 'Ctrl', 'N'), extract_parts_from_menu_label("New\tCtrl+N"))
        self.assertEqual(('New', None, 'Alt+Shift+Ctrl', 'N'), extract_parts_from_menu_label("New\tAlt+Shift+Ctrl+N"))

    def test_text_accelerator_and_shortcut_modifier_and_key(self):
        self.assertEqual(('New', 'N', 'Alt+Shift+Ctrl', 'N'), extract_parts_from_menu_label("&New\tAlt+Shift+Ctrl+N"))
