#!/usr/bin/perl

use Config;
use FileHandle;
use MDK::Common;
use POSIX;
use Carp;

Config->import;
my ($arch) = $Config{archname} =~ /(.*?)-/;

my $default_append = '';
my $default_acpi = '';
my $default_vga = "vga=788 splash quiet";
my $default_iswmd = "noiswmd";
my $timeout = $ENV{BOOT_AUTOMATIC_METHOD} ? 5 : 150;
my $lib = $arch eq 'x86_64' ? 'lib64' : 'lib';
my $isolinux_bin = '/usr/lib/syslinux/isolinux.bin';

my $tmp_mnt = '/tmp/drakx_mnt';
my $tmp_initrd = '/tmp/drakx_initrd';

my $sudo;
if ($>) {
    $sudo = "sudo";
    $ENV{PATH} = "/sbin:/usr/sbin:$ENV{PATH}";
}

sub __ { print @_, "\n"; system(@_) }
sub _ { __ @_; $? and croak "'" . join(' ', @_) . "failed ($?)\n" }

sub mke2fs { 
    my ($f) = @_;
    _ "/sbin/mke2fs -q -m 0 -F -s 1 $f";
    _ "/sbin/tune2fs -c 0 -U clear -T 1970010101 $f";
}

_ "mkdir -p $tmp_mnt";
mkdir "images";

my @kernels = chomp_(cat_('all.kernels/.list'));

my @all_images = (
		  if_($arch =~ /i.86/, 'isolinux', 'boot.iso', 'all.img', 'hd_grub.img'),
		  if_($arch =~ /x86_64/, 'isolinux', 'boot.iso', 'all.img', 'hd_grub.img'),
		 );

my @images = @ARGV ? @ARGV : map { "images/$_" } @all_images;

foreach my $img (@images) {
    my ($type, $I, $extension) = $img =~ m!([^/]*)(64)?\.([^.]*)$!;

    if ($img =~ /hd_grub/) {
	hd_grub($img);
    } elsif ($img =~ /isolinux/) {
	isolinux(\@kernels);

	if (my ($tftpboot) = grep { -e $_ } qw(/tftpboot /var/lib/tftpboot)) {
	    system("/bin/cp -f isolinux/alt0/* $tftpboot");
	}
    } elsif ($img =~ /boot.iso/) {
	boot_iso($img, \@kernels);
    } elsif ($extension eq 'rdz') {
	initrd($type, $I, "$img-$_", $_) foreach @kernels;
    } elsif ($extension eq 'img') {
	print STDERR "calling boot_img_$arch for $img\n";
	$::{"boot_img_$arch"}->($type, $I, "$img-$_", $_, "all.kernels/$_/vmlinuz") foreach @kernels;
	rename("$img-$kernels[0]", $img);
    } else {
	die "unknown image $img";
    }
}

sub syslinux_color {
    "0" . {
	default => '7',
	blue    => '9',
	green   => 'a',
	red     => 'c',
	yellow  => 'e',
	white   => 'f',
    }->{$_[0]} || die "unknown color $_[0]\n";
}

sub syslinux_msg { 
    my ($msg_xml_file, @more_text) = @_;

    require XML::Parser;

    sub xml_tree2syslinux {
	my ($current_color, $tree) = @_;
	my (undef, @l) = @$tree;
	join('', map {
	    my ($type, $val) = @$_;
	    if ($type eq '0') {
		$val;
	    } else {
		syslinux_color($type) . xml_tree2syslinux($type, $val) . syslinux_color($current_color);
	    }
	} group_by2(@l));
    }

    print "parsing $msg_xml_file\n";
    my $tree = XML::Parser->new(Style => 'Tree')->parsefile($msg_xml_file);
    $tree->[0] eq 'document' or die "bad file $msg_xml_file\n";
    my $text = xml_tree2syslinux('default', $tree->[1]);

    ""
      . $text . join('', @more_text)
      . "\n" . syslinux_color('red') . "[F1-Help] [F2-Advanced Help]" . syslinux_color('default') . "\n";
}

