#! /usr/bin/env python

"""
usage: %prog command [--path PATH_TO_REPO] REV_FILE
"""

import os, sys, string
from optparse import OptionParser
import subprocess
import yaml
import re

# This defines which arches are used to satisfy dependencies for other
#  architectures. This is a little looser than the Debian standard because
#  reprepro doesn't appear to expose the Multi-Arch field properly.
# See: http://wiki.debian.org/Multiarch/HOWTO
acceptable_arches = { 'amd64': ('all', 'amd64'), 'i386': ('all', 'i386'),
      'all': ('all', 'amd64', 'i386') }

def expdep(dstr, arch):
  multid = dstr.split('|')
  deps = []
  for d in multid:
    m = re.search('(.+)\s\((.*)\s(.*)\)',d)
    if m:
      deps.append((m.group(1).strip(), m.group(2), m.group(3),
         acceptable_arches[arch]))
    else:
      deps.append((d.strip(), '*', '*', acceptable_arches[arch]))
  return deps
  
class Package(object):
    def __init__(self, pstr):
      self.name,self.arch,self.ver,self.md5,depstr,provstr = pstr.split('\0',5)

      self.deps = []
      if len(depstr) > 0:
        self.deps = [expdep(d, self.arch) for d in depstr.split(',')]

      self.provs = [self.name]
      if len(provstr) > 0:
        self.provs += [p.strip() for p in provstr.split(',')]

      self.subdeps = False

    def satisfies(self,dep):
      if self.arch not in dep[3]:
        return False
      if dep[1] == '*':
        return True
      else:
        dpkgcmd = ['dpkg', '--compare-versions', self.ver, dep[1], dep[2]]
        return (subprocess.call(dpkgcmd) == 0)

    def IsProv(self):
      return False

def main(argv, stdout, environ):

  parser = OptionParser(__doc__.strip())
  parser.add_option("--path",action="store",type="string", dest="path",default=".")
  parser.add_option("--gen",action="store", type="string", dest="version", default=None)

  (options, args) = parser.parse_args()

  if (len(args) < 1):
    parser.error("Needs a command")

  cmd = args[0]

  repo_pkgs = {}
  repo_provs = {}
  filename = args[1]
  reprepro_cmd = ['reprepro', '-b', options.path, """--list-format=${package}\\0${architecture}\\0${version}\\0${MD5Sum}\\0${depends}\\0${provides}\n""", 'list', 'precise']
  (o,e) = subprocess.Popen(reprepro_cmd, stdout=subprocess.PIPE).communicate()
  for l in o.splitlines():
    p = Package(l)
    if not p.name in repo_pkgs:
       repo_pkgs[p.name] = []
    repo_pkgs[p.name].append(p)
    for prov in p.provs:
      if prov in repo_provs:
        prov_list = repo_provs[prov]
      else:
        prov_list = []
        repo_provs[prov] = prov_list
      prov_list.append(p)

  if cmd == "release":
    if options.version:
      pkg_vers = {}
      for _,pkgs in repo_pkgs.iteritems():
        for pkg in pkgs:
          pkg_vers[pkg.name] = {'version':pkg.ver, 'md5':pkg.md5}
      y={'version':options.version, 'pkgs':pkg_vers}
      output = yaml.dump(y,default_flow_style=False)
      f = open(filename,'w')
      f.write(output)
      f.close()
    else:
      y = yaml.load(open(filename,'r'))
      try: 
        ver = y['version']
        pkgs = y['pkgs']
        missing = False
        for (p,d) in pkgs.iteritems():
          v = d['version']
          m = d['md5']
          if p not in repo_pkgs:
            print 'Release file needs package %s (%s)'%(p,v)
            missing = True
            continue
          for pkg in repo_pkgs[p]:
            missing = True
            if v == pkg.ver:
              missing = False
            if m == pkg.md5:
              missing = False
        if missing:
          print "Package %s with version %s and MD5 %s missing from repository"%(p, v, m)
          sys.exit(1)
        else:
          print "All packages accounted for from release file."
          sys.exit(0)
            
      except KeyError, e:
        print e
        parser.error("Release file is missing key.")
         
  elif cmd == "packages":
    pkglist = open(filename,'r').read().splitlines()
    missing = 0
    for p in pkglist:
      if p not in repo_pkgs:
        print >> sys.stderr, "Missing Package: %s"%p
        missing += 1
      else:
        for pkg in repo_pkgs[p]:
          missing += check_deps(repo_pkgs, repo_provs, pkg)

    if missing > 0:
      sys.exit(1)
    else:
      sys.exit(0)

  else:
    parser.error("Invalid argument")

def check_deps(repo_pkgs, repo_provs, pkg):

  if pkg.subdeps == True:
    return 0

  # We can set subdeps to True assuming we will finish if we've started
  # This keeps us from recursing
  pkg.subdeps = True

  missing = 0

  for deplist in pkg.deps:
    found = False
    for d in deplist:
      if d[0] in repo_provs:
        for p in repo_provs[d[0]]:
          if p.satisfies(d):
            found = True
            missing += check_deps(repo_pkgs, repo_provs, p)
            break
    if not found:
      print >> sys.stderr, "Missing Dep: %s For Package: %s (%s)"%(
            deplist, pkg.name, pkg.arch)
      missing += 1

  return missing

if __name__ == "__main__":
  main(sys.argv, sys.stdout, os.environ)
