#!/usr/bin/perl -w

package Autorepo::Intersections;

use strict;
use warnings;
#use Getopt::Long;
use Source::Shared::CLI;
use Autorepo::Config;
use ALTLinux::RepoList 0.005 qw/load_listfile_lambda_fn listdirs2srclists listdirs2binlists/;

our @ISA = qw/Source::Shared::CLI/;

my $dest_mode=0;
my $opt_src_in_dest=0;
my $verbose=0;
$verbose=1 if $ENV{TERM}; # not to pollute cron mail

my @binlists;
my @srclists;

my %binignore;
my %srcignore;

sub print_longopt {
    print "  intersection options:
	--dest dest mode even if AUTOREPO_MERGE_COMPONENTS found
	--src-in-dest also find src doubles in dest mode
";
}
our @LONGOPT=(
    'dest' => sub {$dest_mode=1},
    'src-in-dest!' => \$opt_src_in_dest,
    'src-dest!' => \$opt_src_in_dest,
    'srcdest!' => \$opt_src_in_dest,
    );

__PACKAGE__->new()->get_and_process_cli_options();

my $ignore_file=$CONFIG{'AUTOREPO_HOME'}.'/intersections.ignore.txt';
&__load_ignore($ignore_file) if -e $ignore_file;

if ($CONFIG{'AUTOREPO_MERGE_COMPONENTS'} and !$dest_mode) {
    my @MERGE_ROOTS=split(/\s+/,$CONFIG{'AUTOREPO_MERGE_COMPONENTS'});
    my @listdirs=map {$_.'/'.$AUTOREPO_BRANCH.'/files/list'} @MERGE_ROOTS;
    push @binlists, &listdirs2binlists(@listdirs);
    push @srclists, &listdirs2srclists(@listdirs);
} else {
    if (!$CONFIG{'AUTOREPO_PURGE_DESTPATH'} or $CONFIG{'AUTOREPO_PURGE_DESTPATH'} eq 'none') {
	print STDERR "binary intersections disabled (AUTOREPO_PURGE_DESTPATH not set) \n";
	exit;
    }
    my @listdirs=($AUTOREPO_ROOT.'/files/list', map {s,/?files/SRPMS/?$,,;$_=$_.'/files/list'} split(':', $CONFIG{'AUTOREPO_PURGE_DESTPATH'}));
    push @binlists, &listdirs2binlists(@listdirs);
    push @srclists, &listdirs2srclists(@listdirs) if $opt_src_in_dest;
}

if (grep {! -e $_} @binlists) {
    print "binary intersections disabled (some lists not found)\n";
    print 'not found binlists: ',join(' ',grep {! -e $_} @binlists);
    exit 1;
}

if (grep {! -e $_} @srclists) {
    print "source intersections disabled (some lists not found)\n";
    print 'not found srclists: ',join(' ',grep {! -e $_} @srclists);
    exit 1;
}

my (%binlist2hash,%srclist2hash);
my ($i,$j);
# over diagonal in matrix; do not need self intersections
for ($i=0; $i<$#binlists; $i++) {
    my $ihash=&__get_binhash($binlists[$i]);
    for ($j=$i+1; $j<=$#binlists; $j++) {
	#print "debug: $i $j ($#binlists) $binlists[$i] $binlists[$j]\n";
	my $jhash=&__get_binhash($binlists[$j]);
	my $intersection=&kill_ignore(&calculate_intersection($ihash,$jhash),\%binignore);
	&report_intersection('bin',$binlists[$i],$binlists[$j],$ihash,$jhash,$intersection) if @$intersection;
    }
}
for ($i=0; $i<$#srclists; $i++) {
    my $ihash=&__get_srchash_name2srpm($srclists[$i]);
    for ($j=$i+1; $j<=$#srclists; $j++) {
	#print "debug: $i $j ($#srclists) $srclists[$i] $srclists[$j]\n";
	my $jhash=&__get_srchash_name2srpm($srclists[$j]);
	my $intersection=&kill_ignore(&calculate_intersection($ihash,$jhash),\%srcignore);
	&report_intersection('src',$srclists[$i],$srclists[$j],$ihash,$jhash,$intersection) if @$intersection;
    }
}