sub syslinux_cfg {
    my ($entries, $b_gfxboot) = @_;
    my $default = 'linux';

    my $header = <<EOF;
default $default
prompt 1
timeout $timeout
display help.msg
implicit 1
EOF
    my $header_gfxboot = <<EOF;
ui gfxboot.c32 bootlogo
label harddisk
  com32 chain.c32 hd0 exit
EOF
    my $header_non_gfxboot = <<EOF;
F1 help.msg
F2 advanced.msg
F3 boot.msg
EOF

    my @l = map {
	$_->{append} =~ s/\s+/ /g;
	"label $_->{label}\n" .
	"  kernel $_->{kernel}\n" .
	($_->{initrd} ? "  append initrd=$_->{initrd} $_->{append}\n" : '');
    } @$entries;

    $header . ($b_gfxboot ? $header_gfxboot : $header_non_gfxboot) . join('', @l);
}

sub trim {
   return $_[0] =~ s/^\s+|\s+$//rg;
}

sub initrd {
    my ($type, $I, $img, $kernel) = @_;
    my $stage1_binary = $ENV{USE_LOCAL_STAGE1} ? trim(`realpath ../mdk-stage1/stage1`) : "";
    my $init_binary = $ENV{USE_LOCAL_STAGE1} ? trim(`realpath ../mdk-stage1/init`) : "";
    my ($ext) = $img =~ /rdz-(.*)/ or die "bad initrd name ($img)";
    my $modules = " mgainstaller ";
    my $drivers = `perl ../kernel/modules.pl list_needed_modules $kernel | xargs`;
    my $fakedrivers = `perl ../kernel/modules.pl list_fake_modules $kernel | xargs`;

    if ($ENV{DEBUGSTAGE1} || $ENV{BUILD_KA}) {
      $modules="$modules busybox ";
    }
    $modules="$modules mgakadeploy " if ($ENV{BUILD_KA});

    mkdir_p("build/dracut.conf.d");
    # TODO if --nofscks and --no-hostonly are switched, dracut gives an error - fix or report upstream
    __ "DRAKX_STAGE1_BINARY=$stage1_binary DRAKX_INIT_BINARY=$init_binary DRAKX_FAKE_MODULES='$fakedrivers' dracut --conf dracut.conf --confdir ./build/dracut.conf.d --add ' $modules ' --add-drivers ' $drivers ' '$img' '$kernel'";
    chmod(0644, $img);
}


sub entries_append {
    my ($type) = @_;

    my $automatic = $type =~ /cdrom/ ? 'automatic=method:cdrom ' : '';
    $automatic .= 'changedisk ' if $type =~ /changedisk/;

    my @simple_entries = (
	linux => $default_vga,
	vgalo => "vga=785",
	vgahi => "vga=791",
	text => "text",
#	patch => "patch $default_vga",
	rescue => "audit=0 rescue",
    );
    my @entries = (
        (map { $_->[0] => "$automatic$default_acpi $default_iswmd $_->[1]" } group_by2(@simple_entries)),
	noacpi => "$automatic$default_vga $default_iswmd acpi=off",
#	restore => "$automatic$default_vga restore",
    );

    map { { label => $_->[0], append => join(' ', grep { $_ } $default_append, $_->[1]) } }
      group_by2(@entries);
}

sub syslinux_cfg_all {
    my ($type, $b_gfxboot) = @_;

    syslinux_cfg([
	(map {
	    { kernel => "$arch/vmlinuz", initrd => "$arch/all.rdz", %$_ };
	} entries_append($type)),
	(map_index {
	    { label => "$arch", kernel => "$arch/vmlinuz", initrd => "$arch/all.rdz", 
	      append => join(' ', grep { $_ } $default_append, $default_acpi, $default_vga, $default_iswmd) };
	} @kernels),
	{ label => 'memtest', kernel => 'memtest' },
    ], $b_gfxboot);
}
sub remove_ending_zero {
    my ($img) = @_;
    _(q(perl -0777 -pi -e 's/\0+$//' ) . $img);
}

