#!/usr/bin/perl -w
#
# Project Builder main application
#
# $Id$
#
# Copyright B. Cornec 2007
# Provided under the GPL v2

# Syntax: pb [-p project] <action> [<params>...]

use strict 'vars';
use Switch;
use Getopt::Std;
use Data::Dumper;
use English;
use AppConfig qw(:argcount :expand);
use File::Basename;
use Archive::Tar;
use Time::localtime qw(localtime);
use POSIX qw(strftime);

use vars qw (%defpkgdir %extpkgdir %version %confparam %filteredfiles);
use lib qw (lib);
use common qw (env_init);
use pb qw (pb_init);
use distro qw (distro_init);
use cms;
use changelog qw (changelog);

my %opts;					# CLI Options
my $action;					# action to realize
my $test = "FALSE";
my $option = "";
my @pkgs;
my $pbtag;					# Global TAG variable
my $pbver;					# Global VERSION variable
my $pbrev;					# GLOBAL REVISION variable
my @date=(localtime->sec(), localtime->min(), localtime->hour(), localtime->mday(), localtime->mon(), localtime->year(), localtime->wday(), localtime->yday(), localtime->isdst());
my $pbdate = strftime("%Y-%m-%d", @date);

getopts('p:t',\%opts);

# Handles project name if any
if (defined $opts{'p'}) {
	$ENV{'PBPROJ'} = env_init($opts{'p'});
} else {
	$ENV{'PBPROJ'} = env_init();
}
# Handles test option
if (defined $opts{'t'}) {
	$test = "TRUE";
	# Works only for SVN
	$option = "-r BASE";
}

# Get Action
$action = shift @ARGV;
die "Syntax: pb [-p project] <action> [<params>...]" if (not defined $action);

print "Project $ENV{'PBPROJ'}\n";
#print "Action: $action - ARGV:".Dumper(\@ARGV);

