#!/usr/bin/python

import argparse
import contextlib
import glob
import os
import shutil
import subprocess
import sys
import tempfile
import re

from datetime import datetime


def find_root():
    # expected path is in <top_dir>/packages/
    top_dir = os.environ.get("CLOUD_INIT_TOP_D", None)
    if top_dir is None:
       top_dir = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
    if os.path.isfile(os.path.join(top_dir, 'setup.py')):
        return os.path.abspath(top_dir)
    raise OSError(("Unable to determine where your cloud-init topdir is."
                       " set CLOUD_INIT_TOP_D?"))


# Use the util functions from cloudinit
sys.path.insert(0, find_root())

from cloudinit import templater
from cloudinit import util

# Mapping of expected packages to there full name...
# this is a translation of the 'requires'
# file pypi package name to a redhat/fedora package name.
PKG_MP = {
    'redhat': {
        'argparse': 'python-argparse',
        'cheetah': 'python-cheetah',
        'jinja2': 'python-jinja2',
        'configobj': 'python-configobj',
        'jsonpatch': 'python-jsonpatch',
        'oauth': 'python-oauth',
        'prettytable': 'python-prettytable',
        'pyserial': 'pyserial',
        'pyyaml': 'PyYAML',
        'requests': 'python-requests',
    },
    'suse': {
        'argparse': 'python-argparse',
        'cheetah': 'python-cheetah',
        'configobj': 'python-configobj',
        'jsonpatch': 'python-jsonpatch',
        'oauth': 'python-oauth',
        'prettytable': 'python-prettytable',
        'pyserial': 'python-pyserial',
        'pyyaml': 'python-yaml',
        'requests': 'python-requests',
    }
}

# Subdirectories of the ~/rpmbuild dir
RPM_BUILD_SUBDIRS = ['BUILD', 'RPMS', 'SOURCES', 'SPECS', 'SRPMS']


def get_log_header(version):
    # Try to find the version in the tags output
    cmd = ['bzr', 'tags']
    (stdout, _stderr) = util.subp(cmd)
    a_rev = None
    for t in stdout.splitlines():
        ver, rev = t.split(None)
        if ver == version:
            a_rev = rev
            break
    if not a_rev:
        return None
    
    # Extract who made that tag as the header
    cmd = ['bzr', 'log', '-r%s' % (a_rev), '--timezone=utc']
    (stdout, _stderr) = util.subp(cmd)
    kvs = {
        'comment': version,
    }

    for line in stdout.splitlines():
        if line.startswith('committer:'):
            kvs['who'] = line[len('committer:'):].strip()
        if line.startswith('timestamp:'):
            ts = line[len('timestamp:'):]
            ts = ts.strip()
            # http://bugs.python.org/issue6641
            ts = ts.replace("+0000", '').strip()
            ds = datetime.strptime(ts, '%a %Y-%m-%d %H:%M:%S')
            kvs['ds'] = ds

    return format_change_line(**kvs)


def format_change_line(ds, who, comment=None):
    # Rpmbuild seems to be pretty strict about the date format
    d = ds.strftime("%a %b %d %Y")
    d += " - %s" % (who)
    if comment:
        d += " - %s" % (comment)
    return "* %s" % (d)


def generate_spec_contents(args, tmpl_fn, top_dir, arc_fn):

    # Figure out the version and revno
    cmd = [util.abs_join(find_root(), 'tools', 'read-version')]
    (stdout, _stderr) = util.subp(cmd)
    version = stdout.strip()
    
    cmd = ['bzr', 'revno']
    (stdout, _stderr) = util.subp(cmd)
    revno = stdout.strip()

    # Tmpl params
    subs = {}
    subs['version'] = version
    subs['revno'] = revno
    subs['release'] = "bzr%s" % (revno)
    if args.sub_release is not None:
        subs['subrelease'] = "." + str(args.sub_release)
    else:
        subs['subrelease'] = ''
    subs['archive_name'] = arc_fn

    cmd = [util.abs_join(find_root(), 'tools', 'read-dependencies')]
    (stdout, _stderr) = util.subp(cmd)
    pkgs = [p.lower().strip() for p in stdout.splitlines()]

    # Map to known packages
    requires = []
    for p in pkgs:
        tgt_pkg = PKG_MP[args.distro].get(p)
        if not tgt_pkg:
            raise RuntimeError(("Do not know how to translate pypi dependency"
                                " %r to a known package") % (p))
        else:
            requires.append(tgt_pkg)
    subs['requires'] = requires

    # Format a nice changelog (as best as we can)
    changelog = util.load_file(util.abs_join(find_root(), 'ChangeLog'))
    changelog_lines = []
    missing_versions = 0
    for line in changelog.splitlines():
        if not line.strip():
            continue
        if re.match(r"^\s*[\d][.][\d][.][\d]:\s*", line):
            line = line.strip(":")
            header = get_log_header(line)
            if not header:
                missing_versions += 1
                if missing_versions == 1:
                    # Must be using a new 'dev'/'trunk' release
                    changelog_lines.append(format_change_line(datetime.now(),
                                                              '??'))
                else:
                    sys.stderr.write(("Changelog version line %s does not "
                                      "have a corresponding tag!\n") % (line))
            else:
                changelog_lines.append(header)
        else:
            changelog_lines.append(line)
    subs['changelog'] = "\n".join(changelog_lines)

    if args.boot == 'sysvinit':
        subs['sysvinit'] = True
    else:
        subs['sysvinit'] = False

    if args.boot == 'systemd':
        subs['systemd'] = True
    else:
        subs['systemd'] = False

    subs['defines'] = ["_topdir %s" % (top_dir)]
    subs['init_sys'] = args.boot
    subs['patches'] = [os.path.basename(p) for p in args.patches]
    return templater.render_from_file(tmpl_fn, params=subs)


