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

import pkg_resources
import copy
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 rospkg.stack
except ImportError:
    print("Please pip install -U rospkg", 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)

'''
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 debianize_string(value):
    markup_remover = re.compile(r'<.*?>')
    value = markup_remover.sub('', value)
    value = re.sub('\s+', ' ', value)
    value = value.strip()
    return value

def parse_stack_xml(args):
    xml_path = os.path.join('.', 'stack.xml')
    stack = rospkg.stack.parse_stack_file(xml_path)

    data = {}
    data['Name'] = stack.name
    data['Version'] = stack.version
    data['Description'] = debianize_string(stack.description)
    data['Homepage'] = stack.url

    data['Catkin-ChangelogType'] = ''
    data['Catkin-DebRulesType'] = stack.build_type
    #data['Catkin-CopyrightType'] = stack.copyright
    data['copyright'] = stack.copyright

    data['DebianInc'] = args.debian_inc
    if args.rosdistro == 'backports':
        data['Package'] = sanitize_package_name("%s"%(stack.name))
    else:
        data['Package'] = sanitize_package_name("ros-%s-%s"%(args.rosdistro, stack.name))

    data['ROS_DISTRO'] = args.rosdistro

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

    data['Depends'] = set([ d.name for d in stack.depends])
    data['BuildDepends'] = set([ d.name for d in stack.build_depends])

    maintainers = []
    for m in stack.maintainers:
        maintainer = m.name
        if m.email:
            maintainer += ' <%s>' % m.email
        maintainers.append(maintainer)
    data['Maintainer'] = ', '.join(maintainers)

    # Go over the different subfolders and find all the packages
    package_descriptions = {}

    # search for manifest in current folder and direct subfolders
    for dir_name in [ '.' ] + os.listdir('.'):
        if not os.path.isdir(dir_name):
            continue
        dir_path = os.path.join('.', dir_name)
        for file_name in os.listdir(dir_path):
            if file_name == 'manifest.xml':
                # parse the manifest, in case it is not valid
                manifest = rospkg.parse_manifest_file(dir_path, file_name)
                # remove markups
                if manifest.description is None:
                    manifest.description = ''
                description = debianize_string(manifest.description)
                if dir_name == '.':
                    dir_name = stack.name
                package_descriptions[dir_name] = description
    # Enhance the description with the list of packages in the stack
    if package_descriptions:
        if data['Description']:
            data['Description'] += '\n .\n'
        data['Description'] += ' This stack contains the packages:'
        for name, description in package_descriptions.items():
            data['Description'] += '\n * %s: %s' % (name, description)

    return data

def expand(fname, stack_data, dest_dir, filetype=''):

    # insert template type
    if filetype != '':
        ifilename = (fname + '.' + filetype + '.em')
    else:
        ifilename = fname + '.em'
    
    ifilename = os.path.join('resources', 'em', ifilename)

    print("Reading %s template from %s" % (fname, ifilename))
    file_em = pkg_resources.resource_string('bloom', ifilename)

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

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

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

    deps = stack_data['Depends']
    build_deps = stack_data['BuildDepends']

    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)

    ubuntu_build_deps = set()
    for dep in build_deps:
        resolved = rosdep2.catkin_support.resolve_for_apt(dep, rosdep_view, apt_installer, os_name, debian_distro)
        ubuntu_build_deps.update(resolved)

    print(stack_data['Name'], "has the following dependencies for ubuntu %s" % debian_distro)
    print('Run dependencies:')
    pprint(ubuntu_deps)
    print('Build dependencies:')
    pprint(ubuntu_build_deps)
    return list(ubuntu_deps), list(ubuntu_build_deps)

def generate_deb(stack_data, repo_path, stamp, rosdistro, debian_distro):
    apt_installer = rosdep2.catkin_support.get_apt_installer()
    depends, build_depends = find_deps(stack_data, apt_installer, rosdistro, debian_distro)
    stack_data['Depends'] = depends
    stack_data['BuildDepends'] = build_depends
    stack_data['Distribution'] = debian_distro
    stack_data['Date'] = stamp.strftime('%a, %d %b %Y %T %z')
    stack_data['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_data, dest_dir)
    expand('changelog', stack_data, dest_dir, filetype=stack_data['Catkin-ChangelogType'])
    expand('rules', stack_data, dest_dir, filetype=stack_data['Catkin-DebRulesType'])
    #expand('copyright', stack_data, dest_dir, filetype=stack_data['Catkin-CopyrightType'])
    ofilename = os.path.join(dest_dir, 'copyright')
    ofilestr = open(ofilename, "w")
    print(stack_data['copyright'], file=ofilestr)
    ofilestr.close()

    #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_data, 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_data
    call(repo_path, ['git', 'commit', '-m', message])

def main(args):
    stamp = datetime.datetime.now(dateutil.tz.tzlocal())
    stack_data = parse_stack_xml(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:
            data = copy.copy(stack_data)
            generate_deb(data, ".", stamp, args.rosdistro, debian_distro)
            commit_debian(data, ".")
            tag_name = 'debian/%(Package)s_%(Version)s_%(Distribution)s' % data
            print("tag: %s" % tag_name)
            call(".", ['git', 'tag', '-f', tag_name, '-m', 'Debian release %(Version)s' % data])
    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())