sub boot_img_i386 {
    my ($type, $I, $img, $kernel, $vmlinuz) = @_;

    _ "rm -rf $tmp_mnt"; mkdir $tmp_mnt;
    _ "cat $vmlinuz > $tmp_mnt/vmlinuz";

    output("$tmp_mnt/help.msg", syslinux_msg('help.msg.xml'));
    output("$tmp_mnt/advanced.msg", syslinux_msg('advanced.msg.xml'));

    (my $rdz = $img) =~ s/\.img/.rdz/;
    (my $initrd_type = $type) =~ s/-changedisk//;
    initrd($initrd_type, $I, $rdz, $kernel);
    my $short_type = substr($type, 0, 8);

    output("$tmp_mnt/syslinux.cfg", 
	   syslinux_cfg([ map {
			    { kernel => 'vmlinuz', initrd => "$short_type.rdz", %$_ };
			} entries_append($type) ]));

    _ "cp -f $rdz $tmp_mnt/$short_type.rdz";
    unlink $rdz;

    # mtools wants the image to be a power of 32
    my $syslinux_overhead = 32 * 16;
    my $size = max(ceil(chomp_(`du -s -k $tmp_mnt`) / 32) * 32 + $syslinux_overhead, 1440);
    _ "dd if=/dev/zero of=$img bs=1k count=$size";

    _ "/sbin/mkdosfs $img";
    _ "mcopy -i $img $tmp_mnt/* ::";
    _ "syslinux $img";
    _ "rm -rf $tmp_mnt";
}

# alias to x86 variant, slightly bigger with images though
sub boot_img_x86_64 { &boot_img_i386 }

sub VERSION {
    my ($kernels) = @_;

    map { "$_\n" }
      $ENV{DISTRIB_DESCR},
      scalar gmtime(),
      '', @$kernels;
}

sub syslinux_all_files {
    my ($dir, $kernels) = @_;

    eval { rm_rf($dir) }; mkdir_p($dir);

    @$kernels or die "syslinux_all_files: no kernel\n";

    $default_vga =~ /788/ or die 'we rely on vga=788 for bootsplash';

    each_index {
	mkdir "$dir/$arch", 0777;
	_ "cp all.kernels/$_/vmlinuz $dir/$arch";
	initrd('all', '', "images/all.rdz-$_", $_);
	rename("images/all.rdz-$_", "$dir/$arch/all.rdz");
    } @$kernels;

    _ "install -m 644 -D /boot/memtest* $dir/memtest";

    output("$dir/help.msg", syslinux_msg('help.msg.xml'));
    output("$dir/advanced.msg", syslinux_msg('advanced.msg.xml', 
					     "\nYou can choose the following kernels :\n",
					     map_index { " o  " . syslinux_color('white') . "alt$::i" . syslinux_color('default') . " is kernel $_\n" } @$kernels));
}

sub isolinux {
    my ($kernels) = @_;

    syslinux_all_files('isolinux', $kernels);

    _ "cp $isolinux_bin isolinux/isolinux.bin";
    _ "cp /usr/lib/syslinux/ifcpu.c32 isolinux/ifcpu.c32";
    _ "cp /usr/lib/syslinux/ldlinux.c32 isolinux/ldlinux.c32";
    _ "cp /usr/lib/syslinux/libcom32.c32 isolinux/libcom32.c32";
    _ "cp /usr/lib/syslinux/libgpl.c32 isolinux/libgpl.c32";
    _ "cp /usr/lib/syslinux/libmenu.c32 isolinux/libmenu.c32";
    _ "cp /usr/lib/syslinux/libutil.c32 isolinux/libutil.c32";
    _ "cp /usr/lib/syslinux/gfxboot.c32 isolinux/gfxboot.c32";
    _ "cp /usr/lib/syslinux/chain.c32 isolinux/chain.c32";
    output("isolinux/isolinux.cfg", syslinux_cfg_all('cdrom', 1));

    xbox_stage1() if arch() =~ /i.86/;
}

