#!/usr/bin/env python
from __future__ import print_function

import datetime
import dateutil.tz
import em
import os
import re
import optparse
import subprocess
import sys
import tarfile

from pprint import pprint
from subprocess import Popen, CalledProcessError

try:
    import yaml
except ImportError:
    print("Please install python-yaml", file=sys.stderr)
    sys.exit(1)

try:
    import rosdep2.catkin_support
except ImportError:
    print("Please pip install -U rosdep", file=sys.stderr)
    sys.exit(2)

name_key = 'Catkin-ProjectName'

'''
The Debian binary package file names conform to the following convention:
<foo>_<VersionNumber>-<DebianRevisionNumber>_<DebianArchitecture>.deb

Distribution is an ubuntu distro
Version is the upstream version
DebianInc is some number that is incremental for package maintenance
Changes is a bulleted list of changes.
'''
def parse_options():
    import argparse
    parser = argparse.ArgumentParser(description='Creates/updates a gpb from a catkin project.')
    parser.add_argument('--working', help='A scratch build path. Default: %(default)s',
                        default='.tmp/%u/' % os.getpid())
    parser.add_argument('--debian_inc', dest='debian_inc',
                        help='Bump the changelog debian number.'+
                        ' Please enter a monotonically increasing number from the last upload.',
                        default=0)
    parser.add_argument('--install_prefix', dest='install_prefix',
                        help='The full prefix ...')
    parser.add_argument('--distros', nargs='+',
                        help='A list of debian distros. Default: %(default)s',
                        default=[])

    #ros specific stuff.
    parser.add_argument('rosdistro', help='The ros distro. electric, fuerte, groovy')
    return parser.parse_args()

def call(working_dir, command, pipe=None):
    print('+ cd %s && ' % working_dir + ' '.join(command))
    process = Popen(command, stdout=pipe, stderr=pipe, cwd=working_dir)
    output, unused_err = process.communicate()
    retcode = process.poll()
    if retcode:
        raise CalledProcessError(retcode, command)
    if pipe:
        return output

def check_local_repo_exists(repo_path):
    return os.path.exists(os.path.join(repo_path, '.git'))

def update_repo(working_dir, repo_path, repo_uri, first_release):
    if check_local_repo_exists(repo_path):
        print("please start from a bare working dir::\n\trm -rf %s" % repo_path)
        sys.exit(1)
    if first_release:
        os.makedirs(repo_path)
        call(repo_path, ['git', 'init'])
        call(repo_path, ['git', 'remote', 'add', 'origin', repo_uri])
    else:
        command = ('gbp-clone', repo_uri);
        call(working_dir, command)

    command = ['git', 'config', '--add', 'remote.origin.push', '+refs/heads/*:refs/heads/*']
    call(repo_path, command)

    command = ['git', 'config', '--add', 'remote.origin.push', '+refs/tags/*:refs/tags/*']
    call(repo_path, command)

def generate_rosdep_db(working_dir, rosdistro):
    return rosdep2.catkin_support.get_catkin_view(rosdistro)

def make_working(working_dir):
    if not os.path.exists(working_dir):
        os.makedirs(working_dir)

def sanitize_package_name(name):
    return name.replace('_', '-')

def parse_stack_yaml(args):
    yaml_path = os.path.join('.', 'stack.yaml')
    stack_yaml = yaml.load(open(yaml_path))

    if 'Catkin-ChangelogType' not in stack_yaml:
        stack_yaml['Catkin-ChangelogType'] = ''

    stack_yaml['DebianInc'] = args.debian_inc
    if 'Package' in stack_yaml:
        if stack_yaml['Package'] != sanitize_package_name(stack_yaml['Package']):
            print ("Package: tag is not a valid debian package string. Aka, no underscores!")
            sys.exit(1)
    else:
        if args.rosdistro == 'backports':
            stack_yaml['Package'] = sanitize_package_name("%s"%(stack_yaml[name_key]))
        else:
            stack_yaml['Package'] = sanitize_package_name("ros-%s-%s"%(args.rosdistro, stack_yaml[name_key]))

    stack_yaml['ROS_DISTRO'] = args.rosdistro

    #allow override of these values
    if args.rosdistro == 'backports':
        stack_yaml['INSTALL_PREFIX'] = args.install_prefix if args.install_prefix != None else '/usr'
    else:
        stack_yaml['INSTALL_PREFIX'] = args.install_prefix if args.install_prefix != None else '/opt/ros/%s' % args.rosdistro
    

    deps = stack_yaml['Depends']
    if type(deps) == str:
        stack_yaml['Depends'] = set([x.strip() for x in deps.split(',') if len(x.strip()) > 0])
    else:
        stack_yaml['Depends'] = set(deps)

    maintainer_form = 'maintainer name <maintainer email>'
    if 'Maintainer' not in stack_yaml:
        print('You must have a Maintainer key in your stack.yaml.' +
              '\nValue should be:\n' +
              '\t%s'%maintainer_form, file=sys.stderr)
        sys.exit(1)
    if not re.search(r'.* <.+>', stack_yaml['Maintainer']):
        print('You have a malformed Maintainer field in your stack.yaml.' +
              '\nYours is:\n\t%s' % stack_yaml['Maintainer'] +
              '\nIt should be of the form:\n' +
              '\t%s'%maintainer_form, file=sys.stderr)
        sys.exit(1)
    return stack_yaml

