#!/usr/bin/perl -w
# Copyright 2009-2013 Bernhard M. Wiedemann
# Copyright 2012-2020 SUSE LLC
# SPDX-License-Identifier: GPL-2.0-or-later
#

=head1 SYNOPSIS

isotovideo [OPTIONS] [TEST PARAMETER]

Parses command line parameters, vars.json and tests the given assets/ISOs.

=head1 OPTIONS

=over 4

=item B<-d, --debug>

Enable direct output to STDERR instead of autoinst-log.txt

=item B<--workdir=>

isotovideo will chdir to that directory on startup

=item B<--color=[yes|no]>

Enable or disable color output explicitly. Defaults to "yes". Alternatively
ANSI_COLORS_DISABLED or NO_COLOR can be set to any value to disable colors.

=item B<-v, --version>

Show the current program version and test API version

=item B<-h, -?, --help>

Show this help.

=head1 TEST PARAMETER

All additional command line arguments specified in the C<key=value> format are
parsed as test parameters which take precedence over the settings in the
vars.json file. Lower case key names are transformed into upper case
automatically for convenience.

=cut

use Mojo::Base -strict, -signatures;
use autodie ':all';
no autodie 'kill';

# Avoid "Subroutine JSON::PP::Boolean::(0+ redefined" warnings
# Details: https://progress.opensuse.org/issues/90371
use JSON::PP;

my $installprefix;    # $bmwqemu::scriptdir
my $fatal_error;    # the last error message caught by the die handler

BEGIN {
    # the following line is modified during make install
    $installprefix = undef;

    my ($wd) = $0 =~ m-(.*)/-;
    $wd ||= '.';
    $installprefix ||= $wd;
    unshift @INC, "$installprefix";
}

use log qw(diag);
use Getopt::Long;
use Mojo::File qw(curfile);
use Mojo::IOLoop::ReadWriteProcess::Session 'session';
Getopt::Long::Configure("no_ignore_case");
use OpenQA::Isotovideo::Interface;
use OpenQA::Isotovideo::Runner;
use OpenQA::Isotovideo::Utils qw(git_rev_parse spawn_debuggers handle_generated_assets);

my %options;

sub usage ($r) {
    eval { require Pod::Usage; Pod::Usage::pod2usage($r) };
    die "cannot display help, install perl(Pod::Usage)\n" if $@;
}

sub _get_version_string () {
    my $thisversion = git_rev_parse(curfile->dirname);
    return "Current version is $thisversion [interface v$OpenQA::Isotovideo::Interface::version]";
}

sub version () {
    print _get_version_string() . "\n";
    exit 0;
}

GetOptions(\%options, 'debug|d', 'workdir=s', 'color=s', 'help|h|?', 'version|v') or usage(1);
usage(0) if $options{help};
version() if $options{version};

session->enable;
session->enable_subreaper;

my $color = $options{color} // 'yes';
# User setting has preference, see https://no-color.org/
delete $ENV{NO_COLOR} if $color eq 'yes';
# Term::ANSIColor honors this variable
$ENV{ANSI_COLORS_DISABLED} = 1 if $color eq 'no';

chdir $options{workdir} if $options{workdir};

# global exit status
my $return_code = 1;

# record the last die message
# note: It might *not* be a fatal error so we don't call bmwqemu::serialize_state here
#       immediately but only in the END block.
$SIG{__DIE__} = sub ($e) { $fatal_error = $e };

# make sure all commands coming from the backend will not be in the
# developers's locale - but a defined english one. This is SUSE's
# default locale
$ENV{LC_ALL} = 'en_US.UTF-8';
$ENV{LANG} = 'en_US.UTF-8';

diag(_get_version_string());

# enable debug default when started from a tty
$log::direct_output = $options{debug};

$bmwqemu::scriptdir = $installprefix;

my $runner = OpenQA::Isotovideo::Runner->new;
$runner->_init_bmwqemu(@ARGV);
$runner->prepare;
$runner->start_autotest;
$runner->create_backend;

spawn_debuggers;

$runner->handle_commands;

$return_code = 0;

# enter the main loop: process messages from autotest, command server and backend
$runner->run;

# terminate/kill the command server and let it inform its websocket clients before
$runner->stop_commands('test execution ended');

if ($runner->testfd) {
    # unusual shutdown
    $return_code = 1;    # uncoverable statement
    CORE::close $runner->testfd;    # uncoverable statement
    $runner->stop_autotest();    # uncoverable statement
}

diag 'isotovideo ' . ($return_code ? 'failed' : 'done');

my $clean_shutdown;
if (!$return_code) {
    eval {
        $clean_shutdown = $bmwqemu::backend->_send_json({cmd => 'is_shutdown'});
        diag('backend shutdown state: ' . ($clean_shutdown // '?'));
    };

    # don't rely on the backend in a sane state if we failed - just stop it later
    eval { bmwqemu::stop_vm(); };
    if ($@) {
        bmwqemu::serialize_state(component => 'backend', msg => "unable to stop VM: $@", error => 1);
        $return_code = 1;
    }
}

# read calculated variables from backend and tests
bmwqemu::load_vars();

$return_code = handle_generated_assets($runner->command_handler, $clean_shutdown) unless $return_code;

# clear any previously recorded die message; it was not fatal after all if the execution came this far
$fatal_error = undef;

END {
    $runner->backend->stop if $runner and $runner->backend;
    $runner and $runner->stop_commands('test execution ended through exception');
    $runner and $runner->stop_autotest();

    # in case of early exit, e.g. help display
    $return_code //= 0;

    bmwqemu::serialize_state(component => 'isotovideo', msg => $fatal_error) if $fatal_error;
    print "$$: EXIT $return_code\n";
    $? = $return_code;
}