sub xbox_stage1() {
    my $xbox_kernel = find { /xbox/ } all('all.kernels') or return;

    my $dir = 'isolinux/xbox';
    eval { rm_rf($dir) }; mkdir_p($dir);

    _ "cp all.kernels/$xbox_kernel/vmlinuz $dir";
    initrd('all', '', "images/all.rdz-$xbox_kernel", $xbox_kernel);
    rename("images/all.rdz-$xbox_kernel", "$dir/initrd");

    _ "cp /usr/share/cromwell/xromwell-installer.xbe $dir/default.xbe";
    output("$dir/linuxboot.cfg", <<EOF);
kernel $dir/vmlinuz
initrd $dir/initrd
append root=/dev/ram3 ramdisk_size=36000 automatic=method:cdrom
EOF
}

sub boot_iso {
    my ($iso, $kernels) = @_;

    syslinux_all_files('.boot_iso/isolinux', $kernels);

    output('.boot_iso/VERSION', VERSION($kernels));	   
   
    # for the boot iso, use standard isolinux
    _ "cp $isolinux_bin .boot_iso/isolinux/isolinux.bin";
    _ "cp /usr/lib/syslinux/ifcpu.c32 .boot_iso/isolinux/ifcpu.c32";
    _ "cp /usr/lib/syslinux/ldlinux.c32 .boot_iso/isolinux/ldlinux.c32";
    _ "cp /usr/lib/syslinux/libcom32.c32 .boot_iso/isolinux/libcom32.c32";
    _ "cp /usr/lib/syslinux/libgpl.c32 .boot_iso/isolinux/libgpl.c32";
    _ "cp /usr/lib/syslinux/libmenu.c32 .boot_iso/isolinux/libmenu.c32";
    _ "cp /usr/lib/syslinux/libutil.c32 .boot_iso/isolinux/libutil.c32";
    _ "cp /usr/lib/syslinux/chain.c32 .boot_iso/isolinux/chain.c32";

    my $with_gfxboot = 0;
    _ "cp /usr/share/gfxboot/themes/Mageia/install/* .boot_iso/isolinux" if $with_gfxboot;
# _ "cp /home/pixel/cooker/soft/theme/mandriva-gfxboot-theme/inst/* .boot_iso/isolinux" if $with_gfxboot;
    #_ "cp /home/teuf/mdv/src/mandriva-gfxboot-theme/inst/* .boot_iso/isolinux" if $with_gfxboot;
    _ "cp /usr/lib/syslinux/gfxboot.c32 .boot_iso/isolinux/gfxboot.c32" if $with_gfxboot;

    output('.boot_iso/isolinux/isolinux.cfg', syslinux_cfg_all('', $with_gfxboot));

    if ($ENV{BOOT_AUTOMATIC_METHOD}) {
        _ "sed -i 's#\\(append .*\\)\\(splash quiet\\|rescue\\)\$#\\1\\2 automatic=$ENV{BOOT_AUTOMATIC_METHOD}#' .boot_iso/isolinux/isolinux.cfg"
    }

    my $arch = arch();
    my $options = "-J -joliet-long -r -v -T -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table";
    my $cmd = "xorriso -as mkisofs -U -A 'Mageia-$ENV{DISTRIB_VERSION}-$arch-netinstall' -V 'Mageia-$ENV{DISTRIB_VERSION}-$arch-netinstall' -volset 'Mageia-$ENV{DISTRIB_VERSION}-$arch' $options";
    # create efi stuff on the fly
    if ($arch =~ /x86_64/) {
       _ "mkdir -p .boot_iso/EFI/BOOT/";
       # create efi loader
       my $efi_core    = "configfile normal boot linux loadenv ls reboot search search_label";
       my $efi_part_fs = "part_msdos part_gpt part_apple fat iso9660 udf";
       my $efi_gfx     = "gfxmenu gfxterm efi_gop efi_uga video video_bochs video_cirrus video_fb font png";
       _ "/usr/bin/grub2-mkimage --prefix='/EFI/BOOT' -O x86_64-efi -o .boot_iso/EFI/BOOT/bootx64.efi $efi_core $efi_part_fs $efi_gfx";
       _ "cp -f grub2.config .boot_iso/EFI/BOOT/grub.cfg";
       if ($ENV{BOOT_AUTOMATIC_METHOD}) {
           _ "sed -i 's#\\(linux .*\\)#\\1 automatic=$ENV{BOOT_AUTOMATIC_METHOD}#' .boot_iso/EFI/BOOT/grub.cfg";
           _ "sed -i 's#timeout=[0-9]*#timeout=1#' .boot_iso/EFI/BOOT/grub.cfg";
       }
       # add theme
       _ "cp -r /boot/grub2/{fonts,themes} .boot_iso/EFI/BOOT/";
       _ "cp -f grub2.theme .boot_iso/EFI/BOOT/themes/maggy/theme.txt";
       # create efiboot.img
       my $efisize = ceil(chomp_(`du -s -k .boot_iso/EFI`) / 1024) * 1024;
       my $efi_img = ".boot_iso/isolinux/efiboot.img";
       _ "dd if=/dev/zero of=$efi_img bs=1k count=$efisize";
       _ "/sbin/mkdosfs -F12 $efi_img";
       _ "mcopy -s -i $efi_img .boot_iso/EFI ::";
       # create iso
       _ "$cmd -eltorito-alt-boot -e isolinux/efiboot.img -no-emul-boot -o $iso .boot_iso";
       _ "isohybrid -u $iso";
    } else {
       _ "$cmd -o $iso .boot_iso";
       _ "isohybrid -o 1 $iso";
    }
    rm_rf('.boot_iso');
}

