#!/usr/bin/perl

#- Copyright (C) 2000 MandrakeSoft (fpons@mandrakesoft.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; either version 2, or (at your option)
#- any later version.
#-
#- 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.
#-
#- You should have received a copy of the GNU General Public License
#- along with this program; if not, write to the Free Software
#- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

#- this program is based upon urpmi.

#use strict qw(subs vars refs);
use rpmtools;

#- default options.
my $query = {};
my $datadir = "/var/lib/urpmi";
my $confdir = "/etc/urpmi";

my $depslist = "$datadir/depslist.ordered";
my $provides = "$datadir/provides";
my $compss   = "$datadir/compss";

my @files;
my @names;

my %I18N;
load_po();

sub usage {
    die(_("usage: urpmq [-h] [-d] [-u] [-g] [-v] [-r] package_name|rpm_file [package_names|rpm_files...]\n"));
}

#- parse arguments list.
my @nextargv;
for (@ARGV) {
    /^--help$/ and do { usage; next };
    /^-(.*)$/ and do { foreach (split //, $1) {
	/[\?h]/ and do { usage; next };
	/d/ and do { $query->{deps} = 1; next };
	/u/ and do { $query->{upgrade} = 1; next };
	/g/ and do { $query->{group} = 1; next };
	/v/ and do { $query->{version} = 1; next };
	/r/ and do { $query->{release} = 1; next };
	die "urpmq: unknown option \"-$1\", check usage with --help\n"; } next };
    @nextargv and do { my $r = shift @nextargv; $r and $$r = $_; next };
    /\.rpm$/ and do { -r $_ or print STDERR "urpmq: cannot read rpm file \"$_\"\n", next;
		      push @files, $_; next };
    push @names, $_;
}

#- params contains informations to parse installed system.
my $params = new rpmtools;

open F, $depslist or die "$depslist file not found, run urpmi.addmedia first\n";
$params->read_depslist(\*F);
close F;

if ($query && $query->{group}) {
    open F, $compss or die "$compss file not found, run urpmi.addmedia first\n";
    $params->read_compss(\*F);
    close F;
}

if (@files) {
    #- read provides file which is needed only to compute incremental
    #- dependancies, of files provided.
    open F, $provides or die "$provides file not found, run urpmi.addmedia first\n";
    $params->read_provides(\*F);
    close F;

    #- compute depslist of files provided on command line.
    $params->read_rpms($_) foreach @files;
    $params->compute_depslist;

    #- gets full names of packages, sanity check of pathname.
    m|(.*/)?(.*)\.[^.]+\.rpm$| and push @names, $2 foreach @files;
    m|^/| or $_ = "./$_" foreach @files;
}

#- reparse whole internal depslist to match against newer packages only.
$params->relocate_depslist();

my %exact;
my %found;
my %foundi;
my @packages;

foreach my $v (@names) {
    #- it is a way of speedup, providing the name of a package directly help
    #- to find the package.
    #- this is necessary if providing a name list of package to upgrade.
    if ($params->{info}{$v}) {
	$exact{$v} = $params->{info}{$v}; next;
    }

    my $qv = quotemeta $v;
    foreach (keys %{$params->{info}}) {
	my $info = $params->{info}{$_};
	my $pack = $info->{name} .'-'. $info->{version} .'-'. $info->{release};

	$pack =~ /^$qv-[^-]+-[^-]+$/ and $exact{$v} = $info;
	$pack =~ /^$qv-[^-]+$/ and $exact{$v} = $info;
	$pack =~ /$qv/ and push @{$found{$v}}, $info;
	$pack =~ /$qv/i and push @{$foundi{$v}}, $info; 
    }
}