# Act depending on action
if ($action =~ /^cms2build$/) {
	print "Action: cms2build\n";
	# Get packages list
	if (not defined $ARGV[0]) {
		@pkgs = keys %defpkgdir;
	} elsif ($ARGV[0] =~ /^all$/) {
		@pkgs = keys %defpkgdir;
		if (defined %extpkgdir) {
			my $k = keys %extpkgdir;
			if (defined $k) {
				push(@pkgs, keys %extpkgdir);
			}
		}
	} else {
		@pkgs = @ARGV;
	}
	print "Packages:\n";
	print Dumper(@pkgs);
	cms_init();

	foreach my $pkg (@pkgs) {

		if (-f "$ENV{'PBROOT'}/$pkg/VERSION") {
			open(V,"$ENV{'PBROOT'}/$pkg/VERSION") || die "Unable to open $ENV{'PBROOT'}/$pkg/VERSION";
			$pbver = <V>;
			chomp($pbver);
			close(V);
		} else {
			$pbver = $ENV{'PBVER'};
		}

		if (-f "$ENV{'PBROOT'}/$pkg/TAG") {
			open(T,"$ENV{'PBROOT'}/$pkg/TAG") || die "Unable to open $ENV{'PBROOT'}/$pkg/TAG";
			$pbtag = <T>;
			chomp($pbtag);
			close(T);
		} else {
			$pbtag = $ENV{'PBTAG'};
		}
		$pbrev = $ENV{'PBREVISION'};
		print "Management of $pkg $pbver-$pbtag (rev $pbrev)\n";
		die "Unable to get env var PBDESTDIR" if (not defined $ENV{'PBDESTDIR'});
		# Clean up dest if necessary. The export will recreate it
		my $dest = "$ENV{'PBDESTDIR'}/$pkg-$pbver";
		pbrm_rf($dest) if (-d $dest);

		# Export CMS tree for the concerned package to dest
		# And generate some additional files
		$OUTPUT_AUTOFLUSH=1;
		print "$ENV{'PBCMSEXP'} of $pkg...";
		# computes in which dir we have to work
		my $dir = $defpkgdir{$pkg};
		$dir = $extpkgdir{$pkg} if (not defined $dir);
		system("$ENV{'PBCMSEXP'} $option $ENV{'PBROOT'}/$dir $dest 1>/dev/null");
		if ($? == -1) {
			print "failed to execute: $!\n";
		} elsif ($? & 127) {
			printf "child died with signal %d, %s coredump\n", ($? & 127),  ($? & 128) ? 'with' : 'without';
		} else {
			print " Done under $dest\n";
		}

		# Creates a REVISION file
		open(R,"> $dest/REVISION") || die "Unable to create $dest/REVISION";
		print R "$pbrev\n";
		close(R);

		# Extract cms log history and store it
		system("$ENV{'PBCMSLOG'} $option $ENV{'PBROOT'}/$dir > $dest/$ENV{'PBCMSLOGFILE'}");
		print "$ENV{'PBCMSLOG'} of $pkg...";
		if ($? == -1) {
			print "failed to execute: $!\n";
		} elsif ($? & 127) {
			printf "child died with signal %d, %s coredump\n", ($? & 127),  ($? & 128) ? 'with' : 'without';
		} else {
			print " OK\n";
		}
		open(D,"$ENV{'PBCONF'}/DISTROS") || die "Unable to find $ENV{'PBCONF'}/DISTROS\n";
		while (<D>) {
			my $d = $_;
			my ($dir,$ver) = split(/_/,$d);
			chomp($ver);
			print "Generating build files for $dir ($ver)\n";
			my ($ddir, $dver, $dfam, $dtype, $dsuf) = distro_init($dir,$ver);
			#print Dumper($ddir, $dver, $dfam, $dtype, $dsuf);
			#print "Filtering DDD => $pbdate, TTT => $pbtag, RRR => $pbtag$dsuf, VVV => $pbver\n";

			# Filter build files from the less precise up to the most with overloading
			# Filter all files found, keeping the name, and generating in dest

			# Find all build files first relatively to PBROOT
			my %bfiles;
			#print "dir: $ENV{'PBCONF'}/$pkg\n";
			if (-d "$ENV{'PBCONF'}/$pkg/$dtype") {
				opendir(BDIR,"$ENV{'PBCONF'}/$pkg/$dtype" || die "Unable to open dir $ENV{'PBCONF'}/$pkg/$dtype: $!");
				foreach my $f (readdir(BDIR)) {
					next if ($f =~ /^\./);
					$bfiles{$f} = "$ENV{'PBCONF'}/$pkg/$dtype/$f";
					$bfiles{$f} =~ s~$ENV{'PBROOT'}~~;
				}
				closedir(BDIR);
			} elsif (-d "$ENV{'PBCONF'}/$pkg/$dfam") {
				opendir(BDIR,"$ENV{'PBCONF'}/$pkg/$dfam" || die "Unable to open dir $ENV{'PBCONF'}/$pkg/$dfam: $!");
				foreach my $f (readdir(BDIR)) {
					next if ($f =~ /^\./);
					$bfiles{$f} = "$ENV{'PBCONF'}/$pkg/$dfam/$f";
					$bfiles{$f} =~ s~$ENV{'PBROOT'}~~;
				}
				closedir(BDIR);
			} elsif (-d "$ENV{'PBCONF'}/$pkg/$ddir") {
				opendir(BDIR,"$ENV{'PBCONF'}/$pkg/$ddir" || die "Unable to open dir $ENV{'PBCONF'}/$pkg/$ddir: $!");
				foreach my $f (readdir(BDIR)) {
					next if ($f =~ /^\./);
					$bfiles{$f} = "$ENV{'PBCONF'}/$pkg/$ddir/$f";
					$bfiles{$f} =~ s~$ENV{'PBROOT'}~~;
				}
				closedir(BDIR);
			} elsif (-d "$ENV{'PBCONF'}/$pkg/$ddir-$dver") {
				opendir(BDIR,"$ENV{'PBCONF'}/$pkg/$ddir-$dver" || die "Unable to open dir $ENV{'PBCONF'}/$pkg/$ddir-$dver: $!");
				foreach my $f (readdir(BDIR)) {
					next if ($f =~ /^\./);
					$bfiles{$f} = "$ENV{'PBCONF'}/$pkg/$ddir-$dver/$f";
					$bfiles{$f} =~ s~$ENV{'PBROOT'}~~;
				}
				closedir(BDIR);
			} else {
				print "No Build Files found for $ddir-$dver\n";
				next;
			}
			print "bfiles: ".Dumper(\%bfiles)."\n";

			# Get all filters to apply
			# They're cumulative from less specific to most specific
			# suffix is .pbf
			my @ffiles;
			my ($ffile0, $ffile1, $ffile2, $ffile3);
			if (-d "$ENV{'PBCONF'}/$pkg/pbfilter") {
				$ffile0 = "$ENV{'PBCONF'}/$pkg/pbfilter/$dtype.pbf" if (-f "$ENV{'PBCONF'}/$pkg/pbfilter/$dtype.pbf");
				$ffile1 = "$ENV{'PBCONF'}/$pkg/pbfilter/$dfam.pbf" if (-f "$ENV{'PBCONF'}/$pkg/pbfilter/$dfam.pbf");
				$ffile2 = "$ENV{'PBCONF'}/$pkg/pbfilter/$ddir.pbf" if (-f "$ENV{'PBCONF'}/$pkg/pbfilter/$ddir.pbf");
				$ffile3 = "$ENV{'PBCONF'}/$pkg/pbfilter/$ddir-$dver.pbf" if (-f "$ENV{'PBCONF'}/$pkg/pbfilter/$ddir-$dver.pbf");
				push @ffiles,$ffile0 if (defined $ffile0);
				push @ffiles,$ffile1 if (defined $ffile1);
				push @ffiles,$ffile2 if (defined $ffile2);
				push @ffiles,$ffile3 if (defined $ffile3);
			}
			my $config = AppConfig->new({
							# Auto Create variables mentioned in Conf file
							CREATE => 1,
							DEBUG => 0,
							GLOBAL => {
								# Each conf item is a hash
								ARGCOUNT => AppConfig::ARGCOUNT_HASH
							}
						});
			print "ffiles: ".Dumper(\@ffiles)."\n";
			if (@ffiles) {
				$config->file(@ffiles);
				my $ptr = $config->get("filter");
				print "f:".Dumper($ptr)."\n";

				# Apply now all the filters on all the files concerned
				# All files are relative to PBROOT
				# destination dir depends on the type of file
				if (defined $ptr) {
					foreach my $f (values %bfiles) {
						filter_file($f,$ptr,"$dest/pbconf/$ddir-$dver/".basename($f),$pkg,$dtype,$dsuf);
					}
					foreach my $f (keys %filteredfiles) {
						filter_file($f,$ptr,"$dest/$f",$pkg,$dtype,$dsuf);
					}
				}
			}
		}
		close(D);
		# Prepare the dest directory for archive
		if (-x "$ENV{'PBCONF'}/$pkg/pbpkginit") {
			system("cd $dest ; $ENV{'PBCONF'}/$pkg/pbinit");
			if ($? == -1) {
				print "failed to execute: $!\n";
			} elsif ($? & 127) {
				printf "child died with signal %d, %s coredump\n", ($? & 127),  ($? & 128) ? 'with' : 'without';
			} else {
				print " $dest\n";
			}
		}
		# Archive dest dir
		chdir "$dest/..";
		print "Creating $pkg tar files (gzip... ";
		system("tar cfphz $pkg-$pbver.tar.gz $pkg-$pbver");
		if ($? == -1) {
			print "failed to execute: $!\n";
		} elsif ($? & 127) {
			printf "child died with signal %d, %s coredump\n", ($? & 127),  ($? & 128) ? 'with' : 'without';
		} else {
			print " OK)\n";
			print "Under $dest/../$pkg-$pbver.tar.gz\n";
		}
	}
} else {
	print "'$action' is not available\n";
	print "Available actions are:\n";
	print "    cms2build\n";
}

