#!/usr/bin/env python3

import sys

sys.path.append('/usr/share/botch')
from util import read_graphml, read_tag_file, human_readable_size


# walk backward until a predecessor of arch:any is found
def get_predecessors(g, n):
    seen = set()
    todo = set([n])
    while todo:
        e = todo.pop()
        for pred in g.predecessors(e):
            if g.node[pred]['architecture'] != 'all':
                return True
            if pred not in seen:
                todo.add(pred)
        seen.add(e)
    return False


# walk forward until a successor of arch:any is found but
# stop at m-a:foreign packages
def get_successors(g, n):
    seen = set()
    todo = set([n])
    while todo:
        e = todo.pop()
        for succ in g.successors(e):
            if g.node[succ].get('multiarch', 'no') == 'foreign':
                continue
            if g.node[succ]['architecture'] != 'all':
                return True
            if succ not in seen:
                todo.add(succ)
        seen.add(e)
    return False


def get_affected_packages(g):
    result = list()
    for n, d in g.nodes_iter(data=True):
        if d.get('multiarch', 'no') == 'foreign':
            continue
        if d['architecture'] != 'all':
            continue
        if get_predecessors(g, n) and get_successors(g, n):
            result.append(d['package'])
    return result


def multiarch_interpreter_problem(g, packages=None):
    result = dict([(p, 0) for p in get_affected_packages(g)])
    print("""<html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    </head>
    <body>

    <p>This page lists all Architecture:all and not Multi-Arch:foreign packages
    that are on a dependency path between two Architecture:any packages.</p>

    <p>More precisely, a graph is created with binary packages being the nodes
    and their dependency and provides relationship being the edges. This page
    prints all nodes that have a (possibly transitive) predecessor (a reverse
    dependency) that is not Architecture:all and that have a (possibly
    transitive) successor (a dependency) that is not Architecture:all and not
    Multi-Arch:foreign. If a Multi-Arch:foreign package is encountered while
    walking the successors, then the successors of that package are not further
    traversed.</p>""")

    print("<p>Number of affected binary packages: %d</p>" % len(result))

    if packages:
        pkg2size = dict()
        for pkg in packages:
            pkg2size[pkg['Package']] = int(pkg['Size'])
        result = dict([(p, pkg2size[p]) for p in result.keys()])
        print("<p>Sum of package sizes: %s</p>"
              % human_readable_size(sum(result.values())))
        print("<p>Sum of 95%% smallest packages: %s</p>"
              % human_readable_size(sum(sorted(result.values())
                                        [:(len(result)*95)//100])))
        print("<p>Sum of 90%% smallest packages: %s</p>"
              % human_readable_size(sum(sorted(result.values())
                                        [:(len(result)*9)//10])))

    print("<table>")
    if packages:
        print("<tr><th>Size</th><th>Package name</th></tr>")
    else:
        print("<tr><th>Package name</th></tr>")
    for k in sorted(result.keys()):
        print("<tr>")
        if packages:
            print("<td>%d</td>" % result[k])
        print("<td>%s</td></tr>" % k)

    print("<table></body></html>")

if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser(
        description=('given a dose-ceve pkg graph in graphml format, ' +
                     'find all multiarch interpreter problems'))
    parser.add_argument("g", type=read_graphml, help="Input graph")
    parser.add_argument('-v', '--verbose', action='store_true',
                        help='be verbose')
    parser.add_argument('--packages', type=read_tag_file,
                        help='Packages file to retrieve binary package size ')
    args = parser.parse_args()
    multiarch_interpreter_problem(args.g, args.packages)
