#!/usr/bin/perl
#
# vim: ts=4:noet
#
# sboconfig
# script to handle sbotools configuration
#
# authors: Jacob Pipkin <j@dawnrazor.net>
#          Luke Williams <xocel@iquidus.org>
#          Andreas Guldstrand <andreas.guldstrand@gmail.com>
# maintainer: K. Eugene Carlson <kvngncrlsn@gmail.com>
# license: MIT License

use 5.16.0;
use strict;
use warnings FATAL => 'all';
use SBO::Lib qw/ :config :colors slurp usage_error script_error lint_sbo_config $tempdir open_fh prompt wrapsay show_version /;
use File::Basename;
use Getopt::Long qw(:config no_ignore_case_always);
use File::Copy;
use File::Path qw(make_path);
use File::Temp qw(tempfile);;

my $self = basename($0);
my $label = $is_sbotest ? "sbotest config" : $self;

sub show_usage {
	if ($is_sbotest) {
		show_sbotest_usage();
		return 1;
	}
	print <<"EOF";
Usage: $label option argument ...
       $label

Options:
  -h|--help:
    this screen.
  -v|--version:
    version information.
  -l|--list:
    show current options.
  -n|--non-default:
    show current non-default options.
  --reset:
    restore the default configuration.

Configuration options (defaults shown):
  -B|--branch FALSE:
      GIT_BRANCH: git branch to use, or FALSE for the OS version default.
  -b|--build-ignore FALSE:
      BUILD_IGNORE: if TRUE, only attempt upgrades if the version differs.
  -C|--classic FALSE:
      CLASSIC: if TRUE, BUILD_IGNORE and RSYNC_DEFAULT; use 2.7 output.
  -c|--noclean FALSE:
      NOCLEAN: if TRUE, do not clean working directories after building.
  -D|--dialogrc FALSE:
      DIALOGRC: if a FILE, use it as dialogrc for sbotool.
  -d|--distclean FALSE:
      DISTCLEAN: if TRUE, clean source and package archives after building.
  -e|--etc-profile FALSE:
      ETC_PROFILE: if TRUE, source executable scripts in /etc/profile.d.
  -g|--gpg-verify FALSE:
      GPG_VERIFY: verify the repo with gnupg.
  -j|--jobs FALSE:
      JOBS: numeric -j setting to feed to make for multicore systems.
  -K|--color FALSE:
      COLOR: if TRUE, enable sbotools color output.
  -L|--log-dir FALSE:
      LOG_DIR: if an absolute path, save a log file here after each build.
  -P|--cpan-ignore FALSE:
      CPAN_IGNORE: install scripts even if they are installed from the CPAN.
  -p|--pkg-dir FALSE:
      PKG_DIR: set a directory to store packages in.
  -s|--sbo-home /usr/sbo:
      SBO_HOME: set the SBo directory.
  -O|--obsolete-check FALSE:
      OBSOLETE_CHECK: -current: sbocheck gets obsolete list and perl history.
  -o|--local-overrides FALSE:
      LOCAL_OVERRIDES: a directory containing local overrides.
  -V|--slackware-version FALSE:
      SLACKWARE_VERSION: use the SBo repository for this version.
  -r|--repo FALSE:
      REPO: use a repository other than SBo.
  -R|--rsync FALSE:
      RSYNC_DEFAULT: default mirrors (other than for -current) are rsync.
  -S|--strict-upgrades FALSE:
      STRICT_UPGRADES: only upgrade when the version or build is higher.
  -w|--nowrap FALSE:
      NOWRAP: do not automatically wrap sbotools output.
  -X|--so-check FALSE:
      SO_CHECK: check for .so dependencies after sbocheck and sboupgrade.

Use $label without options for a dialog menu.
EOF
	return 1;
}

