#!/usr/bin/python
# kate: space-indent on; indent-width 4; replace-tabs on; indent-mode python; remove-trailing-space modified;

import future
import argparse
import glob
import os
from launchpadlib.launchpad import Launchpad
import re
import subprocess
import sys
import tempfile
import urllib
import shutil
import re

from lib.utils import *

parser = argparse.ArgumentParser(description="Upload new KDE software releases to a staging PPA.")
parser.add_argument("-d", "--dist", required=True, help="Distribution name")
parser.add_argument("-v", "--version", required=True, help="Upstream version")
parser.add_argument("-m", "--message", help="Changelog and commit message", default="New upstream release")
parser.add_argument("-p", "--packages", help="Override the list of packages to upload (comma separated)")
parser.add_argument("-t", "--tmpdir", help="Temprary dir where the packages are prepared")
parser.add_argument("-y", "--distversion", help="Dist version number, e.g. 12.10")
parser.add_argument("--nopush", action="store_true", help="Obsolete, has no effect")
parser.add_argument("-r", "--releasetype", help="Type [frameworks,plasma,applications]", default="frameworks")
parser.add_argument("--sru", action="store_true", help="Do a Stable Release Upload")
parser.add_argument("--paused", action="store_true", help="Obsolete, has no effect")
parser.add_argument("-b", "--branch", help="branch to merge [stable|unstable]", default="stable")
args = parser.parse_args()

release = args.dist
version = args.version
sru = args.sru
message = args.message
cwd = os.getcwd()
epoch = "4:"
releaseType = args.releasetype
branch = args.branch

# path in debian git
gitPaths = {"frameworks": "frameworks",
            "plasma": "plasma",
            "applications": "kde-applications"
           }

# git base url for pkg-kde on moszumanska:
gitBranch = "git+ssh://git.debian.org/git/pkg-kde/" + gitPaths[releaseType]

def firstLineMatch(expression, lines):
    for line in lines:
        match = expression.search(line)
        if match:
            return line

    return False

def sanitizeBranch(package):
    archiveChangelog = extractDebianFile(package, "debian/changelog")
    if archiveChangelog == True:
        print("=== " + package + " is not in the archive")
        return True
    elif not archiveChangelog:
        print("not archiveChangelog")
        return False
    gitChangelog = readAllFromFile("debian/changelog")
    if not gitChangelog:
        print("not gitChangelog")
        return False

    archiveChangelog = archiveChangelog.splitlines()
    gitChangelog = gitChangelog.splitlines()

    archiveTopline = firstLineMatch(topline, archiveChangelog)
    archiveEndline = firstLineMatch(endline, archiveChangelog)

    if not releaseType == "frameworks" and (archiveTopline not in gitChangelog or archiveEndline not in gitChangelog):
        print("git does not match archive")
        print("missing lines: " + archiveTopline + "\n" + archiveEndline)
        return False

    return True

def extractDebianFile(package, extractfile):
    try:
        source = archive.getPublishedSources(distro_series=series, exact_match=True, pocket="Release", source_name=package)[0]
    except IndexError:
        return True

    pkgFiles = []
    sourceUrls = source.sourceFileUrls()
    for plainUrl in sourceUrls:
        url = urllib.unquote(plainUrl)
        # fetch non-orig tarball files
        match = re.search(r'/([^/]*(?:\.diff\.gz|\.debian\.tar\.[a-z0-9]+|\.dsc))$', url)
        if match:
            localfile = match.group(1)
            if localfile[-4:] == ".dsc":
                dscfile = localfile
            pkgFiles.append(localfile)
            urllib.urlretrieve(plainUrl, localfile)

    if len(pkgFiles) < 2:
        return False

    pDscExtract = subprocess.Popen(["dscextract", dscfile, extractfile], stdout=subprocess.PIPE)
    data = pDscExtract.communicate()[0]
    if pDscExtract.returncode != 0:
        return False

    for filename in pkgFiles:
        os.remove(filename)

    return data

launchpad = Launchpad.login_anonymously("staging-upload", "production")
ubuntu = launchpad.distributions["ubuntu"]
distversion = args.distversion
series = ubuntu.getSeries(name_or_version=release)
if args.distversion == None:
    distversion = series.version
archive = ubuntu.main_archive

if not sru:
    topline = re.compile(r'^(\w%(name_chars)s*) \(([^\(\) \t]+)\)'
                         '((\s+%(name_chars)s+)+)\;'
                         % {'name_chars': '[-+0-9a-z.]'},
                         re.IGNORECASE)
    endline = re.compile('^ -- (.*) <(.*)>(  ?)((\w+\,\s*)?\d{1,2}\s+\w+\s+'
                         '\d{4}\s+\d{1,2}:\d\d:\d\d\s+[-+]\d{4}(\s+\([^\\\(\)]\))?\s*)$')

