#!/usr/bin/python

# TODO:
# - Exclude essential packages from dependencies

import urllib2
import urlparse
import gzip
import sets
import re
import StringIO
import os

seeds = { 'minimal' : {}, 'standard' : {}, 'server' : {}, 'desktop' : {}, 'live' : {} }
architectures = ['i386', 'amd64', 'powerpc', 'ia64', 'sparc', 'hppa']
archive_base_default = 'http://archive.ubuntu.com/ubuntu/'
archive_base_ports = 'http://ports.ubuntu.com/ubuntu-ports/'
archive_base = { 'i386' : archive_base_default,
                 'amd64' : archive_base_default,
                 'powerpc' : archive_base_default,
                 'ia64' : archive_base_ports,
                 'sparc' : archive_base_ports,
                 'hppa' : archive_base_ports,
                }
dist = 'breezy'
seed_base = 'http://people.ubuntu.com/~cjwatson/seeds/edubuntu-%s/' % dist
seed_entry = re.compile(' *\* *(?P<package>\S+) *(\[(?P<arches>[^]]*)\])? *(#.*)?')
components = ['main', 'restricted']
debootstrap_version_file = 'debootstrap-version'
metapackages = map(lambda seed: 'edubuntu-' + seed, seeds.keys())
seed_package_blacklist = sets.Set(metapackages)

def get_debootstrap_version():
    version = os.popen("dpkg-query -W --showformat '${Version}' debootstrap").read()
    if not version:
        raise RuntimeError('debootstrap does not appear to be installed')

    return version

def debootstrap_packages(arch):
    debootstrap = os.popen('debootstrap --arch %s --print-debs %s debootstrap-dir %s' % (arch,dist,archive_base[arch]))
    packages = debootstrap.read().split()
    if debootstrap.close():
        raise RuntimeError('Unable to retrieve package list from debootstrap')
    
    
    # sometimes debootstrap gives empty packages / multiple separators
    packages = filter(None, packages)
    
    packages.sort()

    return packages

def check_debootstrap_version():
    if os.path.exists(debootstrap_version_file):
        old_debootstrap_version = open(debootstrap_version_file).read().strip()
        debootstrap_version = get_debootstrap_version()
        failed = os.system("dpkg --compare-versions '%s' ge '%s'" % (debootstrap_version,
                                                                     old_debootstrap_version))
        if failed:
            raise RuntimeError('Installed debootstrap is older than in the previous version! (%s < %s)' % (
                debootstrap_version,
                old_debootstrap_version
                ))

def update_debootstrap_version():
    open(debootstrap_version_file, 'w').write(get_debootstrap_version() + '\n')

check_debootstrap_version()

print "Loading seed lists..."
for seed_name, seed in seeds.items():
    url = urlparse.urljoin(seed_base, seed_name)
    for line in urllib2.urlopen(url):
        m = seed_entry.match(line)
        if m:
            package = m.group('package')
            arches = m.group('arches')
            if arches is not None:
                archlist = arches.split()
            else:
                archlist = architectures
            if package not in seed_package_blacklist:
                if package not in seed:
                    seed[package] = []
                seed[package].extend(archlist)

print "Merging with available package lists..."
additions = {}
removals = {}
for architecture in architectures:
    packages = sets.Set()
    debootstrap_base = sets.Set(debootstrap_packages(architecture))

    # Download Packages files
    for component in components:
        packages_url = urlparse.urljoin(archive_base[architecture],
                                        'dists/%s/%s/binary-%s/Packages.gz' % (dist, component, architecture))
        packages_file = StringIO.StringIO(urllib2.urlopen(packages_url).read())
        
        for line in gzip.GzipFile(fileobj=packages_file):
            if line.startswith('Package: '):
                package = line[9:].strip()
                packages.add(package)


    # Merge with seeds
    for seed_name, seed in seeds.items():
        output_filename = '%s-%s' % (seed_name,architecture)
        old_list = None
        if os.path.exists(output_filename):
            old_list = sets.Set(map(str.strip,open(output_filename).readlines()))
            os.rename(output_filename, output_filename + '.old')

        new_list = []
        for package in seed.keys():
            if package in packages:
                if architecture not in seed[package]:
                    print "%s/%s: Skipping package %s (not for this architecture)" % (seed_name,architecture,package)
                elif seed_name == 'minimal' and package not in debootstrap_base:
                    print "%s/%s: Skipping minimal package %s (not in debootstrap)" % (seed_name,architecture,package)
                else:
                    new_list.append(package)
            else:
                print "%s/%s: Skipping unavailable package %s" % (seed_name,architecture,package)

        new_list.sort()
        output = open(output_filename, 'w')
        for package in new_list:
            output.write(package)
            output.write('\n')
        output.close()
        

        # Calculate deltas
        if old_list is not None:
            merged = {}
            for package in new_list:
                merged.setdefault(package, 0)
                merged[package] += 1
            for package in old_list:
                merged.setdefault(package, 0)
                merged[package] -= 1

            mergeditems = merged.items()
            mergeditems.sort()
            for package, value in mergeditems:
                #print package, value
                if value == 1:
                    additions.setdefault(package,[])
                    additions[package].append(output_filename)
                elif value == -1:
                    removals.setdefault(package,[])
                    removals[package].append(output_filename)

if additions or removals:
    os.system("dch -i 'Refreshed dependencies'")
    changes = []
    for package, files in additions.items():
        changes.append('Added %s to %s' % (package, ', '.join(files)))
    for package, files in removals.items():
        changes.append('Removed %s from %s' % (package, ', '.join(files)))
    for change in changes:
        print change
        os.system("dch -a '%s'" % change)
    update_debootstrap_version()
else:
    print "No changes found"
