#!/usr/bin/perl

use strict;
use warnings;
use autodie qw/open close/;

use lib './lib';
use lib '../logoved.git/lib';
use lib '../Logoved-Stream.git/lib';
use lib $ENV{'HOME'}.'/src/repo/logoved.git/lib';
use lib $ENV{'HOME'}.'/src/repo/Logoved-Stream.git/lib';
use parent 'Source::Shared::CLI';
use Cwd qw/abs_path/;
use File::Basename;
use File::Path qw(make_path remove_tree);
use File::Spec;
use Logoved::DB;
use Logoved::Stream 0.028;
use Logoved::Stream::LogParser;
use Logoved::Stream::Out::Hasher;
use Logoved::DB::Stream::Listener::Factory;
use Logoved::DB::Stream::Listener::LogFileMangle;
use Logoved::DB::Stream::Listener::Statistics;

my $outdir='LOGS';
my $link_sub=\&symlink2rel;
my $opt_flat_dirreport=1;
my $opt_warnings=0;
my $opt_fixable=1;
my $verbose=0;

sub cli_container_ref {['Logoved::DB', 'Logoved::DB::Stream::Listener::LogFileMangle', 'Logoved::DB::Stream::Listener::Statistics']}
our @LONGOPT=(
    'reportdir|report-dir=s' => \$outdir,
    'copy' => sub {$link_sub=\&cp2},
    'hardlink' => sub {$link_sub=\&hardlink2},
    'symlink' => sub {$link_sub=\&symlink2rel},
    'abs-symlink|absolute-symlink' => sub {$link_sub=\&symlink2abs},
    'rel-symlink|relative-symlink' => sub {$link_sub=\&symlink2rel},
    'warnings!' => \$opt_warnings,
    'flat!' => \$opt_flat_dirreport,
    'nested' => sub {$opt_flat_dirreport=0},
    'v|verbose' => sub {$verbose=1},
    );
sub print_longopt {
    print 'logoved-report options:
  Linking log files:
    --copy	copy log files
    --hard,--hardlink	hardlink log files
    --symlink	symlink log files (default) with relative symlinks
    --abs-symlink	symlink log files with absolute symlinks
    --rel-symlink	symlink log files with relative symlinks
  Output:
    --report-dir <path>	report directory. '.$outdir.' by default.
    --flat	flat directory structure
    --nested	nested directory structure
  Misc:
    -v,--verbose	verbose
'
}


__PACKAGE__->get_and_process_cli_options();

my $ruleset=&Logoved::DB::get_fail_ruleset();
my $listeners_factory=Logoved::DB::Stream::Listener::Factory->instance(ruleset=>$ruleset);
my $parser=Logoved::Stream::LogParser->new(
    listeners_factory => $listeners_factory,
    parser_class => 'Logoved::Stream::Out::Hasher',
    filepath_is_logfile => $Logoved::DB::Stream::Listener::LogFileMangle::check_logpath_format_sub,
    );
$listeners_factory->match_handler(\&Logoved::DB::Stream::Listener::Statistics::handle_file_matches);

map {
    print STDERR "parsing $_ ...\n" if $verbose;
    $parser->parse_arg($_)} @ARGV;

&Logoved::DB::Stream::Listener::LogFileMangle::calculate_mangled_filenames($listeners_factory);
&Logoved::DB::Stream::Listener::Statistics::calculate_report($listeners_factory);
my $file2reportname=Logoved::DB::Stream::Listener::LogFileMangle->file2reportname;
my $by_type=Logoved::DB::Stream::Listener::Statistics->matches_by_type;
my $statistics_by_subpath=Logoved::DB::Stream::Listener::Statistics->statistics_by_subpath;
my $resolved_statistics_by_subpath=Logoved::DB::Stream::Listener::Statistics->resolved_statistics_by_subpath;

remove_tree($outdir);
make_path($outdir);

if ($opt_flat_dirreport) {
    &make_report_dir('',$by_type->{'error'});
} else {
    &make_report_dir('error',$by_type->{'error'});
}
&make_report_dir(&mangle_reportdir('warnings'),$by_type->{'warnings'}) if $opt_warnings;
&make_report_dir(&mangle_reportdir('overcome'),$by_type->{'overcome'});
&make_report_dir(&mangle_reportdir('fixable'),$by_type->{'fixable'}) if $opt_fixable;
&make_report_dir(&mangle_reportdir('fix-unresolved'),$by_type->{'fix-unresolved'}) if $opt_fixable;

&make_filelist_dir(&mangle_reportdir('successful'), $listeners_factory->successful);
&make_filelist_dir(&mangle_reportdir('not-matched'), $listeners_factory->not_matched);

my $batch_fixscript=Logoved::DB::Stream::Listener::Statistics->batch_fixscript;
&write_pipe($batch_fixscript, qw/sort -u -o/, $outdir.'/00FIXSCRIPT') if $batch_fixscript;

