#!/usr/bin/perl

BEGIN {
  unshift @INC, '/usr/lib/build';
}

use strict;
use warnings;

use Build;
use Build::Rpm;

# Used by the 20-files-present-and-referenced and 70-baselibs scripts
# to query certain information from the specfile.

our $st_if = 1;
our $st_ifname = 2;

sub prepare_spec {
  my ($fn, $no_conditionals, $keep_name_conditionals, $unique_sources,
      $disambiguate_sources) = @_;
  return $fn unless $no_conditionals;
  my $spec;
  open(F, '<', $fn) || die("open: $!\n");
  my %seen;
  my $dtags;
  my @ifs;
  my %autonum = (patch => 0, source => 0);

  for (my $line = 0; <F>; $line++) {
    # only process if conditionals in non line continuation contexts
    if (/^\s*%(?:define|global).*\\$/) {
      push @$spec, $_;
      # filter all lines of continuation
      my $ll;
      while ( $ll = <F> ) {
        push @$spec, $ll;
        last unless ($ll =~ /\\$/);
      }
      next;
    }

    if (/^\s*%if\s+.*%\{?name/ && $keep_name_conditionals) {
      unshift @ifs, { $st_ifname => $line };
    } elsif (/^\s*%if/) {
      unshift @ifs, { $st_if => $line };
      next;
    } elsif (/^\s*%elif/) {
      die($line . ": %elif without %if\n") unless @ifs;
      next;
    } elsif (/^\s*%else/) {
      die($line . ": %else without %if\n") unless @ifs;
      if (exists $ifs[0]{$st_ifname}) {
        push @$spec, "%endif";
        $ifs[0] = { $st_if => $line };
      }
      next;
    } elsif (/^\s*%endif/) {
      die($line . ": %endif without %if\n") unless @ifs;
      push @$spec, "%endif" if (exists $ifs[0]{$st_ifname});
      shift @ifs;
      next;
    }

    chomp;
    if (($unique_sources || $disambiguate_sources) &&
        /^(Source|Patch)(\d*)\s*(\s+[^:]*?)?:\s*(\S+)/i) {
      my ($tag, $num, $tagextra, $val) = (lc($1), $2, $3, $4);
      $num = $num ne '' ? 0 + $num : $autonum{$tag};
      $tag = "$tag$num";
      if ($seen{$tag}) {
        push @$spec, "$tag: $val";
        push @{$dtags->{$tag}}, \$spec->[-1];
        next;
      }
      $seen{$tag} = 1;
    }
    push @$spec, $_;
  }
  if (@ifs and exists $ifs[0]{$st_if}) {
    die($ifs[0]{$st_if} . ": %if without %endif\n");
  } elsif (@ifs) {
    die($ifs[0]{$st_ifname} . ": %if name without %endif\n");
  }
  close(F) || die("close: $!\n");
  my @amb = sort(keys(%$dtags));
  die('Ambiguous tags: ' . join(', ', @amb) . "\n") if $unique_sources && @amb;
  return $spec unless $disambiguate_sources;
  # make duplicate source/patch tags unique (such duplicates probably
  # only occur in a "pathological" specfile...)
  for my $tag (sort keys(%$dtags)) {
    my $cnt = 0;
    for my $lref (@{$dtags->{$tag}}) {
      $cnt++ while ($seen{"$tag$cnt"});
      $$lref =~ s/^$tag/$tag$cnt/;
      $seen{"$tag$cnt"} = 1;
    }
  }
  return $spec;
}

# Borrowed from obs-build's expanddeps:
# For %include, get the filename without path and look in the recipe dir.
sub includecallback {
  my ($recipe, $file) = @_;
  $file =~ s/.*\///;
  $recipe =~ s/[^\/]+$//;
  $file = "$recipe$file";
  my $fd;
  my $str;
  if (open($fd, '<', $file)) {
    local $/;
    $str = <$fd>;
    close($fd);
  }
  return $str;
}

sub parse {
  my ($fn, $arch, $no_conditionals, $keep_name_conditionals, $unique_sources,
      $disambiguate_sources, $buildflavor) = @_;
  my $spec = prepare_spec($fn, $no_conditionals, $keep_name_conditionals,
                          $unique_sources, $disambiguate_sources);
  my $config = Build::read_config($arch, []);
  $config->{'buildflavor'} = $buildflavor if $buildflavor;
  local $Build::Rpm::includecallback = sub { includecallback($fn, @_) };
  my $descr = Build::Rpm::parse($config, $spec);
  die("unable to parse specfile: $fn\n") unless $descr;
  my @skeys = sort keys(%$descr);
  $descr->{'sources'} = [map {$descr->{$_}} grep {/^source/} @skeys];
  $descr->{'patches'} = [map {$descr->{$_}} grep {/^patch/} @skeys];
  $descr->{'icons'} = [map {@{$descr->{$_}}} grep {/^icon/} @skeys];
  return $descr;
}

sub print_moveassets {
  my ($descr) = @_;
  print "@{$descr->{'moveassets'} || []} ";
  print "\n";
}

sub print_subpacks {
  my ($descr) = @_;
  print "@{$descr->{'subpacks'} || []} ";
  print "\n";
}

sub print_sources {
  my ($descr) = @_;
  print "@{$descr->{'sources'}} " if @{$descr->{'sources'}};
  print "@{$descr->{'patches'}} " if @{$descr->{'patches'}};
  print "@{$descr->{'icons'}}" if @{$descr->{'icons'}};
  print "\n";
}

sub usage {
  my ($ret) = @_;
  print <<EOF;
Usage: $0 --specfile <specfile> [<options>]
Options:
  --specfile <specfile>:    the specfile that should be queried
  --arch <arch>:            arch that is used during parsing (default: noarch)
  --buildflavor <flavor>:   multibuild flavor that is used during parsing (default: empty)
  --no-conditionals:        do not take %if* conditionals into account during
                            parsing (except if they are used in line
                            continuation contexts)
  --keep-name-conditionals: take conditionals of the form "%if ... %{name} ..."
                            into account (only useful to restrict the
                            --no-conditionals option)
  --unique-sources:         fail if source/patch tags are not unique
  --disambiguate-sources:   disambiguate non-unique source/patch tags (only
                            needed for a "pathological" specfile)
  --print-subpacks:         print names of the main package and all subpackages
  --print-sources:          print names of all sources, patches, and icons

EOF
  exit($ret);
}

my $specfile;
my $arch = 'noarch';
my $print_moveassets;
my $print_subpacks;
my $print_sources;
my $no_conditionals;
my $keep_name_conditionals;
my $unique_sources;
my $disambiguate_sources;
my $buildflavor;

while (@ARGV) {
  my $opt = shift @ARGV;
  if ($opt eq '--specfile') {
    $specfile = shift @ARGV;
    usage(1) unless $specfile;
  } elsif ($opt eq '--arch') {
    $arch = shift @ARGV;
    usage(1) unless $arch;
  } elsif ($opt eq '--buildflavor') {
    $buildflavor = shift @ARGV;
  } elsif ($opt eq '--print-subpacks') {
    $print_subpacks = 1;
  } elsif ($opt eq '--print-moveassets') {
    $print_moveassets = 1;
  } elsif ($opt eq '--print-sources') {
    $print_sources = 1;
  } elsif ($opt eq '--no-conditionals') {
    $no_conditionals = 1;
  } elsif ($opt eq '--keep-name-conditionals') {
    $keep_name_conditionals = 1;
  } elsif ($opt eq '--unique-sources') {
    $unique_sources = 1;
  } elsif ($opt eq '--disambiguate-sources') {
    $disambiguate_sources = 1;
  } elsif ($opt eq '--help') {
    usage(0);
  } else {
    usage(1);
  }
}

my $descr = parse($specfile, $arch, $no_conditionals, $keep_name_conditionals,
                  $unique_sources, $disambiguate_sources, $buildflavor);
print_subpacks($descr) if $print_subpacks;
print_sources($descr) if $print_sources;
print_moveassets($descr) if $print_moveassets;
