#!/usr/bin/perl -T

load_po();

$ENV{PATH} = "/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin";
delete @ENV{qw(ENV BASH_ENV IFS)};
($<, $uid) = ($>, $<);

if (@ARGV and $ARGV[0] =~ /^-?-h/) {
    shift @ARGV;
}

if (@ARGV and $ARGV[0] eq '--auto') {
    $auto = 1;
    shift @ARGV;
}

if (@ARGV and $ARGV[0] eq '--force') {
    $force = 1;
    shift @ARGV;
}

if (@ARGV and $ARGV[0] eq '--X') {
    $X = 1;
    shift @ARGV;
}

if (@ARGV and $ARGV[0] eq '-a') {
    $all = 1;
    shift @ARGV;
}

while (@ARGV and $ARGV[0] eq '--comment') {
    shift @ARGV;
    shift @ARGV;
}

@ARGV or die(_("usage: urpmi [-h] [--auto] [--force] [-a] package_name [package_names...]\n"));

log_it(scalar localtime, " urpmi called with @ARGV\n");

$dir = "/var/lib/urpmi";
$dir2 = "/etc/urpmi";
$depsfile = "$dir/depslist";

open SAVEOUT, ">&STDOUT";
open SAVEERR, ">&STDERR";
open STDOUT, "|tee -a /var/log/urpmi.log" or die;
open STDERR, "|tee -a /var/log/urpmi.log" or die;
select STDERR; $| = 1;     # make unbuffered
select STDOUT; $| = 1;     # make unbuffered

open F, $depsfile or die "run urpmi.addmedia first\n";

if (@files = map { untaint($_) } grep { -e $_ } @ARGV) {
    $uid == 0 or die _("Only superuser is allowed to install local packages");
    open G, "gendepslist2 @files -- $dir/hdlist*.cz2 2>/dev/null |";
    -e $_ and s|(.*/)?(.*)\.[^.]+\.rpm$|$2| foreach @ARGV;
    m|^/| or $_ = "./$_" foreach @files;
}

foreach (<F>, <G>) {
    my ($pack, $size, $deps) = /(\S+)\s+(\S+)\s+(.*)/ or die("urpmi: bad format file $depsfile\n");
    $size{$pack} = $size;
    $deps{$pack} = [ split ' ', $deps ];
    
    foreach $v (@ARGV) { 
	$_ = quotemeta $v;
	$pack =~ /^$_-[^-]+-[^-]+$/ and $exact{$v} = $pack;
	$pack =~ /^$_-[^-]+$/ and $exact{$v} = $pack;
	$pack =~ /^$_$/ and $exact{$v} = $pack;
	$pack =~ /$_/ and push @{$found{$v}}, $pack;
	$pack =~ /$_/i and push @{$foundi{$v}}, $pack; 
    }
}
close F;
close G or die "gendepslist2 failed" if @files;

my $ok = 1;
foreach (@ARGV) {
    my $pack = $exact{$_};
    if ($pack) {
	push @packages, $pack;
    } else {
	my $l = $found{$_} || $foundi{$_};
	if (@$l == 0) { 
	    warn(_("no package named %s\n", $_)); $ok = $force; 
	} elsif (@$l > 1 && !$all) {
	    warn(_("The following packages contain %s: %s\n", $_, join(' ', @$l))); $ok = $force; 
	} else {
	    push @packages, @$l;
	}
    }
}
$ok or exit 1;

@installed = `/bin/rpm -qa`; $? and die(_("rpm database query failed\n"));
foreach (@installed) {
    my ($name, $v, $r) = /(.*)-([^-]+)-([^-]+)\n/;
    exists $installed{$name} && le_version([$v, $r], $installed{$name}) or $installed{$name} = [$v, $r];
}
@installed{map { chop; $_ } @installed} = ();
my %to_install; closure_deps(\%to_install, @packages); @to_install = keys %to_install;

if (!$auto and difference2(\@to_install, \@packages)) {
    my $sum = 0; map { $sum += $size{$_} } @to_install;
    my $msg = _("To satisfy dependencies, the following packages are going to be installed (%d MB)", toMb($sum));
    my $msg2 = _("Is it ok?");
    if ($X) {
	my $p = join "\n", @to_install;
	my $ok = _("Ok");
	my $cancel = _("Cancel");
	`gmessage -default $ok -buttons "$ok:0,$cancel:2" "$msg:\n$p\n\n$msg2"`;
	$? and exit 0;
    } else {
	print SAVEOUT "$msg:\n@to_install\n$msg2 (Y/n) ";
	<STDIN> =~ /n/i and exit 0;
    }
}

$to_install = join '|', map { quotemeta($_) } @to_install;

foreach $list (rpmlistfiles($dir)) {
    open F, "$dir/$list" or die("urpmi: error opening $list\n");
    $list =~ s/list\.//;
    foreach (<F>) { chop; m|/($to_install)| and $long{$1} = $_, $name{$1} = $list }
    close F;
}

foreach (@files) { 
    m|/($to_install)| and $long{$1} = $_
}

foreach $l (@to_install) {
    local $_ = $long{$l} or die("urpmi: package $l is not available\n");
    if (s|removable_(\w+)_(\d*):/||) {
	my $n = "$2-$name{$l}";
	$removables{$n}->{name} = $name{$l};
	$removables{$n}->{device} = $1;
	push @{$removables{$n}->{list}}, $_;
    } else {
	if (s|^file:/||) {
	    if (m|^(/mnt/.*?)/|) {
		-e $_ || $try2mount{$1} or $try2mount{$1} = 1, `mount $1 2>/dev/null`;
	    }
	}
	push @to_install_long, $_;
    }
}

