#!/usr/bin/perl -w

use strict;
use warnings;

use autodie qw/open close/;
use File::Path qw(make_path remove_tree);
use File::Basename;
use HTML::Template::Pro;
use Data::Array2ArrayMap::Hash::XSTree;

use Test::Repocop::PkgId;
use Test::Repocop::Log;
use Test::Repocop::Workdir;
use Test::Repocop::TestDB;
use Test::Repocop::Metadata;
use Test::Repocop::ACLWrapper;
use Test::Repocop::TestDB::Status;
use Test::Repocop::Report::HTML;
use Test::Repocop::CLI::Base;
use Test::Repocop::CLI::Report;
use Test::Repocop::CLI::IN::PkgId;
our @ISA=qw/Test::Repocop::CLI/;
our @LONGOPT_POD2USAGE=qw/-verbose 1 -exitval 1/;

my $report_all_in_one=0;
my ($by_srpm,$by_test,$by_leader,$by_acl)=(1,1,1,1);
our @LONGOPT=(
    "by-acl!"  => \$by_acl,
    "by-leader!"  => \$by_leader,
    "by-srpm!"  => \$by_srpm,
    "by-test!"  => \$by_test,
    'all-in-one!'=> \$report_all_in_one,
);
__PACKAGE__->get_and_process_cli_options();
&Test::Repocop::Workdir::die_if_nothing_to_report();
&Test::Repocop::CLI::IN::PkgId::process_arguments();

my $repocop_reportdir="$repocop_workdir/reports/html";
my $img_dir='img/';

&repocop_note("creating reports in $repocop_reportdir...");

my $TEST2RPM=Data::Array2ArrayMap::Hash::XSTree->new();
my $RPM2TEST=Data::Array2ArrayMap::Hash::XSTree->new();
my $SRPM2RPM=Data::Array2ArrayMap::Hash::XSTree->new();
my $ACL2TEST=Data::Array2ArrayMap::Hash::XSTree->new();
my $LEADER2TEST=Data::Array2ArrayMap::Hash::XSTree->new();
my %TEST;

my $aclmap=Test::Repocop::ACLWrapper->new();
unless ($aclmap) {
    &repocop_note("acl is not available. Disabled reports by acl/leader.") if $by_leader or $by_acl;
    $by_acl=0;
    $by_leader=0;
}
my $metadata=Test::Repocop::Metadata->new();
my $testdb=Test::Repocop::TestDB->new();
my $cache=$testdb->get_pkg_test_status_result_iterator();
while (my ($pkgid,$test,$status,$result)=$cache->iterate4_filtered()) {
    my $rpm=Test::Repocop::PkgId::pkgid2reportid($pkgid);
    warn "got undef: rpm=[$rpm] test=[$test] status=[$status] result=[$result]\n" unless defined($pkgid) and defined($test) and defined($status) and defined($result);
    # html-linearize
    chomp $result;
    chomp $result;
    $result=~s!\n!<br/>!gs;
    #$result=~s!<br/>$!!;
    $TEST{$test}=1;
    my $sourceid=$metadata->sourceid($pkgid);
    warn "got undef:[$sourceid] for $pkgid" unless defined($sourceid);
    my $srpm=Test::Repocop::PkgId::pkgid2reportid($sourceid);
    if ($aclmap) {
	my $srcname=$metadata->name($sourceid);
	if (! defined($srcname)) {
	    warn "source name is not defined for $srpm";
	} else {
	    my @acls=$aclmap->name2acl($srcname);
	    # p8 does not have acls now
	    if (scalar @acls) {
		my $leader=$acls[0];
		$LEADER2TEST->append([$leader,$rpm,$test],[$status,$result]);
		foreach my $acl (@acls) {
		    $ACL2TEST->set([$acl,$leader,$rpm,$test],[$status,$result]);
		}
	    }
	}
    }
    $TEST2RPM->set([$test,$status,$rpm],[$result]);
    $RPM2TEST->set([$rpm,$test],[$status,$result]);
    $SRPM2RPM->set([$srpm,$rpm],[1]);
}

my $tmpl_src =q{
<html>
<head><title><TMPL_VAR NAME="HEAD"></title></head>
<body>
<TMPL_IF NAME="H1">
<h1><TMPL_VAR NAME="H1"></h1>
</TMPL_IF>
<table border="1">
<TMPL_LOOP NAME=TABLEHEADER>
<tr>
<TMPL_IF NAME="BALL"><th><TMPL_VAR NAME="BALL"></th></TMPL_IF>
<TMPL_IF NAME="C1"><th><TMPL_VAR NAME="C1"></th></TMPL_IF>
<TMPL_IF NAME="C2"><th><TMPL_VAR NAME="C2"></th></TMPL_IF>
<TMPL_IF NAME="C3"><th><TMPL_VAR NAME="C3"></th></TMPL_IF>
<TMPL_IF NAME="C4"><th><TMPL_VAR NAME="C4"></th></TMPL_IF>
<TMPL_IF NAME="C5"><th><TMPL_VAR NAME="C5"></th></TMPL_IF>
</tr>
</TMPL_LOOP>
<TMPL_LOOP NAME=TABLEBODY>
<tr>
<TMPL_IF NAME="BALL"><td><img src="}.$img_dir.q{<TMPL_VAR NAME="BALL">.png"></td></TMPL_IF>
<TMPL_IF NAME="C1"><td><TMPL_VAR NAME="C1"></td></TMPL_IF>
<TMPL_IF NAME="C2"><td><TMPL_VAR NAME="C2"></td></TMPL_IF>
<TMPL_IF NAME="C3"><td><TMPL_VAR NAME="C3"></td></TMPL_IF>
<TMPL_IF NAME="C4"><td><TMPL_VAR NAME="C4"></td></TMPL_IF>
<TMPL_IF NAME="C5"><td><TMPL_VAR NAME="C5"></td></TMPL_IF>
</tr>
</TMPL_LOOP>
</table>
<hr/>
<TMPL_VAR NAME="BOTTOM_COMMENT">
</body>
</html>
};

