#!/usr/bin/env python3
# -*- coding: utf8 -*-

'''
This is slow and inefficient, any only useful for detecting circular deps of
packages installed as dependencies, which do not appear in the output of
`pacman -Qdt`
'''

import argparse
import re
from collections import deque
from itertools import chain

from pyalpm import find_satisfier
from pycman import config

AS_EXPLICIT = 0
AS_DEP = 1



def strip_ver_req(dep):
  m = re.match(r'([^=><]+)', dep)
  return m.group(1)



def find_satisfiers(pkgs, d):
  '''
  Find all satisfiers of the given dependency.
  '''
  satisfiers = set()
  while True:
    satisfier = find_satisfier((p for p in pkgs if p.name not in satisfiers), d)
    if satisfier is None:
      return
    else:
      yield satisfier
      satisfiers.add(satisfier.name)



def compute_requiredby(pkgs, optdeps=True):
  # The compute_requiredby method only checks deps, not optdeps.
  req_by = dict()
  for pkg in pkgs:
    name = pkg.name
    deps = list(chain.from_iterable(find_satisfiers(pkgs, d) for d in pkg.depends))
    if optdeps:
      deps.extend(chain.from_iterable(find_satisfiers(pkgs, d.split(':',1)[0]) for d in pkg.optdepends))
    for dep in deps:
      if dep is None:
        continue
      dname = dep.name
      try:
        req_by[dname].add(name)
      except KeyError:
        req_by[dname] = set((name,))
#   for k, v in req_by.items():
#     print(k, ' '.join(sorted(v)), len(v))
  return req_by



def main(args=None):
  parser = argparse.ArgumentParser(description='List packages that are not directly or indirectly required by any explicitly installed packages.')
  parser.add_argument(
    'pkgs', metavar='<pkgname>', nargs='*',
    help='Limit packages to dependency chains of these packages.'
  )
  parser.add_argument(
    '-c', '--config', metavar='<path>', default='/etc/pacman.conf',
    help='Pacman configuration file'
  )
  parser.add_argument(
    '--no-optdeps', action='store_true',
    help='Ignore optional dependencies.',
  )

  pargs = parser.parse_args(args)
  # Initialize the Pacman database.
  h = config.init_with_config(pargs.config)
  required = set()
  unrequired = set()
  seen = set()

  db = h.get_localdb()
  pkgs = deque(db.pkgcache)
  req_by = compute_requiredby(pkgs, optdeps=(not pargs.no_optdeps))


  while pkgs:
    pkg = pkgs.popleft()
    name = pkg.name
    if pkg.reason == AS_EXPLICIT:
      required.add(name)
    else:
      required_by = deque(db.get_pkg(p) for p in req_by.get(name, tuple()))
      if any((p.reason == AS_EXPLICIT or p.name in required) for p in required_by):
        required.add(name)
      elif all((p.name in unrequired) for p in required_by):
        unrequired.add(name)
      elif not name in seen:
        seen.add(name)
        pkgs.append(pkg)
  print('\n'.join(sorted(unrequired)))




if __name__ == "__main__":
  try:
    main()
  except KeyboardInterrupt:
    pass

# vim: set ts=4 sw=4 tw=0 noet:
