#!/usr/bin/env perl

# Put in one table coverage for all programms called from
# finished (pass and checksum fail) tests.

use strict;
#use warnings;
use File::Find;
use File::Basename;
use File::Path;

my $cwd = `dirname $0`; chomp $cwd;
push @INC, "$cwd/../../";

require RRTS::MKModes;
#use RRTS::MKModes;

our $scripts = dirname($0);

my $test_builddir = '-';
my $notest_builddir = '-';
my $source_dir;
my $run_dir;

# Parse command line
sub usage {
	print <<HELP;
Usage: $0 -s <srcdir> -r <rundir> -a <testmode_build> [-b <notestmode_build>]
    -s    directory with sources
    -r    directory with gmake.out
HELP
	exit 1;
}

my $k = -1;
while (++$k <= $#ARGV) {
	my $o = $ARGV[$k];
	if ($o eq '-s') {
		$source_dir = $ARGV[$k+1] if ($k < $#ARGV);
	} elsif ($o eq '-r') {
		$run_dir = $ARGV[$k+1] if ($k < $#ARGV);
	} elsif ($o eq '-a') {
		$test_builddir = $ARGV[$k+1] if ($k < $#ARGV);
	} elsif ($o eq '-b') {
		$notest_builddir = $ARGV[$k+1] if ($k < $#ARGV);
	} else {
		die "Unknown option: $o\n";
	}
	++$k;
}

die "At least one of -a or -b argument must be a directory\n"
	unless (-d $test_builddir or -d $notest_builddir);
die "Bad source directory: $source_dir\n" unless (-d $source_dir);
die "Bad run directory: $run_dir\n" unless (-d $run_dir);

my $revision = `LC_ALL=C svn info $source_dir | awk '/Revision/ {print \$2}'`;
chomp $revision;
die "Bad revision $revision\n" unless ($revision =~ m/^\d{6,}$/);

&MKModes::init_mk();

mkdir "info" or die "can't create directory info: $!";

# make hash programm -> directory (relative to .../arcadia/)
my %source = ();
foreach my $d (($test_builddir, $notest_builddir)) {
	&find_source($d, \%source) if (-d $d);
}

# prepare .info files
my @covered_progs = ();
foreach my $d (($test_builddir, $notest_builddir)) {
	next unless (-d $d);

	foreach my $prog (keys %source) {
		my @gcda = glob("$d/$source{$prog}/CMakeFiles/*/*.gcda");
		next if (@gcda < 1);

		print "processing files in $d/$source{$prog}\n";
		push @covered_progs, $prog;
		`$scripts/lcov -d $d/$source{$prog} -b $source_dir/$source{$prog} -c -o info/$prog.info 1>>info/lcov.log 2>\&1`;
	}
}

# merge .info files
my @info = glob("info/*.info");
@info = grep {! -z $_} @info;
if (@info) {
	my $info = join(' -a ', @info);
	open(my $fd, "> info/total.info");
	close($fd);
	`$scripts/lcov -a $info -o info/total.info 1>>info/lcov.log 2>\&1`;
}

# make nice html pages with coverage
print "Generate html pages with coverage\n";
`$scripts/genhtml -o ./ info/total.info 1>>info/lcov.log 2>\&1`;

# in real we do want to see
#  - only programs with coverage info
#  - only programs from tests (not byacc and so on)
@covered_progs =
	grep {-f "$source{$_}/index.html"} @covered_progs;
@covered_progs = grep {exists $MKModes::prog_test{$_}} @covered_progs;

# prepare comments on coverage
my $comment = &prep_comments($source_dir);

# find coverage details
my $percents = &find_coverage(\%source, @covered_progs);

my @started_tests = glob("$run_dir/gmake.*.err");
@started_tests = map {m/gmake.(.+).err$/; $_ = $1} @started_tests;

# make report.html
my $fout = "report.html";
open(my $fd, "> $fout") or die "can't open $fout: $!";

print $fd &display_html($revision, $run_dir,
	\@covered_progs, \@started_tests, $percents, $comment);

close($fd) or die "can't close $fout: $!";

rmtree(['./info']);

# Functions

our @subdirs;

sub find_coverage {
	my ($source, @programms) = @_;
	my %percents = ();

	foreach my $prog (@programms) {
		my $d = $source->{$prog};

		@subdirs = ();
		find( {no_chdir => 0, wanted => sub {push @subdirs, $_ if (-d and -f "$_/index.html")} }, "$d/");
		for my $subdir (@subdirs) {
			my $i = "$d/$subdir/index.html";
			my $lock = 1;
			my $file;

			open(my $fd, "< $i") or die "can't open $i: $!";
			while(my $line = <$fd>) {
				if ($line =~ m/<a href="(.*\.[^.]+)\.gcov\.html">/) {
					$lock = 0;
					$file = "$subdir/$1";
					$file =~ s/\.\///g;
				}
				next if ($lock);
				if ($line =~ m/coverPer(Lo|Med|Hi)">([0-9.]+)/) {
					my $tmp = "./$d/$file.gcov.html";
					$tmp = "<a href=\"$tmp\">$file</a> $2%";

					unless (exists $percents{$prog}) {
						$percents{$prog} = [];
					}
					push @{$percents{$prog}}, $tmp;
					$lock = 1;
				}
			}
			close($fd) or die "can't close $i: $!";
		}
	}

	return \%percents;
}

sub prep_comments  {
	my $source_dir = $_[0];

	my %comment = ();
	my $cf = "$scripts/coverage_comments";

	# read known comments
	open(my $fd, "< $cf") or die "can't open $cf: $!";
	while (my $line = <$fd>) {
		chomp $line;
		my ($file, $revision, $comment) = split(/\s+/, $line, 3);

		my $rev = 0;
		if (-f $file) {
			my $svn = `svn status -v $source_dir/$file`;
			chomp $svn;
			$rev = (split(/\s+/, $svn))[1];
		}

		if ($rev > $revision) {
			$comment{$file} = "was changed since $revision: $comment";
		} else {
			$comment{$file} = $comment;
		}
	}
	close($fd) or die "can't close $cf: $!";

	return \%comment;
}

sub find_source {
	# Recursively make hash for all executables:
	# programm -> directory
	my ($dir, $out) = @_;
	return if ($dir =~ m/\/(CMakeFiles|SRC|bin)(\/|$)/);

	if (-d $dir) {
		opendir(DIR, $dir) or die "can't opendir $dir: $!";
		my @content = grep {$_ !~ m/^[.]{1,2}$/} readdir(DIR);
		closedir(DIR) or die "can't closedir $dir: $!";
		@content = map {"$dir/$_"} @content;

		map { &find_source($_, $out) } @content;
	} elsif (-X $dir) {
		$dir =~ m/testmode_build[\/]+(.+)\/([^\/]+)$/;
		my ($d, $p) = ($1, $2);
		$out->{$p} = $d;
	}
}

sub summary_table {
	# Display summary table with coverage
	my ($covered_progs, $started_tests, $percents, $comment) = @_;

	my $doc = <<HTML;
	<P>
		<TABLE BORDER=1 CELLSPACING=0 CELLPADDING=2>
			<TR>
				<TH>Tests
				<TH>Programm
				<TH>Code coverage
				<TH>Comment
HTML

	foreach my $prog (@$covered_progs) {
		my @t = ();
		foreach my $a (@{ $MKModes::prog_test{$prog} }) {
			push @t, $a if (grep {$_ eq $a} @$started_tests);
		}

		my $t = join("<BR>\n\t\t\t\t\t", @t);
		my @p = ();
		my @c = ();
		foreach my $u (@{ $percents->{$prog} }) {
			push @p, $u;
			$u =~ m/<a href="\.\/([^"]+)\.gcov\.html">/;
			if (exists $comment->{$1}) {
				push @c, $comment->{$1};
			} else {
				push @c, 'not analyzed yet';
			}
		}
		my $p = join("<BR>\n\t\t\t\t\t", @p);
		my $c = join("<BR>\n\t\t\t\t\t", @c);

		$doc .= <<HTML;
			<TR>
				<TD>
					$t
				<TD>
					$prog
				<TD>
					$p
				<TD>
					$c
HTML
	}

	$doc .= <<HTML;
		</TABLE>
	</P>
HTML

	return $doc;
}

sub tests_status_table {
	# Display table with tests exit status
	my $rundir = $_[0];
	die "must be directory: $rundir\n" unless (-d $rundir);

	# get exit status of tests
	my (@passed, @cksum, @failed);
	foreach my $f (glob("$rundir/gmake.*.err")) {
		$f =~ m/gmake\.(.*)\.err$/;
		my $test = $1;

		if (-f "$rundir/$test.check.dst") {
			push @passed, $test;
		} elsif (-f "$rundir/$test.all.dst") {
			push @cksum, $test;
		} else {
			push @failed, $test;
		}
	}

	# make html table

	my $passed = join("<BR>\n\t\t\t\t\t", @passed);
	my $cksum = join("<BR>\n\t\t\t\t\t", @cksum);
	my $failed = join("<BR>\n\t\t\t\t\t", @failed);

	return <<HTML;
	<P>
		<TABLE BORDER=1 CELLSPACING=0 CELLPADDING=2>
			<TR>
				<TH>Passed
				<TH>Checksum differs
				<TH>Run failed
			<TR VALIGN=TOP>
				<TD>
					$passed
				<TD>
					$cksum
				<TD>
					$failed
		</TABLE>
	</P>
HTML
}

sub display_html {
	# Display html document
	my ($revision, $rundir, $covered_progs, $started_tests,
		$percents, $comment) = @_;

	my $report = <<HTML;
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="en">
<HEAD>
	<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
	<TITLE>Summary Report on Code Coverage</TITLE>
</HEAD>
<BODY>
	<H2>Summary coverage report for revision $revision.</H2>
	<P><A HREF="https://wiki.yandex-team.ru/RobotRegressionTest/CodeCoverage">Documentation.</A></P>
	<P><A HREF="./index.html">Detailed report.</A></P>
HTML

	$report .= &summary_table($covered_progs, $started_tests,
			$percents, $comment);
	$report .= &tests_status_table($rundir);
	$report .= "</BODY>\n</HTML>\n";

	return $report;
}