# Function which applies filter on files
sub filter_file {

my $f=shift;
my $ptr=shift;
my %filter=%$ptr;
my $destfile=shift;
my $pkg=shift;
my $dtype=shift;
my $dsuf=shift;

print "From $f to $destfile\n";
pbmkdir_p(dirname($destfile)) if (! -d dirname($destfile));
open(DEST,"> $destfile") || die "Unable to create $destfile";
open(FILE,"$ENV{'PBROOT'}/$f") || die "Unable to open $f: $!";
while (<FILE>) {
	my $line = $_;
	foreach my $s (keys %filter) {
		# Process single variables
		#print "debug: $filter{$s}\n";
		my $tmp = $filter{$s};
		next if (not defined $tmp);
		# Expand variables if any single one found
		if ($tmp =~ /\$/) {
			eval { $tmp =~ s/(\$\w+)/$1/eeg };
		# special case for ChangeLog
		} elsif (($tmp =~ /^yes$/) && ($s =~ /^PBLOG$/)) {
			my $p = $defpkgdir{$pkg};
			$p = $extpkgdir{$pkg} if (not defined $p);
			$tmp = changelog($dtype, $pkg, $pbtag, $dsuf, $p, \*DEST);
		}
		$line =~ s|$s|$tmp|;
	}
	print DEST $line;
}
close(FILE);
close(DEST);
}
