#!/usr/bin/perl -w
use strict;

my $dn = `dirname $0`;chomp($dn);
my $pwd = `pwd`;chomp($pwd);
if ($dn !~ /^\//) { $dn = $pwd . "/" . $dn; }
push @INC,$dn;
my $cverepobase=`dirname $dn`;
chomp($cverepobase);

use XML::Bare;
use JSON;
use Data::Dumper;

use POSIX qw/strftime setlocale LC_TIME/;

my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
my $curtime = POSIX::strftime("%Y-%m-%dT%H:%M:%S",$sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst );


my $osc = "osc -A https://api.suse.de/";

my $baseurl = "http://download.suse.de/ibs/";

my $outputbase = "/suse/meissner/public_html/images/";

my %allcontainers = ();
my @allimages = ();

# OSC output memory caching ... as every osc call takes 1-2 seconds, try reading
# from a memory cache on commands where we know the content likely wont change during
# the script run. (so something like channel content, or list of files in projects)

my %cachedosc = ();
my $osccachehit = 0;
my $osccachemiss = 0;

sub
cached_osc_call($) {
	my ($cmdline) = @_;

	if ($cachedosc{$cmdline}) {
		print STDERR "CACHED: $osc $cmdline\n" if -t STDERR;
		$osccachehit++;
		return @{$cachedosc{$cmdline}};
	}

	print STDERR "UNCACHED: $osc $cmdline\n" if -t STDERR;
	open(OSC,"$osc $cmdline|");
	my @lines = <OSC>;
	close(OSC)||warn "$osc $cmdline:$!";

	$cachedosc{$cmdline} = \@lines;
	return @lines;
}

# OSC output file caching ... as every osc call takes 1-2 seconds, try reading
# from a filesystem cache on commands where we know the content will never change.
# (e.g. content of timestamped containers, files with build-release encoded etc.)

my $filecachehit = 0;
my $filecachemiss = 0;
sub
filecached_osc_call($) {
	my ($cmdline) = @_;
	my $fn = $cmdline;
	my @lines;

	# in memory caching has no additiional, as we use those mostly once.
	$fn =~ s/ /_/g;
	$fn =~ s/\//_/g;
	if (-f "$cverepobase/smash/osccached/$fn") {
		print STDERR "FILECACHED: $osc $cmdline\n" if -t STDERR;
		open(CACHE,"<$cverepobase/smash/osccached/$fn")||die "$cverepobase/smash/osccached/$fn:$!";
		@lines = <CACHE>;
		close(CACHE);
		$filecachehit++;
	} else {
		$filecachemiss++;
		print STDERR "UNCACHED: $osc $cmdline\n" if -t STDERR;
		open(OSC,"$osc $cmdline|");
		@lines = <OSC>;
		close(OSC)||warn "$osc $cmdline:$!";

		open(CACHE,">$cverepobase/smash/osccached/$fn")||die "$cverepobase/smash/osccached/$fn:$!";
		print CACHE join("",@lines);
		close(CACHE);
	}
	return @lines;
}

# { package => [ version-release	=> [CVE, CVE], ]
#<buildinfo project="SUSE:SLE-15:Update:CR" repository="images" package="sles15-image" downloadurl="http://download.suse.de/ibs">
#  <arch>x86_64</arch>
#  <debuginfo>0</debuginfo>
#  <imagetype>docker</imagetype>
#  <bdep name="ca-certificates" noinstall="1" version="2+git20170807.10b2785" release="7.3.3" arch="noarch" project="SUSE:SLE-15:Update" repository="standard"/>
#  <path project="SUSE:SLE-15:Update:CR" repository="images" url="http://download.suse.de/ibs/SUSE:/SLE-15:/Update:/CR/images/"/>
#  <path project="SUSE:SLE-15:Update" repository="standard" url="http://download.suse.de/ibs/SUSE:/SLE-15:/Update/standard/"/>
# <path project="SUSE:SLE-15:GA" repository="standard" url="http://download.suse.de/ibs/SUSE:/SLE-15:/GA/standard/"/>
#</buildinfo>

my @images = ();

open(CONTAINERPROJECTS,"$osc ls |grep ^SUSE:Containers:|")||die "$osc ls |grep ^SUSE:Containers|: $!\n";
while (my $containerproject = <CONTAINERPROJECTS>) {
	chomp $containerproject;

	
	# EOL
	next if ($containerproject eq "SUSE:Containers:SUSE-CAASP:2");
	next if ($containerproject eq "SUSE:Containers:SUSE-CAASP:3");
	next if ($containerproject eq "SUSE:Containers:SLE-SERVER:12");
	next if ($containerproject eq "SUSE:Containers:SUSE-CAASP:5");
	next if ($containerproject eq "SUSE:Containers:SUSE-CAASP:4.5");
	next if ($containerproject eq "SUSE:Containers:CAPS:2");

	my %containers = ();
	open(CONTAINERS,"$osc ls $containerproject|")|| die"$osc ls $containerproject:$!\n";
	while (my $container = <CONTAINERS>) {
		chomp $container;

		# hide aggregates.
		next if ($container =~ /^15/); # weird 15_0 entries
		next if ($container =~ /^bci_1/);
		next if ($container =~ /^hpc_/);
		next if ($container =~ /^kubevirt_/);
		next if ($container =~ /^patchinfo/);

		next if ($container =~ /^kured/); # no kured package
		next if ($container eq "sles12sp4-image"); # sles12sp4-image without timestamp
		if ($container =~ /^(.*)\.(\d*)$/) {	# usualy container-image.timestamp entries, translate to 
			$containers{"^$1.*"} = $1;
		} else {
			print STDERR "non timestamped container $containerproject $container?\n" if -t STDERR;
			$containers{$container} = $container;
		}
	}
	close(CONTAINERS);
	foreach my $container (sort keys %containers) {
		print STDERR "added $containerproject $container containers x86_64,$containers{$container}\n" if -t STDERR;
		push @images,"$containerproject $container containers x86_64,$containers{$container}";
	}
}
close(CONTAINERPROJECTS)||die "$osc ls |grep ^SUSE:Containers:$!";
# read some projects in a wildcard fashion
open(IMAGEPROJECTS,"<data/imageprojects") ||die "data/imageprojects: $!";
while (<IMAGEPROJECTS>) {
	chomp;
	my ($project, $repo, $arch) = split(/ /);
	# isc pr SUSE:SLE-15-SP1:Update:PubClouds -r images -a x86_64 --xml|grep succeeded

	print STDERR "$osc pr $project -r $repo -a $arch --xml\n";

	open(IMAGES,"$osc pr $project -r $repo -a $arch --xml|");
	while (<IMAGES>) {
		print STDERR "debug: $_" if -t STDERR;
		chomp;
		# next unless /succeeded/;

		#<status package="SLES15-SP1-Manager-4-0-EC2-HVM-BYOS:Server" code="succeeded"/>
		if (/status package="([^"]*)" code/) {
			my $pkg = $1;
			my $name = $1;

			$name =~ s/:/-/; # avoid :
			push @images,"$project $pkg $repo $arch,$name";
			print STDERR "added $project $pkg $repo $arch,$name\n" if -t STDERR;
		}

	}
	close(IMAGES);
}
close(IMAGEPROJECTS);

