#!/usr/bin/perl

#- Copyright (C) 2005 MandrakeSoft SA
#- Copyright (C) 2005-2007 Mandriva SA

use strict;
use warnings;

BEGIN { #- set up a safe path and environment
    $ENV{PATH} = "/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin";
    delete @ENV{qw(ENV BASH_ENV IFS CDPATH)};
}

use gurpmi;
use urpm::install;
use urpm::media;
use urpm::signature;
use urpm::get_pkgs;
use urpm::msg;
use urpm::select;
use urpm::main_loop;
use Gtk2;

#- default options.
our $allow_medium_change = 0;
our $auto_select = 0;
our $force = 0;
our $test = 0;
our $use_provides = 1;

#- GUI globals
my ($mainw, $mainbox);

#- Replaces the contents of the main window with the specified box
#- (avoids popup multiplication)
sub change_mainw {
    $mainw->remove($mainbox);
    ($mainbox) = @_;
    $mainw->add($mainbox);
    $mainw->show_all;
}

sub sync () {
    $mainw->show;
    Gtk2->main_iteration while Gtk2->events_pending;
}

#- sets the window to a please-wait message
sub wait_label {
    my ($o_text) = @_;
    my $wait_vbox = Gtk2::VBox->new(0, 5);
    my $wait_label = Gtk2::Label->new($o_text || N("Please wait..."));
    $wait_label->set_alignment(0.5, 0.5);
    $wait_vbox->pack_start($wait_label, 1, 1, 0);
    change_mainw($wait_vbox);
    sync();
}

my @all_rpms = gurpmi::parse_command_line();

$> and fatal(N("Must be root"));

#- Now, the graphical stuff.

Gtk2->init;
Gtk2->croak_execeptions;

#- Create main window

$mainw = Gtk2::Window->new('toplevel');
$::main_window = $mainw;
$mainw->set_border_width(3);
$mainw->set_title(N("RPM installation"));
$mainw->signal_connect(destroy => \&quit);
$mainw->set_position('center');
$mainw->set_modal(0);
$mainbox = Gtk2::VBox->new(0, 5);
$mainw->add($mainbox);

#- Performs installation

my $urpm = configure_urpm();
my $state = {};
my %requested = $urpm->register_rpms(@all_rpms);
if (@gurpmi::names) {
    urpm::select::search_packages($urpm, \%requested, [ @gurpmi::names ],
                                  use_provides => $use_provides,
                              ) || $force or exit 1;
}

#- return value is true if program should be restarted (in order to take care of important
#- packages being upgraded (problably urpmi and perl-URPM, but maybe rpm too, and glibc also ?).
my $restart_itself = urpm::select::resolve_dependencies($urpm,
    $state,
    \%requested,
    callback_choices => \&ask_choice,
    auto_select => $::auto_select,
    priority_upgrade => $urpm->{options}{'priority-upgrade'},
);
my @ask_unselect = urpm::select::unselected_packages($urpm, $state);
@ask_unselect
? ask_continue(N(
	"Some requested packages cannot be installed:\n%s\nContinue installation anyway?",
	urpm::select::translate_why_unselected($urpm, $state, @ask_unselect)
    ), \&do_install)
: do_install();

$mainw->show_all;
Gtk2->main;

my ($rpm_lock, $urpmi_lock);

#- Creates and configure an urpm object for this application to use.
sub configure_urpm() {
    my $urpm;
    { 
        local @ARGV = @ARGV;
        $urpm = urpm->new_parse_cmdline;
    }

    $urpm->{fatal} = sub {
	Gtk2::MessageDialog->new($mainw, [qw(modal destroy-with-parent)], 'error', 'ok', Locale::gettext::iconv($_[1], undef, 'UTF-8'))->run;
	quit();
	exit $_[0];
    };
    $urpm->{error} = sub {
	my ($message) = @_;
	my $nb_lines = $message =~ tr/\n/\n/;
	my $w;
	if ($nb_lines > 30) {
	    $w  = Gtk2::Dialog->new(N("Warning"), $mainw, [qw(modal destroy-with-parent)], N("Ok"), 'ok');
	    $w->vbox->add(my $f = Gtk2::Frame->new);
	    my $sw = create_scrolled_window(my $text = Gtk2::TextView->new);
	    $sw->set_border_width(2);
	    $f->add($sw);
	    $text->get_buffer->set_text($message);
	    $_->show foreach $f, $sw, $text;
	    $w->set_size_request(400, 400);
	    $w->set_default_response('ok');
	} else {
	    $w  = Gtk2::MessageDialog->new($mainw, [qw(modal destroy-with-parent)], 'warning', 'ok', $message);
	}
	$w->run;
	$w->destroy;
    };
    urpm::select::set_priority_upgrade_option($urpm, $gurpmi::options{previous_priority_upgrade});
    $rpm_lock = urpm::lock::rpm_db($urpm, 'exclusive');
    $urpmi_lock = urpm::lock::urpmi_db($urpm);
    urpm::media::configure($urpm,
	root	    => $gurpmi::options{root},
	media	    => $gurpmi::options{media},
	searchmedia => $gurpmi::options{searchmedia},
    );
    $urpm->{options}{'verify-rpm'} = 0 if $gurpmi::options{'no-verify-rpm'};
    $urpm;
}