install(@to_install_long) unless $removables{0} || $removables{1};

foreach (sort keys %removables) {
    my $f = @{$removables{$_}->{list}}[0];
    my $dev = $removables{$_}->{device};
    `mount /dev/$dev 2>/dev/null`;
    unless (-e $f) {
	`umount /dev/$dev  2>/dev/null ; eject $dev  2>/dev/null`;
	message(_("Please insert the %s named %s", $removables{$_}->{device}, $removables{$_}->{name}));
	unless ($X) {
	    print SAVEOUT _("Press enter when it's done...");
	    <STDIN>;
	}
	`mount /dev/$dev  2>/dev/null`;
	unless (-e $f) {
	    message(_("Sorry can't find file %s, exiting", $f));
	    exit 1;
	}
    }
    install(@{$removables{$_}->{list}});
}

install(@to_install_long) if $removables{0} || $removables{1};

$something_was_installed or message(_("everything already installed"));

sub install {
    @_ or return;
	
    $something_was_installed = 1;
    print SAVEOUT _("installing %s\n", join(' ', @_));
    log_it(scalar localtime, " @_\n");
    system($X ? "grpmi" : ("rpm", "-U"), @_);
    if ($?) {
	message(_("Installation failed"));
	$X and exit 1; #- grpmi handles --nodeps and --force by itself

	m|^/| && !-e $_ and exit 1 foreach @_; #- missing local file

	print SAVEOUT _("Try installation without checking dependencies?"), " (Y/n) ";
	$force or <STDIN> =~ /n/i and exit 1;
	system("rpm", "-U", "--nodeps", @_);	

	if ($?) {
	    message(_("Installation failed"));
	    print SAVEOUT _("Try installation even more strongly (--force)?"), " (Y/n) ";
	    $force or <STDIN> =~ /n/i and exit 0;
	    system("rpm", "-U", "--nodeps", "--force", @_);	
	}
    }
}

# uses @deps and @installed
sub closure_deps {
    my $to_install = shift;
    foreach (@_) {
	exists $to_install->{$_} and next;
	my ($name, $v, $r) = /(.*)-([^-]+)-([^-]+)/;
	exists $installed{$name} && le_version([$v, $r], $installed{$name}) and next;

	$to_install->{$_} = undef;
	my @notfound;
	my @d = @{$deps{$_}} or next;
      up: foreach (@d) {
	  my %l; 
	  foreach (split '\|') { 
	      exists $to_install->{$_} and next up;
	      /^NOTFOUND_(.*)/ and push(@notfound, $1), next up;

	      my ($name, $v, $r) = /(.*)-([^-]+)-([^-]+)/;
	      if (exists $installed{$name}) {
		  le_version([$v, $r], $installed{$name}) and next up;
		  %l = ($name => [$v, $r]);
		  last;
	      }	      
	      $l{$name} = [$v, $r] unless $l{$name} && le_version([$v, $r], $l{$name});
	  }
	  my ($l) = my @l = map { "$_-$l{$_}[0]-$l{$_}[1]" } keys %l;

	  if (@l > 1 && !$auto) {
	      $msg = _("One of the following packages is needed:");
	      my $n;
	      if ($X) {
		  `gchooser "$msg" @l`;
		  $n = $? >> 8 || die;
	      } else {		  
		  print SAVEOUT "$msg\n";
		  my $i = 0; foreach (@l) { print SAVEOUT " ", ++$i, "- $_\n"; }
		  while (1) {
		      print SAVEOUT _("What is your choice? (1-%d) ", $i);
		      $n = <STDIN>;
		      1 <= $n && $n <= $i and last;
		      print SAVEOUT _("Sorry, bad choice, try again\n");
		  }
	      }
	      $l = $l[$n - 1];
	  }
	  closure_deps($to_install, $l);
      }
      if (@notfound) {
	  message(_("Failed dependencies: %s requires %s", $_, join(", ", @notfound)));
	  exit 1;
      }
    }
}

#- compare a version string.
sub version_compare {
    my ($a, $b) = @_;
    local $_;

    while ($a || $b) {
	my ($sb, $sa) =  map { $1 if $a =~ /^\W*\d/ ? s/^\W*0*(\d+)// : s/^\W*(\D+)// } ($b, $a);
	$_ = length($sa) cmp length($sb) || $sa cmp $sb and return $_;
    }
}
sub le_version {
    my $cmp = version_compare($_[0][0], $_[1][0]);
    return $cmp < 0 || $cmp == 0 && version_compare($_[0][1], $_[1][1]) <= 0;
}

sub toMb {
    my $nb = $_[0] / 1024 / 1024;
    int $nb + 0.5;
}

sub message { $X ? `gmessage -default Ok -buttons Ok "$_[0]"` : print SAVEOUT "$_[0]\n"; }
sub member($@) { my $e = shift; foreach (@_) { $e eq $_ and return 1 } 0 }
sub uniq { my %l; @l{@_} = (); keys %l }
sub difference2(\@\@) { my %l; @l{@{$_[1]}} = (); grep { !exists $l{$_} } @{$_[0]} }
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 log_it {
    local *LOG;
    open LOG, ">>/var/log/urpmi.log" or die "can't ouptput to log file\n";
    print LOG @_;
}

sub untaint {
    my @r = ();
    foreach (@_) {
        /(.*)/;
	push @r, $1;
    }
    @r == 1 ? $r[0] : @r
}

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/;
    }
}

sub rpmlistfiles {
    my ($d) = @_;
    local *F;
    opendir F, $d or die "all: can't open dir $d: $!\n";
    my @l = grep { /^list\.*/ } readdir F;
    closedir F;

    @l;
}