def main():
    
    parser = argparse.ArgumentParser()
    parser.add_argument("-d", "--distro", dest="distro",
                        help="select distro (default: %(default)s)",
                        metavar="DISTRO", default='redhat',
                        choices=('redhat', 'suse'))
    parser.add_argument("-b", "--boot", dest="boot",
                        help="select boot type (default: %(default)s)", 
                        metavar="TYPE", default='sysvinit',
                        choices=('sysvinit', 'systemd'))
    parser.add_argument("-v", "--verbose", dest="verbose",
                        help=("run verbosely"
                              " (default: %(default)s)"),
                        default=False,
                        action='store_true')
    parser.add_argument('-s', "--sub-release", dest="sub_release",
                        metavar="RELEASE",
                        help=("a 'internal' release number to concat"
                              " with the bzr version number to form"
                              " the final version number"),
                        type=int,
                        default=None)
    parser.add_argument("-p", "--patch", dest="patches",
                        help=("include the following patch when building"),
                        default=[],
                        action='append')
    args = parser.parse_args()
    capture = True
    if args.verbose:
        capture = False

    # Clean out the root dir and make sure the dirs we want are in place
    root_dir = os.path.expanduser("~/rpmbuild")
    if os.path.isdir(root_dir):
        shutil.rmtree(root_dir)

    arc_dir = util.abs_join(root_dir, 'SOURCES')
    build_dirs = [root_dir, arc_dir]
    for dname in RPM_BUILD_SUBDIRS:
        build_dirs.append(util.abs_join(root_dir, dname))
    build_dirs.sort()
    util.ensure_dirs(build_dirs)

    # Archive the code
    cmd = [util.abs_join(find_root(), 'tools', 'make-tarball')]
    (stdout, _stderr) = util.subp(cmd)
    archive_fn = stdout.strip()
    real_archive_fn = os.path.join(arc_dir, os.path.basename(archive_fn))
    shutil.move(archive_fn, real_archive_fn)
    print("Archived the code in %r" % (real_archive_fn))

    # Form the spec file to be used
    tmpl_fn = util.abs_join(find_root(), 'packages',
                            args.distro, 'cloud-init.spec.in')
    contents = generate_spec_contents(args, tmpl_fn, root_dir,
                                      os.path.basename(archive_fn))
    spec_fn = util.abs_join(root_dir, 'cloud-init.spec')
    util.write_file(spec_fn, contents)
    print("Created spec file at %r" % (spec_fn))
    for p in args.patches:
        util.copy(p, util.abs_join(arc_dir, os.path.basename(p)))

    # Now build it!
    print("Running 'rpmbuild' in %r" % (root_dir))
    cmd = ['rpmbuild', '-ba', spec_fn]
    util.subp(cmd, capture=capture)

    # Copy the items built to our local dir
    globs = [] 
    globs.extend(glob.glob("%s/*.rpm" %
                           (util.abs_join(root_dir, 'RPMS', 'noarch'))))
    globs.extend(glob.glob("%s/*.rpm" %
                           (util.abs_join(root_dir, 'RPMS', 'x86_64'))))
    globs.extend(glob.glob("%s/*.rpm" %
                           (util.abs_join(root_dir, 'RPMS'))))
    globs.extend(glob.glob("%s/*.rpm" %
                           (util.abs_join(root_dir, 'SRPMS'))))
    for rpm_fn in globs:
        tgt_fn = util.abs_join(os.getcwd(), os.path.basename(rpm_fn))
        shutil.move(rpm_fn, tgt_fn)
        print("Wrote out %s package %r" % (args.distro, tgt_fn))

    return 0


if __name__ == '__main__':
    sys.exit(main())