#!/usr/bin/perl
#
# vim: ts=2:et
#
# sbocutleaves
# script to remove leaf packages.
#
# author: Jacob Pipkin <jacob.pipkin@icloud.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 $descriptions_generated get_all_available get_installed_packages get_requires get_sbo_description in lint_sbo_config on_blacklist prompt show_version slackbuilds_or_fetch usage_error wrapsay wrapsay wrapsay_color /;
use File::Basename;
use Getopt::Long qw/ :config no_ignore_case_always bundling /;

my $self = basename($0);

sub show_usage {
  print <<"EOF";
Usage: $self [options]

Options:
  -h|--help:
    this screen.
  -v|--version:
    version information.
  --(no)color:
    (do not) use sbotools color output.
  --(no)wrap:
    (do not) wrap sbotools output.
  -l|--list:
     list packages without reverse dependencies and exit.
  --no-descriptions:
     do not show a short description for each script.
  --raw:
     like --list, but space-delineated with no other formatting.
EOF
    return 1;
}

my ($help, $vers, $list, $no_descriptions, $raw, $color, $nocolor, $nowrap, $wrap);

my $options_ok = GetOptions(
  'help|h'          => \$help,
  'version|v'       => \$vers,
  'list|l'          => \$list,
  'no-descriptions' => \$no_descriptions,
  'raw'             => \$raw,
);

if ($help) {
  show_usage();
  wrapsay "\nNon-root users can call $self with -l, --raw, -h and -v." unless $< == 0;
  exit 0
}
if ($vers) { show_version(); exit 0 }
unless ($< == 0 or $list or $raw) {
  show_usage();
  usage_error "\nNon-root users can call $self with -l, --raw, -h and -v.";
}
unless ($options_ok) {
  show_usage();
  usage_error "\nOne or more invalid options detected.";
}

$config{COLOR} = $color ? 'TRUE' : 'FALSE' if $color xor $nocolor;
$config{NOWRAP} = $nowrap ? 'TRUE' : 'FALSE' if $wrap xor $nowrap;
lint_sbo_config($self, %config);

$list = $raw ? $raw : $list;

slackbuilds_or_fetch();

if ($config{LOCAL_OVERRIDES} ne "FALSE" and not -d $config{LOCAL_OVERRIDES}) {
  unless ($list) {
    exit 1 unless prompt($color_lesser, "$config{LOCAL_OVERRIDES} is specified as the overrides directory, but does not exist.\nContinue anyway?", default => 'no');
  } elsif (not $raw) {
    wrapsay_color $color_lesser, "$config{LOCAL_OVERRIDES} is specified as the overrides directory, but does not exist.", 1;
  }
}

get_all_available();

unless ($raw or $no_descriptions) {
  wrapsay_color $color_lesser, "Run sbocheck to generate descriptions.", 1 unless $descriptions_generated;
}

my @seen;

CUTLEAVES:

my @installed = @{get_installed_packages("SBO", 1)};
unless (@installed) {
  wrapsay_color $color_notice, "No _SBo packages are installed. Exiting." unless $raw;
  exit 0;
}
my $installed_pkg = +{ map {; $_->{name}, $_->{pkg} } @installed };

my @delete = ();
my @leaves = ();

for my $sbo (sort keys %$installed_pkg) {
    next if in $sbo, @seen;
    next if on_blacklist $sbo and not $list;
    my $found = 0;
    for my $pkg (keys %$installed_pkg) {
        next if $pkg eq $sbo;
        if (in $sbo, @{get_requires($pkg)}) {
          ++$found;
          last;
        }
    }
    next if $found;

  if ($list) {
    push @leaves, $sbo;
  } else {
    wrapsay $sbo;
    unless ($no_descriptions) {
      my $description = get_sbo_description($sbo);
      wrapsay $description if defined $description;
    }
    if (prompt($color_lesser, "Mark $sbo for removal?", default => 'no')) {
        push @delete, $sbo;
        wrapsay " * Added to remove queue.", 1;
    } else {
        wrapsay " * Keeping $sbo.", 1;
    }

    push @seen, $sbo;
  }
}

# This situation would imply the presence of circular dependencies,
# but leaving a message here just in case.
if ($list and not @leaves) {
  wrapsay_color $color_notice, "All _SBo packages have installed reverse dependencies." unless $raw;
  exit 0;
}
if ($raw) {
  print "$_ " for (sort @leaves);
  exit 0;
}
if ($list) {
  my $grammar = @leaves > 1 ? "packages have" : "package has";
  wrapsay_color $color_notice, "The following $grammar no installed reverse dependencies:";
  for (sort @leaves) {
    my $line = $_;
    my $blacklisted = on_blacklist($line) ? " (blacklisted)" : "";
    $line .= $blacklisted;
    unless ($no_descriptions or not $descriptions_generated) {
      my $description = get_sbo_description($_);
      if (defined $description) {
        $line .= " ($description)\n";
      } else {
        $line .= "\n";
      }
    } else {
      $line .= "\n";
    }
    print $line;
  }
  exit 0;
}

unless (@delete) {
  wrapsay_color $color_notice, "\nNo packages are marked for removal. Exiting.";
  exit 0;
}

my $grammar = @delete > 1 ? "packages are" : "package is";
wrapsay_color $color_warn, "\nDone. The following $grammar marked for removal:";
print "  $_\n" for (sort @delete);
exit 0 unless prompt($color_warn, "\nProceed with removal?", default => 'no');
system("/sbin/removepkg", $_) for @delete;

exit 0 unless prompt($color_notice, "\nGo on with new leaf packages?", default => 'yes');

splice @delete;
goto CUTLEAVES;

END { say "" if not $raw; }
