#! /usr/bin/python3

from __future__ import print_function
import json
from collections import defaultdict
from datetime import datetime
import sys
sys.path.append('/usr/share/botch')
from util import write_plain, read_yaml_file


def print_package(pkgname, version=None):
    if pkgname.startswith('src:'):
        pkgname = pkgname[4:]
        bugs = srcpkgbugs.get(pkgname)
        if version:
            pkgstring = '<a title="%s" style="">src:%s</a>' % (
                version, pkgname)
        else:
            pkgstring = 'src:%s' % pkgname
    else:
        arch, pkgname = pkgname.split(':', 1)
        bugs = binpkgbugs.get(pkgname)
        if version:
            pkgstring = '<a title="%s">%s:%s</a>' % (version, pkgname, arch)
        else:
            pkgstring = '%s:%s' % (pkgname, arch)
    if bugs:
        bugstring = " ".join('<a href="http://bugs.debian.org/%s">%s</a>' % (b,
                                                                             b)
                             for b in bugs)
        return '<span style="color:#f00">%s</span> (%s)' % (pkgstring,
                                                            bugstring)
    else:
        return pkgstring


def cross_problems(yamlin, outfile, srcpkgbugs=set(),
                   binpkgbugs=set(), online=False, verbose=False):
    missing = defaultdict(lambda: defaultdict(set))
    conflict = defaultdict(lambda: defaultdict(set))
    data = {"bin": defaultdict(set), "src": defaultdict(set)}
    srcpkgs = set()

    for p in yamlin['report']:
        n, v = p['package'], p['version']
        srcpkgs.add(n)
        # only add name to avoid duplicates when more than one
        # version exists
        if p['status'] == 'broken':
            for r in p['reasons']:
                if r.get('missing'):
                    unsatdep = print_package(
                        r['missing']['pkg']['unsat-dependency'].split(' ',
                                                                      1)[0])
                    c = r['missing'].get('depchains')
                    if c:
                        depchains = tuple(
                            [tuple([print_package(p['package'], p['version'])
                             for p in depchain['depchain']] + [unsatdep])
                             for depchain in c])
                    else:
                        depchains = tuple()
                    missing[unsatdep][depchains].add(print_package(n, v))
                    data['bin'][r['missing']['pkg']['unsat-dependency']
                                .split(' ', 1)[0].split(':',
                                                        1)[1]].add('missing')
                    data['src'][n.split(':', 1)[1]].add('missing')
                if r.get('conflict'):
                    pkg1 = print_package(r['conflict']['pkg1']['package'],
                                         r['conflict']['pkg1']['version'])
                    pkg2 = print_package(r['conflict']['pkg2']['package'],
                                         r['conflict']['pkg2']['version'])
                    c1 = r['conflict']['depchain1']
                    if c1:
                        depchain1 = tuple(
                            [tuple([print_package(p['package'], p['version'])
                             for p in depchain['depchain']] + [pkg1])
                             for depchain in c1])
                    else:
                        depchain1 = tuple()
                    c2 = r['conflict']['depchain2']
                    if c2:
                        depchain2 = tuple(
                            [tuple([print_package(p['package'], p['version'])
                             for p in depchain['depchain']] + [pkg2])
                             for depchain in c2])
                    else:
                        depchain2 = tuple()
                    conflict[(pkg1, pkg2)][
                        (depchain1, depchain2)].add(print_package(n, v))
                    data['bin'][r['conflict']['pkg1'][
                        'package'].split(':', 1)[1]].add('conflict')
                    data['bin'][r['conflict']['pkg2'][
                        'package'].split(':', 1)[1]].add('conflict')
                    data['src'][n.split(':', 1)[1]].add('conflict')

    print("""<html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <style>
    table, th, td
    {
        border: 1px solid black;
    }
    </style>
    </head>
    <body>
    """, file=outfile)

    print("<p>Below are two tables showing the two problem classes that " +
          "prevent generation of a build order for cross compilation. This " +
          "example tries to build all %d base source packages on amd64 for "
          % len(srcpkgs) +
          "armhf in current Debian sid. The source package selection was " +
          "made according to the problematic packages found by <a " +
          "href=\"http://bootstrap.debian.net\">bootstrap.debian.net</a></p>",
          file=outfile)

    print("<p>A machine parsable version can be retrieved in <a " +
          "href=\"cross.json\">json format</a></p>", file=outfile)

    if online:
        print("<p>Bugs are associated with packages on this page if they " +
              "carry the usertag \"cross-satisfiability\" of the user "
              "\"debian-cross@lists.debian.org\".</p>", file=outfile)
        print("<p>You can get an overview of all bugs tagged like that in " +
              "the <a href=\"https://bugs.debian.org/cgi-bin/pkgreport.cgi?t" +
              "ag=cross-satisfiability;users=debian-cross@lists.debian.org\"" +
              ">Debian bts</a></p>", file=outfile)

    print("<h1>missing</h1>", file=outfile)
    print("<p>The source packages in the last column cannot satisfy their " +
          "(possibly transitive) cross build dependencies because the " +
          "binary package in the first column. This is mostly because the " +
          "binary package in the first column is Multi-Arch:none. Some of " +
          "these packages need to be Multi-Arch:foreign instead. The " +
          "depchains column shows the dependency chain from the source " +
          "package to the binary package where applicable.</p>", file=outfile)
    print("<table><tr><th>Unsatisfied dependency</th><th>Depchains</th>" +
          "<th>Source packages</th></tr>", file=outfile)

    for pkg, v in list(missing.items()):
        rows = len(v)
        for c in list(v.keys()):
            if c:
                rows += len(c) - 1
        print('<tr><td rowspan="%d">%s</td>' % (rows, pkg), file=outfile)
        first = True
        for chain, v in list(v.items()):
            if chain:
                c = " → ".join(chain[0])
            else:
                c = ""
            if not first:
                print("<tr>", file=outfile)
            else:
                first = False
            print('<td>%s</td><td rowspan="%d">%s</td></tr>' %
                  (c, len(chain), " ".join(v)), file=outfile)
            for i in range(1, len(chain)):
                print('<tr><td>%s</td></tr>' %
                      (" → ".join(chain[i])), file=outfile)

    print("</table>", file=outfile)
    print("<h1>conflict</h1>", file=outfile)
    print("<p>The source packages in the second column cannot satisfy their " +
          "cross build dependencies because the binary packages in the " +
          "first column cannot be installed together. The build arch binary " +
          "package often gets pulled in through build-essential. The " +
          "problem can be fixed by depending on a cross variant of the " +
          "respective tool instead (gcc-${host} syntax). This table often " +
          "contains false entries of conflicts that are only produced by " +
          "Multi-Arch:same version skews in Debian unstable. The two " +
          "depchain columns show the paths from the source package in the " +
          "second column to each of the conflicting binary packages in the " +
          "first column, respectively. Sometimes, there exist multiple " +
          "dependency chains exist.<p>", file=outfile)
    print(
        "<table><tr><th>Conflict</th><th>Source packages</th><th>Depchain " +
        "1</th><th>Depchain2</th></tr>", file=outfile)

    for (pkg1, pkg2), v in list(conflict.items()):
        rows = len(v)
        for c1, c2 in list(v.keys()):
            if max(len(c1), len(c2)) > 1:
                rows += max(len(c1), len(c2)) - 1
        print('<tr><td rowspan="%d">%s ↔ %s</td>' %
              (rows, pkg1, pkg2), file=outfile)
        first = True
        for (chain1, chain2), v in list(v.items()):
            maxnumchains = max(len(chain1), len(chain2))
            if chain1:
                c1 = " → ".join(chain1[0])
            else:
                c1 = ""
            if chain2:
                c2 = " → ".join(chain2[0])
            else:
                c2 = ""
            if not first:
                print("<tr>", file=outfile)
            else:
                first = False
            print('<td td rowspan="%d">%s</td><td>%s</td><td>%s</td></tr>' %
                  (maxnumchains, " ".join(v), c1, c2), file=outfile)
            for i in range(1, maxnumchains):
                if len(chain1) >= i + 1:
                    c1 = " → ".join(chain1[i])
                else:
                    c1 = ""
                if len(chain2) >= i + 1:
                    c2 = " → ".join(chain2[i])
                else:
                    c2 = ""
                print('<tr><td>%s</td><td>%s</td></tr>' %
                      (c1, c2), file=outfile)
    print("</table>", file=outfile)

    footer = """
    <p>The JSON data used to generate these pages was computed using botch, the
    bootstrap/build ordering tool chain. The source code of botch can be
    redistributed under the terms of the LGPL3+ with an OCaml linking
    exception. The source code can be retrieved from <a
    href="https://gitorious.org/debian-bootstrap/botch">
    https://gitorious.org/debian-bootstrap/botch</a></p>

    <p>The html pages were generated by code which can be retrieved from <a
    href="https://gitorious.org/debian-bootstrap/bootstrap_debian_net">
    https://gitorious.org/debian-bootstrap/bootstrap_debian_net</a> and which
    can be redistributed under the terms of the AGPL3+</p>

    <p>For questions and bugreports please contact j [dot] schauer [at] email
    [dot] de.</p>
    """

    print("<p>generated: %s</p>" % (datetime.now().isoformat()), file=outfile)
    print("%s</body></html>" % footer, file=outfile)

    for pkg in data['bin']:
        data['bin'][pkg] = list(data['bin'][pkg])
    for pkg in data['src']:
        data['src'][pkg] = list(data['src'][pkg])
    return data