foreach (@names) {
    my $info = $exact{$_};
    if ($info) {
	push @packages, $info->{id};
    } else {
	my $l = $found{$_} || $foundi{$_};
	if (@{$l || []} == 0) {
	    warn(_("no package named %s\n", $_));
	} elsif (@$l > 1 && !$all) {
	    warn(_("The following packages contain %s: %s\n", $_, join(' ', map { $_->{name} } @$l)));
	} else {
	    push @packages, map { $_->{id} } @$l;
	}
    }
}

#- keep in mind the packages asked by the user, just for interactive activity.
my %packages; @packages{@packages} = ();

#- compute closure of package to install/upgrade before examining installed
#- packages, this help speed up the process to retrieve installed packages in
#- the given list.
my %packages_to_install;
my @packages_installed;
if (rpmtools::get_packages_installed('', \@packages_installed, [ 'basesystem' ])) {
    #- if basesystem is installed and need to be updated.
    #- we have to add it in the list explicitely.
    #- in order to take care of all basesystem packages.
    my $pkg = $params->{info}{basesystem};
    foreach (@packages_installed) {
	my $cmp = rpmtools::version_compare($pkg->{version}, $_->{version});
	if ($cmp > 0 || $cmp == 0 && rpmtools::version_compare($pkg->{release}, $_->{release}) > 0) {
	    push @packages, $pkg->{id}; last;
	}
    }
}

my $id;
while (defined($id = shift @packages)) {
    if (ref $id) {
	#- in such case, this is a dependancy on a choices, check
	#- if one of the package is already selected, in such case, the choice
	#- can safely be dropped. in other cases, the choices has to be registered.
	foreach (@$id) {
	    exists $packages_to_install{$_} && !$packages_to_install{$_} and $id = undef, last;
	}
	#- registering the choice is done by adding the class in any referenced id.
	if ($id) {
	    foreach (@$id) {
		push @{$packages_to_install{$_} ||= []}, $id;
	    }
	}
    } else {
	if ($packages_to_install{$id}) {
	    drop_in_choices(\%packages_to_install, $id);
	}
	exists $packages_to_install{$id} and next;
	#- force selection as mandatory package.
	$packages_to_install{$id} = undef;
	#- rebuild requires_id array according to deps as requires_id is no more available (because of speed :-)
	#- and because id have been relocated.
	my @rebuild_requires_id;
	foreach (split /\s+/, $params->{depslist}[$id]{deps}) {
	    if (/\|/) {
		push @rebuild_requires_id, [ map { $params->{depslist}[$_]{id} } split /\|/, $_ ];
	    } else {
		push @rebuild_requires_id, $params->{depslist}[$_]{id};
	    }
	}
	#- get all dependancies to add them.
	#- this is a partial closure, it assumes it has already be done before.
	foreach (@rebuild_requires_id) {
	    if (ref $_) {
		push @packages, $_;
	    } else {
		$packages_to_install{$_} = undef;
	    }
	}
    }
}

if ($query->{upgrade}) {
    #- now the packages_to_install contains as keys all packages that may be selected,
    #- this is used to determine a restricted list of packages installed, as it can
    #- improve performance on some cases.
    my @packages_to_install = map { $params->{depslist}[$_]{name} } keys %packages_to_install;
    my @packages_installed;
    if (@packages_to_install > 100) {
	rpmtools::get_all_packages_installed('', \@packages_installed);
    } else {
	rpmtools::get_packages_installed('', \@packages_installed, \@packages_to_install);
    }

    #- examine installed packages, determine if a package need upgrade or not.
    #- this list may be bigger than packages than really need to be upgraded because they
    #- are selected.
    foreach (@packages_installed) {
	my $pkg = $params->{info}{$_->{name}};
	#- if package has not event been selected by upgrade, continue.
	#- but if the package is part of a choice, if it need upgrade, the choice will
	#- be dropped, if it does not need upgrade, the choice has to been dropped.
	#- in any case, a choice has to be dropped.
	exists $packages_to_install{$pkg->{id}} or next;
	if ($packages_to_install{$pkg->{id}}) {
	    drop_in_choices(\%packages_to_install, $pkg->{id});
	}
	#- at this point, $_ is the package installed and $pkg is the package candidate to install.
	my $cmp = rpmtools::version_compare($pkg->{version}, $_->{version});
	if ($cmp < 0 || $cmp == 0 && rpmtools::version_compare($pkg->{release}, $_->{release}) <= 0) {
	    #- the package $pkg is older or the same as the installed package,
	    #- this means it has to be removed from the list, and considered to be
	    #- already installed.
	    delete $packages_to_install{$pkg->{id}};
	} else {
	    $packages_to_install{$pkg->{id}} = undef;
	}
    }
}

