# -*- coding: utf-8 -*-

# Copyright (C) 2013-2018 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties 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 this program; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#

from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals

import operator

import libcomps
import libdnf.transaction

import dnf.comps
import dnf.exceptions
import dnf.persistor
import dnf.util

import tests.support
from tests.support import mock


TRANSLATION = u"""Tato skupina zahrnuje nejmenší možnou množinu balíčků. Je vhodná například na instalace malých routerů nebo firewallů."""


class LangsTest(tests.support.TestCase):
    @mock.patch('locale.getlocale', return_value=('cs_CZ', 'UTF-8'))
    def test_get(self, _unused):
        langs = dnf.comps._Langs().get()
        self.assertEqual(langs, ['cs_CZ.UTF-8', 'cs_CZ', 'cs.UTF-8', 'cs', 'C'])


class CompsTest(tests.support.TestCase):
    def setUp(self):
        comps = dnf.comps.Comps()
        comps._add_from_xml_filename(tests.support.COMPS_PATH)
        self.comps = comps

    def test_by_pattern(self):
        comps = self.comps
        self.assertLength(comps.groups_by_pattern('Base'), 1)
        self.assertLength(comps.groups_by_pattern('*'), tests.support.TOTAL_GROUPS)
        self.assertLength(comps.groups_by_pattern('Solid*'), 1)

        group = dnf.util.first(comps.groups_by_pattern('Base'))
        self.assertIsInstance(group, dnf.comps.Group)

    def test_categories(self):
        cat = self.comps.categories[0]
        self.assertEqual(cat.name_by_lang['cs'], u'Základ systému')
        self.assertEqual(cat.desc_by_lang['de'],
                         u'Verschiedene Kernstücke des Systems.')
        self.assertCountEqual((id_.name for id_ in cat.group_ids),
                              ('base', ))
        self.assertCountEqual((id_.default for id_ in cat.group_ids),
                              (False, ))
        self.assertTrue(all(isinstance(grp, dnf.comps.Group)
                            for grp in cat.groups_iter()))

    def test_environments(self):
        env = self.comps.environments[0]
        self.assertEqual(env.name_by_lang['cs'], u'Prostředí Sugar')
        self.assertEqual(env.desc_by_lang['de'],

                         u'Eine Software-Spielwiese zum Lernen des Lernens.')
        self.assertCountEqual((id_.name for id_ in env.group_ids),
                              ('somerset', 'Peppers'))
        self.assertEqual(2, len(env.mandatory_groups))
        self.assertTrue(all(isinstance(grp, dnf.comps.Group)
                            for grp in env.mandatory_groups))
        self.assertCountEqual((id_.default for id_ in env.group_ids),
                              (True, False))
        self.assertCountEqual((id_.name for id_ in env.option_ids),
                              ('base',))
        self.assertEqual(1, len(env.optional_groups))
        self.assertTrue(all(isinstance(grp, dnf.comps.Group)
                            for grp in env.optional_groups))
        self.assertTrue(all(isinstance(grp, dnf.comps.Group)
                            for grp in env.groups_iter()))

    def test_groups(self):
        g = self.comps.group_by_pattern('base')
        self.assertTrue(g.visible)
        g = self.comps.group_by_pattern('somerset')
        self.assertFalse(g.visible)

    def test_group_packages(self):
        g = self.comps.group_by_pattern('base')
        self.assertCountEqual(map(operator.attrgetter('name'), g.packages_iter()),
                              ('tour', 'pepper'))

    def test_iteration(self):
        comps = self.comps
        self.assertEqual([g.name for g in comps.groups_iter()],
                         ['Base', 'Solid Ground', "Pepper's", "Broken Group", None])
        self.assertEqual([c.name for c in comps.categories_iter()],
                         ['Base System'])
        g = dnf.util.first(comps.groups_iter())
        self.assertEqual(g.desc_by_lang['cs'], TRANSLATION)

    def test_group_display_order(self):
        self.assertEqual([g.name for g in self.comps.groups],
                         ["Pepper's", 'Base', 'Solid Ground', 'Broken Group', None])

    def test_packages(self):
        comps = self.comps
        group = dnf.util.first(comps.groups_iter())
        self.assertSequenceEqual([pkg.name for pkg in group.packages],
                                 (u'pepper', u'tour'))
        self.assertSequenceEqual([pkg.name for pkg in group.mandatory_packages],
                                 (u'pepper', u'tour'))

    def test_size(self):
        comps = self.comps
        self.assertLength(comps, 7)
        self.assertLength(comps.groups, tests.support.TOTAL_GROUPS)
        self.assertLength(comps.categories, 1)
        self.assertLength(comps.environments, 1)

    @mock.patch('locale.getlocale', return_value=('cs_CZ', 'UTF-8'))
    def test_ui_name(self, _unused):
        comps = self.comps
        group = dnf.util.first(comps.groups_by_pattern('base'))
        self.assertEqual(group.ui_name, u'Kritická cesta (Základ)')

    @mock.patch('locale.getlocale', return_value=('cs_CZ', 'UTF-8'))
    def test_ui_desc(self, _unused):
        comps = self.comps
        env = dnf.util.first(comps.environments_by_pattern('sugar-*'))
        self.assertEqual(env.ui_description, u'Software pro výuku o vyučování.')


