#!/usr/bin/perl -w

use strict;
use warnings;
use Test::Repocop::Common;
use Test::Repocop::Options;
use Test::Repocop::TestDB;
use Test::Repocop::Metadata;
use Test::Repocop::ACLWrapper;
use Test::Repocop::Fixscripts;
use Data::Array2ArrayMap::Hash::XSTree;
use RPM::Source::Editor;
use RPM::Source::Transformation::Factory::RaiseRelease;
use File::Basename;
use File::Path;
use Carp;


my ($diffmode,$nmumode,$reportmode,$output_hasher_tar,$output_unchanged,
$changelog_entry,$changelog_packager,$release,$tag_packager_replace);
my $outdir='.';
my $digestmode=1;
my $report_by_leader=1;
my $report_by_packager=0;
my $next_release_policy='qa';
my $changelog='- NMU (by repocop). See http://www.altlinux.org/Tools/Repocop';
# possible values are: explicit value;
# acl, packager (old value) if availiable (how? from srpm?, changelog (packager), ...?
my $tag_packager_default='';

#$Repocop::arg::reportlevel='experimental';
&Test::Repocop::Options::get_common_options(
    'c|changelog=s' => \$changelog,
    "changelog-entry=s"   => \$changelog_entry,
    'diff'        => \$diffmode,
    'digest!'     => \$digestmode,
    'nmu'         => \$nmumode,
    'hashertar'   => \$output_hasher_tar,
    'unchanged'   => \$output_unchanged,
    'write-report!'=> \$reportmode,
    "outdir=s"    => \$outdir,
    'changelog-packager=s'  => \$changelog_packager,
    'tag-packager-replace=s'  => \$tag_packager_replace,
    'tag-packager-default=s'  => \$tag_packager_default,
    "release=s"   => \$release,
    "nextrel|next-release-policy=s" => \$next_release_policy,
);

&Test::Repocop::Options::die_if_nothing_to_report();

