#!/usr/bin/python3
import os
import tempfile
import unittest
from unittest.mock import patch, ANY
from keyman_config import __version__
from keyman_config.get_kmp import InstallLocation
from keyman_config.install_kmp import InstallError, InstallKmp


class InstallKmpTests(unittest.TestCase):

    def setUp(self):
        patcher1 = patch('keyman_config.install_kmp.install_to_ibus')
        self.mockInstallToIbus = patcher1.start()
        self.addCleanup(patcher1.stop)
        patcher2 = patch('keyman_config.install_kmp.restart_ibus')
        self.mockRestartIbus = patcher2.start()
        self.addCleanup(patcher2.stop)
        patcher3 = patch('keyman_config.install_kmp.get_ibus_bus')
        self.mockGetIbusBus = patcher3.start()
        self.addCleanup(patcher3.stop)
        patcher4 = patch('keyman_config.install_kmp.GnomeKeyboardsUtil')
        self.mockGnomeKeyboardsUtilClass = patcher4.start()
        self.addCleanup(patcher4.stop)
        patcher5 = patch('keyman_config.install_kmp.get_keyboard_dir')
        self.mockGetKeyboardDir = patcher5.start()
        self.addCleanup(patcher5.stop)
        patcher6 = patch('keyman_config.install_kmp.extract_kmp')
        self.mockExtractKmp = patcher6.start()
        self.addCleanup(patcher6.stop)
        patcher7 = patch('keyman_config.install_kmp.extractico')
        self.mockExtractIco = patcher7.start()
        self.addCleanup(patcher7.stop)
        patcher8 = patch('keyman_config.install_kmp.is_gnome_shell')
        self.mockIsGnomeShell = patcher8.start()
        self.addCleanup(patcher8.stop)
        self.mockIsGnomeShell.return_value = False
        patcher9 = patch('keyman_config.install_kmp.is_fcitx_running')
        self.mockIsFcitxRunning = patcher9.start()
        self.addCleanup(patcher9.stop)
        self.mockIsFcitxRunning.return_value = False

    def test_InstallKeyboardsToIbus_NoIbus(self):
        # Setup
        self.mockGetIbusBus.return_value = None
        # Execute
        InstallKmp()._install_keyboards_to_ibus([], None)
        # Verify
        self.mockRestartIbus.assert_not_called()

    def test_InstallKeyboardsToIbus_SingleKbNoLanguages(self):
        # Setup
        bus = self.mockGetIbusBus.return_value
        keyboards = [{'id': 'foo1'}]
        # Execute
        InstallKmp()._install_keyboards_to_ibus(keyboards, 'fooDir')
        # Verify
        self.mockInstallToIbus.assert_called_once_with(ANY, 'fooDir/foo1.kmx')
        self.mockRestartIbus.assert_called_once()
        bus.destroy.assert_called_once()

    def test_InstallKeyboardsToIbus_MultipleKbsNoLanguages(self):
        # Setup
        bus = self.mockGetIbusBus.return_value
        keyboards = [{'id': 'foo1'}, {'id': 'foo2'}]
        # Execute
        InstallKmp()._install_keyboards_to_ibus(keyboards, 'fooDir')
        # Verify
        self.mockInstallToIbus.assert_any_call(ANY, 'fooDir/foo1.kmx')
        self.mockInstallToIbus.assert_any_call(ANY, 'fooDir/foo2.kmx')
        self.mockRestartIbus.assert_called_once()
        bus.destroy.assert_called_once()

    def test_InstallKeyboardsToIbus_SingleKbSingleLanguage(self):
        # Setup
        bus = self.mockGetIbusBus.return_value
        keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}]}]
        # Execute
        InstallKmp()._install_keyboards_to_ibus(keyboards, 'fooDir')
        # Verify
        self.mockInstallToIbus.assert_called_once_with(ANY, 'en:fooDir/foo1.kmx')
        self.mockRestartIbus.assert_called_once()
        bus.destroy.assert_called_once()

    def test_InstallKeyboardsToIbus_SingleKbMultipleLanguages(self):
        # Setup
        bus = self.mockGetIbusBus.return_value
        keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}, {'id': 'fr'}]}]
        # Execute
        InstallKmp()._install_keyboards_to_ibus(keyboards, 'fooDir')
        # Verify
        self.mockInstallToIbus.assert_called_once()
        self.mockInstallToIbus.assert_called_with(ANY, 'en:fooDir/foo1.kmx')
        # self.mockInstallToIbus.assert_not_called_with(ANY, 'fr:fooDir/foo1.kmx')
        self.mockRestartIbus.assert_called_once()
        bus.destroy.assert_called_once()

    def test_InstallKeyboardsToIbus_SingleKbMultipleLanguages_GivenLanguage(self):
        # Setup
        bus = self.mockGetIbusBus.return_value
        keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}, {'id': 'fr'}]}]
        # Execute
        InstallKmp()._install_keyboards_to_ibus(keyboards, 'fooDir', 'fr')
        # Verify
        self.mockInstallToIbus.assert_called_once()
        self.mockInstallToIbus.assert_called_with(ANY, 'fr:fooDir/foo1.kmx')
        # self.mockInstallToIbus.assert_not_called_with(ANY, 'en:fooDir/foo1.kmx')
        self.mockRestartIbus.assert_called_once()
        bus.destroy.assert_called_once()

    def test_InstallKeyboardsToIbus_SingleKbMultipleLanguages_OtherLanguage(self):
        # Setup
        bus = self.mockGetIbusBus.return_value
        keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}, {'id': 'fr'}]}]
        # Execute
        InstallKmp()._install_keyboards_to_ibus(keyboards, 'fooDir', 'de')
        # Verify
        self.mockInstallToIbus.assert_called_once()
        self.mockInstallToIbus.assert_called_with(ANY, 'de:fooDir/foo1.kmx')
        # self.mockInstallToIbus.assert_not_called_with(ANY, 'en:fooDir/foo1.kmx')
        self.mockRestartIbus.assert_called_once()
        bus.destroy.assert_called_once()

    def test_InstallKeyboardsToGnome_SingleKbNoLanguages(self):
        # Setup
        mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
        mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [('xkb', 'en')]
        keyboards = [{'id': 'foo1'}]
        # Execute
        InstallKmp()._install_keyboards_to_gnome(keyboards, 'fooDir')
        # Verify
        mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
            [('xkb', 'en'), ('ibus', 'fooDir/foo1.kmx')])
        self.mockRestartIbus.assert_not_called()

    def test_InstallKeyboardsToGnome_MultipleKbsNoLanguages(self):
        # Setup
        mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
        mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [('xkb', 'en')]
        keyboards = [{'id': 'foo1'}, {'id': 'foo2'}]
        # Execute
        InstallKmp()._install_keyboards_to_gnome(keyboards, 'fooDir')
        # Verify
        mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
            [('xkb', 'en'), ('ibus', 'fooDir/foo1.kmx'), ('ibus', 'fooDir/foo2.kmx')])
        self.mockRestartIbus.assert_not_called()

    def test_InstallKeyboardsToGnome_SingleKbSingleLanguage(self):
        # Setup
        mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
        mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [('xkb', 'en')]
        keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}]}]
        # Execute
        InstallKmp()._install_keyboards_to_gnome(keyboards, 'fooDir')
        # Verify
        mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
            [('xkb', 'en'), ('ibus', 'en:fooDir/foo1.kmx')])
        self.mockRestartIbus.assert_not_called()

    def test_InstallKeyboardsToGnome_SingleKbMultipleLanguages(self):
        # Setup
        mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
        mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [('xkb', 'en')]
        keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}, {'id': 'fr'}]}]
        # Execute
        InstallKmp()._install_keyboards_to_gnome(keyboards, 'fooDir')
        # Verify
        mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
            [('xkb', 'en'), ('ibus', 'en:fooDir/foo1.kmx')])
        self.mockRestartIbus.assert_not_called()

    def test_InstallKeyboardsToGnome_SingleKbMultipleLanguages_GivenLanguage(self):
        # Setup
        mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
        mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [('xkb', 'en')]
        keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}, {'id': 'fr'}]}]
        # Execute
        InstallKmp()._install_keyboards_to_gnome(keyboards, 'fooDir', 'fr')
        # Verify
        mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
            [('xkb', 'en'), ('ibus', 'fr:fooDir/foo1.kmx')])
        self.mockRestartIbus.assert_not_called()

    def test_InstallKeyboardsToGnome_SingleKbMultipleLanguages_OtherLanguage(self):
        # Setup
        mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
        mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [('xkb', 'en')]
        keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}, {'id': 'fr'}]}]
        # Execute
        InstallKmp()._install_keyboards_to_gnome(keyboards, 'fooDir', 'de')
        # Verify
        mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
            [('xkb', 'en'), ('ibus', 'de:fooDir/foo1.kmx')])
        self.mockRestartIbus.assert_not_called()

    def test_normalizeLanguage(self):
        # Setup
        languages = [
            {'id': 'de'},
            {'id': 'esi-Latn'},
            {'id': 'dyo'}
        ]

        for testcase in [
            {'given': 'de', 'expected': 'de'},
            {'given': 'esi', 'expected': 'esi-Latn'},
            {'given': 'esi-Latn', 'expected': 'esi-Latn'},
            {'given': 'es', 'expected': None},
            {'given': 'en', 'expected': None},
            {'given': None, 'expected': None},
            # #3399
            {'given': 'dyo-latn', 'expected': 'dyo-Latn'},
            {'given': 'dyo', 'expected': 'dyo-Latn'},
        ]:
            with self.subTest(data=testcase):
                # Execute
                result = InstallKmp()._normalize_language(languages, testcase['given'])

                # Verify
                self.assertEqual(result, testcase['expected'])

    def test_normalizeLanguage_noLanguages(self):
        # Setup
        languages = []

        # Execute
        result = InstallKmp()._normalize_language(languages, 'en')

        # Verify
        self.assertEqual(result, '')

    def _createEmptyKmp(self, workdir):
        kmpfile = os.path.join(workdir, 'foo.kmp')
        with open(kmpfile, 'w') as file:
            file.write('')
        return kmpfile

    def _createKmpJson(self, packagedir, fileVersionString):
        with open(os.path.join(packagedir, 'kmp.json'), 'w') as file:
            file.write('''{
                "system": {
                    "keymanDeveloperVersion": "15.0"
                    ''' + fileVersionString + '''
                },
                "files": [ {
                    "name": "khmer_angkor.kmx",
                    "description": "Keyboard Khmer Angkor"
                    }, {
                    "name": "kmp.json",
                    "description": "Package information (JSON)"
                    } ],
                "keyboards": [ {
                    "name": "Khmer Angkor",
                    "id": "khmer_angkor",
                    "version": "1.1",
                    "languages": [ {
                        "name": "Central Khmer (Khmer, Cambodia)",
                        "id": "km"
                        } ]}
                ]}''')

    def test_InstallKmp_FutureKeymanVersion(self):
        # Setup
        workdir = tempfile.TemporaryDirectory().name
        os.makedirs(workdir)
        packagedir = tempfile.TemporaryDirectory().name
        os.makedirs(packagedir)
        self.mockGetKeyboardDir.return_value = packagedir
        kmpfile = self._createEmptyKmp(workdir)
        self._createKmpJson(packagedir, ', "fileVersion": "99.0"')

        # Execute
        with self.assertRaises(InstallError) as context:
            InstallKmp()._install_kmp(kmpfile, False, 'km', InstallLocation.User)

        # Verify
        self.assertTrue('foo.kmp requires Keyman 99.0 or higher' in context.exception.message)
        self.mockInstallToIbus.assert_not_called()

    def test_InstallKmp(self):
        for testcase in [
            {'name': 'PreviousKeymanVersion', 'fileVersion': ', "fileVersion": "7.0"'},
            {'name': 'SameKeymanVersion', 'fileVersion': ', "fileVersion": "' + __version__ + '"'},
            {'name': 'NoFileVersion', 'fileVersion': ''}
        ]:
            with self.subTest(msg=testcase['name'], data=testcase['fileVersion']):
                # Setup
                self.mockInstallToIbus.reset_mock()
                workdir = tempfile.TemporaryDirectory().name
                os.makedirs(workdir)
                packagedir = tempfile.TemporaryDirectory().name
                os.makedirs(packagedir)
                self.mockGetKeyboardDir.return_value = packagedir
                kmpfile = self._createEmptyKmp(workdir)
                self._createKmpJson(packagedir, testcase['fileVersion'])

                # Execute
                InstallKmp()._install_kmp(kmpfile, False, 'km', InstallLocation.User)

                # Verify
                self.mockInstallToIbus.assert_called_once()


if __name__ == '__main__':
    unittest.main()
