#!/usr/bin/env python3

"""
See the help message (--help).
"""

import argparse
import errno
import logging
import os.path
import re
import subprocess

from pycman.config import init_with_config
import pyalpm



LDD_TARGET_REGEX = re.compile(rb'^\s+(.+?)\s+=>\s+not found$')


parser = argparse.ArgumentParser(description='Print unsatisfied dependencies.')
parser.add_argument(
  'pkgs', metavar='<pkgname>', nargs='*',
  help='the packages to check'
)
parser.add_argument(
  '-c', '--config', metavar='<path>', default='/etc/pacman.conf',
  help='Pacman configuration file'
)
parser.add_argument(
  '-o', '--object', action='store_true',
  help='Check object dependencies too.'
)
parser.add_argument(
  '-q', '--quiet', action='store_true',
  help='Suppress version specification.'
)



# Adapted from makedep.
def strip_version(name):
  '''
  Strip version specification from dependencies.
  '''
  for x in ('>=', '<=', '>', '<', '='):
    try:
      name, ver = name.split(x,1)
      return name
    except ValueError:
      continue
  else:
    return name



def missing_linked_objects(filepaths):
  cmd = ['ldd']
  cmd.extend(filepaths)
  try:
    ldd_output = subprocess.run(
      cmd,
      check=False,
      stdout=subprocess.PIPE,
      stderr=subprocess.DEVNULL,
      env={'LC_ALL':'C'}
    ).stdout
  except OSError as e:
    if e.errno == errno.E2BIG:
      middle_index = (len(cmd) // 2) + 1
      yield from missing_linked_objects(cmd[1:middle_index])
      yield from missing_linked_objects(cmd[middle_index:])
    else:
      raise e
  else:
    for line in ldd_output.split(b'\n'):
      m = LDD_TARGET_REGEX.match(line)
      if m:
        yield m.group(1).decode()



def unsatisfied_linked_object_deps(pkg):
  paths = ('/'+p for (p,_,_) in pkg.files)
  filepaths = (p for p in paths if os.path.isfile(p))
  yield from missing_linked_objects(filepaths)



def main(args=None):
  pargs = parser.parse_args(args)
  h = init_with_config(pargs.config)
  installed_pkgs = h.get_localdb().pkgcache

  if pargs.pkgs:
    pkgs = [h.get_localdb().get_pkg(p) for p in pargs.pkgs]
  else:
    pkgs = installed_pkgs


  missing_deps = dict()
  missing_objs = dict()
  for pkg in pkgs:
    pkgname = pkg.name
    logging.info('Checking dependencies of {}.'.format(pkgname))
#     if not p:
#       continue
    for dep in pkg.depends:
      if not pyalpm.find_satisfier(installed_pkgs, dep):
        try:
          missing_deps[dep].add(pkgname)
        except KeyError:
          missing_deps[dep] = set((pkgname,))

    if pargs.object:
      logging.info('Checking linked objects of {}.'.format(pkgname))
      for obj in set(unsatisfied_linked_object_deps(pkg)):
        try:
          missing_objs[obj].add(pkgname)
        except KeyError:
          missing_objs[obj] = set((pkgname,))

  if pargs.quiet:
    print('\n'.join(sorted(strip_version(m) for m in missing_deps)))
    print('\n'.join(sorted(missing_objs)))
  else:
    for dep, pkgnames in sorted(missing_deps.items()):
      print('{} ({})'.format(dep, ' '.join(sorted(pkgnames))))
    for obj, pkgnames in sorted(missing_objs.items()):
      print('{} ({})'.format(obj, ' '.join(sorted(pkgnames))))



if __name__ == '__main__':
  logging.basicConfig(
    format='[{asctime:s}] {levelname:s}: {message:s}',
    style='{',
    datefmt='%Y-%m-%d %H:%M:%S',
    level=logging.INFO
  )
  try:
    main()
  except KeyboardInterrupt:
    pass
