#!/usr/bin/perl

use strict;
use warnings;
use Carp;
use Getopt::Long;
use DistroDBTools;
use ProjectDBTools;

# TODO: add contents_index based unique files mapping based criteria
# (like, mod %bin%, mod usr/share (not doc) if unique-mapping is dence enough (> 50% in the area)?
# to fix likes of broken mapping
# wesnoth-data    python-module-wesnoth

my $skip_id_map;
my $verbose=0;
my $help;
my $opt_dry_run;
my $binary_output_file='binary/40-distrodb-new.txt';

my $distromap_config=&DistroDBTools::read_distromap_config();
my $origin_repo=$distromap_config->{'ORIGINREPO'};
my $origin_branch=$distromap_config->{'ORIGINBRANCH'};
my $dest_repo=$distromap_config->{'DESTREPO'};
my $dest_branch=$distromap_config->{'DESTBRANCH'};
my $useprojectdb=$distromap_config->{'USE_PROJECTDB'};

my $ORIGIN=$origin_repo.'/'.$origin_branch if $origin_repo and $origin_branch;
my $DEST=$dest_repo.'/'.$dest_branch if $dest_repo and $dest_branch;

# for specific package debugging
my $debug_obname='';

GetOptions (
    'origin=s'  => \$ORIGIN,
    'dest=s'  => \$DEST,
    'dry-run' => \$opt_dry_run,
    'warn-no-url!' => \$ProjectDBTools::opt_warn_nourl,
    'force-projectdb-yes-always' => \$ProjectDBTools::opt_force_yes_always,
    'force-projectdb-yes-same-name' => \$ProjectDBTools::opt_force_yes_same_name,
    'force-projectdb-yes-same-nv' => \$ProjectDBTools::opt_force_yes_same_name_version,
    'debug-obname=s' => \$debug_obname,
    'help'  => \$help,
    'id!'  => \$skip_id_map,
    'projectdb!'  => \$useprojectdb,
    'O|output-file=s' => \$binary_output_file,
    'verbose+'  => \$verbose,
);

if ($help or !$ORIGIN or !$DEST) {
	_usage();
	exit(0);
}

$DistroDBTools::verbose=$verbose;
ProjectDBTools::init_project_db() if $useprojectdb;

my %strict_source_tags=map {$_=>1} qw/TDE KDE3 KDE4 KDE5/;
my @optional_binary_tags_order=qw/font-100dpi font-75dpi QT KDE QT3 QT4 QT5/;
my %optional_binary_tags=map {$_=>1} @optional_binary_tags_order;

my $origin={};
my $dest={};

# bin: not used
foreach my $stem (qw/sourcename/) {
    $origin->{$stem}={} unless $origin->{$stem};
    $dest->{$stem}={} unless $dest->{$stem};
    &DistroDBTools::read_distrodb_first_one2one($dest->{$stem}, $DEST, $stem);
    &DistroDBTools::read_distrodb_first_one2one($origin->{$stem}, $ORIGIN, $stem);
}
foreach my $stem (qw/sourceurl.raw/) {
    $origin->{$stem}={} unless $origin->{$stem};
    $dest->{$stem}={} unless $dest->{$stem};
    &DistroDBTools::read_distrodb_one2one($dest->{$stem}, $DEST, $stem);
    &DistroDBTools::read_distrodb_one2one($origin->{$stem}, $ORIGIN, $stem);
}
foreach my $stem (qw/fonts.raw/) {
    $origin->{$stem}={} unless $origin->{$stem};
    $dest->{$stem}={} unless $dest->{$stem};
    &DistroDBTools::read_distrodb_tsv_map($dest->{$stem}, $DEST, $stem);
    &DistroDBTools::read_distrodb_tsv_map($origin->{$stem}, $ORIGIN, $stem);
}
foreach my $stem (qw/buildrequires.raw cmake devel-libs headers gir path perl php.raw pkg-config plugins.raw provides python2.raw python3.raw requires.raw static-libs/) {
    $origin->{$stem}={} unless $origin->{$stem};
    $dest->{$stem}={} unless $dest->{$stem};
    &DistroDBTools::read_distrodb_map($dest->{$stem}, $DEST, $stem);
    &DistroDBTools::read_distrodb_map($origin->{$stem}, $ORIGIN, $stem);
}

my $originreqflag={};
map {$originreqflag->{$_}=1} keys(%{$origin->{'buildrequires.raw'}}), keys(%{$origin->{'requires.raw'}});
my $originurl=$origin->{'sourceurl.raw'};
my $desturl=$dest->{'sourceurl.raw'};