#- query of dependancies will make use of packages_to_install, else just
#- need to use @names where all informations are given, with eventual
#- limitation on packages already installed.
my $l = $query->{deps} ? \%packages_to_install : \%packages;

#- print sub for query.
my $query_sub = sub {
	my ($id) = @_;
	my $info = $params->{depslist}[$id];
	my $str = '';
	$query->{group} and $str .= $info->{group} . '/';
	$str .= $info->{name};
	$query->{version} and $str .= '-' . $info->{version};
	$query->{release} and $str .= '-' . $info->{release};
	$str;
};

foreach my $id (keys %$l) {
    my $class = $l->{$id};
    if ($class) {
	my %hack_only_one;
	foreach my $choices_id (@{$class || []}) {
	    exists $hack_only_one{$choices_id} and next;
	    print join('|', map { $query_sub->($_) } @$choices_id), "\n";
	    drop_in_choices($l, $choices_id->[0]);
	    $hack_only_one{$choices_id} = undef;
	}
    } else {
	exists $l->{$id} and print $query_sub->($id), "\n";
    }
}

#- remove any reference to package in choices,
#- it is NECESSARY the package to be in a choice, or it will die.
sub drop_in_choices {
    my ($packages_to_install, $id) = @_;

    #- the package here is listed in a choices, drop any reference to the choices
    #- as this package will have to be selected.
    my %class_to_drop; @class_to_drop{@{$packages_to_install->{$id}}} = ();
    foreach (keys %$packages_to_install) {
	if ($packages_to_install->{$_}) {
	    my @keeped_choices = grep { ! exists $class_to_drop{$_} } @{$packages_to_install->{$_}};
	    if (@keeped_choices) {
		$packages_to_install->{$_} = \@keeped_choices;
	    } else {
		delete $packages_to_install->{$_};
	    }
	}
    }
}

sub _ {
    my $s = shift @_; my $t = translate($s);
    $t && ref $t or return sprintf $t, @_;
    my ($T, @p) = @$t;
    sprintf $T, @_[@p];
}
sub translate { $I18N{$_[0]} || $_[0]; }

sub load_po {
    my ($from, $to, $state, $fuzzy);

    my $lang = $ENV{LANGUAGE} || $ENV{LC_ALL} || $ENV{LC_MESSAGES} || $ENV{LANG};
    my $f; -e ($f = "/usr/share/locale/$_/LC_MESSAGES/urpmi.po") and last foreach split ':', $lang;

    local *F; open F, $f or return;
    foreach (<F>) {
	/^msgstr/ and $state = 1;
	/^msgid/  && !$fuzzy and $state = 2;

	if (/^(#|$)/ && $state != 3) {
	    $state = 3;
            s/\\n/\n/g foreach $from, $to;

	    if (my @l = $to =~ /%(\d+)\$/g) {
		$to =~ s/%(\d+)\$/%/g;
		$to = [ $to, map { $_ - 1 } @l ];
	    }
	    $I18N{$from} = $to if $from;
	    $from = $to = '';
	}
	$to .= (/"(.*)"/)[0] if $state == 1;
	$from .= (/"(.*)"/)[0] if $state == 2;

	$fuzzy = /^#, fuzzy/;
    }
}