#!/usr/bin/env python3

import argparse
from collections import deque
from pycman import config

parser = argparse.ArgumentParser(
  description='List all packages older than at least one of their dependencies.'
)

parser.add_argument(
  'pkgs', metavar='<pkgname>', nargs='*',
  help='Limit output to the given dependencies.'
)

parser.add_argument(
  '-c', '--config', metavar='<path>', default='/etc/pacman.conf',
  help='Pacman configuration file. Default: %(default)s'
)

parser.add_argument(
  '-f', '--foreign', action='store_true',
  help='Limit output to foreign packages.'
)

parser.add_argument(
  '-s', '--shared', action='store_true',
  help='Limit ouput to packages depending on shared objects (.so)'
)

parser.add_argument(
  '-g', '--graph', action='store_true',
  help='Print a directed graph in graphviz dot format.'
)



def get_native(h):
  names = set()
  for db in h.get_syncdbs():
    names |= set(p.name for p in db.pkgcache)
  return names



def includes_shared_object(pkg):
  for f in pkg.files:
    p = f[0]
    if p.endswith('.so') or '.so.' in p:
      return True
  else:
    return False



def get_built_required_by(db, pkg):
  for n in pkg.compute_requiredby():
    p = db.get_pkg(n)
    if p.arch != 'any':
      yield p



def get_all_dependents(db, pkg):
  q = deque(get_built_required_by(db, pkg))
  ds = set()
  gs = set((pkg.name, d.name) for d in q)
  while q:
    p = q.popleft()
    n = p.name
    if n in ds:
      continue
    else:
      ds.add(n)
      rs = set(get_built_required_by(db, p))
      q.extend(rs)
      gs |= set((n, r.name) for r in rs)
  return ds, gs



def print_graph(matches):
  print('digraph PKGS {')
  for n, d in matches:
    print('  "{}" -> "{}"'.format(n, d))
  print('}')



def main(args=None):
  pargs = parser.parse_args(args)
  h = config.init_with_config(pargs.config)
  native = get_native(h)
  old = set()
  ldb = h.get_localdb()
  graph = set()

  if pargs.pkgs:
    pkgs = list()
    for p in pargs.pkgs:
      pkg = ldb.get_pkg(p)
      if pkg is not None:
        pkgs.append(pkg)
  else:
    pkgs = ldb.pkgcache

  for pkg in pkgs:
    n = pkg.name
    required_by = set(get_built_required_by(ldb, pkg))

    if n in old or (pargs.shared and not includes_shared_object(pkg)):
      continue


    for rpkg in required_by:
      if pkg.builddate > rpkg.builddate and rpkg.arch != 'any':
        ds, gs = get_all_dependents(ldb, rpkg)
        old |= ds
        graph.add((n, rpkg.name))
        graph |= gs



  if pargs.foreign:
    graph = set((n,d) for n,d in graph if d not in native)
    old -= native

  if pargs.graph:
    print_graph(sorted(graph))
  else:
    for o in sorted(old):
      print(o)

if __name__ == '__main__':
  try:
    main()
  except (KeyboardInterrupt, BrokenPipeError):
    pass
