#!/usr/bin/python3
#
# Copyright (C) 2007  Enrico Zini <enrico@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 3 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, see <http://www.gnu.org/licenses/>.

# Navigate among related Debian packages

import sys

# Requires python-extractor, python-magic and python-debtags
from debian import debtags
import re
from optparse import OptionParser
import apt


VERSION="0.1"

class Parser(OptionParser):
    def __init__(self, *args, **kwargs):
        OptionParser.__init__(self, *args, **kwargs)

    def error(self, msg):
        sys.stderr.write("%s: error: %s\n\n" % (self.get_prog_name(), msg))
        self.print_help(sys.stderr)
        sys.exit(2)

if __name__ == '__main__':
    parser = Parser(usage="usage: %prog [options] pkgname",
            version="%prog "+ VERSION,
            description="walk through Debian packages")
    parser.add_option("--tagdb", default="/var/lib/debtags/package-tags", help="Tag database to use (default: %default)")

    (options, args) = parser.parse_args()

    if len(args) == 0:
        parser.error("Please provide the name of an initial package")

    # Read full database
    db = debtags.DB()
    tag_filter = re.compile(r"^special::.+$|^.+::TODO$")
    db.read(open(options.tagdb, "r"), lambda x: not tag_filter.match(x))

    apt_cache = apt.Cache()

    # Maximum number of previous packages to remember
    maxlen = 5
    # Initial package selection
    trail = [ args[0] ]

    # Loop until the user chooses to quit
    done = False
    while not done:
        # Compute a package weight according to how old it is in the
        # trail
        pkgweight = {}
        for idx, pkg in enumerate(trail):
            pkgweight[pkg] = 1.-(idx/maxlen)

        # For every tag, find the number of packages in trail that have the tag
        tagscores = {}
        for pkg in trail:
            for tag in db.tags_of_package(pkg):
                if tag in tagscores:
                    tagscores[tag] += pkgweight[pkg]
                else:
                    tagscores[tag] = pkgweight[pkg]

        # Divide every tag score by the number of packages in the trail,
        # obtaining a 'tag weight'.  A package can be later scored by summing
        # the weight of all its tags.
        for tag in tagscores:
            tagscores[tag] = float(tagscores[tag]) / float(len(trail))

        # Find the merged tagset of the packages in trail
        trailtags = set(tagscores.keys())

        # Get the list of packages whose tagsets intersect the trail tagset
        nextpkgs = set()
        for pkg, tags in db.iter_packages_tags():
            if trailtags & tags:
                nextpkgs.add(pkg)

        # Score every package by the sum of the weight of its tags
        def pkgscore(pkg):
            score = 0.0
            for tag in db.tags_of_package(pkg):
                if tag in tagscores:
                    score += tagscores[tag]
            return score

        # Show the first 20 packages in reverse score order
        #display = sorted(nextpkgs - set(trail), key=pkgscore, reverse=True)[:20]
        display = sorted(nextpkgs, key=pkgscore, reverse=True)[:20]
        for num, pkg in enumerate(display):
            aptpkg = apt_cache[pkg]
            desc = aptpkg.raw_description.split("\n")[0]
            print("%2d) %s - %s" % (num + 1, pkg, desc))

        # Ask the user to choose a new package
        while True:
            ans = input("> ").strip()
            if ans[0] == 'q':
                done = True
                break
            elif ans.isdigit():
                num = int(ans) - 1
                if num < len(display):
                    # TODO: on a different kind of interface, display the full
                    # description of pkg
                    trail = [display[num]] + trail[:maxlen]
                    break
                else:
                    print("The number is too high")


# vim:set ts=4 sw=4:
