#!/usr/bin/perl

#   Perl script to manage all post-operations of the user-hw-extras package extending xkeyboard-config
#
#   Usage: not to be used outside of update operations!
#
#   How RPM uses this:
#   →   PARAM1: installDir
#   →   PARAM2: is_compat_call : BOOLEAN
#   →   PARAM3: command : ONE_OF[ 2=install|0=uninstall|1=update ]
#
use strict;
use warnings;
use File::Basename;
use File::Copy qw(copy);
use File::Path qw(make_path remove_tree);
use XML::LibXML;
use constant { REMOVE => 0, UPGRADE => 1, INSTALL => 2 };
my ($xkbextdir, $is_compat_call, $command) = @ARGV;
die "Usage: $0 <installDir> <is_compat_call:0|1> <0=uninstall|1=upgrade|2=install>\n"
    unless $xkbextdir && defined $is_compat_call && defined $command
        && $command =~ /^(0|1|2)$/;

my $xkbsysdir = '/usr/share/X11/xkb';
my $pkgname   = basename($xkbextdir);
my @subs      = qw(geometry keycodes rules symbols);

my %dispatch = (
    REMOVE()  => \&do_uninstall,
    UPGRADE() => \&do_upgrade,
    INSTALL() => \&do_install,
);
$dispatch{$command}->();

sub do_install {
    if ( $is_compat_call == 0 ) {   # normal libxkbregistry install
        remove_compat();
        install_normal();
    } else {    # compatibility installation - NOT RECOMMENDED!
        remove_normal();
        install_compat();
    }
}
sub do_uninstall {
    if ( $is_compat_call == 0 ) {   # normal libxkbregistry uninstall
        remove_compat();
        remove_normal();
    } else {    # compatibility uninstallation - NOT RECOMMENDED!
        remove_compat();
        install_normal();
    }
}
sub do_upgrade {
    l0g( "upgrading…" ); # dependency-upgrade could "unpatch"…
    $is_compat_call ? do { install_compat() unless is_patched() }
                   : do { install_normal() unless ! -d "$xkbextdir/rules" };
    l0g( "upgraded…" );
}
sub install_normal {
    l0g("building libxkbregistry subsystem…");
    # interne Pfade mit Links füllen
    build_subs( $xkbextdir );
    # evdev bauen
    open my $out, '>', "$xkbextdir/rules/evdev" or die $!;
    for my $part (qw(rules.evdev_incl.extra rules.evdev_body.extra)) {
        open my $in, '<', "$xkbextdir/$part" or die $!;
        print $out $_ while <$in>;
    }
}
sub install_compat {
    l0g("building xkbcomp compatibility subsystem … backing up system evdev files first");
    copy("$xkbsysdir/rules/evdev",     "$xkbextdir/backup.evdev")     or die $!;
    copy("$xkbsysdir/rules/evdev.xml", "$xkbextdir/backup.evdev.xml") or die $!;
    build_subs( $xkbsysdir );
    l0g("patching evdev");
    open my $fh,   '>>', "$xkbsysdir/rules/evdev"                or die $!;
    open my $body, '<',  "$xkbextdir/rules.evdev_body.extra"     or die $!;
    print $fh $_ while <$body>;
    l0g("patching evdev.xml");
    my $doc = XML::LibXML->new->parse_file("$xkbsysdir/rules/evdev.xml");
    my $src = XML::LibXML->new->parse_file("$xkbextdir/rules.evdev.xml.extra");
    my ($modelList) = $doc->documentElement->getElementsByTagName('modelList');
    my ($src_list) = $src->documentElement()->getElementsByTagName('modelList');
    if ($src_list) {
        for my $model ($src_list->getElementsByTagName('model')) {
            $modelList->appendChild($doc->importNode($model, 1));
        }
    }
    $doc->toFile("$xkbsysdir/rules/evdev.xml", 1);
}
sub build_subs {    # Dreh-und-Angelpunkt der Optimierung...
    l0g( "build links to: $_[0]" );
    for my $sub (@subs) {
        make_path("$_[0]/$sub");
        for my $f (glob "$xkbextdir/$sub.*.extra") {        # stem beschaffen: .extra streichen, sub herauslösen
            my $stem = basename($f, '.extra'); $stem =~ s/^[^.]+\.//;
            next if $stem =~ /^(evdev_incl|evdev_body)$/;   # evdev-Bauteile werden nie verlinkt
            next if ( $stem =~ /[.]/ ) && $is_compat_call;      # link_compat: nur Dateien ohne Extension!
            symlink $f, "$_[0]/$sub/$stem"
                and l0g("'$_[0]/$sub/$stem' linked");
        }
    }
}
sub remove_compat { # Deinstallation der Compatibility
    if (-f "$xkbextdir/backup.evdev") {
        l0g("restoring system files from backup");
        copy("$xkbextdir/backup.evdev",     "$xkbsysdir/rules/evdev")     or die $!;
        copy("$xkbextdir/backup.evdev.xml", "$xkbsysdir/rules/evdev.xml") or die $!;
        l0g("…system files restored");
    } else {
        l0g("WARNING: no backup found — please reinstall xkeyboard-config") unless $command == INSTALL;
    }
    for my $fsi ( ( map { "$xkbsysdir/$_/userhw" } @subs ), "$xkbextdir/backup.evdev", "$xkbextdir/backup.evdev.xml" ) {
        unlink $fsi and l0g("unlinked '$fsi'…");
    }
    l0g("compat installation removed.");
}
sub remove_normal {
    $command == REMOVE ? remove_tree("$xkbextdir")
                       : remove_tree( map { "$xkbextdir/$_" } @subs );
    l0g("normal installation removed.");
}
sub is_patched {
    open my $fh, '<', "$xkbsysdir/rules/evdev" or return 0;
    while (<$fh>) { return 1 if /BEGIN_USER_HW_EXTRAS/ }
    return 0;
}
sub l0g { system("logger", "-t", $pkgname, $_[0]) }
