#!/usr/bin/python2

import os
import shutil
from stat import *
import string
import sys
import tempfile
import re
import smtplib
from optparse import OptionParser
from yum.constants import *
from yum.misc import getCacheDir
from collections import defaultdict

# HAAACK
import imp
sys.modules['repoclosure'] = imp.load_source(
    "repoclosure", "/usr/bin/repoclosure")
import repoclosure

owners = {}
deps = {}

owner_template = "%s-owner@fedoraproject.org"
from_address = "buildsys@fedoraproject.org"


def generateConfig(distdir, treename, arch):
    if not os.path.exists(os.path.join(distdir, arch)):
        return None
    if arch == 'source' or arch == 'SRPMS':
        return None
    if os.path.exists(os.path.join(distdir, arch, "os")):
        subdir = "os"
    else:
        subdir = ""
    if not os.path.exists(os.path.join(distdir, arch, subdir, "repodata", "repomd.xml")):
        return None

    (fd, conffile) = tempfile.mkstemp()
    confheader = """
[main]
debuglevel=2
logfile=/var/log/yum.log
pkgpolicy=newest
distroverpkg=fedora-release
reposdir=/dev/null
keepcache=0

[%s-%s]
name=Fedora %s Tree - %s
baseurl=file://%s/%s/%s
enabled=1

""" % (treename, arch, treename, arch, distdir, arch, subdir)
    os.write(fd, confheader)
    os.close(fd)
    return conffile


def libmunge(match):
    if match.groups()[1].isdigit():
        return "%s%d" % (match.groups()[0], int(match.groups()[1]) + 1)
    else:
        return "%s%s" % (match.groups()[0], match.groups()[1])


def addOwner(list, pkg):
    if list.get(pkg):
        return True

    if list.has_key(pkg):
        return False

    f = owner_template % pkg
    list[pkg] = f
    if f:
        return True
    return False


def getSrcPkg(pkg):
    if pkg.arch == 'src':
        return pkg.name
    srpm = pkg.returnSimple('sourcerpm')
    if not srpm:
        return None
    srcpkg = string.join(srpm.split('-')[:-2], '-')
    return srcpkg


def printableReq(pkg, dep):
    (n, f, v) = dep
    req = '%s' % n
    if f:
        flag = LETTERFLAGS[f]
        req = '%s %s' % (req, flag)
    if v:
        req = '%s %s' % (req, v)
    return "%s requires %s" % (pkg, req,)


def assignBlame(resolver, dep, guilty):
    def __addpackages(sack):
        for package in sack.returnPackages():
            p = getSrcPkg(package)
            if addOwner(guilty, p):
                list.append(p)

    # Given a dep, find potential responsible parties

    list = []

    # The dep itself
    list.append(dep)

    # Something that provides the dep
    __addpackages(resolver.whatProvides(dep, None, None))

    # Libraries: check for variant in soname
    if re.match("lib.*\.so\.[0-9]+", dep):
        new = re.sub("(lib.*\.so\.)([0-9]+)", libmunge, dep)
        __addpackages(resolver.whatProvides(new, None, None))
        libname = dep.split('.')[0]
        __addpackages(resolver.whatProvides(libname, None, None))

    return list


def generateSpam(pkgname, treename, sendmail=True):

    package = deps[pkgname]
    guilty = owners[pkgname]
    conspirators = []

    for s in package.keys():
        subpackage = package[s]
        for arch in subpackage.keys():
            brokendeps = subpackage[arch]
            for dep in brokendeps:
                for blame in dep[2]:
                    # We might not have an owner here for virtual deps
                    try:
                        party = owners[blame]
                    except:
                        continue
                    if party != guilty and party not in conspirators:
                        conspirators.append(party)

    data = """

%s has broken dependencies in the %s tree:
""" % (pkgname, treename)

    for s in package.keys():
        subpackage = package[s]
        for arch in subpackage.keys():
            data = data + "On %s:\n" % (arch)
            brokendeps = subpackage[arch]
            for dep in brokendeps:
                data = data + "\t%s\n" % printableReq(dep[0], dep[1])

    data = data + "Please resolve this as soon as possible.\n\n"

    fromaddr = from_address
    toaddrs = [guilty]
    if conspirators:
        toaddrs = toaddrs + conspirators

    msg = """From: %s
To: %s
Cc: %s
Subject: Broken dependencies: %s

%s
""" % (fromaddr, guilty, string.join(conspirators, ','), pkgname, data)
    if sendmail:
        try:
            server = smtplib.SMTP('localhost')
            server.set_debuglevel(1)
            server.sendmail(fromaddr, toaddrs, msg)
        except:
            print 'sending mail failed'