#- Callback for choices
sub ask_choice {
    my (undef, undef, undef, $choices) = @_;
    return $choices->[0] if $gurpmi::options{auto};
    my $radio;
    my @radios = map {
	$radio = Gtk2::RadioButton->new_with_label(
	    $radio ? $radio->get_group : undef,
	    (scalar $_->fullname) . " : " . $_->summary
	    . ($_->flag_installed ? N(" (to upgrade)") : '')
	    . ($_->flag_upgrade   ? N(" (to install)") : '')
	);
    } @$choices;
    my $d = Gtk2::Dialog->new(N("Package choice"), $mainw, [], N("_Cancel") => 0, N("_Ok") => 1);
    my $label = Gtk2::Label->new(N("One of the following packages is needed:"));
    $label->set_alignment(0.5, 0.5);
    $d->vbox->pack_start($label, 1, 1, 0);
    $d->vbox->pack_start($_, 1, 1, 0) foreach @radios;
    my $n = 0;
    $d->signal_connect(response => sub {
	if ($_[1] == 1) { #- "ok"
	    foreach (@radios) { last if $_->get_active; ++$n }
	}
	$d->destroy;
	exit(1) if $_[1] == 0; #- "cancel"
    });
    $radios[0]->set_active(1);
    $d->set_default_response(1); # defaults to ok
    $d->show_all;
    $d->run;
    $choices->[$n];
}

sub ask_continue {
    my ($msg, $nextclosure) = @_;
    my $vbox = Gtk2::VBox->new(0, 5);
    $vbox->pack_start(new_label($msg), 1, 1, 0);
    my $continue_button = Gtk2::Button->new(but(N("_Ok")));
    my $quit_button = Gtk2::Button->new(but(N("_Abort")));
    $quit_button->signal_connect(clicked => \&quit);
    $continue_button->signal_connect(clicked => sub { goto &$nextclosure });
    add_button_box($vbox, $quit_button, $continue_button);
    change_mainw($vbox);
    # default is to continue, but according to some HIG, warning should reverse the choise and defaults to abort
    $mainw->set_focus($continue_button); # also set_default should be called but it gives a warning!
}

sub ask_continue_blocking {
    my ($msg) = @_;
    my $w = Gtk2::MessageDialog->new($mainw, [qw(modal destroy-with-parent)], 'question', 'yes-no', $msg);
    my $answer = $w->run;
    $w->destroy;
    exit(1) if $answer eq 'no';
}

sub do_install {
    wait_label();
    my @ask_remove = urpm::select::removed_packages($urpm, $state);
    @ask_remove
	? ask_continue(N(
	    "The following packages have to be removed for others to be upgraded:\n%s\nContinue installation anyway?",
	    urpm::select::translate_why_removed($urpm, $state, @ask_remove)
	), \&do_install_2)
	: goto &do_install_2;
}

sub do_install_2 () {
    my @to_install;
    my $sum;
    foreach my $pkg (sort { $a->name cmp $b->name } @{$urpm->{depslist}}[keys %{$state->{selected}}]) {
        if ($pkg->arch ne 'src') {
            push @to_install, scalar $pkg->fullname;
            $sum += $pkg->size;
        }
    }
    $urpm->{nb_install} = @to_install;
    @to_install > 1 && !$gurpmi::options{auto}
	? ask_continue(
		(scalar(@to_install) == 1 ?
		N("To satisfy dependencies, the following package is going to be installed:")
		: N("To satisfy dependencies, the following packages are going to be installed:"))
       	. join("\n", '', @to_install, '') 
	. P("(%d package, %d MB)", "(%d packages, %d MB)", scalar(@to_install), scalar(@to_install), toMb($sum)),
                    , \&do_install_3)
	: goto \&do_install_3;
}

