#!/usr/bin/python

# debtags - Implement package tags support for Debian
#
# Copyright (C) 2003--2006  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 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

import sys
import re
import subprocess
from debian_bundle import debtags
import apt

class SmartSearcher:
    def __init__(self, fullcoll, query):
        self.aptCache = apt.Cache()

        self.wanted = set()
        self.unwanted = set()
        self.ignored = set()
        self.interesting = []

        self.tagsInMenu = []

        self.fullcoll = fullcoll

        self.subcoll = fullcoll

        # Initialise the search
        self.computeInteresting(query)

    def computeInteresting(self, query):
        input = subprocess.Popen("apt-cache search " + query, shell=True, stdout = subprocess.PIPE, close_fds = True)

        # Read the package list and create the subcollection
        pkgs = []
        for pkg in input.stdout:
            pkg, none = pkg.rstrip("\n").split(' - ', 1)
            #print pkg
            pkgs.append(pkg)

        subcoll = self.fullcoll.choosePackages(pkgs)

        relIndex = debtags.relevanceIndexFunction(self.fullcoll, subcoll)

        # Get all the tags sorted by increasing relevance
        self.interesting = sorted(self.subcoll.iterTags(), lambda b, a: cmp(relIndex(a), relIndex(b)))

    def tagMatch(self, pkg):
        tags = self.fullcoll.tagsOfPackage(pkg)
        if len(self.wanted) > 0 and not self.wanted.issubset(tags):
            return False
        if len(self.unwanted) > 0 and len(tags.intersection(self.unwanted)) > 0:
            return False
        return True

    def refilter(self):
        # Regenerate subcoll
        self.subcoll = self.fullcoll.filterPackages(self.tagMatch)

    def showSet(self, tags, type):
        for tag in tags:
            self.tagsInMenu.append(tag)
            print "%d) %s (%s)" % (len(self.tagsInMenu), tag, type)

    def showChoiceSequence(self, seq, max = 7):
        for tag in seq:
            if tag in self.wanted or tag in self.unwanted or tag in self.ignored:
                continue
            self.tagsInMenu.append(tag)
            print "%d) %s (%d/%d)" % \
                (len(self.tagsInMenu), tag, self.subcoll.card(tag), self.subcoll.packageCount())
            max = max - 1
            if max == 0: break

    def showTags(self):
        self.tagsInMenu = []
        self.showSet(self.wanted, "wanted")
        self.showSet(self.unwanted, "unwanted")
        self.showSet(self.ignored, "ignored")
        print
        self.showChoiceSequence(self.interesting)
        print
        # Compute the most interesting tags by discriminance
        discr = sorted(self.subcoll.iterTags(), \
                  lambda a, b: cmp(self.subcoll.discriminance(a), self.subcoll.discriminance(b)))
        self.showChoiceSequence(discr)

    def outputPackages(self):
        for pkg in self.subcoll.iterPackages():
            aptpkg = self.aptCache[pkg]
            desc = aptpkg.rawDescription.split("\n")[0]
            print pkg, "-", desc

    def interact(self):
        done = False
        while not done:
            print "Tag selection:"
            self.showTags()
            print self.subcoll.packageCount(), " packages selected so far."

            changed = False

            sys.stdout.write("Your choice (+#, -#, =#, K word, View, Done, Quit, ?): ")
            ans = sys.stdin.readline()
            if ans == None:
                break
            ans = ans.strip(" \t\n")

            # If we're setting a new keyword search, process now and skip
            # processing as a list
            if ans == "?":
                print "+ number  select the tag with the given number as a tag you want"
                print "- number  select the tag with the given number as a tag you do not want"
                print "= number  select the tag with the given number as a tag you don't care about"
                print "K word    recompute the set of interesting tags from a full-text search using the given word"
                print "V         view the packages selected so far"
                print "D         print the packages selected so far and exit"
                print "Q         quit debtags smart search"
                print "?         print this help information"
            elif ans[0] == 'k' or ans[0] == 'K':
                # Strip initial command and empty spaces
                ans = ans[1:].strip();
                if len(ans) == 0:
                    print "The 'k' command needs a keyword to use for finding new interesting tags."
                else:
                    self.computeInteresting(ans)
                ans = ''
            else:
                # Split the answer by spaces
                for cmd in ans.split():
                    if cmd[0] == '+' or cmd[0] == '-' or cmd[0] == '=':
                        try:
                            idx = int(cmd[1:])
                        except ValueError:
                            print cmd, "should have a number after +, - or ="
                            continue
                        if idx > len(self.tagsInMenu):
                            print "Tag", idx, "was not on the menu."
                        else:
                            tag = self.tagsInMenu[idx - 1]
                            # cout << "Understood " << ans << " as " << ans[0] << tag.fullname() << endl;

                            if cmd[0] == '+':
                                self.wanted.add(tag)
                                if tag in self.unwanted: self.unwanted.remove(tag)
                                if tag in self.ignored: self.ignored.remove(tag)
                            if cmd[0] == '-':
                                if tag in self.wanted: self.wanted.remove(tag)
                                self.unwanted.add(tag)
                                if tag in self.ignored: self.ignored.remove(tag)
                            if cmd[0] == '=':
                                if tag in self.wanted: self.wanted.remove(tag)
                                if tag in self.unwanted: self.unwanted.remove(tag)
                                self.ignored.add(tag)
                            changed = True
                    elif cmd == "V" or cmd == "v":
                        self.outputPackages()
                    elif cmd == "D" or cmd == "d":
                        self.outputPackages()
                        done = True;
                    elif cmd == "Q" or cmd == "q":
                        done = True;
                    else:
                        print "Ignoring command \"%s\"" % (cmd)
            if changed:
                self.refilter()


if len(sys.argv) < 3:
    print sys.stderr, "Usage: %s tagdb keywords..." % (sys.argv[0])
    sys.exit(1)


# Main starts

fullcoll = debtags.DB()
# Read full database 
tagFilter = re.compile(r"^special::.+$|^.+::TODO$")
fullcoll.read(open(sys.argv[1], "r"), lambda x: not tagFilter.match(x))

searcher = SmartSearcher(fullcoll, " ".join(sys.argv[2:]))
searcher.interact()

# vim:set ts=4 sw=4 expandtab:
