#!/usr/bin/perl

# Must be done as early as possible to avoid issues when displaying translated
# strings
BEGIN {
    push @::textdomains, 'draklive-install';
    $::isLiveInstall = 1;
}

use lib qw(/usr/lib/libDrakX);
use standalone;
use interactive;
use fs;
use fs::any;
use fs::type;
use fs::partitioning;
use fs::partitioning_wizard;
use partition_table;
use MDK::Common;
use common;
use feature qw(state);
use File::Basename;
use ugtk3;

($::rootwidth, $::rootheight) = (Gtk3::Gdk::Screen::width, Gtk3::Gdk::Screen::height);
$::real_windowwidth  = $::rootwidth  > 800 ? 750 : 600;
$::real_windowheight = $::rootheight > 600 ? 500 : 400;

{
    use diskdrake::interactive;
    package diskdrake::interactive;
    my $old = \&hd_possible_actions_base;
    undef *hd_possible_actions_base;
    *hd_possible_actions_base = sub {
	#- for the partition wizard to show the auto-allocate option
	local $::isInstall = 1;
	&$old;
    };
    undef *Done;
    #- skip the fstab/reboot checks (see below FIXME note)
    *Done = \&diskdrake_interactive_Done;
    #- don't ask whether to Move/Hide old files (FIXME: add a method to skip that to diskdrake::interactive)
    undef *need_migration;
    *need_migration = sub { 'hide' };
}

install_live();

sub install_live() {
    my $in = 'interactive'->vnew('su');
    $in->{pop_wait_messages} = 0;

    $::isWizard = 1;
    $::Wizard_no_previous = 1;
    $::Wizard_pix_up = "draklive-install";
    any::set_wm_hints_if_needed($in);

    my $all_hds = {};
    my $fstab = [];
    $::prefix = '/mnt/install';

    my $system_file = '/etc/sysconfig/draklive-install';
    my %settings = getVarsFromSh($system_file);

    my $copy_source = $settings{SOURCE} || '/';

    my $persistent = -e '/run/mgalive/persistent';

    display_start_message();
    init_hds($in, $all_hds, $fstab);
    ask_partitions_loop($in, $all_hds, $fstab, $copy_source);
    remove_unused_packages($in, $copy_source) if !$persistent;
    prepare_root($in);
    copy_root($in, $copy_source);
    remove_unused_packages($in, $::prefix) if $persistent;
    complete_install($in, $all_hds);
    setup_bootloader($in, $all_hds, $fstab);
    finish_installation($fstab);
    display_end_message($in);
    $in->exit(0);
}

sub umount_all {
    my ($fstab, $o_except_swap) = @_;
    #- make sure nothing is mounted in the new root
    foreach (sort { $b cmp $a } grep { /^$::prefix/ } map { (split)[1] } cat_('/proc/mounts')) {
        system('umount', $_);
    }
    #- make sure selected devices aren't mounted, and swap isn't used
    if (!$o_except_swap) {
        foreach (grep { isSwap($_) } @$fstab) {
            eval { fs::mount::swapoff($_->{device}) };
        }
    }
    foreach (grep { $_->{isMounted} && !isSwap($_) } @$fstab) {
        system('umount', "/dev/$_->{device}");
        $_->{isMounted} = 0;
    }
}

sub on_reboot_needed {
    my ($in) = @_;
    fs::partitioning_wizard::warn_reboot_needed($in);
    $in->exit(0);
}

sub display_start_message() {
    require any;
    my $has_running_wm = to_bool(any::running_window_manager());
    local $::isStandalone = $has_running_wm; # center me if run in xsetup.d script
    mygtk3::enable_quit_popup(1);
    my $w = ugtk3->new(N("Mageia Live"));
    my $dialogue = ugtk3::gtknew('VBox', children_centered => [
                       ugtk3::gtkcreate_img("MageiaLive-install"),
                       ugtk3::gtknew('Label', height => 5),
                       ugtk3::gtknew('Label', text => N("This wizard will help you to install the live distribution.")),
                   ]);
    $dialogue->set_vexpand(1);
    ugtk3::gtkadd($w->{window},
                  ugtk3::gtknew('VBox', children_tight => [
                      $dialogue,
                      ugtk3::create_okcancel($w),
                  ]));
    $w->{ok}->grab_focus;
    $w->main;
}

sub umount_first_pass() {
    local $::prefix = undef;
    my $all_hds = fsedit::get_hds();
    fs::get_raw_hds('', $all_hds);
    fs::get_info_from_fstab($all_hds);
    my $fstab = [ fs::get::fstab($all_hds) ];
    fs::merge_info_from_mtab($fstab);

    #- inlined from fs::mount::umount_all to go on when one umount fail
    #- (maybe the sort function could be shared)
    log::l("unmounting all filesystems");
    foreach (sort { $b->{mntpoint} cmp $a->{mntpoint} } 
	       grep { $_->{mntpoint} && !$_->{real_mntpoint} } @$fstab) {
	eval { fs::mount::umount_part($_) };
	log::l("error unmounting $_->{mntpoint}: $@") if $@;
    }
}