my $headerstr='Repocop reports';

my @tests=sort keys %TEST;

my @table;

if ($report_all_in_one) {
    my $dir_all_in_one=&prepare_report_subdir('');
    &_copy_images($dir_all_in_one);
    @table=();
    foreach my $srpm (sort $SRPM2RPM->keys_at([])) {
	my @rpms = sort $SRPM2RPM->keys_at([$srpm]);
	foreach my $rpm (@rpms) {
	    foreach my $test (@tests) {
		my ($status,$result)= $RPM2TEST->get([$rpm,$test]);
		push @table, {BALL=>$status, C1=> $rpm, C2=> $test, C3=> $status, C4=>$result} if $status;
	    }
	}
    }
    &output_tmpl($headerstr,
		 "$dir_all_in_one/all_in_one.html",
		 {C1=>'rpm id',C2=>'test',C3=>'status',C4=>'message'},
		 \@table);
}

if ($by_test) {
    my $dir_by_test=&prepare_report_subdir('by-test');
    &_copy_images($dir_by_test);
    foreach my $test (sort $TEST2RPM->keys_at([])) {
	@table=();
	foreach my $status (sort $TEST2RPM->keys_at([$test])) {
	    foreach my $rpm (sort $TEST2RPM->keys_at([$test,$status])) {
		my ($result)=$TEST2RPM->get([$test,$status,$rpm]);
		push @table, {BALL=>$status, C1=> $status, C2=> $rpm, C3=>$result};
	    }
	}
	&output_tmpl($headerstr.' by test',
		     "$dir_by_test/$test.html",
		     {C1=>'status',C2=>'rpm id',C3=>'message'},
		     \@table);
    }
}

if ($by_srpm) {
    my $dir_by_srpm=&prepare_report_subdir('by-srpm');
    &_copy_images($dir_by_srpm);
    foreach my $srpm (sort $SRPM2RPM->keys_at([])) {
	@table=();
	my @rpms = sort $SRPM2RPM->keys_at([$srpm]);
	foreach my $rpm (@rpms) {
	    foreach my $test (@tests) {
		my ($status,$result)= $RPM2TEST->get([$rpm,$test]);
		push @table, {BALL=>$status, C1=> $rpm, C2=> $test, C3=> $status, C4=>$result} if $status;
	    }
	}
	&output_tmpl($headerstr.' by srpm',
		     "$dir_by_srpm/$srpm.html",
		     {C1=>'rpm id',C2=>'test',C3=>'status',C4=>'message'},
		     \@table);
    }
}

if ($by_acl and $aclmap) {
    my $dir_by_acl=&prepare_report_subdir('by-acl');
    &_copy_images($dir_by_acl);
    foreach my $acl (sort $ACL2TEST->keys_at([])) {
	@table=();
	my @packager = sort $ACL2TEST->keys_at([$acl]);
	foreach my $packager (@packager) {
	    my @rpms = sort $ACL2TEST->keys_at([$acl,$packager]);
	    foreach my $rpm (@rpms) {
		my @tests = sort $ACL2TEST->keys_at([$acl,$packager,$rpm]);
		foreach my $test (@tests) {
		    my ($status,$result)=$ACL2TEST->get([$acl,$packager,$rpm,$test]);
		    push @table, {BALL=>$status, C1=>$packager, C2=> $rpm, C3=> $test, C4=> $status, C5=>$result};
		}
	    }
	}
	&output_tmpl($headerstr.' by acl',
		     "$dir_by_acl/$acl.html",
		     {C1=>'packager',C2=>'rpm id',C3=>'test',C4=>'status',C5=>'message'},
		     \@table);
    }
}

if ($by_leader and $aclmap) {
    &__triplet_by_1($LEADER2TEST,'by-leader');
}