foreach my $image ("SUSE:SLE-15-SP4","SUSE:SLE-15-SP5") {
        print STDERR "QU image: $image\n";

        push @images, "$image:GA 000product:SLES-cd-Full-x86_64 images local,$image:GA";

	# SUSE:SLE-15-SP3:Update:QR:QU2 000product:SLES-cd-Full-x86_64 images local,SUSE:SLE-15-SP3:Update:QR:QU2

        open(OSC,"$osc ls|grep \"^$image:Update:QR:QU[1-9]\"|");
        # note all of them are created before usage, so are still empty ...
        while (<OSC>) {
                print STDERR $_;
                chomp;
        	push @images, "$_ 000product:SLES-cd-Full-x86_64 images local,$_";
        }
        close(OSC) || warn "$osc ls|grep \"^$image:Update:QR:QU[1-9]\":$!";
        # push as last also the current build.
       	push @images, "$image:Update:QR 000product:SLES-cd-Full-x86_64 images local,$image:Update:QR";
}

foreach my $image ("SUSE:SLE-15-SP4","SUSE:SLE-15-SP5","SUSE:SLE-15-SP6") {
        print STDERR "SP image: $image\n";

        push @images, "$image:GA 000product:SLES-cd-Full-x86_64 images local,$image:GA";
}

