#!/usr/bin/perl
#
# Author: Petter Reinholdtsen <pere@hungry.com>
# Date:   2001-08-23
#
# Generate the control file used by the Debian Edu task package.

use warnings;
use strict;

use Getopt::Std;
use File::Path;

use vars qw(%opts %available %excluded %included @wanted %missing
	    @tasks $debug);
my @arch = qw(alpha arm i386 ia64 m68k mips mipsel powerpc s390 sparc hppa);

my $debug = 0;
my $nodepends = 0;

my %taskinfo = ();

my $aptsources = "./sources.list.sarge";

getopts("cdaemis:tD", \%opts);

$aptsources = $opts{'s'} if ($opts{'s'});

$debug = 1 if ($opts{'d'});
$nodepends = 1 if ($opts{'D'});

load_available_packages();

load_tasks();

if ($opts{'c'}) {
    gen_control();
} elsif ($opts{'e'}) {
    print_excluded_packages();
} elsif ($opts{'a'}) {
    print_all_packages();
} elsif ($opts{'t'}) {
    print_task_desc();
} else {
    print_available_packages();
}
print_missing_packages() if ($opts{'m'});

sub apt {
    my $op = shift;

    my $aptdir  = "./tmp/apt";
    my @aptopts = ("Dir::Etc::sourcelist=$aptsources",
		   "Dir::State=$aptdir/state",
		   "Dir::Cache=$aptdir/cache",
		   "Dir::State::Status=/dev/null",
		   "Debug::NoLocking=true");

    # Stupid apt-get and apt-cache do not understand the same arguments!
    # I have to map them to different formats to get both working.

    if ("update" eq $op) {
	mkpath "$aptdir/state/lists/partial";
	mkpath "$aptdir/cache/archives/partial";

	my $aptget   = "apt-get --assume-yes -o " . join(" -o ", @aptopts);

	print STDERR "aptget: $aptget\n" if $debug;
	system("$aptget update 1>&2");
    } elsif ("apt-cache" eq "$op") {
	my $aptcache = "apt-cache -o=" . join(" -o=", @aptopts);
	print STDERR "aptcache: $aptcache\n" if $debug;
	return $aptcache;
    }
}

sub gen_control {
    my $task;
    for $task (sort keys %taskinfo) {
	print "Package: $task\n";
	my $header;
	for $header (qw(Section Architecture Priority)) {
	    print "$header: $taskinfo{$task}{$header}\n"
		if (defined $taskinfo{$task}{$header});
	}

	if ($nodepends) {
		# degrade dependencies to recommends and recommends to
		# suggests
		print "Depends: education-tasks\n";
	    	print "Recommends: ", join(", ", sort @{$taskinfo{$task}{Depends}}),"\n"
			if defined $taskinfo{$task}{Depends};
		my @suggests;
		push @suggests, @{$taskinfo{$task}{Recommends}} if defined $taskinfo{$task}{Recommends};
		push @suggests, @{$taskinfo{$task}{Suggests}} if defined $taskinfo{$task}{Suggests};
	    	print "Suggests: ", join(", ", sort @suggests),"\n" if @suggests;
	}
	else {
		for $header (qw(Depends Suggests Recommends)) {
		    print "$header: ", join(", ", sort @{$taskinfo{$task}{$header}}),"\n"
			if defined $taskinfo{$task}{$header};
		}
	}

	# Description Description-long
	print "Description: $taskinfo{$task}{Description}\n";
	print "$taskinfo{$task}{'Description-long'}"; # Already contain newline

	print "\n";
    }
}

sub print_task_desc {
	if (! -d "tasksel") {
		mkdir("tasksel") || die "mkdir tasksel: $!";
	}
	
	foreach my $task (sort keys %taskinfo) {
		next if (exists $taskinfo{$task}{'Leaf'} &&
			$taskinfo{$task}{'Leaf'} eq 'false');
		
		print "Task: $task\n";
		print "Section: debian-edu\n";
		print "Description: $taskinfo{$task}{Description}\n";
		print "$taskinfo{$task}{'Description-long'}"; # Already contain newline
		print "Packages: task-files\n";
		print "Relevance: 10\n";
		print "Key: \n";
		print " $task\n";
		
		print "\n";

		open (OUT, ">tasksel/$task") || die "tasksel/$task: $!";
		print OUT "$task\n";
		foreach my $package (task_packages($task)) {
			print OUT "$package\n";
		}
		close OUT;
	}
}