sub __triplet_by_1 {
    my ($KEYSTORE,$subdirname)=@_;
    my $dir_by_packager=&prepare_report_subdir($subdirname);
    &_copy_images($dir_by_packager);
    foreach my $leader (sort $KEYSTORE->keys_at([])) {
	my @rpms = sort $KEYSTORE->keys_at([$leader]);
	@table=();
	foreach my $rpm (@rpms) {
	    my @tests = sort $KEYSTORE->keys_at([$leader,$rpm]);
	    foreach my $test (@tests) {
		my ($status,$result)=$KEYSTORE->get([$leader,$rpm,$test]);
		push @table, {BALL=>$status, C1=> $rpm, C2=> $test, C3=> $status, C4=>$result};
	    }
	}
	&output_tmpl($headerstr.' for '.$subdirname.' '.$leader,
		     "$dir_by_packager/$leader.html",
		     {C1=>'rpm id',C2=>'test',C3=>'status',C4=>'message'},
		     \@table);
    }
}

sub prepare_report_subdir {
    my ($name)=@_;
    &repocop_note("$name...") if $name;
    my $dir_by_name="$repocop_reportdir/$name";
    remove_tree($dir_by_name);
    make_path($dir_by_name);
    return $dir_by_name;
}

sub output_tmpl {
    my ($header,$file,$headersref,$tableref)=@_;
    open (my $fh, ">", $file); #, print_to=>*CURFILE does not work :(
    my $tmpl = HTML::Template::Pro->new(scalarref => \$tmpl_src);
    $tmpl->param(HEAD=>$header);
    $tmpl->param(H1=>$header);
    $tmpl->param(TABLEBODY=>$tableref);
    $tmpl->param(TABLEHEADER=>[{BALL=>'&nbsp;',%$headersref}]);
    $tmpl->param(BOTTOM_COMMENT=>'generated by repocop at '.localtime());
    print $fh $tmpl->output();
    close ($fh);
}

sub _copy_images {
    my $dirprefix=shift;
    &Test::Repocop::Report::HTML::copy_repocop_status_patch_images($dirprefix.'/'.$img_dir);
}

&repocop_note("done.");

=head1	NAME

repocop-report-html - a tool that creates html reports on repocop unit tests results.

=head1	SYNOPSIS

B<repocop-report-html>
[B<-h|--help>]
[B<-v|--verbose>]
[B<-q|--quiet>]
[B<--workdir> I<workdir>]
[B<--acl-file> I<file>]
[B<--acl-altlinux> [B<--acl-branch I<sisyphus|p9|...>] [B<--acl-expire> I<time>] ]
[B<--report> I<warn|fail|skip|experimental|ok>]
[B<--report-et|--report-exclude-test> I<comma separated list of tests>]
[B<--report-it|--report-include-test> I<comma separated list of tests>]
[B<--report-include-distrotests>]
[B<--except>]
[B<--given>]
[B<--last-run>]
[B<--by-src-name> name ...]
[dir|rpm file|repocop_id ...]

=head1	DESCRIPTION

B<repocop-report-html> processes results of repocop unit tests, created with
repocop-run command, stored in I<repocop workdir> and creates results in html form.
Precise subset of tests can be selected using B<--include>
and B<--exclude> options.


=head1 ARGUMENTS

The arguments are the set of repocop ids. Repocop IDs can be extracted
from rpm files, so you can use an argument like /path/to/rpm/files/*.rpm
or just /path/to/rpm/files.

Note that repocop ids are used for internal representation of rpm packages.
They differ from pkgids used in repocop reports.

Repocop report package ids are used in repocop reports to
identify packages in a human readable way.
The repocop report package ID is a string
<NAME>-<VERSION>-<RELEASE>.ARCH for binary rpm packages and is a string
<NAME>-<VERSION>-<RELEASE>.src for source rpm packages.

Internal Repocop ids are subject to change; current repocop ID is a string
<NAME>-<VERSION>-<RELEASE>.ARCH@timestamp for binary rpm packages and is a string
<NAME>-<VERSION>-<RELEASE>.src for source rpm packages.


=head1	OPTIONS

=over


=item	B<-r, --report> I<skip|experimental|ok|warn|fail>

The level of test results reported. Test results below this level
are not reported. Default is warn.

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

Include all tests exept the given excluded set.

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

Include the given set of tests.

=item	B<--report-include-distrotests>

Include all distrotests.



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

The file content is ACL db,
which is used to sort result by ACL.
The argument is generic acl file compatible with Sisyphus' list.src.classic.

Alternatively, you can provide B<--acl-altlinux>.

=item	B<--acl-altlinux>

This option is ALTLinux-specific. if provided, repocop use ALTLinux::ACL
(perl-ALTLinux-ACL is required). Also, the following options of ALTLinux::ACL
become available:

B<--acl-branch> I<sisyphus|p9|...> Name of branch. Default is sisyphus.

B<--acl-expire> I<days> Cache expiration time, float, in days. Default is 1/5 day.


=item	B<--workdir> I<dir>

Provides alternative location for the Repocop workdir. Repocop workdir
is a place where test results and packages metadata information are stored.
Default is I<~/.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<--except>, B<--given>

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

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

Use the repocop ids of packages processed at last run as an input.

=item	B<--by-src-name>

Input arguments are expected to be src.rpm names, not repocop ids.
Repocop ids are calculated from given src.rpm names.


=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-2020 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

