#!/usr/bin/env python3

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

from functools import cmp_to_key
from glob import iglob
from pyalpm import vercmp
from pycman import config
from sys import argv, stderr
import argparse
import errno
import os

verkey = cmp_to_key(vercmp)

def format_bytes(x):
  if x < 1024:
    return str(x) + ' B'
  else:
    x /= 1024.0

  b = 'Y'
  for c in ('k', 'M', 'G', 'T', 'P', 'E', 'Z'):
    if x < 1024:
      b = c
      break
    else:
      x /= 1024.0
  return '%.2f %ciB' % (x,c)


def parse_fname(name, src=False):
  try:
    if src:
      name, ver, rest = name.rsplit('-', 2)
      rel, ext = rest.split('.', 1)
      return name, ver, rel, ext
    else:
      return name.rsplit('-', 3)
  except ValueError:
    return None

def scan_dir(dpath, src=False):
  if src:
    pattern = '*.src.*'
  else:
    pattern = '*.pkg.*'
  pkgs = dict()
  for path in iglob(os.path.join(dpath, pattern)):
    if path.endswith('.sig'):
      continue
    try:
      name = os.path.basename(path)
      name, version, rel, suffix = parse_fname(name, src=src)
    except ValueError:
      print("unexpected name:", name)
      continue
    pkgver = version + '-' + rel
    path = os.path.normpath(path)
    try:
      pkgs[name].append((path, pkgver))
    except KeyError:
      pkgs[name] = [(path, pkgver)]
  return pkgs


def clean_dir(path, args, h):
  pkgs = scan_dir(path, src=args.src)
  old = list()
  for name, lst in pkgs.items():
    if args.installed:
      if not h.get_localdb().get_pkg(name):
        old.extend(x[0] for x in lst)
        continue
    if args.foreign:
      for db in h.get_syncdbs():
        if db.get_pkg(name):
          old.extend(x[0] for x in lst)
          break
    lst = sorted(lst, key=lambda x: verkey(x[1]), reverse=True)
    old.extend(x[0] for x in lst[args.number:])
  n = len(old)
  if n > 0:
    size = 0
    for f in old:
      size += os.path.getsize(f)
    size = format_bytes(size)
    print("Packages to discard:")
    print('\n'.join('  ' + x for x in sorted(old)))
    keeping = ''
    if args.installed:
      keeping += 'installed '
    if args.foreign:
      keeping += 'foreign '
    if args.noconfirm:
      ans = 'y'
    else:
      ans = input("Remove %d files (%s) keeping %d for each %spackage? [y/N] " % (n, size, args.number, keeping))
    if ans.lower() == 'y':
      for path in old:
        os.unlink(path)
        try:
          os.unlink(path + '.sig')
        except OSError as e:
          if e.errno != errno.ENOENT:
            raise(e)
      print("done\n")


def parse_args(args=None):
  parser = argparse.ArgumentParser(description='Pacman package cache cleaning tool.')
  parser.add_argument(
    'paths', metavar='<dirpath>', nargs='+',
    help='the directories from which to remove packages'
  )
  parser.add_argument(
    '-c', '--config', metavar='<path>', default='/etc/pacman.conf',
    help='Pacman configuration file'
  )
  parser.add_argument(
    '-i', '--installed', action='store_true',
    help='only keep installed packages'
  )
  parser.add_argument(
    '-f', '--foreign', action='store_true',
    help='only keep foreign packages, i.e. those not available in any repo'
  )
  parser.add_argument(
    '-n', '--number', metavar='<int>', action='store', type=int, default=1,
    help='the number of packages to keep'
  )
  parser.add_argument(
    '--noconfirm', action='store_true',
    help='skip confirmation prompt'
  )
  parser.add_argument(
    '-s', '--src', action='store_true',
    help='clean up source (*.src.*) archives created with "makepkg --source" instead of packages'
  )
  return parser.parse_args(args)


def main(args):
  args = parse_args(args)
  args.number = max(0, args.number)

  h = config.init_with_config(args.config)

  for path in args.paths:
    clean_dir(path, args, h)

if __name__ == '__main__':
  try:
    main(argv[1:])
  except KeyboardInterrupt:
    stderr.write('\naborted\n')
    pass