sub do_install_3 () {
    wait_label(N("Package installation..."));
    my ($local_sources, $blists) = urpm::get_pkgs::selected2local_and_blists($urpm, $state->{selected});
    $local_sources || $blists or $urpm->{fatal}(3, N("unable to get source packages, aborting"));
    my $vbox = Gtk2::VBox->new(0, 5);
    my $progress_label = Gtk2::Label->new('-');
    $vbox->pack_start($progress_label, 1, 1, 0);
    my $progressbar = Gtk2::ProgressBar->new;
    $progressbar->set_size_request(450, -1);
    $vbox->pack_start($progressbar, 0, 0, 0);
    change_mainw($vbox);
    my $progress_nb;
	    my $callback_inst = sub {
		my ($urpm, $type, $id, $subtype, $amount, $total) = @_;
		my $pkg = defined $id ? $urpm->{depslist}[$id] : undef;
		if ($subtype eq 'start') {
		    if ($type eq 'trans') {
			$progress_label->set_label(N("Preparing..."));
		    } else {
			defined $pkg
			    and $progress_label->set_label(
				N("Installing package `%s' (%s/%s)...", $pkg->name, ++$progress_nb, $urpm->{nb_install})
			    );
		    }
		} elsif ($subtype eq 'progress') {
		    $progressbar->set_fraction($amount / $total);
		}
		sync();
	    };

    my $exit_code = urpm::main_loop::run($urpm, $state, scalar(@gurpmi::names),  \@ask_unselect, \%requested, {
        bad_signature => sub {
            my ($msg, $msg2) = @_;
            ask_continue_blocking("$msg\n$msg2");
        },
        copy_removable => sub {
            #FIXME: use use hal to wait-for/mount cdroms: 
            my $w = Gtk2::MessageDialog->new($mainw, [qw(modal destroy-with-parent)], 'warning', 'ok-cancel',
                                             N("Please insert the medium named \"%s\"", $_[0])
                                         );
            my $response = $w->run;
            $w->destroy;
            exit 1 if $response eq 'cancel';
            1;
        },
        trans_log => sub {
            my ($mode, $file, $percent) = @_;
            if ($mode eq 'start') {
                $file =~ s|/*\s*$||; $file =~ s|.*/||;
                $progress_label->set_label(N("Downloading package `%s'...", $file));
                select(undef, undef, undef, 0.1); #- hackish
            } elsif ($mode eq 'progress') {
                $progressbar->set_fraction($percent / 100);
            } elsif ($mode eq 'end') {
                $progressbar->set_fraction(1);
            }
            sync();
        },
        ask_yes_or_no => sub {
            my ($title, $msg) = @_;
            my $w = Gtk2::MessageDialog->new($mainw, [qw(modal destroy-with-parent)], 'warning', 'yes-no', $msg);
            my $response = $w->run;
            $w->destroy;
            return $response eq 'ok';
        },
        completed => sub {
            $urpmi_lock->unlock;
            $rpm_lock->unlock;
            urpm::removable::try_umounting_removables($urpm);
            $vbox = Gtk2::VBox->new(0, 5);
            $progress_label = Gtk2::Label->new('-');
            exit 0 if $gurpmi::options{auto};
            my $sw = create_scrolled_window($progress_label);
            $sw->set_size_request(500, 200);
            $vbox->pack_start($sw, 1, 1, 0);
            my $quit_button = Gtk2::Button->new(but(N("_Done")));
            $quit_button->signal_connect(clicked => \&quit);
            add_button_box($vbox, $quit_button);
            change_mainw($vbox);
            $mainw->set_focus($quit_button);
        },
        missing_files_summary => sub {
            my ($error_sources) = @_;
            $progress_label->set_label(N("Installation failed, some files are missing:\n%s\nYou may want to update your urpmi database",
                                         join("\n", map { s|([^:]*://[^/:\@]*:)[^/:\@]*(\@.*)|$1xxxx$2|; "    $_" }
                                                values %$error_sources)));
        },
        trans_error_summary => sub {
            my ($_nok, $errors) = @_;
            $progress_label->set_label(N("Installation failed:") . "\n" . join("\n",  map { "\t$_" } @$errors));
        },
        # TODO: use urpmi strings:
        already_installed_or_not_installable => sub {
            my ($_msg1, $_msg2) = @_;
            $progress_label->set_label(N("The package(s) are already installed"));
        },
        success_summary => sub { $progress_label->set_label(N("Installation finished")) },
        callback_report_uninst => sub { $progress_label->set_label(N("removing %s", $_[0])) },
        inst => $callback_inst,
        trans => $callback_inst,
    }
                     );

    #- restart gurpmi if needed, keep command line for that.
    if ($restart_itself && !$exit_code) {
        print N("restarting urpmi"), "\n";
        #- it seems to work correctly with exec instead of system, provided
        #- added --previous-priority-upgrade to allow checking if yet if
        #-   priority-upgrade list has changed. and make sure we don't uselessly restart
        @ARGV = ('--previous-priority-upgrade=' . $urpm->{options}{'priority-upgrade'}, 
                grep { !/^--no-priority-upgrade$|--previous-priority-upgrade=/ } @ARGV);
        exec $0, @ARGV;
    }
    exit $exit_code;
}