sub task_packages {
	my $task=shift;
	my @packages=@_;
	foreach my $package (@{$taskinfo{$task}{Depends}}) {
		if ($package=~/\|/) {
			# Tasksel doesn't allow boolean oring of
			# dependencies. Just take the first one that is
			# available.
			my $ok=0;
			foreach my $alternative (split(' | ', $package)) {
	    			if (! exists $taskinfo{$alternative} &&
				    ! exists $available{$alternative}) {
				    	if (! exists $missing{$alternative}) {
				    		$missing{$alternative} = 1;
				    	}
				}
				else {
					print STDERR "task_packages: choosing $alternative from $package\n" if $debug;
					$package=$alternative;
					$ok=1;
					last;
				}
			}
			if (! $ok) {
				next;
			}
		}
		if (exists $taskinfo{$package}) {
			# Add packages from task recursively, since
			# tasksel does not support dependent tasks of
			# the type used by debian-edu
			push @packages, $package, task_packages($package);
		}
		else {
			push @packages, $package;
		}
	}
	return @packages;
}

#
# Check the APT cache, and find the packages currently available.
#
sub load_available_packages
{
    apt("update");
    my $aptcache = apt("apt-cache");
    open(APT, "$aptcache dump |") || die "Unable to start apt-cache";
    my $pkg;
    while (<APT>) {
	chomp;
	if (/^Package: (.+)$/) {
	    $pkg = $1;
	    print STDERR "Found pkg '$pkg'\n" if $debug;
	}
	if (/^\s+Version:\s+(.+)/) {
	    print STDERR " pkg $pkg = ver $1\n" if $debug;
#	    print "C: $pkg $available{$pkg} lt $1\n" if ( exists $available{$pkg});
	    $available{$pkg} = $1 if ( ! exists $available{$pkg} ||
				       $available{$pkg} lt $1 );
	}
    }
}

#
# Load all tasks
#
sub load_tasks {
    my $taskfile;

    # First document their existence, so they can depend on each other.
    for $taskfile (<tasks/*>) {
	next if ("tasks/CVS" eq $taskfile);
	next if ($taskfile =~ m/~$/);

	my $curpkg = $taskfile;
	$curpkg =~ s%tasks/%education-%;
	$available{$curpkg} = "n/a";

	push(@tasks, "$taskfile:$curpkg");
    }

    # Next, load their content.
    my $foo;
    for $foo (@tasks) {
	my ($taskfile, $curpkg) = $foo =~ m/^(.+):(.+)$/;
	next if ("tasks/CVS" eq $taskfile);
	
	load_task($taskfile, $curpkg);
    }
}

sub process_pkglist {
    my $pkgstring = shift;
    my @pkglist = ();
    my @missinglist = ();
    my $packages;
    for $packages (split(/\s*,\s*/, $pkgstring)) {
	print "E: double comma?: $_\n" if ($packages =~ /^\s*$/ && $debug);
	my $package;
	my @alternates=split(/\s*\|\s*/, $packages);
	my $alternatecount=0;
	for $package (@alternates) {
	    print STDERR "Loading pkg '$package'\n" if $debug;
	    if ($package =~ /^-(.+)$/) {
		$excluded{$1} = 1;
	    } elsif ( !exists $available{$package} ) {
		if ( !exists $missing{$package}) {
		    $missing{$package} = 1;
		}
		push(@missinglist, $package);
	    } else {
		if ($alternatecount == 0) {
		    push(@pkglist, $package);
		}
		else {
		    $pkglist[-1].=" | $package";
		}
		$alternatecount++;

	        if ( ! $included{$package} ) {
		    push(@wanted, $package);
		    $included{$package} = 1;
		}
	    }
	}
    }
    return (\@pkglist, \@missinglist);
}