class PackageTest(tests.support.TestCase):
    def test_instance(self):
        lc_pkg = libcomps.Package('weather', libcomps.PACKAGE_TYPE_OPTIONAL)
        pkg = dnf.comps.Package(lc_pkg)
        self.assertEqual(pkg.name, 'weather')
        self.assertEqual(pkg.option_type, dnf.comps.OPTIONAL)


class TestTransactionBunch(tests.support.TestCase):

    def test_adding(self):
        t1 = dnf.comps.TransactionBunch()
        t1.install = {'right'}
        t1.upgrade = {'tour'}
        t1.remove = {'pepper'}
        t2 = dnf.comps.TransactionBunch()
        t2.install = {'pepper'}
        t2.upgrade = {'right'}
        t1 += t2
        self.assertTransEqual(t1.install, ('right', 'pepper'))
        self.assertTransEqual(t1.upgrade, ('tour', 'right'))
        self.assertEmpty(t1.remove)


class SolverGroupTest(tests.support.DnfBaseTestCase):

    REPOS = []
    COMPS = True
    COMPS_SOLVER = True

    def test_install(self):
        group_id = 'base'
        trans = self.solver._group_install(group_id, dnf.comps.MANDATORY)
        self.assertLength(trans.install, 2)
        self._swdb_commit()

        swdb_group = self.history.group.get(group_id)
        self.assertCountEqual([i.getName() for i in swdb_group.getPackages()], ['pepper', 'tour'])
        self.assertEqual(swdb_group.getPackageTypes(), dnf.comps.MANDATORY)

    def test_removable_pkg(self):
        comps_group = self.comps.group_by_pattern('base')
        self.solver._group_install(comps_group.id, dnf.comps.MANDATORY, [])

        tsis = []

        pkg1 = self.base.sack.query().filter(name="pepper", epoch=0, version="20", release="0", arch="x86_64")[0]
        self.history.rpm.add_install(pkg1, reason=libdnf.transaction.TransactionItemReason_GROUP)

        pkg3 = self.base.sack.query().filter(name="tour", version="5", release="0", arch="noarch")[0]
        self.history.rpm.add_install(pkg3, reason=libdnf.transaction.TransactionItemReason_GROUP)

        group_id = "dupl"
        swdb_group = self.history.group.new(group_id, group_id, group_id, dnf.comps.DEFAULT)
        swdb_group.addPackage("tour", True, dnf.comps.MANDATORY)
        self.history.group.install(swdb_group)
        self._swdb_commit(tsis)

        # pepper is in single group with reason "group"
        self.assertTrue(self.solver._removable_pkg('pepper'))
        # right's reason is "dep"
        self.assertFalse(self.solver._removable_pkg('right'))
        # tour appears in more than one group
        self.assertFalse(self.solver._removable_pkg('tour'))

        swdb_group = self.history.group.get(group_id)
        self.history.group.remove(swdb_group)

        # tour appears only in one group now
        self.assertTrue(self.solver._removable_pkg('tour'))

    def test_remove(self):
        grp = self.comps.group_by_pattern('base')
        self.solver._group_install(grp.id, dnf.comps.MANDATORY, [])

        grps = self.history.group.search_by_pattern('base')
        for grp in grps:
            self.solver._group_remove(grp)

        # need to load groups again - loaded object is stays the same
        grps = self.history.group.search_by_pattern('base')
        for grp in grps:
            self.assertFalse(grp.installed)

    def test_upgrade(self):
        # setup of the "current state"
        group_id = 'base'

        swdb_group = self.history.group.new(group_id, group_id, group_id, dnf.comps.MANDATORY)
        for pkg_name in ['pepper', 'handerson']:
            swdb_group.addPackage(pkg_name, True, dnf.comps.MANDATORY)
        self.history.group.install(swdb_group)
        self._swdb_commit()

        swdb_group = self.history.group.get(group_id)
        self.assertCountEqual([i.getName() for i in swdb_group.getPackages()], ('handerson', 'pepper'))

        comps_group = self.comps.group_by_pattern(group_id)

        trans = self.solver._group_upgrade(group_id)
        self.assertTransEqual(trans.install, ('tour',))
        self.assertTransEqual(trans.remove, ('handerson',))
        self.assertTransEqual(trans.upgrade, ('pepper',))
        self._swdb_commit()

        swdb_group = self.history.group.get(group_id)
        self.assertCountEqual([i.getName() for i in swdb_group.getPackages()], ('tour', 'pepper'))