my $tags_origin={};
my $tags_dest={};
my $origin_SST_db={};
my $origin_BOT_db={};
my $dest_SST_db={};
my $dest_BOT_db={};
$tags_origin->{'source'}->{'strict'}=$origin_SST_db;
$tags_origin->{'binary'}->{'optional'}=$origin_BOT_db;
$tags_dest->{'source'}->{'strict'}=$dest_SST_db;
$tags_dest->{'binary'}->{'optional'}=$dest_BOT_db;

&read_tags_from_requires($origin->{'sourcename'},$origin->{'requires.raw'},$tags_origin);
&read_tags_from_requires($dest->{'sourcename'},$dest->{'requires.raw'},$tags_dest);
&read_tags_from_pkgname($origin->{'sourcename'},$tags_origin);
&read_tags_from_pkgname($dest->{'sourcename'},$tags_dest);

my $origin2dest={};

&generic_multival_translate('devel-libs');
&generic_multival_translate('static-libs');
&generic_multival_translate('pkg-config');
&generic_multival_translate('headers');
&generic_multival_translate('gir');
&generic_multival_translate('cmake');
&generic_multival_translate('path');
&generic_multival_translate('perl');
&generic_multival_translate('php.raw');
&generic_multival_translate('plugins.raw');
&generic_multival_translate('fonts.raw');
# map python
&generic_multival_translate('python2.raw');
&generic_multival_translate('python3.raw');

my $destprov=$dest->{'provides'};
my $originprov=$origin->{'provides'};
my ($originlibs, $destlibs)=({},{});
&read_lib_provides($originlibs,$originprov);
&read_lib_provides($destlibs,$destprov);

# map libs
foreach my $dep_key (keys(%$originlibs)) {
    my $destlibset=$destlibs->{$dep_key};
    if ($destlibset) {
	#print STDERR "key=$dep_key\n";
	my $originlibset=$originlibs->{$dep_key};
	foreach my $originlib (keys (%$originlibset)) {
	    my $originset=$originlibset->{$originlib};
	    foreach my $destlib (keys (%$destlibset)) {
		if ($originlib eq $destlib) {
		    my $destset=$destlibset->{$destlib};
		    next if not $destset or @$destset!=1;
		    foreach my $originpkg (@$originset) {
			my $destpkg=$destset->[0];
			my $message="stem=same-libs key=$dep_key [$originlib]";
			&add_distromap($originpkg,$destpkg) if &_is_same_url($originpkg, $destpkg, $message);
		    }
		}
	    }
	}
    }
}

