# UpdateList.py
# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*-
#
#  Copyright (c) 2004-2012 Canonical
#
#  Author: Michael Vogt <mvo@debian.org>
#
#  This program 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 2 of the
#  License, or (at your option) any later version.
#
#  This program 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 this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
#  USA

from __future__ import print_function

from gettext import gettext as _

import apt
import logging
import operator
import random
import subprocess


class UpdateOrigin(object):
    def __init__(self, desc, importance):
        self.packages = []
        self.importance = importance
        self.description = desc


class OriginsImportance:
    # filed in by us
    SECURITY = 10
    UPDATES = 9
    PROPOSED = 8
    BACKPORTS = 7
    ARCHIVE = 6
    # this is filed in by MyCache
    OTHER = 0
    # this is used by us
    OTHER_UNKNOWN = -1


class UpdateList(object):
    """
    class that contains the list of available updates in
    self.pkgs[origin] where origin is the user readable string
    """

    # the key in the debian/control file used to add the phased
    # updates percentage
    PHASED_UPDATES_KEY = "Phased-Update-Percentage"

    # the file that contains the uniq machine id
    UNIQ_MACHINE_ID_FILE = "/var/lib/dbus/machine-id"

    # the configuration key to turn phased-updates always on
    ALWAYS_INCLUDE_PHASED_UPDATES = (
        "Update-Manager::Always-Include-Phased-Updates")

    def __init__(self, parent):
        # a map of packages under their origin
        try:
            dist = subprocess.check_output(
                ["lsb_release", "-c", "-s"], universal_newlines=True).strip()
        except subprocess.CalledProcessError as e:
            print("Error in lsb_release: %s" % e)
            dist = None
        self.distUpgradeWouldDelete = 0
        self.pkgs = {}
        self.num_updates = 0
        self.matcher = self.initMatcher(dist)
        self.random = random.Random()
        # a stable machine uniq id
        with open(self.UNIQ_MACHINE_ID_FILE) as f:
            self.machine_uniq_id = f.read()

    def initMatcher(self, dist):
        # (origin, archive, description, importance)
        matcher_templates = [
            (None, None, _("Other updates"), OriginsImportance.OTHER_UNKNOWN)
        ]
        if dist:
            matcher_templates += [
                ("%s-security" % dist, "Ubuntu",
                 _("Important security updates"), OriginsImportance.SECURITY),
                ("%s-updates" % dist, "Ubuntu",
                 _("Recommended updates"), OriginsImportance.UPDATES),
                ("%s-proposed" % dist, "Ubuntu",
                 _("Proposed updates"), OriginsImportance.PROPOSED),
                ("%s-backports" % dist, "Ubuntu",
                 _("Backports"), OriginsImportance.BACKPORTS),
                (dist, "Ubuntu",
                 _("Distribution updates"), OriginsImportance.ARCHIVE),
            ]
        matcher = {}
        for (origin, archive, desc, importance) in matcher_templates:
            matcher[(origin, archive)] = UpdateOrigin(desc, importance)
        return matcher

    def is_ignored_phased_update(self, pkg):
        """ This will test if the pkg is a phased updated and if
            it needs to get installed or ignored.

            :return: True if the updates should be ignored
        """
        # allow the admin to override this
        if apt.apt_pkg.config.find_b(
                self.ALWAYS_INCLUDE_PHASED_UPDATES, False):
            return False

        if self.PHASED_UPDATES_KEY in pkg.candidate.record:
            # its important that we always get the same result on
            # multiple runs of the update-manager, so we need to
            # feed a seed that is a combination of the pkg/ver/machine
            self.random.seed("%s-%s-%s" % (
                pkg.name, pkg.candidate.version,
                self.machine_uniq_id))
            threshold = pkg.candidate.record[self.PHASED_UPDATES_KEY]
            percentage = self.random.randint(0, 100)
            if percentage > int(threshold):
                logging.info("holding back phased update (%s < %s)" % (
                    threshold, percentage))
                return True
        return False

    def update(self, cache):
        self.held_back = []

        # do the upgrade
        self.distUpgradeWouldDelete = cache.saveDistUpgrade()

        #dselect_upgrade_origin = UpdateOrigin(_("Previous selected"), 1)

        # sort by origin
        for pkg in cache:
            if pkg.is_upgradable or pkg.marked_install:
                if getattr(pkg.candidate, "origins", None) is None:
                    # can happen for e.g. locked packages
                    # FIXME: do something more sensible here (but what?)
                    print("WARNING: upgradable but no candidate.origins?!?: ",
                          pkg.name)
                    continue
                # check where the package belongs
                origin_node = cache.match_package_origin(pkg, self.matcher)

                # see if its a phased update and *not* a security update
                # or shadowing a security update
                if (origin_node.importance != OriginsImportance.SECURITY and
                        self.is_ignored_phased_update(pkg)):
                    continue

                if origin_node not in self.pkgs:
                    self.pkgs[origin_node] = []
                self.pkgs[origin_node].append(pkg)
                self.num_updates = self.num_updates + 1
            if pkg.is_upgradable and not (pkg.marked_upgrade or
                                          pkg.marked_install):
                self.held_back.append(pkg.name)
        for l in self.pkgs.keys():
            self.pkgs[l].sort(key=operator.attrgetter("name"))
        self.keepcount = cache._depcache.keep_count