sub init_hds {
    my ($in, $all_hds, $fstab) = @_;
    my $wait = $in->wait_message('', N("Please wait"));
    umount_first_pass();
    eval { fs::any::get_hds($all_hds, $fstab, [], {}, 'skip_mtab', $in) };

    #- fs::any::get_hds does not return mounts that are not in fstab
    my @mounted = fs::read_fstab('', '/proc/mounts');

    my $live_device = basename(cat_('/run/mgalive/basedev'));
    chomp($live_device);
    #- remove live device from the detected hds, so that bootloader is not installed on it:
    #-  bootloader installation uses first device from detect_devices::get, which tries to list devices
    #-  by booting order, and our live system is likely to be here in first position
    #- it can be either a partition (USB) or the full disk (Hybrid on USB)
    @{$all_hds->{hds}} = grep {
        $_->{device} ne $live_device &&
        !member($live_device, map { $_->{device} } partition_table::get_normal_parts_and_holes($_));
    } @{$all_hds->{hds}} if $live_device;

    #- some of the selected devices may have been manually mounted or auto-mounted, and we need to
    #- ensure they will be unmounted before we start partitioning (mga#20268)
    foreach my $p (@$fstab) {
        $p->{isMounted} = 1 if grep { $_->{device} eq $p->{device} } @mounted;
    }

    my $err = $@;
    umount_all($fstab);
    if ($err) {
        undef $wait;
        $in->ask_warn(N("Error"), [ formatError($err) ]);
        $in->exit(1);
    }
}

sub ask_partitions_loop {
    my ($in, $all_hds, $fstab, $copy_source) = @_;

    while (1) {
        eval { ask_partitions($in, $all_hds, $fstab, $copy_source) };
        my $err = $@ or last;
        $in->exit(1) if $err =~ /wizcancel/ ||
          !$in->ask_warn(N("Error"), [ N("An error occurred"), formatError($err) ]);
    }
}

sub ask_partitions {
    my ($in, $all_hds, $fstab, $copy_source) = @_;
    fs::partitioning_wizard::main($in, $all_hds, $fstab, [], undef, {}, 'skip_mtab');

    mkdir_p($::prefix) or die "unable to create $::prefix";

    # There is an unidentified condition which stops udevd from calling the
    # BLKRRPART ioctl when the "use entire disk" solution is selected. Adding
    # this udevadm settle' call appears to fix it (although it may just be the
    # extra delay that does the job). Look at removing this once we switch to
    # using libparted for all partition table types.
    system('udevadm', 'settle');

    fs::any::write_hds($all_hds, $fstab, undef, sub { on_reboot_needed($in) }, {});
    fs::any::check_hds_boot_and_root($all_hds, $fstab);
    fs::partitioning::choose_partitions_to_format($in, $fstab);

    my $total = get_total_size($in, $copy_source);
    my $available = fs::any::getAvailableSpace($fstab, 'skip_mounted', 1);
    $total += fs::any::getNeededMinSpace($available);
    die N("Not enough space available (%s available while %s are needed)",
          formatXiB($available), formatXiB($total)) . "\n"
      if $total > $available;

    umount_all($fstab);
    fs::partitioning::format_mount_partitions($in, $all_hds, $fstab);
}

sub remove_unused_packages {
    my ($in, $o_prefix) = @_;
    require pkgs;
    #in remove_unused_packages, we want to get the locale from the currently
    #running system, but we want to remove unused packages from the 
    #system based in $o_prefix, that's why we use an extra arg instead of 
    #directly using $::prefix
    local $::prefix;
    pkgs::remove_unused_packages($in, $in->do_pkgs, $o_prefix);
    run_program::rooted($o_prefix, 'urpme', '--auto', '--auto-orphans');
}

sub prepare_root {
    my ($in) = @_;
    #- create required directories and devices (early to have a consistent root before calling other programs)
    my $_wait = $in->wait_message('', N("Please wait"));
    fs::any::prepare_minimal_root();
    #- /media isn't included in the minimal root, and we don't copy it to avoid copying stale mount points
    mkdir_p("$::prefix/media");
}

sub build_copy_command {
    my ($source, $dest) = @_;
    join(' ',
         'tar', 'c', '--one-file-system', '--exclude=./media', '--exclude=./mnt', '-C', $source, '.',
         '|',
         'tar', 'xvv', '-C', $dest,
     );
}

sub get_total_size {
    my ($in, $source) = @_;
    state %total;
    return $total{$source} if $total{$source};
    my $_wait = $in->wait_message('', N("Computing total size"));
    $total{$source} = first(split(/\s+/, `du -sbx /run/mgalive/ovlsize 2>/dev/null`));
}