sub show_sbotest_usage {
	print <<"EOF";
Usage: $label option argument ...
       $label

Options:
  -h|--help:
    this screen.
  -v|--version:
    version information.
  -l|--list:
    show current options.
  -n|--non-default:
    show current non-default options.
  --reset:
    restore the default configuration.

Configuration options (defaults shown):
  -A|--sbo-archive /usr/sbotest/archive:
      SBO_ARCHIVE: the directory for package reuse.
  -B|--branch FALSE:
      GIT_BRANCH: git branch to use, or FALSE for the OS version default.
  -C|--classic FALSE:
      CLASSIC: if TRUE, BUILD_IGNORE and RSYNC_DEFAULT; use 2.7 output.
  -c|--noclean FALSE:
      NOCLEAN: if TRUE, do not clean working directories after building.
  -d|--distclean FALSE:
      DISTCLEAN: if TRUE, clean source and package archives after building.
  -e|--etc-profile TRUE:
      ETC_PROFILE: if FALSE, do not source executables in /etc/profile.d.
  -g|--gpg-verify FALSE:
      GPG_VERIFY: verify the repo with gnupg.
  -j|--jobs FALSE:
      JOBS: numeric -j setting to feed to make for multicore systems.
  -K|--color FALSE:
      COLOR: if TRUE, enable sbotools color output.
  -L|--log-dir /usr/sbotest/logs:
      LOG_DIR: if an absolute path, save a log file here after each build.
  -P|--cpan-ignore TRUE:
      CPAN_IGNORE: install scripts even if they are installed from the CPAN.
  -p|--pkg-dir /usr/sbotest/tests:
      PKG_DIR: set a directory to store packages in.
  -s|--sbo-home /usr/sbotest:
      SBO_HOME: set the SBo directory.
  -O|--obsolete-check FALSE:
      OBSOLETE_CHECK: -current: sbocheck gets obsolete list and perl history.
  -o|--local-overrides FALSE:
      LOCAL_OVERRIDES: a directory containing local overrides.
  -V|--slackware-version FALSE:
      SLACKWARE_VERSION: use the SBo repository for this version.
  -r|--repo FALSE:
      REPO: use a repository other than SBo.
  -R|--rsync FALSE:
      RSYNC_DEFAULT: default mirrors (other than for -current) are rsync.
  -S|--strict-upgrades FALSE:
      STRICT_UPGRADES: with --archive-rebuild, only delete when version or
      build is lower.
  -w|--nowrap FALSE:
      NOWRAP: do not automatically wrap sbotest output.
  -X|--so-check FALSE:
      SO_CHECK: check for missing .so dependencies when running sbocheck.
EOF
	return 1;
}

my $all_clear = 1 unless @ARGV;
my %options;

unless ($is_sbotest) {
	GetOptions(\%options, 'help|h', 'version|v', 'list|l', 'non-default|n', 'reset', 'classic|C=s', 'noclean|c=s',
		'distclean|d=s', 'jobs|j=s', 'pkg-dir|p=s', 'sbo-home|s=s',
		'local-overrides|o=s', 'slackware-version|V=s', 'repo|r=s',
		'build-ignore|b=s', 'branch|B=s', 'rsync|R=s', 'gpg-verify|g=s', 'strict-upgrades|S=s',
		'cpan-ignore|P=s', 'obsolete-check|O=s', 'etc-profile|e=s', 'log-dir|L=s',
		'nowrap|w=s', 'color|K=s', 'so-check|X=s', 'dialogrc|D=s');
} else {
	GetOptions(\%options, 'help|h', 'version|v', 'list|l', 'non-default|n', 'reset', 'classic|C=s', 'noclean|c=s',
		'distclean|d=s', 'jobs|j=s', 'pkg-dir|p=s', 'sbo-home|s=s',
		'local-overrides|o=s', 'slackware-version|V=s', 'repo|r=s',
		'build-ignore|b=s', 'branch|B=s', 'rsync|R=s', 'gpg-verify|g=s', 'strict-upgrades|S=s',
		'cpan-ignore|P=s', 'obsolete-check|O=s', 'etc-profile|e=s', 'log-dir|L=s',
		'nowrap|w=s', 'color|K=s', 'so-check|X=s', 'sbo-archive|A=s', 'dialogrc|D=s');
}

$options{list} = 1 if exists $options{'non-default'};
if ($options{help}) {
	show_usage();
	wrapsay "\nNon-root users can call $label with -l, -n, -h and -v." unless $< == 0;
	exit 0;
}
if ($options{version}) { show_version(); exit 0 }
unless ($< == 0 or $options{list} or $all_clear) {
	show_usage();
	usage_error "\nNon-root users can call $label with -l, -n, -h and -v.";
}