class SolverEnvironmentTest(tests.support.DnfBaseTestCase):

    REPOS = []
    COMPS = True
    COMPS_SOLVER = True

    def test_install(self):
        env_id = 'sugar-desktop-environment'
        env = self.comps._environment_by_id(env_id)
        trans = self.solver._environment_install(env_id, dnf.comps.MANDATORY, [])
        self._swdb_commit()

        self.assertCountEqual([pkg.name for pkg in trans.install], ('pepper', 'trampoline', 'hole', 'lotus'))

        sugar = self.history.env.get(env_id)
        self.assertCountEqual([i.getGroupId() for i in sugar.getGroups()], ('Peppers', 'somerset', 'base'))

        somerset = self.history.group.get('somerset')
        self.assertIsNotNone(somerset)
        self.assertEqual(somerset.getPackageTypes(), dnf.comps.MANDATORY)

        base = self.history.group.get('base')
        self.assertEqual(base, None)

    def test_remove(self):
        env_id = 'sugar-desktop-environment'
        comps_env = self.comps.environment_by_pattern(env_id)

        self.solver._environment_install(comps_env.id, dnf.comps.MANDATORY, [])
        self._swdb_commit()

        swdb_env = self.history.env.get(comps_env.id)
        self.assertEqual(swdb_env.getPackageTypes(), dnf.comps.MANDATORY)
        group_ids = [i.getGroupId() for i in swdb_env.getGroups()]

        self.solver._environment_remove(comps_env.id)
        self._swdb_commit()

        swdb_env = self.history.env.get(comps_env.id)
        # if swdb_env is None, then it's removed
        self.assertIsNone(swdb_env)
        # test if also all groups were removed
        for group_id in group_ids:
            swdb_group = self.history.group.get(group_id)
            self.assertIsNone(swdb_group)

        # install it again with different pkg_types
        self.solver._environment_install(comps_env.id, dnf.comps.OPTIONAL, [])
        self._swdb_commit()

        swdb_env = self.history.env.get(comps_env.id)
        self.assertIsNotNone(swdb_env)
        self.assertEqual(swdb_env.getPackageTypes(), dnf.comps.OPTIONAL)

        group_ids = [i.getGroupId() for i in swdb_env.getGroups()]
        self.assertTrue(len(group_ids))
        for group_id in group_ids:
            swdb_group = self.history.group.get(group_id)
            if group_id == "base" and swdb_group is None:
                continue
            self.assertEqual(swdb_group.getPackageTypes(), dnf.comps.OPTIONAL)

    def test_upgrade(self):
        """Upgrade environment, the one group it knows is no longer installed."""
        env_id = "sugar-desktop-environment"
        comps_env = self.comps.environment_by_pattern(env_id)

        self.solver._environment_install(comps_env.id, dnf.comps.ALL_TYPES, [])
        self._swdb_commit()

        swdb_env = self.history.env.get(comps_env.id)
        self.assertNotEqual(swdb_env, None)

        # create a new transaction item for group Peppers with no packages
        self._swdb_commit()
        swdb_group = self.history.group.get('Peppers')
        swdb_group = self.history.group.new(swdb_group.getGroupId(), swdb_group.getName(), swdb_group.getTranslatedName(), swdb_group.getPackageTypes())
        self.history.group.install(swdb_group)
        self._swdb_commit()

        trans = self.solver._environment_upgrade(comps_env.id)
        self._swdb_commit()
        self.assertTransEqual(trans.install, ('hole', 'lotus'))
        self.assertTransEqual(trans.upgrade, ('pepper', 'trampoline', 'lotus'))
        self.assertEmpty(trans.remove)