my $alttoolsdir=dirname($0);
my @fixscripts= map {s!^/usr/share/repocop/fixscripts/!!; $_} glob('/usr/share/repocop/fixscripts/*.pl');
push @fixscripts, map {s!^$alttoolsdir/fixscripts/'!!; $_} glob ($alttoolsdir.'/fixscripts/*.pl') if -d $alttoolsdir.'/fixscripts';
my %test_with_fixscript=map {$_=>1} map {s!.pl$!!;$_} @fixscripts;
print STDERR "fixscripts:", join(',',keys(%test_with_fixscript)),"\n" if $verbose;

my $RPM2TEST=Data::Array2ArrayMap::Hash::XSTree->new();
my $RPM2NAME=Data::Array2ArrayMap::Hash::XSTree->new();


my $aclmap=Test::Repocop::ACLWrapper->new();
my $metadata=Test::Repocop::Metadata->new();
my $testdb=Test::Repocop::TestDB->new();
my $cache=$testdb->get_pkg_test_status_iterator();

my $sourceid2specfilename=Test::Repocop::Metadata::sourceid2specfilename();

# hack to process all known test results
undef %Repocop::arg::pkgtable;
while (my ($rpm,$test,$status)=$cache->iterate3_filtered()) {
    #print STDERR "$rpm:$test:$status\n";
    my $sourceid=$metadata->sourceid($rpm);
    my $name=$metadata->name($rpm);
    $RPM2TEST->append([$sourceid],[$test]);
    $RPM2NAME->append([$sourceid],[$name]);
}

my ($by_acl_dir,$by_test_dir,$by_nick_dir,$by_leader_dir,$qa_robot_report_dir);
if ($diffmode && $reportmode) {
    my $repocop_reportdir="$repocop_cachedir/reports/diff";
    $outdir=$repocop_reportdir.'/by-srpm';
    $by_acl_dir=$repocop_reportdir.'/by-acl';
    $by_test_dir=$repocop_reportdir.'/by-test';
    $by_nick_dir=$repocop_reportdir.'/by-packager';
    $by_leader_dir=$repocop_reportdir.'/by-leader';
    $qa_robot_report_dir=$repocop_reportdir.'/qa-robot';
    rmtree([$repocop_reportdir]);
    mkpath([$outdir, $by_test_dir, $qa_robot_report_dir]);
    if ($aclmap) {
	mkpath([$by_acl_dir]);
	mkpath([$by_nick_dir]) if $report_by_packager;
	mkpath([$by_leader_dir]) if $report_by_leader;
    }
} else {
    mkpath([$outdir]);
}

my $release_raiser = ReleaseRaiser -> new (
    -release => $release,
    -changelog_entry => $changelog_entry,
    -changelog_packager => $changelog_packager,
    -packager => $tag_packager_replace,
    -default_packager_policy => $tag_packager_default,
    -nextrel => $next_release_policy,
    );

foreach my $file (@Repocop::arg::pkglist) {
    my ($srpmfile, $specfile, $sourceid);
    if ($file=~/\.rpm$/) {
	$srpmfile = $file;
	$sourceid=&Test::Repocop::Common::rpmfile2key($srpmfile);
    } elsif ($file=~/\.spec$/) {
	$specfile=$file;
	$sourceid=&Test::Repocop::Common::specfile2srcid($specfile);
    } else {
	warn "Invalid argument: $file: not a .rpm or .spec!\n";
	next;
    }

    unless ($sourceid =~ /src$/) {
	warn "$sourceid: not a src.rpm!\n" if $verbose;
	next;
    }
    my @tests=$RPM2TEST->get([$sourceid]);
    my @tests_with_fixscript=grep {$test_with_fixscript{$_}} @tests;
    if (scalar @tests_with_fixscript) {
	print "$sourceid:\n";
	my @names=$RPM2NAME->get([$sourceid]);

	eval {
	    #---------------------------------------------------------
	    my $rpmeditor=RPM::Source::Editor->new(
		SOURCERPM => $srpmfile,
		SPECFILE=> $specfile, 
		OUTPUTDIR=> $outdir,
		VERBOSE=> $verbose,
		CHANGELOG => $changelog,
		);

	    my $fixer=Test::Repocop::Fixscripts->new(
		EDITOR=>$rpmeditor,
		FIX_PKGLIST => \@names,
		FIX_TESTNAMELIST => \@tests,
		VERBOSE=> $verbose,
		);

	    if ($diffmode) {
		$fixer->write_diff(
		    OUTPUTDIR=> $outdir,
		    BY_TEST_DIR=> $by_test_dir,
		    SOURCEID => $sourceid,
		    SPECNAME => $sourceid2specfilename->{$sourceid},
		    DIGESTMODE => $digestmode,
		    RAISE_RELEASE => $nmumode ? $release_raiser : undef,
		    OUTPUT_UNCHANGED => $output_unchanged,
		    );
	    } else {
		my $is_changed=$fixer->apply_fixscripts();
		$release_raiser->raise_release($rpmeditor,undef) if $nmumode;
		if ($output_unchanged || $is_changed) {
		    if (!$srpmfile) {
			warn "strange: no srpm, not a diff mode...\n";
		    } else {
			if ($output_hasher_tar) {
			    $rpmeditor->write_hasher_tar();
			} else {
			    $rpmeditor->write_rpm();
			}
		    }
		}
	    }
	    $rpmeditor->cleanup();
	    #---------------------------------------------------------
	};
	warn "Got exception: $@" if $@;
    }
}

print STDERR "finished writing diffs\n" if $verbose;

#===============================================

if ($diffmode && $reportmode) {
    my %srcid;
    foreach (glob "$outdir/*") {
	next unless -e $_ and -s $_; # skipping empty diffs
	print "linking $_\n" if $verbose and $verbose>2;
	my $diffpath=$_;
	my $diffname=basename($_);
	my $id=&Test::Repocop::Common::patchname2srcid($diffname);
	$srcid{$id}=1;
	if ($aclmap) {
	    my $srcname=$metadata->name($id);
	    my @acls=$aclmap->name2acl($srcname);
	    foreach my $acl (@acls) {
		mkdir "$by_acl_dir/$acl";
		link $diffpath, "$by_acl_dir/$acl/$diffname";
	    }
	    if ($report_by_leader and $acls[0]) {
		mkdir "$by_leader_dir/$acls[0]";
		link $diffpath, "$by_leader_dir/$acls[0]/$diffname";
	    }
	}
	if ($report_by_packager) {
	    my $packager=$metadata->nick($id);
	    mkdir "$by_nick_dir/$packager";
	    link $diffpath, "$by_nick_dir/$packager/$diffname";
	}
    }
    # ------------ qa-robot support -------------------------
    open (my $fh, '>', $qa_robot_report_dir.'/diff.ls') || die "can't open $qa_robot_report_dir/patches.ls: $!";
    foreach (keys(%srcid)) {
	print $fh $metadata->name($_),"\t",$_,"\n";
    }
    close ($fh);
}

package ReleaseRaiser;

sub new {
    my $class=shift;
    my $self = {
	-nextrel=>'qa',
	@_
    };
    bless $self, $class;
    return $self;
}


sub raise_release {
    my ($opt,$spec,$parent)=@_;
    &RPM::Source::Transformation::Factory::RaiseRelease::opt_handler(undef,$spec,$parent,$opt);
}

=head1	NAME

repocop-fix-srpm - a tool that repairs srpm using repocop unit tests results.

=head1	SYNOPSIS

B<repocop-fix-srpm>
[B<--changelog> I<changelog entry>]
[B<--diff>]
[B<--nmu>]
[B<--packager> I<name email>]
[B<-h|--help>]
[B<-v|--verbose>]
[B<-q|--quiet>]
[B<-c|--cachedir> I<cachedir>]
[B<--et|--exclude-test> I<comma separated list of tests>]
[B<--it|--include-test> I<comma separated list of tests>]
[B<--ep|--exclude-packager> I<comma separated list of packager's nicks>]
[B<--ip|--include-packager> I<comma separated list of packager's nicks>]
[B<--pkgcollectors-dir> I<comma separated list of local collectors' dirs>]
[B<--srccollectors-dir> I<comma separated list of local collectors' dirs>]
[B<--pkgtests-dir> I<comma separated list of local tests' dirs>]
[B<--srctests-dir> I<comma separated list of local tests' dirs>]
[B<--ex|--except>] 
[B<-g|--given>] 
[B<-l|--last-run>] 
[B<--newer>] I<filename>
[B<-r|--report> <s[kip]|o[k]|w[arn]|f[ail]>]
[I<DIR>...] [I<FILE>...]

=head1	DESCRIPTION

B<repocop-fix-srpm> processes results of repocop unit tests, created with 
repocop-run command, stored in <cachedir> and use them to generate patches
or just repair source rpms.
Presize subset of tests can be selected using B<--include>
and B<--exclude> options.

=head1	OPTIONS

=over

=item	B<-c,--cachedir> I<dir>

Provides alternative location for cachedir. 
Repocop cachedir is a place where test results and 
packages metadata information are stored.

=item	B<--except>, B<--given>

Control processing of rpm arguments. 
B<--given> (default) means processing only given rpm arguments.
B<--except>  means processing all data except given rpm arguments.

=item	B<--et, --exclude-test> I<comma separated list of tests>

Report all processed tests exept the given excluded set.

=item	B<--it, --include-test> I<comma separated list of tests>

Report the given set of tests.

=item	B<--ep, --exclude-packager> I<comma separated list of tests>

=item	B<--it, --include-packager> I<comma separated list of tests>

Exclude/include packages according to Packager: tag.

=item [B<--pkgcollectors-dir> I<comma separated list of local collectors' dirs>]

=item [B<--srccollectors-dir> I<comma separated list of local collectors' dirs>]

=item [B<--pkgtests-dir> I<comma separated list of local tests' dirs>]

=item [B<--srctests-dir> I<comma separated list of local tests' dirs>]

Append user's local tests and collectors to repocop.

=item	B<-h, --help>

Display this help and exit.

=item	B<-v, --verbose>, B<-q, --quiet>

Verbosity level. Multiple -v increase the verbosity level, -q sets it to 0.

=item	B<-l, --last-run>

Use the set of packages processed at last run as an argument.

=item	B<--newer> I<filename>

Process packages newer then I<filename> only.
Note: this filtering does not apply to B<--last-run> option.

=item	B<--acl-file> I<file>

the argument is /path/to/Sisyphus/files/list/list.src.classic
This option is ALTLinux-specific. The file content is ACL db,
which is used to sort result by ALTLinux ACL.


=item	B<--changelog> I<changelog entry>

The specified value replaces default repocop-generated changelog.

=item	B<--diff>

Diff mode. In this mode only diffs are generated. By default, the generated diff
does not include changelog.

=item	B<--nmu>

Non-Maintainer Upload (NMU). Raise release. 
In diff mode it will force inclusion of 
new release and changelog into the generated diff.

=item	B<--packager> I<name email>

If spec file has no Packager: tag, the specified value will be used.

=back

=head1	AUTHOR

Written by Igor Vlasenko <viy@altlinux.org>.

=head1	ACKNOWLEGEMENTS

To Alexey Torbin <at@altlinux.org>, whose qa-robot package
had a strong influence on repocop. 

=head1	COPYING

Copyright (c) 2008-2012 Igor Vlasenko, ALT Linux Team.

This is free software; you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later version.

=cut

