#!/usr/bin/env python

from argparse import ArgumentParser
from glob import glob
from tempfile import NamedTemporaryFile
import logging, sys, yaml
from logging import debug, error, info
from os import remove
from subprocess import Popen, PIPE, STDOUT

def format(s, **kwds):
  return s % kwds

def execute(command):
  popen = Popen(command, shell=(isinstance(command, str)), stdout=PIPE, stderr=STDOUT)
  linesIterator = iter(popen.stdout.readline, "")
  for line in linesIterator:
    debug(line.strip("\n"))  # yield line
  output = popen.communicate()[0]
  debug(output)
  exitCode = popen.returncode
  return exitCode

def deps(recipesDir, topPackage, outFile, buildRequires, transitiveRed, disable):
  dot = {}
  keys = [ "requires" ]
  if buildRequires:
    keys.append("build_requires")
  for p in glob("%s/*.sh" % recipesDir):
    debug(format("Reading file %(filename)s", filename=p))
    try:
      recipe = yaml.safe_load(open(p).read().split("---")[0])
      name = recipe["package"]
      if name in disable:
        debug("Ignoring %s, disabled explicitly" % name)
        continue
    except Exception as e:
      error(format("Error reading recipe %(filename)s: %(type)s: %(msg)s",
                   filename=p, type=type(e).__name__, msg=str(e)))
      sys.exit(1)
    dot[name] = dot.get(name, [])
    for k in keys:
      for d in recipe.get(k, []):
        d = d.split(":")[0]
        d in disable or dot[name].append(d)

  selected = None
  if topPackage != "all":
    if not topPackage in dot:
      error(format("Package %(topPackage)s does not exist", topPackage=topPackage))
      return False
    selected = [ topPackage ]
    olen = 0
    while len(selected) != olen:
      olen = len(selected)
      selected += [ d
                    for s in selected if s in dot
                    for d in dot[s] if not d in selected ]
    selected.sort()

  with NamedTemporaryFile(delete=False) as fp:
    fp.write("digraph {\n")
    for p,deps in list(dot.items()):
      if selected and not p in selected: continue
      fp.write("  \"%s\";\n" % p)
      for d in deps:
        fp.write("  \"%s\" -> \"%s\";\n" % (p,d))
    fp.write("}\n")
  try:
    if transitiveRed:
      execute(format("tred %(dotFile)s > %(dotFile)s.0 && mv %(dotFile)s.0 %(dotFile)s",
              dotFile=fp.name))
    execute(["dot", fp.name, "-Tpdf", "-o", outFile])
  except Exception as e:
    error(format("Error generating dependencies with dot: %(type)s: %(msg)s",
                 type=type(e).__name__, msg=str(e)))
  else:
    info(format("Dependencies graph generated: %(outFile)s", outFile=outFile))
  remove(fp.name)
  return True

def main():
  parser = ArgumentParser()
  parser.add_argument("topPackage")
  parser.add_argument("--dist", dest="distDir", default="alidist",
                      help="Recipes directory")
  parser.add_argument("--output-file", "-o", dest="outFile", default="dist.pdf",
                      help="Output file (PDF format)")
  parser.add_argument("--debug", "-d", dest="debug", action="store_true", default=False,
                      help="Debug output")
  parser.add_argument("--build-requires", "-b", dest="buildRequires", action="store_true",
                      default=False, help="Debug output")
  parser.add_argument("--neat", "-n", dest="neat", action="store_true", default=False,
                      help="Neat graph with transitive reduction")
  parser.add_argument("--disable", dest="disable", default="",
                      help="List of packages to ignore")
  args = parser.parse_args()

  args.disable = args.disable.split(",")

  logger = logging.getLogger()
  loggerHandler = logging.StreamHandler()
  logger.addHandler(loggerHandler)

  loggerHandler.setFormatter(logging.Formatter('%(levelname)-5s: %(message)s'))
  if args.debug: logger.setLevel(logging.DEBUG)
  else: logger.setLevel(logging.INFO)

  deps(recipesDir=args.distDir,
       topPackage=args.topPackage,
       outFile=args.outFile,
       buildRequires=args.buildRequires,
       transitiveRed=args.neat,
       disable=args.disable) or sys.exit(1)

if __name__ == "__main__":
  main()