# prov's that are really required are routed to the already calculated deps
foreach my $key (keys(%$originreqflag)) {
    next if $key =~ /\(/; #(64bit)...
    next if $key =~ m!/!; #/path/
    next if $key =~ m!^lib.*\.so\b!;
    # perl provides a lot of modules 
    next if $key =~ m!^perl-!;
    next if $origin2dest->{$key};
    my $originsetprov=$originprov->{$key};
    next if not $originsetprov or scalar @$originsetprov>1;
    my $originpkg=$originsetprov->[0];
    my $destpkgset=$origin2dest->{$originpkg};
    next unless $destpkgset;
    my $destsetproviders=$destprov->{$key};
    next if $destsetproviders and scalar @$destsetproviders>0 and &are_arrays_intersect($destsetproviders,$destpkgset);
    $origin2dest->{$key}=[@$destpkgset];
}

# aliases:
foreach my $key (keys(%$origin2dest)) {
    my $destpkg=$origin2dest->{$key};
    my $alias=$key;
    #print STDERR "try: $key\n";
    # python-foo Provides aliases for python2-foo
    if ($alias=~s/^python2-/python-/) {
	my $originset=$originprov->{$alias};
	#print STDERR "possible alias: $alias for $key\n" if $originpkg;
	if ($originset and @$originset==1  and $originset->[0] eq $key) {
	    map {&add_distromap($alias,$_)} @$destpkg;
	}
    # TODO lib-devel
    } elsif (0) {

    }
}

my @force_map= # not <...>-common
qw/
automake	automake
autoconf	autoconf
gcc		gcc
gcc-c++	gcc-c++
libtool		libtool
/;
#gcc-gfortran	gcc-fortran
while (scalar @force_map) {
    &set_distromap(shift @force_map, shift @force_map);
}

# long lists of aliases and difference in modules bundled
&set_distromap('perl','perl');

exit if $opt_dry_run;

&DistroDBTools::sort_print_hasharray($origin2dest,$skip_id_map,$binary_output_file);

sub generic_multival_translate {
    my ($stem, $filters)=@_;
    print STDERR "stem=$stem starting...\n" if $verbose;
    my $destdb=$dest->{$stem};
    my $origindb=$origin->{$stem};
    foreach my $key (keys(%$origindb)) {
	my $destset=$destdb->{$key};
	next unless $destset;
	my $originset=$origindb->{$key};
	foreach my $originpkg (@$originset) {
	    my $locdebug=1 if $verbose>2 or $debug_obname and $debug_obname eq $originpkg;
	    my (@same_dist_set,@replaceable_dist_set);
	    foreach my $destpkg (@$destset) {
		my $is_same=&_is_same_url($originpkg, $destpkg, "stem=$stem key=$key",$locdebug);
		print STDERR "DEBUG: stem=$stem key=$key originpkg=$originpkg destpkg=$destpkg sameurl=$is_same\n" if $locdebug;
		push @same_dist_set, $destpkg if $is_same== ProjectDBTools::SAME;
		push @replaceable_dist_set, $destpkg if $is_same== ProjectDBTools::REPLACEABLE;
	    }
	    my $originsrc=$origin->{'sourcename'}->{$originpkg};
	    if (!defined($originsrc)) {
		warn "W: can't find origin src for $originpkg" unless defined $originsrc;
		# 30% chance
		$originsrc=$originpkg;
	    }
	    my $origin_SST=$origin_SST_db->{$originsrc};
	    my $origin_BOT=$origin_BOT_db->{$originpkg};
	    my @origin_BOT_list=&_get_ordered_BOT_list($origin_BOT);
	    print STDERR "DEBUG: $originpkg: stem=$stem\n",
		"\tsame_dist_set=",join(' ',@same_dist_set),"\n",
		"\treplaceable_dist_set=",join(' ',@replaceable_dist_set),"\n",
		"\torigin_SST=",join(' ',keys(%$origin_SST)),"\n",
		"\torigin_BOT=",join(' ',@origin_BOT_list),"\n",
		if $locdebug;
	    my @SST_same_dist_set=grep {&_the_same_SST($origin_SST,$_,$locdebug)} @same_dist_set;
	    print STDERR "\tSST_same_dist_set=",join(' ',@SST_same_dist_set),"\n" if $locdebug;
	    @same_dist_set=@SST_same_dist_set if @SST_same_dist_set;
	    unless (@same_dist_set) {
		my @SST_replaceable_dist_set=grep {&_the_same_SST($origin_SST,$_,$locdebug)} @replaceable_dist_set;
		print STDERR "\tSST_replaceable_dist_set=",join(' ',@SST_replaceable_dist_set),"\n" if $locdebug;
		@replaceable_dist_set=@SST_replaceable_dist_set if @SST_replaceable_dist_set;
	    }
	    if (@same_dist_set>1) {
		# tag filtering
		my @filered=&_filter_by_BOT(\@origin_BOT_list,\@same_dist_set,$locdebug);
		if (@filered) {
		    @same_dist_set=@filered;
		    print STDERR "\tsame_filtered=",join(' ',@filered),"\n" if $locdebug;
		}
	    }
	    if (@same_dist_set>1) {

		# to do: version corellation, like in
		# libqalculate-devel vs libqalculate4-devel for stem=devel-libs key=qalculate

		# compat filtering
		# vala-compat-tools vs vala-tools for stem=pkg-config key=vapigen
		# flex-devel vs flex-old for stem=static-libs key=l

	    } elsif (1==@same_dist_set) {
		&add_distromap($originpkg,$same_dist_set[0]);
	    } else {
		if (@replaceable_dist_set>1) {
		    # tag filtering
		    my @filered=&_filter_by_BOT(\@origin_BOT_list,\@replaceable_dist_set,$locdebug);
		    if (@filered) {
			@replaceable_dist_set=@filered;
			print STDERR "\treplaceable_filtered=",join(' ',@filered),"\n" if $locdebug;
		    }
		}
		if (@replaceable_dist_set>1) {
		    # the same to do

		} elsif (1==@replaceable_dist_set) {
		    &add_distromap($originpkg,$replaceable_dist_set[0]);
		} else {
		    # todo
		}
	    }
	}
    }
}

sub _get_ordered_BOT_list {
    my ($BOT)=@_;
    return unless $BOT;
    my @out;
    foreach my $tag (@optional_binary_tags_order) {
	push @out, $tag if $BOT->{$tag};
    }
    return @out;
}

sub _filter_by_BOT {
    my ($origin_BOT_list,$destset,$locdebug)=@_;
    my @oldlist=@$destset;
    my @newlist;
    foreach my $tag (@$origin_BOT_list) {
	foreach my $destpkg (@oldlist) {
	    if ($dest_BOT_db->{$destpkg}->{$tag}) {
		push @newlist, $destpkg;
	    }
	}
	print STDERR "DEBUG: _filter_by_BOT: tag=$tag @newlist=",join(' ',@newlist),"\n" if $locdebug;
	return @newlist if @newlist==1;
	return @oldlist if not @newlist;
	@oldlist=@newlist;
	@newlist=();
    }
    return @newlist if @newlist;
    return @oldlist;
}

sub _the_same_SST {
    my ($origin_SST,$destpkg,$locdebug)=@_;
    my $destsrc=$dest->{'sourcename'}->{$destpkg};
    my $dest_SST=$dest_SST_db->{$destsrc};
    print STDERR "DEBUG: destpkg $destpkg: dest_SST=",join(' ',keys(%$dest_SST)),"\n" if $locdebug;
    return 0 if scalar keys(%$origin_SST) != scalar keys(%$dest_SST);
    foreach my $tag (keys(%$origin_SST)) {
	return 0 unless $dest_SST->{$tag};
    }
    return 1;
}

sub set_distromap {
    my ($from,$to)=@_;
    $origin2dest->{$from}=[$to];
}

sub _is_same_url {
    my ($originpkg, $destpkg,$message,$locdebug)=@_;
    my $same_proj=ProjectDBTools::SAME;
    if ($useprojectdb) {
	my $originsrc=$origin->{'sourcename'}->{$originpkg};
	my $destsrc=$dest->{'sourcename'}->{$destpkg};
	if (!defined($originsrc) or !defined($destsrc)) {
	    warn "W: can't find origin src for $originpkg" unless defined $originsrc;
	    warn "W: can't find dest src for $destpkg" unless defined $destsrc;
	} else {
	    $message//='[?]';
	    $same_proj=&ProjectDBTools::same_url(
		$originsrc,$destsrc,$originurl->{$originsrc}, $desturl->{$destsrc},
		"$originpkg vs $destpkg for $message",$locdebug);
	}
    }
    return $same_proj;
}

sub add_distromap {
    my ($from,$to)=@_;
    return if $to =~ /^python3?-module-.+-tests$/;
    # mageia lib64 hack
    my $libmangled=$from;
    if ($libmangled=~ s/^lib64/lib/) {
	my $oprovset=$originprov->{$libmangled};
	if ($oprovset and @$oprovset==1 and $oprovset->[0] eq $from) {
	    &__add_distromap($libmangled,$to);
	}
    }
    # end mageia lib64 hack
    &__add_distromap($from,$to);
}

sub __add_distromap {
    my ($from,$to)=@_;
    my $list=$origin2dest->{$from};
    unless ($list) {
	$list=[];
	$origin2dest->{$from}=$list;
    }
    #print STDERR "collision for $from: $origin2dest->{$from} $to\n" if ($old and $old ne $to);
    push @$list, $to unless grep {$_ eq $to} @$list;
}

sub read_lib_provides {
    my ($map,$prov)=@_;
    foreach my $provide (keys(%$prov)) {
	# TODO old -> new (why does not work ?) test!!
	#if ($provide=~/^(lib([^\.]+)\.so\.[\d\.]+)(?:\(\S*\))?\(64bit\)/) {
	if ($provide=~/^(lib([^\.]+)\.so\.[\d\.]+)(?:\(\))?\(64bit\)/) {
	    my $dep_key=$2;
	    my $lib=$1;
	    $lib=~s,(\.so\.\d+)\.[\.\d]+$,$1,;
	    my $pkgset=$prov->{$provide};
	    my $libref=$map->{$dep_key}->{$lib};
	    unless ($libref) {
		$map->{$dep_key}->{$lib}=[@$pkgset];
	    } else {
		push @{$map->{$dep_key}->{$lib}}, @$pkgset;
	    }
	}
    }
}

sub read_tags_from_pkgname {
    my ($sourcenameref,$tagsflag)=@_;
    foreach my $pkgname (keys(%$sourcenameref)) {
	if ($pkgname=~/\bfont/ and $pkgname=~/(100|75)(?:DPI|dpi)/) {
	    my $tag='font-'.$1.'dpi';
	    $tagsflag->{'binary'}->{'optional'}->{$pkgname}->{$tag}=1;
	    #print STDERR "DEBUG: $pkgname got optional tag $tag\n";
	}
    }
}

sub read_tags_from_requires {
    my ($sourcenameref,$req,$tagsflag)=@_;
    foreach my $dep (keys(%$req)) {
	# TODO old -> new
	#if ($dep=~/^(lib[^\.]+\.so\.\d+)[\d\.]*(?:\(\S*\))?\(64bit\)/) {
	if ($dep=~/^(lib[^\.]+\.so\.\d+)[\d\.]*(?:\(\))?\(64bit\)/) {
	    my $shortlib=$1;
	    my @tags=&_lib2tag($shortlib);
	    my @SST=grep {$strict_source_tags{$_}} @tags;
	    my @OBT=grep {$optional_binary_tags{$_}} @tags;
	    my $pkgset=$req->{$dep};
	    &__set_source_tags($tagsflag->{'source'}->{'strict'},$pkgset,\@SST,$sourcenameref) if @SST;
	    &__set_binary_tags($tagsflag->{'binary'}->{'optional'},$pkgset,\@OBT) if @OBT;
	}
    }
}

sub __set_source_tags {
    my ($tagsflag,$pkgset,$tagset,$sourcenameref)=@_;
    foreach my $pkg (@$pkgset) {
	my $srcname=$sourcenameref->{$pkg};
	foreach my $tag (@$tagset) {
	    $tagsflag->{$srcname}->{$tag}=1;
	}
    }
}

sub __set_binary_tags {
    my ($tagsflag,$pkgset,$tagset)=@_;
    foreach my $pkg (@$pkgset) {
	foreach my $tag (@$tagset) {
	    $tagsflag->{$pkg}->{$tag}=1;
	}
    }
}
	
sub _lib2tag {
    my $lib=shift;
    if ($lib=~/^lib(?:kdecore|kdeui|kdefx|kio)\.so\.4/) {
	# KDE3
	#libkdecore.so.4
	#libkdeui.so.4
	#libkio.so.4
	return 'KDE3', 'KDE';
    } elsif ($lib=~/^libtde(?:core|ui|fx|io)\.so\./) {
	# TDE
	#libtdecore.so.14
	#libtdefx.so.14
	#libtdeui.so.14
	#	$lib=~/^libtqt(?:|-mt)\.so\./
	#libtqt-mt.so.3
	#libtqt.so.4
	return 'TDE', 'KDE';
    } elsif ($lib=~/^lib(?:kdecore|kdeui|kdefx|kio)\.so\.5/ or
	     $lib=~/^libkde3support.so.4/ or
	     $lib=~/^libplasma.so.3/ or
	     $lib=~/^libphonon.so.4/
	) {
	# KDE4
	#libkdeclarative.so.5
	#libkdecore.so.5
	#libkio.so.5
	#libplasma.so.3
	return 'KDE4', 'KDE';
    } elsif ($lib=~/^libKF5.*\.so\./) {
	#libKF5.*\.so
	return 'KDE5', 'KDE';
    } elsif ($lib=~/^libqt(?:|-mt)\.so\./) {
	#return 'QT3', 'QT';
	return 'QT';
    } elsif ($lib=~/^libtqt(?:|-mt)\.so\./) {
	#return 'TQT', 'QT';
	return 'QT';
    } elsif ($lib=~/^libQt.*\.so\.4/) {
	#libQtCore.so.4
	#libQt.*\.so.4
	#return 'QT4', 'QT';
	return 'QT';
    } elsif ($lib=~/^libQt5.*\.so\.5/) {
	#libQt5Core.so.5
	#libQt5.*\.so.5
	#return 'QT5', 'QT';
	return 'QT';
    }
    return;
}

sub are_arrays_intersect {
    my ($arrayref1,$arrayref2)=@_;
    foreach my $element1 (@$arrayref1) {
	foreach my $element2 (@$arrayref2) {
	    return 1 if $element1 eq $element2;
	}
    }
    return 0;
}

sub is_in_array {
    my ($item,$arrayref)=@_;
    foreach my $element (@$arrayref) {
	return 1 if $item eq $element;
    }
    return 0;
}
