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

import datetime
import dateutil.tz
import em
import os
import re
import optparse
import rospkg
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)

    # Go over the different subfolders and find all the packages
    package_descriptions = {}
    import string    
    markup_remover = re.compile(r'<.*?>')

    for root, dirs, files in os.walk('.'):
        for file_name in files:
            if file_name == 'manifest.xml':
	   	# parse the manifest, in case it is not valid
                manifest = rospkg.parse_manifest_file(root, file_name)
                # remove markups
                description = markup_remover.sub('', manifest.description)
                description = re.sub('\s+', ' ', description)
                description = description.strip()
                package_descriptions[os.path.split(root)[-1]] = description
    # Enhance the description with the list of packages in the stack
    if package_descriptions:
        if 'Description' in stack_yaml:
            stack_yaml['Description'] += '\n .'
        else:
            stack_yaml['Description'] = ''
        stack_yaml['Description'] += '\n This stack contains the packages:'
        for name, description in package_descriptions.items():
            stack_yaml['Description'] += '\n * %s: %s' % (name, description)

    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())
