#!/usr/bin/python
# -*- coding: utf-8 -*-
# ##################################################################
#
# Copyright (C) 2010, Evan Broder <evan@ebroder.net>
# Copyright (C) 2010, Benjamin Drung <bdrung@ubuntu.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; version 2.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# See file /usr/share/common-licenses/GPL-2 for more details.
#
# ##################################################################

import optparse
import os
import shutil
import subprocess
import sys
import tempfile

from launchpadlib.launchpad import Launchpad
import lsb_release

from ubuntutools.archive import UbuntuSourcePackage, DownloadError
from ubuntutools.config import UDTConfig, ubu_email
from ubuntutools.builder import get_builder
from ubuntutools.logger import Logger
from ubuntutools.question import YesNoQuestion

def error(msg):
    Logger.error(msg)
    sys.exit(1)

def check_call(cmd, *args, **kwargs):
    Logger.command(cmd)
    ret = subprocess.call(cmd, *args, **kwargs)
    if ret != 0:
        error('%s returned %d.' % (cmd, ret))

def parse(args):
    usage = 'Usage: %prog [options] <source package name or .dsc URL/file>'
    parser = optparse.OptionParser(usage)
    parser.add_option('-d', '--destination',
                      dest='dest_releases',
                      default=[],
                      action='append',
                      help='Backport to DEST release '
                           '(default: current release)',
                      metavar='DEST')
    parser.add_option('-s', '--source',
                      dest='source_release',
                      default=None,
                      help='Backport from SOURCE release '
                           '(default: devel release)',
                      metavar='SOURCE')
    parser.add_option('-S', '--suffix',
                      dest='suffix',
                      default=None,
                      help='Suffix to append to version number '
                           '(default: ~ppa1)',
                      metavar='SUFFIX')
    parser.add_option('-b', '--build',
                      dest='build',
                      default=False,
                      action='store_true',
                      help='Build the package before uploading '
                           '(default: %default)')
    parser.add_option('-B', '--builder',
                      dest='builder',
                      default=None,
                      help='Specify the package builder (default: pbuilder)',
                      metavar='BUILDER')
    parser.add_option('-U', '--update',
                      dest='update',
                      default=False,
                      action='store_true',
                      help='Update the build environment before '
                           'attempting to build')
    parser.add_option('-u', '--upload',
                      dest='upload',
                      help='Specify an upload destination',
                      metavar='UPLOAD')
    parser.add_option('-y', '--yes',
                      dest='prompt',
                      default=True,
                      action='store_false',
                      help='Do not prompt before uploading to a PPA')
    parser.add_option('-v', '--version',
                      dest='version',
                      default=None,
                      help='Package version to backport (or verify)',
                      metavar='VERSION')
    parser.add_option('-w', '--workdir',
                      dest='workdir',
                      default=None,
                      help='Specify a working directory '
                           '(default: temporary dir)',
                      metavar='WORKDIR')
    parser.add_option('-m', '--mirror',
                      dest='ubuntu_mirror',
                      default=None,
                      help='Preferred Ubuntu mirror (default: Launchpad)',
                      metavar='INSTANCE')
    parser.add_option('-l', '--lpinstance',
                      dest='lpinstance',
                      default=None,
                      help='Launchpad instance to connect to '
                           '(default: production)',
                      metavar='INSTANCE')
    parser.add_option('--no-conf',
                      dest='no_conf',
                      default=False,
                      help="Don't read config files or environment variables",
                      action='store_true')

    opts, args = parser.parse_args(args)
    if len(args) != 1:
        parser.error('You must specify a single source package or a .dsc '
                     'URL/path.')
    config = UDTConfig(opts.no_conf)
    if opts.builder is None:
        opts.builder = config.get_value('BUILDER')
    if not opts.update:
        opts.update = config.get_value('UPDATE_BUILDER', boolean=True)
    if opts.workdir is None:
        opts.workdir = config.get_value('WORKDIR')
    if opts.lpinstance is None:
        opts.lpinstance = config.get_value('LPINSTANCE')
    if opts.ubuntu_mirror is None:
        opts.ubuntu_mirror = config.get_value('UBUNTU_MIRROR')
    if not opts.upload and not opts.workdir:
        parser.error('Please specify either a working dir or an upload target!')

    return opts, args