sub sync_logs() {
    cp_af('/var/log', $::prefix . '/var');
}

sub copy_root {
    my ($in, $copy_source) = @_;
    my $total = get_total_size($in, $copy_source);

    my ($wait, $update_progress) = copying_message_with_progress_bar($in, N("Copying in progress"));
    open(my $OUTPUT, '-|', build_copy_command($copy_source, $::prefix));
    {
        local $_;
        my $current = my $previous = 0;
        while (<$OUTPUT>) {
            (undef, undef, my $size) = split;
            $current += $size;
            if ($current <= $total && $current/$total > $previous/$total + 0.001) {
                $update_progress->('', $current, $total);
                $previous = $current;
            }
        }
    }
    if (!close($OUTPUT)) {
        undef $wait;
        undef $update_progress;
        $in->ask_warn(N("Error"), N("Unable to copy files to new root"));
        $in->exit(1);
    }
    sync_logs();
}

sub clean_harddrake_hds {
    my ($prefix) = @_;
    #- remove harddisks from harddrake's config file, so that hardddisks
    #- are automatically rediscovered at first boot
    require Storable;
    my $harddrake_file = $prefix . "/etc/sysconfig/harddrake2/previous_hw";
    my $harddrake_conf = eval { Storable::retrieve($harddrake_file) };
    if ($harddrake_conf) {
        delete $harddrake_conf->{HARDDISK};
        Storable::store($harddrake_conf, $harddrake_file);
    }
}


sub complete_install {
    my ($in, $all_hds) = @_;
    my $_wait = $in->wait_message('', N("Please wait"));

    my $real_rpm_dir = "/tmp/rpm/real";
    cp_f(glob($real_rpm_dir . "/*"), $::prefix . "/var/lib/rpm") if -d $real_rpm_dir;

    #- FIXME: maybe factorize with draklive, using draklive --clean-chroot ?
    #- remove unwanted files and packages
    my $live_user = chomp_(cat_('/etc/draklive-install.d/user'));
    my $live_user_desktop = $live_user && chomp_(run_program::rooted_get_stdout($::prefix, "su - $live_user -c 'xdg-user-dir DESKTOP'"));
    unlink(map { $::prefix . $_ } '/.autofsck',
           chomp_(cat_(glob('/etc/draklive-install.d/remove.d/*'))),
           if_($live_user_desktop,
               $live_user_desktop . '/draklive-copy-wizard.desktop',
               $live_user_desktop . '/draklive-install.desktop'),
           );
    {
        #- do not allow update-menus to create home directory with invalid perms
        local $ENV{HOME} = '/root';
        system('chroot', $::prefix, 'rpm', '-e', 'draklive-install');
    }

    foreach (glob('/etc/draklive-install.d/run.d/*')) {
	    run_program::rooted($::prefix, $_);
    }

    #- copy sysconfig files for first boot
    cp_f(glob('/etc/draklive-install.d/sysconfig/*'), $::prefix . '/etc/sysconfig');

    #- unselect live user in kdm
    my $kdm_cfg = common::read_alternative('kdm4-config');
    update_gnomekderc($::prefix . $kdm_cfg,
                      'X-:0-Greeter' => (PreselectUser => 'None', DefaultUser => '')) if -f $kdm_cfg;
    my $autologin = any::get_autologin();
    delete $autologin->{user};
    any::set_autologin($in->do_pkgs, $autologin);

    #- force regeneration of initrd
    unlink($_) foreach glob("$::prefix/boot/initrd*.img");

    #- allow to install doc in disk install
    substInFile { undef $_ if /^\%_excludedocs/ } $::prefix . '/etc/rpm/macros';

    fs::write_fstab($all_hds, $::prefix);

    clean_harddrake_hds($::prefix);

    # enable back some disabled services
    require services;
    services::start_service_on_boot($_) foreach chomp_(cat_('/etc/draklive-install.d/services'));

    sync_logs();
}

sub setup_bootloader {
    my ($in, $all_hds, $fstab) = @_;
    use bootloader;
    my $bootloader = {};
    my $_wait = $in->wait_message('', N("Preparing initial startup program..."));
    any::setupBootloaderBeforeStandalone($in->do_pkgs, $bootloader, $all_hds, $fstab);
    undef $_wait;
    local $::Wizard_no_previous = 0;
    #- don't use any::setupBootloaderUntilInstalled - it creates unwanted mount
    #- points in the live system
    do {
        any::setupBootloader($in, $bootloader, $all_hds, $fstab, $ENV{SECURE_LEVEL}) or $in->exit;
    } while !any::installBootloader($in, $bootloader, $all_hds);
    sync_logs();
}