# TODO: print to a file
sub report_intersection {
    my ($type,$listi,$listj,$ihash,$jhash,$intersection)=@_;
    return unless @$intersection;
    print "==================================================================\n";
    print "Intersection: $type $listi, $listj\n";
    print "==================================================================\n";
    foreach my $element (@$intersection) {
	print "$element\t$ihash->{$element} $jhash->{$element}\n";
    }
    print "==================================================================\n";
}

sub kill_ignore {
    my ($intersection,$ignore)=@_;
    my @new_intersection;
    foreach my $element (@$intersection) {
	next if $ignore->{$element};
	push @new_intersection, $element;
    }
    return \@new_intersection;
}

sub __get_binhash {
    my ($list)=@_;
    my $hash=$binlist2hash{$list};
    return $hash if $hash;
    ($hash)=&load_listfile_lambda_fn($list,\&__load_altbinlist_fn);
    $binlist2hash{$list}=$hash;
    return $hash;
}

sub __get_srchash_name2srpm {
    my ($list)=@_;
    my $hash=$srclist2hash{$list};
    return $hash if $hash;
    ($hash)=&load_listfile_lambda_fn($list,\&__load_altsrclist_fn_srchash_name2srpm);
    $srclist2hash{$list}=$hash;
    return $hash;
}



sub __load_altsrclist_fn_srchash_name2srpm {
    my ($fn)=@_;
    my %name2srpm;
    while (my $line=<$fn>) {
	chomp $line;
	my @list = split(/\s+/,$line);
	next if @list!=3;
	my ($name,$evr,$srpm)=@list;
	$name2srpm{$name}=$srpm;
    }
    return \%name2srpm;
}

sub __load_altbinlist_fn {
    my ($fn)=@_;
    my %name2srpm;
    while (my $line=<$fn>) {
	chomp $line;
	my @list = split(/\s+/,$line);
	next if @list!=5;
	my ($name,$evr,$arch,$rpm, $srpm)=@list;
	$name2srpm{$name}=$srpm;
    }
    return \%name2srpm;
}

sub calculate_intersection {
    my ($setref1,$setref2)=@_;
    my @intersection;
    foreach my $element1 (keys(%$setref1)) {
	push @intersection, $element1 if $setref2->{$element1};
    }
    return \@intersection;
}

sub __load_ignore {
    my ($file)=@_;
    open (my $fn, '<', $file) or die "can't open file $file\n";
    while (my $line=<$fn>) {
	chomp $line;
	next if $line=~/^#/;
	next if $line=~/^\s*$/;
	my @list = split(/\s+/,$line);
	die "invalid format of ignore file $file: $_" if @list!=2;
	my ($type,$name)=@list;
	if ($type eq 'src') {
	    $srcignore{$name}=1;
	} elsif ($type eq 'bin') {
	    $binignore{$name}=1;
	} else {
	    die "bad type in ignore file $file: $_";
	}
    }
    close($fn);
}


=head1	NAME

autorepo-health-find-intersections - find intersections between altlinux repositories.

=head1	SYNOPSIS

B<autorepo-health-find-intersections>
[B<--dest>]
[B<--src-in-dest>]

=head1	DESCRIPTION

B<autorepo-health-find-intersections> finds intersections between altlinux repositories.
there are 2 main modes of operation: merge mode and dest mode.
Merge mode is activated by default if AUTOREPO_MERGE_COMPONENTS found.
In merge mode sub repositories in AUTOREPO_MERGE_COMPONENTS are checked against each other.
In dest mode the managed autorepo repository component is checked against the alt linux
main repository (Sisyphus or branch).

=head1	OPTIONS

=over

=item	B<--dest>

Dest mode even if AUTOREPO_MERGE_COMPONENTS found

=item	B<--src-in-dest>

Include src.rpm comparison in dest mode.
Disabled by default, as this functionality overlaps with purge script.

=back

=head1	ARGUMENTS

None.

=head1	AUTHOR

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

=head1	COPYING

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