my %valid_confs = (
	classic             => 'CLASSIC',
	noclean             => 'NOCLEAN',
	distclean           => 'DISTCLEAN',
	jobs                => 'JOBS',
	'pkg-dir'           => 'PKG_DIR',
	'sbo-home'          => 'SBO_HOME',
	'local-overrides'   => 'LOCAL_OVERRIDES',
	'slackware-version' => 'SLACKWARE_VERSION',
	'repo'              => 'REPO',
	rsync               => 'RSYNC_DEFAULT',
	'branch'            => 'GIT_BRANCH',
	'build-ignore'      => 'BUILD_IGNORE',
	'gpg-verify'        => 'GPG_VERIFY',
	'strict-upgrades'   => 'STRICT_UPGRADES',
	'cpan-ignore'       => 'CPAN_IGNORE',
	'obsolete-check'    => 'OBSOLETE_CHECK',
	'etc-profile'       => 'ETC_PROFILE',
	'log-dir'           => 'LOG_DIR',
	color               => 'COLOR',
	nowrap              => 'NOWRAP',
	'so-check'          => 'SO_CHECK',
	'dialogrc'          => 'DIALOGRC',
);
$valid_confs{"sbo-archive"} = 'SBO_ARCHIVE' if $is_sbotest;

my %params = (
	CLASSIC           => 'C|--classic',
	NOCLEAN           => 'c|--noclean',
	DISTCLEAN         => 'd|--distclean',
	GPG_VERIFY        => 'g|--gpg-verify',
	JOBS              => 'j|--jobs',
	PKG_DIR           => 'p|--pkg-dir',
	SBO_HOME          => 's|--sbo-home',
	LOCAL_OVERRIDES   => 'o|--local-overrides',
	SLACKWARE_VERSION => 'V|--slackware-version',
	REPO              => 'r|--repo',
	RSYNC_DEFAULT     => 'R|--rsync',
	GIT_BRANCH        => 'B|--branch',
	BUILD_IGNORE      => 'b|--build-ignore',
	STRICT_UPGRADES   => 'S|--strict-upgrades',
	CPAN_IGNORE       => 'P|--cpan-ignore',
	OBSOLETE_CHECK    => 'O|--obsolete-check',
	ETC_PROFILE       => 'e|--etc-profile',
	LOG_DIR           => 'L|--log-dir',
	COLOR             => 'K|--color',
	NOWRAP            => 'w|--nowrap',
	SO_CHECK          => 'X|--so-check',
	DIALOGRC          => 'D|--dialogrc',
);
$params{SBO_ARCHIVE} = 'A|--sbo-archive' if $is_sbotest;

if (exists $options{list}) {
	my @keys = sort {$a cmp $b} keys %config;
	if (exists $options{'non-default'} and not $is_sbotest) {
		for my $item (@keys) {
			next if $config{$item} eq 'FALSE';
			next if $item eq 'SBO_HOME' and $config{$item} eq '/usr/sbo';
			say "$label -$params{$item}:\n    $item=$config{$item}";
		}
	} elsif (exists $options{'non-default'} and $is_sbotest) {
		for my $item (@keys) {
			if ($item eq 'ETC_PROFILE' or $item eq 'CPAN_IGNORE') { next if $config{$item} eq 'TRUE'; }
			if ($item eq 'SBO_HOME') { next if $config{$item} eq 'FALSE' or $config{$item} eq '/usr/sbotest'; }
			if ($item eq 'SBO_ARCHIVE') { next if $config{$item} eq 'FALSE' or $config{$item} eq "$config{'SBO_HOME'}/archive"; }
			if ($item eq 'PKG_DIR') { next if $config{$item} eq 'FALSE' or $config{$item} eq "$config{'SBO_HOME'}/tests"; }
			if ($item eq 'LOG_DIR') { next if $config{$item} eq 'FALSE' or $config{$item} eq "$config{'SBO_HOME'}/logs"; }
			next if $item ne 'ETC_PROFILE' and $item ne 'CPAN_IGNORE' and $config{$item} eq 'FALSE';
			say "$label -$params{$item}:\n    $item=$config{$item}";
		}
	} else {
		say "$label -$params{$_}:\n    $_=$config{$_}" for @keys;
	}
	wrapsay "\nWarning: Local overrides directory $config{LOCAL_OVERRIDES} does not exist." if $config{LOCAL_OVERRIDES} ne "FALSE" and not -d $config{LOCAL_OVERRIDES};
	exit 0;
}