sub clean_live_system_hds() {
    #- clean fstab and harddrake config in the live system
    #- since partitions UUIDs of the installed system have been modified
    #- (useful for persistent live systems)
    local $::prefix = undef;
    clean_harddrake_hds($::prefix);
    my $all_hds = fs::get::empty_all_hds(); #- skip real harddisks
    fs::get_raw_hds('', $all_hds);
    fs::get_info_from_fstab($all_hds);
    fs::write_fstab($all_hds, $::prefix);
}

sub finish_installation {
    my ($fstab) = @_;
    #- remove the extra authorisation draklive added
    run_program::rooted($::prefix, 'sed', '-i', '/for draklive-install/d', '/etc/pam.d/polkit-1');
    #- signal to the dracut shutdown script that the Live medium should be ejected
    system('touch', '/run/mgalive/installed');
    sync_logs();
    #- cleanly umount here, it will avoid fs journals to be corrupted after a hackish reboot
    #- but leave swap space, to avoid out-of-memory error on machines with little memory
    #- draklive-install-lock-storage will unmount swap after we exit and free up some memory
    umount_all($fstab, 1);
    clean_live_system_hds();
}

sub display_end_message {
    my ($in) = @_;
    $::Wizard_finished = 1;
    $in->ask_okcancel(N("Congratulations"), N("Please halt your computer, remove your live system, and restart your computer."));
}

###
### duplicate code
###

# FIXME: code begins to fork
# FIXME: => in mga4 add diskdrake::interactive::set_skip_fstab_reboot_steps() and use it instead

#- from disdrake::interactive
{
    package diskdrake::interactive;
  sub diskdrake_interactive_Done {
    my ($in, $all_hds) = @_;
    eval { raid::verify($all_hds->{raids}) };
    if (my $err = $@) {
	$::expert or die;
	$in->ask_okcancel('', [ formatError($err), N("Continue anyway?") ]) or return;
    }
    foreach (@{$all_hds->{hds}}) {
	if (!write_partitions($in, $_, 'skip_check_rebootNeeded')) {
	    return if !$::isStandalone;
	    $in->ask_yesorno(N("Quit without saving"), N("Quit without writing the partition table?"), 1) or return;
	}
    }
    #- skip that fstab/reboot steps
    if (!$::isInstall && 0) { 
	my $new = fs::fstab_to_string($all_hds);
	if ($new ne $all_hds->{current_fstab} && $in->ask_yesorno('', N("Do you want to save /etc/fstab modifications?"), 1)) {
	    $all_hds->{current_fstab} = $new;
	    fs::write_fstab($all_hds);
	}
	update_bootloader_for_renumbered_partitions($in, $all_hds);

	if (any { $_->{rebootNeeded} } @{$all_hds->{hds}}) {
	    $in->ask_warn('', N("You need to reboot for the partition table modifications to take place"));
	    tell_wm_and_reboot();
	}
    }
    if (my $part = find { $_->{mntpoint} && !maybeFormatted($_) } fs::get::fstab($all_hds)) {
	$in->ask_okcancel('', N("You should format partition %s.
Otherwise no entry for mount point %s will be written in fstab.
Quit anyway?", $part->{device}, $part->{mntpoint})) or return if $::isStandalone && 0; #- no, please
    }
    1;
  }
}

# forked from interactive::wait_message
sub copying_message {
    my ($o, $title, $message, $b_temp) = @_;

    my $dialogue = ugtk3::gtknew('VBox', padding => 5, children_centered => [
        ugtk3::gtkcreate_img("MageiaLive-advert"),
        $message,
    ]);
    $dialogue->set_vexpand(1);
    my $w = $o->wait_messageW($title, N("Copying in progress"), $dialogue);
    push @tempory::objects, $w if $b_temp;
    my $b = before_leaving { $o->wait_message_endW($w) };

    #- enable access through set
    MDK::Common::Func::add_f4before_leaving(sub { $o->wait_message_nextW($_[1], $w) }, $b, 'set');
    $b;
}

# forked from interactive::gtk::wait_message_with_progress_bar
sub copying_message_with_progress_bar {
    my ($in, $o_title) = @_;

    my $progress = Gtk3::ProgressBar->new;
    my $w = copying_message($in, $o_title, $progress);
    my $displayed;
    $progress->signal_connect(draw => sub { $displayed = 1; 0 });
    $w, sub {
	my ($msg, $current, $total) = @_;
	if ($msg) {
	    $w->set($msg);
	}

	if ($total) {
	    $progress or internal_error('You must first give some text to display');
	    my $fraction = min(1, $current / $total);
	    if ($fraction != $progress->get_fraction) {
		$progress->set_fraction($fraction);
		$progress->show;
		$displayed = 0;
		mygtk3::flush() while !$displayed;
	    }
	} else {
	    $progress->hide;
	    mygtk3::flush();
	}
    };
}