# ----------------------------------------------------

sub mangle_reportdir {
    my ($dir)=@_;
    return $dir if !$opt_flat_dirreport;
    return 'LOGOVED-'.$dir;
}

sub mangle_subpath {
    my ($dir)=@_;
    return $dir if !$opt_flat_dirreport;
    while ($dir=~s!/\d+$!!){};
    return $dir=~s!/!-!gr;
}

sub make_filelist_dir {
    my ($subdir, $filelist_ref)=@_;
    my $reportdir=$outdir;
    $reportdir.='/'.$subdir if defined $subdir and $subdir ne '';
    make_path($reportdir) if @$filelist_ref;
    foreach my $file (@$filelist_ref) {
	my $destfile=$reportdir.'/'.$file2reportname->{$file};
	die $subdir,': ',$destfile,' already exists!' if -e $destfile;
	&$link_sub($file, $destfile);
    }
}

sub make_report_dir {
    my ($subdir, $by_rule)=@_;
    my $reportdir=$outdir;
    $reportdir.='/'.$subdir if defined $subdir and $subdir ne '';
    return unless $by_rule and %$by_rule;
    foreach my $rule_subpath (keys(%$by_rule)) {
	my $ruledir=$reportdir.'/'.&mangle_subpath($rule_subpath);
	my $files=$by_rule->{$rule_subpath};
	next if ! %$files;
	make_path($ruledir);
	foreach my $file (keys(%$files)) {
	    # TODO: multiple matches - warning if error?
	    #my $matches=$files->{$file};
	    my $destfile=$ruledir.'/'.$file2reportname->{$file};
	    if (-e $destfile) {
		warn 'rule conflict: ',$destfile,' already exists!',"\n";
		next;
	    }
	    &$link_sub($file, $destfile);
	}
	my $resolved_statistics=$resolved_statistics_by_subpath->{$rule_subpath};
	my $statistics=$statistics_by_subpath->{$rule_subpath};
	if (defined $resolved_statistics) {
	    &write_file($ruledir.'/00statistics_resolved',$resolved_statistics);
	    &write_file($ruledir.'/00statistics_unresolved',$statistics) if defined $statistics;
	} else {
	    &write_file($ruledir.'/00statistics',$statistics) if defined $statistics;
	}
	my $rule=$ruleset->get_rule_by_subpath($rule_subpath);
	my $descriptionref=$rule->description;
	&write_file($ruledir.'/00description',join("\n",@$descriptionref)) if defined $descriptionref;
    }
}

sub symlink2abs {
    &run('ln','-s', abs_path($_[0]), $_[1]);
}

sub symlink2rel {
    &run('ln','-s', File::Spec->abs2rel( abs_path($_[0]), dirname($_[1]) ) , $_[1]);
}
sub hardlink2 {
    &run('ln', abs_path($_[0]), $_[1]);
}
sub cp2 {
    &run('cp', abs_path($_[0]), $_[1]);
}

sub run {
    system(@_)==0 or die "command ",join(' ',@_), ' failed: ',$!;
}

sub write_file {
    my ($file,$content)=@_;
    open (my $fh, '>', $file);
    print $fh $content;
    close($fh);
}

sub write_pipe {
    my ($content, @args)=@_;
    open (my $fh, '|-', @args);
    print $fh $content;
    close($fh);
}

__END__

=head1	NAME

logoved-report - sort hasher build logs accrording to error patterns

=head1	SYNOPSIS

B<logoved-report>
[B<--help>]
[B<--copy>]
[B<--hard>]
[B<--hardlink>]
[B<--symlink>]
[B<--abs-symlink>]
[B<--rel-symlink>]
[B<--flat>]
[B<--nested>]
[B<--report-dir I<path>>]
[B<other logoved db options here, see --help>]
I<log files / dirs with log files> ...

=head1	DESCRIPTION

B<logoved-report> sort hasher build logs accrording to error patterns
stored in logoved db and creates logoved's 00FIXSCRIPT for later
batch fixing of packages that failed its build.

=head1	OPTIONS

There are lots of logoved db options that are inherited from Logoved::DB
library. To view full list of available options, run logoved-report --help.

=head2 logoved-report specific OPTIONS

=over

=item	B<-h, --help>

Display this help and exit.

=item B<--copy> | B<--hardlink> | B<--symlink> | B<--abs-symlink> | B<--rel-symlink>

Linking log files options:
    --copy	copy log files
    --hard,--hardlink	hardlink log files
    --symlink,--rel-symlink	symlink log files (default) with relative symlinks
    --abs-symlink	symlink log files with absolute symlinks

=item B<--report-dir I<path>>

Report directory. LOGS by default.

=item B<--flat> | B<--nested>

Output directory structure:
    --flat	flat directory structure
    --nested	nested directory structure


=back

=head1	AUTHOR

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

=head1	COPYING

Copyright (c) 2018-2021 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