# rest projects we read by hand
open(IMAGES,"data/images")||die  "data/images: $!";
while (my $image = <IMAGES>) {
	chomp $image;
	push @images,$image;
}
close(IMAGES);

print STDERR "images:\n" . join("\n", @images);

foreach my $image (@images) {
	my ($buildinfo,$output) = split(/,/,$image);

	my ($project,$package,$repo,$arch) = split(/ /,$buildinfo);

	my $containerinfo;

	if (	($package =~ /(.*)\.\*$/)	||
		($package =~ /(.*)\.\*:(.*)$/)

	) {
		# either IMAGE.TIMESTAMP or IMAGE.TIMESTAMP:FLAVOR
		# This if part currently reads _released_ containers. This is what customers have available.
		# They usually have the pattern of NAME.TIMESTAMP: "containername.20200520123456"
		# We do not need a fallback method.

		my $match = $1;
		my $flavor = $2;
		my $imagename = "unknown";
		if (defined($flavor)) {
			$imagename = "$match:$flavor";
			$match = "$match\..*:$flavor";
		} else {
			$imagename = $match;
			$match = "$match\..*";
		}

		$imagename =~ s/^\^//;


		my @images = ();
		my $lastimage;
		my $index;

		open(ALLIMAGES,"$osc pr $project -r $repo -a $arch --xml|");
		while (my $ximage = <ALLIMAGES>) {
			chomp $ximage;
			next unless ($ximage =~ /status package="([^"]*)"/);
			my $pkg = $1;
			next unless ($pkg =~ /$match$/);
			$index = $1;

			push @images,$pkg;
			$lastimage = $pkg;
		}
		close(ALLIMAGES)|| die "$osc pr $project -r $repo -a $arch --xml:$!";

		if (-f "$outputbase/$output.$index.newpatches.txt") {
			print STDERR "already generated all for $output container...\n";
			#next;
		}
		my $first = 1;
		foreach my $ximage (@images) {
			next unless ($ximage =~ /$match$/);
			my $index = $1;
			my $filename;

			print STDERR "$osc api /build/$project/$repo/$arch/$ximage\n" if -t STDERR;

			undef $containerinfo;
			# open(BUILDINFO,"$osc api /build/$project/$repo/$arch/$ximage|");
			my @lines = filecached_osc_call("api /build/$project/$repo/$arch/$ximage");
			while ($_ = shift @lines) {
				if (/<binary filename="(.*report)" size=/) {
					my $report = $1;

					print STDERR "report filename of $buildinfo is $report\n";

					if ($report =~ /Media/) {
						# for QU or SP or GA, we have multiple media, take the Media1 report
						if ($report =~ /Media1/ && !defined($filename)) {
							$filename = $report;
						}
					} else {
						# for containers we just have 1 report I hope
						if (defined($filename)) {
							print STDERR "$project $ximage, have filename $filename already, now $report?\n";
						}
						$filename = $report;
					}
				}
				if (/<binary filename="(.*containerinfo)" size=/) {
					$containerinfo = $1;
				}
			}
			if (!defined($filename)) {
				print STDERR "Skipping $project/$ximage - no report file?\n";
				next;
			}
			print STDERR "filename of $buildinfo is $filename\n" if -t STDERR;
			print STDERR "filename of containerinfo is $containerinfo\n" if -t STDERR;

			my $version = "unknown";
			if ($filename =~ /\.$arch-(.*).report$/) {
				$version = $1;
				print STDERR "version is $version\n" if -t STDERR;
			}

			# get the file
			my @text = filecached_osc_call("api /build/$project/$repo/$arch/$ximage/$filename");

			print STDERR "$buildinfo $ximage\n" if -t STDERR;

			my @cinfo;
			if (defined($containerinfo)) {
				# get containerinfo ... it is a perl map already, so use it....
				@cinfo = filecached_osc_call("api /build/$project/$repo/$arch/$ximage/$containerinfo");
			}
			#parse_builddep($buildinfo,join("",@cinfo),join("\n",@text), "$output.$index",1,$version,$first);
			$first = 0;
			# save a copy of the buildinfo
			mkdir("$cverepobase/smash/buildinfo/$imagename");
			open(SAVEDBUILDINFO,">$cverepobase/smash/buildinfo/$imagename/$version.new")||die "$cverepobase/smash/buildinfo/$imagename/$version.new:$!";
			print SAVEDBUILDINFO @text;
			close(SAVEDBUILDINFO);
			if (-f "$cverepobase/smash/buildinfo/$imagename/$version.new") {
				if (!system("diff -u $cverepobase/smash/buildinfo/$imagename/$version $cverepobase/smash/buildinfo/$imagename/$version.new")) {
					#rename("$cverepobase/smash/buildinfo/$imagename/$version.new","$cverepobase/smash/buildinfo/$imagename/$version");
					#print STDERR "no diff ?\n";
					unlink("$cverepobase/smash/buildinfo/$imagename/$version.new");
				} else {
					rename("$cverepobase/smash/buildinfo/$imagename/$version.new","$cverepobase/smash/buildinfo/$imagename/$version");
				}
			} else {
				rename("$cverepobase/smash/buildinfo/$imagename/$version.new","$cverepobase/smash/buildinfo/$imagename/$version");
			}
		}
	} else {
		print STDERR "$osc api /build/$project/$repo/$arch/$package\n" if -t STDERR;

		undef $containerinfo;
		my $filename;
		my @text = filecached_osc_call("api /build/$project/$repo/$arch/$package");
		while ($_ = shift @text) {
			if (/<binary filename="(.*report)" size=/) {
				my $report = $1;

				print STDERR "report filename of $buildinfo is $report\n";

				if ($report =~ /Media/) {
					# for QU or SP or GA, we have multiple media, take the Media1 report
					if ($report =~ /Media1/ && !defined($filename)) {
						$filename = $report;
					}
				} else {
					# for containers we just have 1 report I hope
					if (defined($filename)) {
						print STDERR "$project $package, have filename $filename already, now $report?\n";
					}
					$filename = $report;
				}
			}
			if (/<binary filename="(.*containerinfo)" size=/) {
				$containerinfo = $1;
			}
		}
		print STDERR "filename of $buildinfo is $filename\n" if -t STDERR;
		# get the file
		@text = filecached_osc_call("api /build/$project/$repo/$arch/$package/$filename");

		if (join("",@text) eq "<binarylist/>\n") {
			warn "binarylist is empty, not saving.\n";
			next;
		}


		#SLES15-SP1-CHOST-BYOS.x86_64-1.0.5-GCE-Build2.3.report

		my $version = "unknown";
		if ($filename =~ /\.$arch-(.*).report$/) {
			$version = $1;
			print STDERR "version is $version\n" if -t STDERR;
		}

		if ($package =~ /00product/) {	# for QU media
			$package = $output;
		}

		# save a copy of the buildinfo
		mkdir("$cverepobase/smash/buildinfo/$package");
		open(SAVEDBUILDINFO,">$cverepobase/smash/buildinfo/$package/$version.new");
		print SAVEDBUILDINFO @text;
		close(SAVEDBUILDINFO);
		if (-f "$cverepobase/smash/buildinfo/$package/$version.new") {
			if (!system("diff -u $cverepobase/smash/buildinfo/$package/$version $cverepobase/smash/buildinfo/$package/$version.new")) {
				#rename("$cverepobase/smash/buildinfo/$package/$version.new","$cverepobase/smash/buildinfo/$package/$version");
				#print STDERR "no diff ?\n";
				unlink("$cverepobase/smash/buildinfo/$package/$version.new");
			} else {
				rename("$cverepobase/smash/buildinfo/$package/$version.new","$cverepobase/smash/buildinfo/$package/$version");
			}
		} else {
			rename("$cverepobase/smash/buildinfo/$package/$version.new","$cverepobase/smash/buildinfo/$package/$version");
		}
	}
}
close(IMAGES);

print "SUCCESS\n";