if (exists $options{reset}) {
	if (prompt($color_warn, "Reset all options to the default setting?", default => 'no')) {
		say "Restoring default configuration...";
	} else {
		say "Exiting without changes.";
		exit 0;
	}
}

# setup what's being changed, sanity check.
my %changes;
for my $key (keys %valid_confs) {
	my $value = $valid_confs{$key};
	if (exists $options{reset}) {
		$changes{$value} = "FALSE";
	} else {
		$changes{$value} = $options{$key} if exists $options{$key};
	}
}
$changes{'SBO_HOME'} = '/usr/sbo' if exists $options{reset};
if (exists $options{reset} and $is_sbotest) {
	$changes{'SBO_HOME'} =  '/usr/sbotest';
	$changes{'PKG_DIR'} = '/usr/sbotest/tests';
	$changes{'LOG_DIR'} = '/usr/sbotest/logs';
	$changes{'SBO_ARCHIVE'} = '/usr/sbotest/archive';
	$changes{'ETC_PROFILE'} = 'TRUE';
	$changes{'CPAN_IGNORE'} = 'TRUE';
}

if ($all_clear and not $is_sbotest) {
	system('/usr/sbin/sbotool --config');
	exit;
} elsif (not %options or $all_clear) {
	show_usage();
	exit 1;
}

lint_sbo_config($self, %changes);

my $change_requested;

sub config_write {
	script_error('config_write requires at least two arguments.') unless @_ >= 2;

	if (! -d $conf_dir) {
		mkdir $conf_dir or usage_error("Unable to create $conf_dir. Exiting.");
	}

	my $conf = slurp($conf_file) || '';
	_fixup_conf($conf);

	while (@_ >= 2) {
		my $key = shift;
		my $val = shift;
		next if $key eq 'BUILD_IGNORE' and $is_sbotest;
		next if $key eq 'COLOR' and $is_sbotest;
		say "Setting $key to $val..." unless exists $options{reset};

		# Comment default values when written in
		my $comment = '';
		unless ($is_sbotest) {
			$comment = '#' if $val eq 'FALSE';
			$comment = '#' if $key eq 'SBO_HOME' and $val eq '/usr/sbo';
		} else {
			$comment = '#' if $key ne 'ETC_PROFILE' and $key ne 'CPAN_IGNORE' and $val eq 'FALSE';
			$comment = '#' if ($key eq 'ETC_PROFILE' or $key eq 'CPAN_IGNORE') and $val eq 'TRUE';
			$comment = '#' if $key eq 'SBO_HOME' and $val eq '/usr/sbotest';
			$comment = '#' if $key eq 'PKG_DIR' and $val eq '/usr/sbotest/tests';
			$comment = '#' if $key eq 'LOG_DIR' and $val eq '/usr/sbotest/logs';
			$comment = '#' if $key eq 'SBO_ARCHIVE' and $val eq '/usr/sbotest/archive';
		}

		if ($conf =~ /^#(\s*)\Q$key\E=/m) {
			$conf =~ s/^#(\s*)\Q$key\E=.*$/$comment$key=$val/m;
		} elsif ($conf =~ /^\Q$key\E=/m) {
			$conf =~ s/^\Q$key\E=.*$/$comment$key=$val/m;
		} else {
			$conf .= "$comment$key=$val\n";
		}
	}

	_fixup_conf($conf);

	my ($conffh, $exit) = open_fh($conf_file, '>');
	error_code("Failed to open $conf_file; exiting.", $exit) if $exit;
	print {$conffh} $conf;
}

# make sure there are no duplicate keys in the config
sub _fixup_conf {
	my @lines = split /\n/, $_[0];
	my @fixed;
	my %keys;
	foreach my $line (@lines) {
		# if it's a comment or blank line, just pass it through
		if ($line =~ /^(#|\s*$)/) { push @fixed, $line; next; }

		my ($key, $val) = split /=/, $line;
		next if exists $keys{$key};
		$keys{$key}++;
		push @fixed, $line;
	}

	$_[0] = join "\n", @fixed, ''; # make sure we end with a newline if there are any lines
}

if (%changes) {
	config_write(%changes);
}

END { say ""; }