def template_dir():
    return os.path.join(os.path.dirname(__file__), 'em')

def expand(fname, stack_yaml, source_dir, dest_dir, filetype=''):
    #where normal templates live
    templatedir = template_dir()
    #the default input template file path
    ifilename = os.path.join(templatedir, fname)

    if filetype != '':
        if filetype.startswith('+'):
            ifilename = os.path.join(source_dir, filetype[1:])
        else:
            ifilename += ('.' + filetype + '.em')
    else:
        ifilename += '.em'

    print("Reading %s template from %s" % (fname, ifilename))
    file_em = open(ifilename).read()

    s = em.expand(file_em, **stack_yaml)

    ofilename = os.path.join(dest_dir, fname)
    ofilestr = open(ofilename, "w")
    print(s, file=ofilestr)
    ofilestr.close()
    if fname == 'rules':
        os.chmod(ofilename, 0755)

def find_deps(stack_yaml, apt_installer, rosdistro, debian_distro):
    os_name = 'ubuntu'

    deps = stack_yaml['Depends']

    rosdep_view = rosdep2.catkin_support.get_catkin_view(rosdistro, os_name, debian_distro)
    
    ubuntu_deps = set()
    for dep in deps:
        resolved = rosdep2.catkin_support.resolve_for_apt(dep, rosdep_view, apt_installer, os_name, debian_distro)
        ubuntu_deps.update(resolved)

    print(stack_yaml[name_key], "has the following dependencies for ubuntu %s" % debian_distro)
    pprint(ubuntu_deps)
    return list(ubuntu_deps)

def generate_deb(stack_yaml, repo_path, stamp, rosdistro, debian_distro):
    apt_installer = rosdep2.catkin_support.get_apt_installer()
    depends = find_deps(stack_yaml, apt_installer, rosdistro, debian_distro)
    stack_yaml['DebDepends'] = depends
    stack_yaml['Distribution'] = debian_distro
    stack_yaml['Date'] = stamp.strftime('%a, %d %b %Y %T %z')
    stack_yaml['YYYY'] = stamp.strftime('%Y')

    source_dir = '.' # repo_path
    print("source_dir=%s"%source_dir)
    dest_dir = os.path.join(source_dir, 'debian')
    if not os.path.exists(dest_dir):
        os.makedirs(dest_dir)

    #create control file:
    expand('control', stack_yaml, source_dir, dest_dir)
    expand('changelog', stack_yaml, source_dir, dest_dir, filetype=stack_yaml['Catkin-ChangelogType'])
    expand('rules', stack_yaml, source_dir, dest_dir, filetype=stack_yaml['Catkin-DebRulesType'])
    expand('copyright', stack_yaml, source_dir, dest_dir, filetype=stack_yaml['Catkin-CopyrightType'])

    #compat to quiet warnings, 7 .. lucid
    ofilename = os.path.join(dest_dir, 'compat')
    ofilestr = open(ofilename, "w")
    print("7", file=ofilestr)
    ofilestr.close()

    #source format, 3.0 quilt
    if not os.path.exists(os.path.join(dest_dir, 'source')):
        os.makedirs(os.path.join(dest_dir, 'source'))
    ofilename = os.path.join(dest_dir, 'source/format')
    ofilestr = open(ofilename, "w")
    print("3.0 (quilt)", file=ofilestr)
    ofilestr.close()

def commit_debian(stack_yaml, repo_path):
    call(repo_path, ['git', 'add', 'debian'])
    message = '''+ Creating debian mods for distro: %(Distribution)s, rosdistro: %(ROS_DISTRO)s, upstream version: %(Version)s
''' % stack_yaml
    call(repo_path, ['git', 'commit', '-m', message])

def main(args):
    stamp = datetime.datetime.now(dateutil.tz.tzlocal())
    stack_yaml = parse_stack_yaml(args)
    make_working(args.working)

    debian_distros = args.distros
    if not debian_distros:
        debian_distros = rosdep2.catkin_support.get_ubuntu_targets(args.rosdistro)

    tags = []
    try:
        for debian_distro in debian_distros:
            generate_deb(stack_yaml, ".", stamp, args.rosdistro, debian_distro)
            commit_debian(stack_yaml, ".")
            tag_name = 'debian/%(Package)s_%(Version)s_%(Distribution)s' % stack_yaml
            print("tag: %s" % tag_name)
            call(".", ['git', 'tag', '-f', tag_name, '-m', 'Debian release %(Version)s' % stack_yaml])
    except rosdep2.catkin_support.ValidationFailed as e:
        print(e.args[0], file=sys.stderr)
        sys.exit(1)
    except (KeyError, rosdep2.ResolutionError) as e:
        if isinstance(e, KeyError):
            rosdep_key = str(e)
        else:
            rosdep_key = e.rosdep_key
        print("""Cannot resolve dependency [%(rosdep_key)s].

If [%(rosdep_key)s] is catkin project, make sure it has been added to the gbpdistro file.

If [%(rosdep_key)s] is a system dependency, make sure there is a rosdep.yaml entry
for it in your sources.
"""%locals(), file=sys.stderr)
        sys.exit(1)
    sys.exit(0)

if __name__ == "__main__":
    main(parse_options())