versionParts = version.split(".")
lastDigit = int(versionParts[-1])

if lastDigit >= 80:
    stability = "unstable"
else:
    stability = "stable"

if args.packages:
    packages = args.packages.split(",")
else:
    packages = readPackages("package-name-lists/" + releaseType + "-" + release)

# move libraries to the front
packages.sort(key=lambda package: package.find("lib") != -1 and "_" + package or package)
packages.sort(key=lambda package: package.find("extra-cmake-modules") != -1 and "_" + package or package)

if args.tmpdir:
    basedir = os.path.abspath(args.tmpdir)
else:
    basedir = tempfile.mkdtemp()
uploaddir = basedir + "/upload"

try:
    os.mkdir(uploaddir)
except OSError:
    print("using " + uploaddir)

try:
    os.mkdir(basedir + "/manual")
except OSError:
    print("using " + basedir + "/manual")

moved = []

with open('packaging-exceptions.json') as data_file:
    packagingExceptions = json.load(data_file)
    packagesWithEpoch = packagingExceptions['epoch-packages']

for package in packages:
    print("package: " + package)
    upstreamPackName = upstreamName(package)

    # check if it's a package with an epoch
    changelogEpoch = epoch if package in packagesWithEpoch else ""
    print("setting epoch to %s for %s" % (changelogEpoch, package))

    archive = ubuntu.getArchive(name='primary')
    archive_package = archive.getPublishedSources(source_name=package)
    if len(archive_package) > 0:
        archive_version = archive_package[0].source_package_version
        if version in archive_version:
            print("=== Skipping %s because version %s is already in the archive" % (package, version))
            continue

    try:
        os.mkdir(basedir + "/" + package)
    except OSError:
        print("removing existing " + basedir + "/" + package)
        shutil.rmtree(basedir + "/" + package)
        os.mkdir(basedir + "/" + package)
    os.chdir(basedir + "/" + package)
    if package in packagingExceptions:
        upstreamVersion = packagingExceptions[package]
    else:
        upstreamVersion = version

    splitVersion = re.split("\.", version)
    if releaseType == "frameworks":
        versionDirOnDepot = splitVersion[0] + "." + splitVersion[1]
    else:
        versionDirOnDepot = version
    if upstreamPackName in packagingExceptions["packages-in-porting-aids"]:
        subdir = "portingAids/"
    elif releaseType == "applications":
        subdir = "src"
    else:
        subdir = ""
    remote = "depot.kde.org:%s/%s/%s/%s/%s-%s.tar.xz" % \
          (stability, releaseType, versionDirOnDepot, subdir, upstreamPackName, upstreamVersion)

    try:
        subprocess.check_call(["rsync", "--progress", "-z", "--compress-level=9", "-e ssh", remote, "."])
    except:
        print("==== Skipping %s, failed to fetch tarball from depot.kde.org, moving to manual/" % (package))
        shutil.move(basedir + "/" + package, basedir + "/manual/" + package)
        moved.append((package, "failed to fetch tarball from depot"))
        continue
    orig = "%s_%s.orig.tar.xz" % (package, upstreamVersion)
    os.rename("%s-%s.tar.xz" % (upstreamPackName, upstreamVersion), orig)

    if sru:
        subprocess.check_call(["tar", "xJf", orig])

        # fetch and extract previous version
        try:
            subprocess.check_call(["pull-lp-source", "-d", package, release + "-proposed"])
        except:
            try:
                subprocess.check_call(["pull-lp-source", "-d", package, release])
            except:
                moved.append((package, "something wicked happened when pulling package from launchpad"))

        dscname = glob.glob("%s_*.dsc" % (package,))[0]
        match = re.match(re.escape(package) + r'_([\d\.a-z]+)-', dscname)
        if not match:
            print("==== Skipping %s, version parsing failed, moving to manual/" % (package,))
            shutil.move(basedir + "/" + package, basedir + "/manual/" + package)
            moved.append((package, "version parsing failed"))
            continue

        prevVersion = match.group(1)

        subprocess.check_call(["dpkg-source", "-x", "--skip-patches", dscname])
        os.rename("%s-%s/debian" % (package, prevVersion), "./debian")

        p = subprocess.Popen(["diff", "-Nurw", "%s-%s" % (package, prevVersion), "%s-%s" % (package, upstreamVersion)], stdout=subprocess.PIPE)
        diff, _ = p.communicate()
        p = subprocess.Popen(["diffstat", "-l", "-p1"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
        diffstat, _ = p.communicate(diff)
        if p.returncode != 0:
            raise subprocess.CalledProcessError(p.returncode)
        diffstatLines = diffstat.strip("\r\n\t ").splitlines()
        diffstatLines = filter(lambda line: not line.endswith("/index.cache.bz2"), diffstatLines)

        # we need to upload all packages that in kde-sc-dev-latest Breaks
        # except for the packages that have a stable ABI
        # TODO what about bindings?
        if package == "kde4libs" or package == "kdepimlibs" or package == "kde-workspace":
            significantChanges = True
        else:
            if not diffstatLines:
                print("==== Skipping %s, same as the previous version" % (package,))
                continue
            significantChanges = False

        for line in diffstatLines:
            if line.endswith(".cpp") or line.endswith(".h") or line.endswith(".desktop"):
                significantChanges = True
                break

        if not significantChanges:
            p = subprocess.Popen(["less"], stdin=subprocess.PIPE)
            p.communicate("Package: %s\n\n=== diffstat ===\n\n%s\n\n=== diff ===\n\n%s" % (package, diffstat, diff))
            sys.stdout.write("Update package [Y/n]? ")
            choice = raw_input().lower()
            if choice not in ("yes", "y", ""):
                continue

        if upstreamName(package) != package:
            os.rename("%s-%s" % (upstreamName(package), version), "%s-%s" % (package, version))
        os.rename("./debian", "%s-%s/debian" % (package, version))
        os.chdir("%s-%s" % (package, version))
    else: #not sru
        subprocess.check_call(["git", "clone", gitBranch + "/" + upstreamName(package), "git"])
        os.chdir("git")
        subprocess.check_call(["git", "checkout", "kubuntu_%s_archive" % (release)])
        if releaseType == "frameworks":
            branch = "unstable" # no stable branch to merge in frameworks
        try:
            subprocess.check_call(["git", "merge", "origin/kubuntu_%s" % (branch)])
        except:
            print("could not merge %s kubuntu_%s" % (package, branch))

        if not sanitizeBranch(package):
            print "==== Skipping %s, git branch has unexpected content, moving to manual/" % (package,)
            shutil.move(basedir + "/" + package, basedir + "/manual/" + package)
            moved.append((package, "git unclean or out of sync"))
            continue

    # bump build-dep versions for packages that were in kde-sc-dev-latest
    bumpVersionArgs = [cwd + "/bump-build-dep-versions",
                       "--dist", release,
                       "--version", upstreamVersion,
                       "--releasetype", releaseType]
    #TODO sort this out for applications
    if releaseType != "applications":
        subprocess.check_call(bumpVersionArgs)

    debianVersion = "0ubuntu"
    if sru:
        debianVersion += "0.1~ubuntu" + distversion + "~ppa1"
    else:
        debianVersion += "1"

    if sru:
        changelogRelease = release
    else:
        changelogRelease = "UNRELEASED"
    pkgChangelog = readAllFromFile("debian/changelog")
    # Don't run dch again if the message is already part of the current
    # changelog entry for this upstream version
    pattern = re.compile("^%s \((\d+:)?%s.*?\).*?\n --" % (package, upstreamVersion), re.DOTALL)
    if not re.match(pattern, pkgChangelog) or re.match(pattern, pkgChangelog).group(0).find(message) < 0:
        subprocess.check_call(["dch", "-v", "%s%s-%s" % (changelogEpoch, upstreamVersion, debianVersion), "-D", changelogRelease, message])
    else:
        print "No dch for " + package + " because it already has the same message in it"

    try:
        if sru:
            subprocess.check_call(["debuild", "-S", "-nc"])
        else:
            subprocess.call(["debcommit", "-a"])
            debianVersion += "~ubuntu" + distversion + "~ppa1"
            subprocess.check_call(["git-buildpackage-ppa", "-d", release, "--distversion", distversion, "--", "-nc"])

        print("pwd" + os.getcwd())
        if sru:
            subprocess.check_call(["dcmd", "cp", "../%s_%s-%s_source.changes" % (package, upstreamVersion, debianVersion), uploaddir])
        else:
            subprocess.check_call(["dcmd", "cp", "../build-area/%s_%s-%s_source.changes" % (package, upstreamVersion, debianVersion), uploaddir])
    except subprocess.CalledProcessError, e:
        # package build failed, manual intervention necessary
        print "==== " + package + " build was interrupted, moving to manual/"
        shutil.rmtree(basedir + "/manual/" + package, True)
        shutil.move(basedir + "/" + package, basedir + "/manual/" + package)
        moved.append((package, "build was interrupted"))

print("\n\n=== Packages are ready for upload in " + uploaddir)

if len(moved) > 0:
    output = []
    for item in moved:
        output.append(": ".join(item))
    print("\n=== The following packages couldn't be processed and have been moved to manual/")
    print("\n".join(output))

print("Remember to push the git changes!")