sub load_task {
    my ($taskfile, $curpkg) = @_;
    open(TASKFILE, "<$taskfile") || die "Unable to open $taskfile";
    my $line;

    $taskinfo{$curpkg} = ();

    print STDERR "Loading task $curpkg\n" if $debug;

    while (<TASKFILE>) {
	chomp;
	next if (m/^\#/); # Skip comments
	$line = $_;

	# Append multi-line
	while ($line =~ /\\$/) {
	    $line =~ s/\s*\\//;
	    $_ = <TASKFILE>;
	    chomp;
	    $line .= $_;
	}
	# Remove trailing space
	$line =~ s/\s+$//;

	$_ = $line;
	$taskinfo{$curpkg}{'Section'}      = $1 if (m/^Section:\s+(.+)$/);
	$taskinfo{$curpkg}{'Architecture'} = $1 if (m/^Architecture:\s+(.+)$/);

	$taskinfo{$curpkg}{'Priority'}     = $1 if (m/^Priority:\s+(.+)$/);
	
	$taskinfo{$curpkg}{'Leaf'}         = $1 if (m/^Leaf:\s+(.+)$/);

	if (m/^Description:\s+(.+)$/) {
	    $taskinfo{$curpkg}{'Description'} = $1;
	    $taskinfo{$curpkg}{'Description-long'} = "";
	    while (<TASKFILE>) {
		# End of description, pass next line to pattern matching
		last if (m/^\S+/ || m/^\s*$/);

		$taskinfo{$curpkg}{'Description-long'} .= $_;
	    }
	}

	next unless defined $_;

	my $header;
	for $header (qw(Depends Suggests Recommends)) {
	    if (m/^$header:\s+(.+)$/ && $1 !~ /^\s*$/) {
		$taskinfo{$curpkg}{$header} = ()
		    if (! exists $taskinfo{$curpkg}{$header});
		my ($pkglist, $missinglist) = process_pkglist($1);
		push(@{$taskinfo{$curpkg}{$header}}, @{$pkglist});

		# Avoid missing packages in Depends lists, allow them
		# in the two others.  Insert missing depends in
		# suggests list.
		if (@{$missinglist}) {
		    if ("Depends" eq $header) {
			push(@{$taskinfo{$curpkg}{'Suggests'}}, @{$missinglist});
		    } else {
			push(@{$taskinfo{$curpkg}{$header}}, @{$missinglist});
		    }
		}
	    }
	}

	if (/^Avoid:\s+(.+)$/) {
	    my @pkgs = split(/\s*,\s*/, $1);
	    my $packages;
	    for $packages (@pkgs) {
	        my $package;
	    	for $package (split(/\s*\|\s*/, $packages)) {
		    $excluded{$package} = 1;
		}
	    }
	}

	if (/^Ignore:\s+(.+)$/) {
	    my @pkgs = split(/\s*,\s*/, $1);
	    my $packages;
	    for $packages (@pkgs) {
	        my $package;
	    	for $package (split(/\s*\|\s*/, $packages)) {
		    # Remove explanations, ie the paranteses at the end.
		    $package =~ s/\s*\([^\)]*\)\s*$//;
		    $missing{$package} = 1;
		}
	    }
	}
    }
    close(TASKFILE);
}

sub print_excluded_packages {
    print join("\n", sort keys %excluded),"\n";
}

sub print_available_packages {
    print join("\n", @wanted),"\n";
}

sub print_all_packages {
    print STDERR "Printing all packages\n" if $debug;
    print join("\n", @wanted, keys %missing),"\n";
}

sub print_missing_packages {
    if (%missing) {
	print STDERR "Missing or avoided packages:\n";
	my $package;
	for $package (sort keys %missing) {
	    if (exists $available{$package}) {
	        print STDERR "  $package (v$available{$package} available)\n";
	    } else {
	        print STDERR "  $package\n";
	    }
	}
	exit 1 unless $opts{'i'};
    }
}
