use strict;
use XML::Simple;
use List::Util qw(min);
use URI::Escape;

my @packagesFiles = ();
my @urlPrefixes = ();

# rpm-closure.pl (<package-file> <url-prefix>)+ <toplevel-pkg>+

while(-f $ARGV[0]) {
    my $packagesFile = shift @ARGV;
    my $urlPrefix = shift @ARGV;
    push(@packagesFiles, $packagesFile);
    push(@urlPrefixes, $urlPrefix);
}


sub rpmvercmp {
    my ($version1, $version2) = @_;
    my @vercmps1 = split /\./, $version1;
    my @vercmps2 = split /\./, $version2;
    my $l1 = scalar(@vercmps1);
    my $l2 = scalar(@vercmps2);
    my $l = min($l1, $l2);

    for(my $i=0; $i<$l; $i++) {
        my $v1 = $vercmps1[$i];
        my $v2 = $vercmps2[$i];

        if($v1 =~ /^[0-9]*$/ && $v2 =~ /^[0-9]*$/) {
            if ( int($v1) > int($v2) ) {
                return 1;
            }
            elsif ( int($v1) < int($v2) ) {
                return -1;
            }
        } else {
            if ( $v1 gt $v2 ) {
                return 1;
            }
            elsif ( $v1 lt $v2 ) {
                return -1;
            }
        }
    }
    if($l1 == $l2) {
        return 0;
    } elsif ($l1 > $l2) {
        return 1;
    } elsif ($l1 < $l2) {
        return -1;
    }
}

my @toplevelPkgs = @ARGV;

my @archs = split ' ', ($ENV{'archs'} or "");

my %pkgs;
for (my $i = 0; $i < scalar(@packagesFiles); $i++) {
    my $packagesFile = $packagesFiles[$i];
    print STDERR "parsing packages in $packagesFile...\n";

    my $xml = XMLin($packagesFile, ForceArray => ['package', 'rpm:entry', 'file'], KeyAttr => []) or die;

    print STDERR "$packagesFile contains $xml->{packages} packages\n";

    foreach my $pkg (@{$xml->{'package'}}) {
        if (scalar @archs > 0) {
            my $arch = $pkg->{arch};
            my $found = 0;
            foreach my $a (@archs) { $found = 1 if $arch eq $a; }
            next if !$found;
        }
        if (defined $pkgs{$pkg->{name}}) {
            my $earlierPkg = $pkgs{$pkg->{name}};
            print STDERR "WARNING: duplicate occurrence of package $pkg->{name}\n";
            #   <version epoch="0" ver="1.28.0" rel="2.el6"/>
            my $cmp = rpmvercmp($pkg->{'version'}->{ver}, $earlierPkg->{'version'}->{ver});
            if ($cmp > 0 || ($cmp == 0 && rpmvercmp($pkg->{'version'}->{rel}, $earlierPkg->{'version'}->{rel})>0)) {
                print STDERR "WARNING: replaced package $pkg->{name} (".$earlierPkg->{'version'}->{ver}." ".$earlierPkg->{'version'}->{rel}.") with newer one (".$pkg->{'version'}->{ver}." ".$pkg->{'version'}->{rel}.")\n";
                $pkg->{urlPrefix} = $urlPrefixes[$i];
                $pkgs{$pkg->{name}} = $pkg;
            }
            next;
        }
        $pkg->{urlPrefix} = $urlPrefixes[$i];
        $pkgs{$pkg->{name}} = $pkg;
    }
}

my %provides;
PKG: foreach my $pkgName (sort(keys %pkgs)) {
    #print STDERR "looking at $pkgName\n";
    my $pkg = $pkgs{$pkgName};

    # Skip packages that conflict with a required package.
    my $conflicts = $pkg->{format}->{'rpm:conflicts'}->{'rpm:entry'} // [];
    foreach my $conflict (@{$conflicts}) {
        next if $conflict->{flags} // "" eq "LT" || $conflict->{flags} // "" eq "LE";
        #print STDERR "  $pkgName conflicts with $conflict->{name}\n";
        if (grep { $_ eq $conflict->{name} } @toplevelPkgs) {
            print STDERR "skipping package $pkgName because it conflicts with a required package\n";
            next PKG;
        }
    }

    my $provides = $pkg->{format}->{'rpm:provides'}->{'rpm:entry'} or die;
    foreach my $req (@{$provides}) {
        #print STDERR "  $pkgName provides $req->{name}\n";
        #die "multiple provides for $req->{name}" if defined $provides{$req->{name}};
        $provides{$req->{name}} = $pkgName;
    }

    if (defined $pkg->{format}->{file}) {
        foreach my $file (@{$pkg->{format}->{file}}) {
          #print STDERR "  provides file $file\n";
          $provides{$file} = $pkgName;
        }
    }
}


my %donePkgs;
my @needed = ();

sub closePackage {
    my $pkgName = shift;

    return if defined $donePkgs{$pkgName};
    $donePkgs{$pkgName} = 1;

    print STDERR ">>> $pkgName\n";

    my $pkg = $pkgs{$pkgName} or die "package $pkgName doesn't exist";

    my $requires = $pkg->{format}->{'rpm:requires'}->{'rpm:entry'} || [];

    my @deps = ();
    foreach my $req (@{$requires}) {
        next if $req->{name} =~ /^rpmlib\(/;
        #print STDERR "  needs $req->{name}\n";
        my $provider = $provides{$req->{name}};
        if (!defined $provider) {
            print STDERR "    WARNING: no provider for $req->{name}\n";
            next;
        }
        #print STDERR "    satisfied by $provider\n";
        push @deps, $provider;
    }

    closePackage($_) foreach @deps;

    push @needed, $pkgName;
}


foreach my $pkgName (@toplevelPkgs) {
    # If the package doesn't exist by name, check if something provides it
    if (!defined $pkgs{$pkgName} && defined $provides{$pkgName}) {
        print STDERR "package $pkgName is provided by $provides{$pkgName}\n";
        closePackage $provides{$pkgName};
    } else {
        closePackage $pkgName;
    }
}


# Generate the output Nix expression.
print "# This is a generated file.  Do not modify!\n";
print "# Following are the RPM packages constituting the closure of: @toplevelPkgs\n\n";
print "{fetchurl}:\n\n";
print "[\n\n";

foreach my $pkgName (@needed) {
    my $pkg = $pkgs{$pkgName};
    # URL-encode each path segment separately to handle special characters like '+'
    my $href = $pkg->{location}->{href};
    my @segments = split('/', $href);
    my @encoded_segments = map { uri_escape($_) } @segments;
    my $encoded_href = join('/', @encoded_segments);
    print "  (fetchurl {\n";
    print "    url = \"$pkg->{urlPrefix}/$encoded_href\";\n";
    if ($pkg->{checksum}->{type} eq "sha") {
        print "    sha1 = \"$pkg->{checksum}->{content}\";\n";
    } elsif ($pkg->{checksum}->{type} eq "sha256") {
        print "    sha256 = \"$pkg->{checksum}->{content}\";\n";
    } else {
        die "unsupported hash type";
    }
    print "  })\n";
    print "\n";
}

print "]\n";
