#!/usr/bin/env perl

=pod

=encoding UTF-8

=head1 NAME

kdesrc-run - Run KDE applications built with kdesrc-build.

=head1 SYNOPSIS

kdesrc-run [options] <module-name> [arguments]

=head1 OPTIONS

  -e, --exec <program>    Specify program of the module. Default to module name.
  -f, --fork              Launch the program in a new session.
  -q, --quiet             Do not print run information.
  -h, --help              Print usage information and exit.
  --list-installed        Print installed modules and exit.

=head1 EXAMPLES

B<kdesrc-run -f kate -l 5 file1.txt>

  Launch kate in a new session with '-l 5 file1.txt' arguments.

B<kdesrc-run -e kate-syntax-highlighter kate --list-themes>

  Launch kate-syntax-highlighter of module kate with '--list-themes' argument.

=cut

use 5.014;
use strict;
use warnings;
use autodie;

use Pod::Usage;
use Getopt::Long qw(:config pass_through require_order);
use JSON::PP;
use List::Util qw(first);

binmode STDOUT, 'encoding(UTF-8)';
binmode STDERR, 'encoding(UTF-8)';

our $optExec;
our $optFork          = 0;
our $optHelp          = 0;
our $optQuiet         = 0;
our $optListInstalled = 0;

GetOptions(
    'exec|e=s'       => \$optExec,
    'fork|f'         => \$optFork,
    'help|h'         => sub { pod2usage(0); },
    'quiet|q'        => \$optQuiet,
    'list-installed' => \$optListInstalled,
);

if ($#ARGV == -1 && not $optListInstalled) {
    pod2usage(0);
}

my $module = shift @ARGV;
my $exec   = $optExec // $module;

# According to XDG spec, if $XDG_STATE_HOME is not set, then we should default
# to ~/.local/state
my $xdgStateHome = $ENV{XDG_STATE_HOME} // "$ENV{HOME}/.local/state";
my $dataFileName = "kdesrc-build-data";
my @possibleDataPaths = ("./.$dataFileName", "$xdgStateHome/$dataFileName");
my $buildDataFile = first { -e $_ } (@possibleDataPaths);

if (not defined $buildDataFile) {
    say qq("$dataFileName" file is not available. Exit now.);
    exit 1;
}

my $buildData = do {
    open my $fh, '<', $buildDataFile;
    local $/ = undef;
    decode_json(<$fh>);
};

if ($optListInstalled) {
    say foreach sort { $a cmp $b }
        grep { defined $buildData->{$_}{'install-dir'} } keys %$buildData;
    exit;
}

if (not defined $buildData->{$module}) {
    say qq(Module "$module" has not been built yet.);
    exit 1;
}

my $buildDir   = $buildData->{$module}{'build-dir'};
my $installDir = $buildData->{$module}{'install-dir'};
my $revision   = $buildData->{$module}{'last-build-rev'};
my $execPath   = "$installDir/bin/$exec";

if (not -e $execPath) {
    say qq(Program "$exec" does not exist.);
    say qq(Try to set executable name with -e option.);
    exit 127;    # Command not found
}

# Most of the logic is done by Perl, so the shell script here should be POSIX
# compliant. Consider using ShellCheck to make sure of that.
my $script = qq{
    #!/bin/sh

    # Set up environment variables (dot command).
    . "$buildDir/prefix.sh";

    # Launch the program with optional arguments.
    if [ "$optFork" = 1 ]; then
        setsid -f "$execPath" "\$@";
    else
        "$execPath" "\$@";
    fi;
};

# Print run information
if (not $optQuiet) {
    print '#' x 80, "\n";
    print ' ' x 35, 'kdesrc-run', "\n";
    say "Module:             $module";
    say "Program:            $exec";
    say "Revision:           $revision";
    say "Arguments:          @ARGV";
    print '#' x 80, "\n";
    print "\n";
}

# Instead of embedding @ARGV in shell script with string interpolation, pass
# them as arguments of the script. Let the shell handle the list through "$@",
# so it will do the quoting on each one of them.
#
# Run the script with sh options specification:
#     sh -c command_string command_name $1 $2 $3...
exec('/bin/sh', '-c', $script, $exec, @ARGV);