if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser(
        description='given a buildcheck result, create a html overview')
    parser.add_argument('yamlin', type=read_yaml_file,
                        help='input in yaml format (dose3 output)')
    parser.add_argument(
        'jsonout', type=write_plain, help='output in json format')
    parser.add_argument(
        'htmlout', type=write_plain, help='output in html format')
    parser.add_argument('--online', action='store_true',
                        help='generate online version (puts bts links)')
    parser.add_argument(
        '-v', '--verbose', action='store_true', help='be verbose')
    args = parser.parse_args()
    srcpkgbugs = defaultdict(list)
    binpkgbugs = defaultdict(list)

    if args.online:
        # this only corks with python2 because SOAPpy is not available for
        # python3
        # import SOAPpy

        # server = SOAPpy.SOAPProxy('http://bugs.debian.org/cgi-bin/soap.cgi',
        #                           'Debbugs/SOAP')
        # bugnrs = server.get_usertag('debian-cross@lists.debian.org',
        #                             'cross-satisfiability')
        # buginfos = server.get_status(bugnrs['cross-satisfiability'])

        # for bug in buginfos['item']:
        #    k = bug['key']
        #    v = bug['value']
        #    if v['package']:
        #        binpkgbugs[v['package']].append(k)
        #    else:
        #        for s in v['source'].split(','):
        #            s = s.strip()
        #            srcpkgbugs[s].append(k)
        import subprocess
        res = subprocess.check_output(
            "bts select users:debian-cross@lists.debian.org "
            + "tag:cross-satisfiability | bts status "
            + "fields:source,package,bug_num file:-", shell=True)
        for b in res.decode().split("\n\n"):
            d = {k: v for k, v in [l.split("\t")
                                   for l in b.split("\n") if l.strip()]}
            binpkgbugs[d['package']].append(d['bug_num'].encode("utf8"))
            for s in d['source'].split(','):
                s = s.strip()
                srcpkgbugs[s].append(d['bug_num'].encode("utf8"))

    with args.htmlout as f:
        data = cross_problems(
            args.yamlin, f, srcpkgbugs, binpkgbugs, args.online, args.verbose)
    with args.jsonout as f:
        json.dump(data, f)