def find_release_package(launchpad, package, version, source_release):
    ubuntu = launchpad.distributions['ubuntu']
    archive = ubuntu.main_archive
    series = ubuntu.getSeries(name_or_version=source_release)
    status = 'Published'
    for pocket in ('Updates', 'Security', 'Release'):
        try:
            srcpkg = archive.getPublishedSources(source_name=package,
                                                 distro_series=series,
                                                 pocket=pocket,
                                                 status=status,
                                                 exact_match=True)[0]
            break
        except IndexError:
            continue
    else:
        error('Unable to find package %s in release %s.' %
              (package, source_release))

    if version and version != srcpkg.source_package_version:
        error('Requested backport of version %s but %s is at version %s.' %
              (version, package, srcpkg.source_package_version))

    return srcpkg

def find_package(launchpad, mirror, workdir, package, version, source_release):
    "Returns the SourcePackage"
    if package.endswith('.dsc'):
        return UbuntuSourcePackage(version=version, dscfile=package,
                                   workdir=workdir, lp=launchpad,
                                   mirrors=[mirror])

    if not source_release and not version:
        source_release = launchpad.distributions['ubuntu'].current_series.name

    component = None
    # If source_release is specified, then version is just for verification
    if source_release:
        srcpkg = find_release_package(launchpad, package, version,
                                      source_release)
        version = srcpkg.source_package_version
        component = srcpkg.component_name

    return UbuntuSourcePackage(package, version, component, workdir=workdir,
                               lp=launchpad, mirrors=[mirror])

def get_backport_version(version, suffix, upload, release):
    backport_version = version + ('~%s1' % release)
    if suffix is not None:
        backport_version += suffix
    elif upload and upload.startswith('ppa:'):
        backport_version += '~ppa1'
    return backport_version

def get_backport_dist(upload, release):
    if not upload or upload == 'ubuntu':
        return '%s-backports' % release
    else:
        return release

def do_build(workdir, dsc, release, builder, update):
    builder = get_builder(builder)
    if not builder:
        return

    if update:
        if 0 != builder.update(release):
            sys.exit(1)

    return builder.build(os.path.join(workdir, dsc),
                         release,
                         os.path.join(workdir, "buildresult"))

def do_upload(workdir, package, bp_version, changes, upload, prompt):
    print 'Please check %s %s in file://%s carefully!' % \
          (package, bp_version, workdir)
    if prompt or upload == 'ubuntu':
        question = 'Do you want to upload the package to %s' % upload
        answer = YesNoQuestion().ask(question, "yes")
        if answer == "no":
            return

    check_call(['dput', upload, changes], cwd=workdir)

def do_backport(workdir, pkg, suffix, release, build, builder, update, upload,
                prompt):
    dirname = '%s-%s' % (pkg.source, release)
    pkg.unpack(dirname)
    srcdir = os.path.join(workdir, dirname)

    bp_version = get_backport_version(pkg.version.full_version, suffix,
                                      upload, release)
    bp_dist = get_backport_dist(upload, release)

    check_call(['dch',
                '--allow-lower-version',
                '--force-distribution',
                '--preserve',
                '--newversion', bp_version,
                '--distribution', bp_dist,
                'No-change backport to %s' % release],
               cwd=srcdir)
    check_call(['debuild', '--no-lintian', '-S', '-sa'], cwd=srcdir)

    fn_base = pkg.source + '_' + bp_version.split(':', 1)[-1]

    if build:
        if 0 != do_build(workdir, fn_base + '.dsc', release, builder, update):
            sys.exit(1)
    if upload:
        do_upload(workdir, pkg.source, bp_version, fn_base + '.changes',
                  upload, prompt)

    shutil.rmtree(srcdir)

def main(args):
    os.environ['DEB_VENDOR'] = 'Ubuntu'
    ubu_email()

    opts, (package_or_dsc,) = parse(args[1:])

    script_name = os.path.basename(sys.argv[0])
    launchpad = Launchpad.login_anonymously(script_name, opts.lpinstance)

    if not opts.dest_releases:
        distinfo = lsb_release.get_distro_information()
        try:
            opts.dest_releases = [distinfo['CODENAME']]
        except KeyError:
            error('No destination release specified and unable to guess yours.')

    if opts.workdir:
        workdir = os.path.expanduser(opts.workdir)
    else:
        workdir = tempfile.mkdtemp(prefix='backportpackage-')

    if not os.path.exists(workdir):
        os.makedirs(workdir)

    try:
        pkg = find_package(launchpad,
                           opts.ubuntu_mirror,
                           workdir,
                           package_or_dsc,
                           opts.version,
                           opts.source_release)
        pkg.pull()

        for release in opts.dest_releases:
            do_backport(workdir,
                        pkg,
                        opts.suffix,
                        release,
                        opts.build,
                        opts.builder,
                        opts.update,
                        opts.upload,
                        opts.prompt)
    except DownloadError, e:
        error(str(e))
    finally:
        if not opts.workdir:
            shutil.rmtree(workdir)

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