sub hd_grub {
    my ($img) = @_;
    my $mapfile = '/tmp/device.map.tmp';

    my ($grub_dir) = glob("/lib/grub/*-mageia");
    my @grub_files = map { "$grub_dir/$_" } qw(stage1 stage2);

    # mtools wants the image to be a power of 32
    my $size = ceil((40_000 + sum(map { -s $_ } @grub_files)) / 32 / 1024) * 32;

    _ "dd if=/dev/zero of=$img bs=1k count=$size";

    _ "rm -rf $tmp_mnt"; mkdir $tmp_mnt;
    _ "cp @grub_files $tmp_mnt";

    output("$tmp_mnt/menu.lst", <<EOF);
timeout 10
default 0
fallback 1

title Mageia Install

root (hd0,0)
kernel /cooker/isolinux/alt0/vmlinuz $default_append $default_acpi $default_vga $default_iswmd automatic=method:disk
initrd /cooker/isolinux/alt0/all.rdz

title Help

pause To display the help, press <space> until you reach "HELP END"
pause .
pause Please see http://doc.mageia.org/ for a friendlier solution
pause .
pause To specify the location where Mageia is copied,
pause choose "Mageia Install", and press "e".
pause Then change "root (hd0,0)". FYI:
pause - (hd0,0) is the first partition on first bios hard drive (usually hda1)
pause - (hd0,4) is the first extended partition (usually hda5)
pause - (hd1,0) is the first partition on second bios hard drive
pause Replace /cauldron to suit the directory containing Mageia
pause .
pause HELP END
EOF

    _ "/sbin/mkdosfs $img";
    _ "mcopy -i $img $tmp_mnt/* ::";
    _ "rm -rf $tmp_mnt";

    output($mapfile, "(fd0) $img\n");

    open(my $G, "| grub --device-map=$mapfile --batch");
    print $G <<EOF;
root (fd0)
install /stage1 d (fd0) /stage2 p /menu.lst
quit
EOF
    close $G;
    unlink $mapfile;
}