def doit(dir, treename, mail=True):
    for arch in os.listdir(dir):
        conffile = generateConfig(dir, treename, arch)
        if not conffile:
            continue
        if arch == 'i386':
            carch = 'i686'
        elif arch == 'ppc':
            carch = 'ppc64'
        elif arch == 'sparc':
            carch = 'sparc64v'
        elif arch == 'armhfp':
            carch = 'armv7hnl'
        elif arch == 'arm':
            carch = 'armv7l'
        else:
            carch = arch
        my = repoclosure.RepoClosure(config=conffile, arch=[carch])
        cachedir = getCacheDir()
        my.repos.setCacheDir(cachedir)
        my.readMetadata()
        baddeps = my.getBrokenDeps(newest=False)
        pkgs = baddeps.keys()
        tmplist = [(x.returnSimple('name'), x) for x in pkgs]
        tmplist.sort()
        pkgs = [x for (key, x) in tmplist]
        if len(pkgs) > 0:
            print "Broken deps for %s" % (arch,)
            print "----------------------------------------------------------"
        naughtydd = defaultdict(list)
        for pkg in pkgs:
            srcpkg = getSrcPkg(pkg)

            addOwner(owners, srcpkg)

            if not deps.has_key(srcpkg):
                deps[srcpkg] = {}

            pkgid = "%s-%s" % (pkg.name, pkg.printVer())

            if not deps[srcpkg].has_key(pkgid):
                deps[srcpkg][pkgid] = {}

            broken = []
            for (n, f, v) in baddeps[pkg]:
                brokendep = "\t%s" % printableReq(pkg, (n, f, v))
                naughtydd[srcpkg].append(brokendep)

                blamelist = assignBlame(my, n, owners)

                broken.append((pkg, (n, f, v), blamelist))

            deps[srcpkg][pkgid][arch] = broken

        brokensources = dict((k, list(v)) for k, v in naughtydd.iteritems())
        for source in sorted(brokensources.keys()):
            print "[%s]" % (source)
            for d in brokensources[source]:
                print d

        print "\n\n"
        os.unlink(conffile)
        shutil.rmtree(cachedir, ignore_errors=True)

    pkglist = deps.keys()
    for pkg in pkglist:
        generateSpam(pkg, treename, mail)

if __name__ == '__main__':

    parser = OptionParser("usage: %prog [options] <directory>")
    parser.add_option("--nomail", action="store_true",
                      help="Don't mail the results")
    parser.add_option("--treename", default="rawhide",
                      help="Name of the tree to use in messages")
    parser.add_option("--fromaddr", default="buildsys@fedoraproject.org",
                      help="Address to send mail from (default: buildsys@fedoraproject.org)")
    parser.add_option("--owneraddr", default="%s-owner@fedoraproject.org",
                      help="Template for package owner addresses to send mail to (default: %s-owner@fedoraproject.org)")
    (options, args) = parser.parse_args(sys.argv[1:])
    if len(args) != 1:
        parser.error("incorrect number of arguments")
        sys.exit(1)
    if options.nomail:
        mail = False
    else:
        mail = True
    if options.fromaddr:
        from_address = options.fromaddr
    if options.owneraddr:
        owner_template = options.owneraddr
    doit(args[0], options.treename, mail)
