package Mkcd::List;

my $VERSION = '2.4.2';

use strict;
use File::NCopy qw(copy);       
use File::Path;
use URPM qw(ranges_overlap);
use Mkcd::Package qw(rpmVersionCompare);
use Mkcd::Tools qw(log_);
use Mkcd::Optimize qw(optimize_space get_pkgs_deps);

=head1 NAME

List - mkcd module

=head1 SYNOPSYS

    require Mkcd::List;

=head1 DESCRIPTION

C<mkcd::List> include the mkcd packages list functions.

=head1 SEE ALSO

mkcd

=head1 COPYRIGHT

Copyright (C) 2000 MandrakeSoft <warly@mandrakesoft.com>

This program 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, or (at your option)
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

=cut

my $config;

sub new {
    my ($class, $conf) = @_;
    $config = $conf;
    bless {
	   config       => $config,
	  }, $class;
}

sub processDiff {
    my ($class, $groups, $diff, $discsFiles) = @_;
    my (@cd, @action);
    my %new;
    my $prev = $diff->{previous_idx} || {};
    foreach (@{$diff->{idx}}) {
	push @{$action[1]}, $_ if !$prev->{$_};
	$new{$_} = 1
    }
    foreach (keys %$prev) {
	push @{$action[2]}, $_ if !$new{$_}
    }
    foreach my $op (2,1) {
	foreach my $idx (@{$action[$op]}) {
	    my $d = $diff->{data}[$idx];
	    log_("ERROR processDiff: THIS MUST NOT HAPPEN action is null ($d) op $op idx $idx\n", $config->{verbose}, $config->{LOG}) and next if !$d;
	    my ($curdir, $grp, $list,undef,undef, $data) = @$d;
	    my $cd = $curdir->[0];
	    foreach my $ent (@$data) {
		my $rpm = $ent->[0];
		log_("LOG disc $cd group $grp: ($op) $rpm ($groups->[$grp]{size}{$rpm}{$list}[1])\n", $config->{verbose}, $config->{LOG},3);
		if (!$rpm) {
		    foreach (@$ent) {
			if (ref $_) { log_("ERROR processDiff: @$_\n", $config->{verbose}, $config->{LOG},2) }
			else { log_("ERROR processDiff: $_\n", $config->{verbose}, $config->{LOG},2) }
		    }
		}
		$rpm or next;
		my $source = $groups->[$grp]{size}{$rpm}{$list}[1];
		push @{$cd[$cd]{$curdir->[1]}{$list}{$source}}, [$op, "$groups->[$grp]{urpm}{rpmkey}{rpm}{$rpm}.rpm"];
		if ($op == 1) { $discsFiles->[$cd]{$curdir->[1]}{$list}{$groups->[$grp]{urpm}{rpmkey}{rpm}{$rpm}} = $source }
		elsif ($op == 2) { delete $discsFiles->[$cd]{$curdir->[1]}{$list}{$groups->[$grp]{urpm}{rpmkey}{rpm}{$rpm}} }
	    }
	}
    }
    my %new_diff;
    # clear diff
    foreach my $idx (@{$diff->{idx}}) {
	my $nidx = push @{$new_diff{data}}, $diff->{data}[$idx];
	push @{$new_diff{idx}}, $nidx - 1;
	$new_diff{previous_idx}{$nidx - 1} = 1
    }
    return (\@cd, \%new_diff)
}

sub getDoneList {
    my ($config, $group, $listnumber, $discsFiles) = @_;

    my $topdir = "$config->{topdir}/build/$config->{name}";
    foreach my $type ('rpm', 'srpm') {
	foreach my $curdir (@{$group->{list}{$listnumber}{$type}}) {
	    my ($cd, $rep) = @$curdir;
	    my $path = $config->{disc}[$cd]{function}{data}{dir}{$rep};
	    my $dir = "$topdir/$cd/$path";
	    log_("getDoneList: $listnumber disc $cd rep $rep\n", $config->{verbose}, $config->{LOG},2);
	    if ($config->{nolive}) {
		if ($config->{list}[$listnumber]{packages}) {
		    foreach (@{$config->{list}[$listnumber]{packages}}) {
			log_("getDoneList: @$_\n",1, $config->{LOG},3);
			my ($dir, @srpms) = @$_;
			local *RPMS; opendir RPMS, $dir or log_("WARNING getGroupReps: cannot open $dir\n",1, $config->{LOG},0) and return;
			foreach (readdir RPMS) {
			    /(.*)\.rpm$/ or next;
			    /src\.rpm$/ and next;
			    my $rpm = $group->{urpm}{rpmkey}{key}{$1};
			    $group->{done}{$rpm} = $group->{orderedrep}{rpm}{"$cd/$rep"};
			    $discsFiles->[$cd]{$rep}{$listnumber}{$1} = $dir
			} 
		    }
		} else {
		    log_("ERROR getDoneList: could not find data for $listnumber disc $cd rep $rep\n", $config->{verbose}, $config->{LOG},0);
		}
	    } elsif (-d $dir) {
		local *RPMS; opendir RPMS, $dir or log_("WARNING getGroupReps: cannot open $dir\n",1, $config->{LOG}) and return;
		foreach (readdir RPMS) {
		    /(.*)\.rpm$/ or next;
		    /src\.rpm$/ and next;
		    my $rpm = $group->{urpm}{rpmkey}{key}{$1};
		    $group->{done}{$rpm} = $group->{orderedrep}{rpm}{"$cd/$rep"};
		    $discsFiles->[$cd]{$rep}{$listnumber}{$1} = $dir
		}
	    } else {
		log_("ERROR getDoneList: could not find data for $listnumber disc $cd rep $rep\n", $config->{verbose}, $config->{LOG},0);
		next
	    }
	    $config->{list}[$listnumber]{disc}{$cd}{$rep}{done} = 1
	}
    }

    foreach my $d (@{$config->{list}[$listnumber]{virtual}}) {
	my $cd = $d->{disc};
	my $path = $d->{path};
	my $rep = $d->{repname};
	my $dir = "$topdir/$cd/$path";
	log_("getDoneList: virtual disc $cd path $path in $dir\n",1, $config->{LOG},2);
	foreach my $list_rep (@{$group->{rep}{$listnumber}}) {
	    $list_rep->{rpm}{$dir} or next;
	    foreach (@{$list_rep->{rpm}{$dir}}) {
		my $rpm = $group->{urpm}{rpmkey}{key}{$_};
		$group->{done}{$rpm} = $group->{orderedrep}{rpm}{"$cd/$rep"};
		$discsFiles->[$cd]{$rep}{$listnumber}{$_} = $dir
	    }
	}
	$config->{list}[$listnumber]{disc}{$cd}{$rep}{done} = 1;
    }
}

sub getList {
    my ($class, $group, $discsFiles) = @_;
    my $config = $class->{config};
    my %filelist;
    my @norpmsrate;
    log_("getList: group list " . join(' ', (keys %{$group->{list}})) . "\n", $config->{verbose}, $config->{LOG},1);
    foreach my $listnumber (keys %{$group->{list}}) {
	my $done = $config->{list}[$listnumber]{done};
	$done and getDoneList($config, $group, $listnumber, $discsFiles);
	log_("getList: FILE LIST listnumber $listnumber ($config->{list}[$listnumber]{filelist}) or ($config->{list}[$listnumber]{prelist})\n", $config->{verbose}, $config->{LOG},2);
	if ($config->{list}[$listnumber]{filelist} || $config->{list}[$listnumber]{prelist}) {
	    foreach (@{$config->{list}[$listnumber]{filelist}}) {
		log_("getList: FILE LIST listnumber $listnumber ($_)\n", $config->{verbose}, $config->{LOG},2);
		open my $A, $_ or log_("ERROR: cannot open $_, ignoring\n",1, $config->{LOG}) and next;
		local $_;
		while (<$A>) {
		    s/#.*//;
		    next if !$_ || /^\s*$/;
		    my ($name, $options) = /\s*(\S+)\s*(.*)/;
		    my @options = split ',', $options;
		    log_("FILESLIST: $_ -> $name options @options\n", $config->{verbose}, $config->{LOG},3);
		    my %opt;
		    foreach (@options) {
			s/^\s*//;
			/norpmsrate/ and push @norpmsrate, $name and next;
			/^(?:(?:nosrc|section|noalternatives|regexp|ignore|nodeps|force|limit|exclude)|(rate|notondisc|rpmsrate|needed|section) (\d+))$/ or log_("WARNING: getList: $_: unknown option\n",1, $config->{LOG}) and next;
			$_ = $1 || $_; 
			$opt{$_} = $2 || 1;
		    }
		    log_("Adding $name -- " . join(' ', keys %opt) . "\n", $config->{verbose}, $config->{LOG},4);
		    push @{$filelist{$listnumber}}, [ $name, \%opt ];	
		}
		close $A
	    }
	    foreach my $p (@{$config->{list}[$listnumber]{prelist}}) {
		log_("Prelist Adding $p->[0] -- " . join(' ', keys %{$p->[1]}) . "\n", $config->{verbose}, $config->{LOG},3);
		$p->[1]{norpmsrate} and push @norpmsrate, $_->[0] and next;
		push @{$filelist{$listnumber}}, $p
	    }
	} else {
	    if (!$done && $config->{list}[$listnumber]{auto}) {
		push @{$filelist{$listnumber}}, [ "INSTALL", { section =>1, force => 1 } ];
		push @{$filelist{$listnumber}}, [ "SYSTEM", { section =>1, force => 1 } ];
		push @{$filelist{$listnumber}}, [ "kernel-(smp-|)[0-9].*", { regexp => 1, force => 1 } ];
		push @{$filelist{$listnumber}}, [ "kernel.*linus", { regexp => 1, noalternatives => 1 } ];
		push @{$filelist{$listnumber}}, [ ".*", { regexp => 1 } ]
	    } else {
		log_("getList: FILE LIST listnumber $listnumber defaulting to .* regexp\n", $config->{verbose}, $config->{LOG},2);
	    	push @{$filelist{$listnumber}}, [ ".*" , { regexp => 1 } ]
	    }
	}
	my $listdone = 1;
	foreach my $r (@{$group->{list}{$listnumber}{rpm}}) {
	    my ($cd, $rep, $repopt, $opt) = @$r;
	    log_("getList: searching for done rep ($cd/$rep)\n", $config->{verbose}, $config->{LOG},2);
	    if ($config->{list}[$listnumber]{disc}{$cd}{$rep}{done}) {
		if (!$opt->{dup}) {
		    foreach my $rpmkey (keys %{$discsFiles->[$cd]{$rep}{$listnumber}}) {
			my $rpm = $group->{urpm}{rpmkey}{key}{$rpmkey};
			$group->{done}{$rpm} = $group->{orderedrep}{rpm}{"$cd/$rep"};
			log_("getList: $rpm in $cd/$rep -> $group->{done}{$rpm}\n", $config->{verbose}, $config->{LOG},3);
			push @{$filelist{$listnumber}}, [$rpm, { done => $group->{done}{$rpm}, regexp => 1, udpate => $r->[2]{update} }];
		    }
		}
	    } else { $listdone = 0 }
	}
	$listdone and log_("getList: setting list $listnumber as done\n", $config->{verbose}, $config->{LOG},2) and $config->{list}[$listnumber]{done} = 1;
    }
    (\%filelist, \@norpmsrate)
}

#
# compute individual scoring (max_size*(rpmsrate+1)*rpmsrate_factor/(size*size_factor))
# then add dependencies sons score ( score + deps_factor*(sons_score)
# 
# special rpmsrate groups score could be added in the rpmsrate value
#
# FIXME current scoring rules make size only significant for equaly dependent packages,
# dependencies get far more importance for packages a lot of packages depend on.
#
# Size scoring could be added afterwards, but this will break the autodeps created with
# this scheme.
#
# TODO
# add scoring rules to include srpm size in score.
# 
#
sub scoreList {
    my ($class, $group) = @_;
    my $scoreweight = $group->{score};
    my $urpm = $group->{urpm};
    my $rpmsrate = $group->{rpmsrate};
    my $maxsize = $group->{maxsize} || 1;
    $scoreweight ||= [1,1,1];
    log_("scoreList: SCORE for group: @$scoreweight\n", $config->{verbose}, $config->{LOG},2);
    log_("scoreList: Individual scoring\n", $config->{verbose}, $config->{LOG},2);
    my ($sf, $i, $total);
    my (@min, @max);
    if ($scoreweight->[1]) {
	(@min,@max) = (($maxsize*$scoreweight->[0]*6/($scoreweight->[1]*1),0), (0,0))
    } else {
	(@min,@max) = (($maxsize*$scoreweight->[0]*6,0), (0,0))
    }
    my @specialdeps;
    foreach (keys %{$urpm->{rpm}}) {
	# print "INFO KEYS $_\n";
	my ($ratekey) = /(.*)-[^-]+-[^-]+\.[^.]+$/;
	# FIXME take the bigger size when package appears in multiple lists
	my $size;
	foreach my $list (keys %{$group->{size}{$_}}) { $size = $group->{size}{$_}{$list}[0] if $size < $group->{size}{$_}{$list}[0] }
	$size or log_("WARNING scoreList: $_ has zero size\n",1, $config->{LOG});
	my $s;
	my $rate = $group->{brokendeps}{$_} ? 0 : (defined $group->{pkgrate}{$_} ? $group->{pkgrate}{$_} : $rpmsrate->[0]{$ratekey});
	if ($scoreweight->[1]) {
	    $sf = ($size*9)/$maxsize + 1; # from 1 to 10
	    $s = $scoreweight->[0]*($rate + 1)/($scoreweight->[1]*$sf);
	} else {
	    $s = $scoreweight->[0]*($rate + 1);
	}
	$group->{scorelist}{$_} = $s;
	$s < $min[0] and @min = ($s, $_);
	$s > $max[0] and @max = ($s, $_);

	$total+=$s;
	$i++
    }
    $i and log_("scoreList: minimal $min[0] ($min[1]), maximal $max[0] ($max[1]), average " . $total/$i . "\n", $config->{verbose}, $config->{LOG},3);
    1
}

sub autodeps {
    my ($class, $group, $rpmlist) = @_;
    my $scoredeps = $group->{score}[2];
    $scoredeps or log_("autodeps: deps score is null, bypassing autodeps\n",1, $config->{LOG},1) and return 1;
    log_("autodeps: compute reversed depslist.ordered ($scoredeps)\n", $config->{verbose}, $config->{LOG},2);
    my $revDeps = $group->{revdeps};
    my %rpm;
    foreach my $k (values %$rpmlist) { foreach (keys %$k) { $rpm{$_} = $k->{$_} } }
    # FIXME this algo is not correct
    ref $group->{urpm}{depslist} or return 0;
    for (my $i = @{$group->{urpm}{depslist}} - 1; $i >= 0; $i--) {
	my $rpm = $group->{depslistid}[$i];
	if (!$rpm{$rpm}) {  
	    #log_("autodeps: ignoring $rpm\n",1, $config->{LOG});
	    #push @{$group->{rejected}{$rpm}}, [ "autodeps", $1 ];
	    next
	}
	if ($rpm{$rpm}{ignore}) { log_("autodeps: $rpm has ignore flag, do not add deps score\n", $config->{verbose}, $config->{LOG},3); next }
	foreach (@{$revDeps->[$i]}) {
	    $group->{scorelist}{$rpm} += $scoredeps * $group->{scorelist}{$group->{depslistid}[$_]};
	}
    }
    1
}

sub reverseDepslist {
    my ($class, $group) = @_;
    my $urpm = $group->{urpm};
    my $depslist = $urpm->{depslist};
    $depslist or return ();
    my $locales = $group->{lang};
    my (@revdeps, %skip);
    log_("reverseDepslist\n", $config->{verbose}, $config->{LOG},2);
    for (my $i; $i < @$depslist; $i++) {
	my $d = $depslist->[$i];
	my $rpm = sprintf "%s-%s-%s.%s", $d->name, $d->version, $d->release, $d->arch;
	$group->{depslistid}[$i] = $rpm;
	my %rev;
	# FIXME deps is deprecated, the correct function to use it packages->requires
	foreach (split(' ', $urpm->{deps}[$i])) {
	    if (!$group->{options}{nodeps} && !$class->{config}{nodeps} && m/NOTFOUND_(\S*)/) {
		$skip{$i} = 1;
		$group->{brokendeps}{$rpm} = 2;
		push @{$group->{rejected}{$rpm}}, [ "deps", $1 ];
		log_("WARNING reverseDepslist: $rpm has unresolved dependencies ($1)\n", $config->{verbose}, $config->{LOG}, 1);
		next
	    }
	    if (/\|/) { 
		my $s = [split '\|', $_];
		my (@tmp_s, $msg_tot);
		foreach (@$s) { 
		    my ($ok, $msg) = check_version($d, $depslist->[$_], $rpm);
		    log_("ERROR check_version: $msg\n", $config->{verbose}, $config->{LOG}, 0) if $msg;
		    if ($ok) {
			push @tmp_s, $_ 
		    } else { 
			$msg_tot .= $msg
		    }
		    $skip{$_} or push @{$revdeps[$_]}, $i 
		}
		if (@tmp_s) {
		    push @{$group->{pkgdeps}{$rpm}}, \@tmp_s
		} else {
		    $skip{$i} = 1;
		    $group->{brokendeps}{$rpm} = 2;
		    log_("WARNING reverseDepslist: rejecting $rpm on $_\n", $config->{verbose}, $config->{LOG}, 4);
		    push @{$group->{rejected}{$rpm}}, [ "deps", $msg_tot ]
		}
	    } else { 
		if ($locales && $group->{depslistid}[$_] =~ /locales-([^-]+)-[^-]+-[^-]+\.[^.]+/)  {
		    if (!$locales->{$1}) {
			log_("reverseDepslist: locale $1 ($group->{depslistid}[$_]) skipped for $rpm\n", $config->{verbose}, $config->{LOG}, 2) and $skip{$i} = 1;
			!$group->{brokendeps}{$rpm} and $group->{brokendeps}{$rpm} = 1	
		    }
		}
	
		my ($ok, $msg) = check_version($d, $depslist->[$_], $rpm);
		log_("ERROR check_version: $msg\n", $config->{verbose}, $config->{LOG}, 0) if $msg;
		if ($ok) {
		    push @{$group->{pkgdeps}{$rpm}}, $_ 
		} else { 
		    $skip{$i} = 1;
		    $group->{brokendeps}{$rpm} = 2;
		    log_("WARNING reverseDepslist: rejecting $rpm on $_\n", $config->{verbose}, $config->{LOG}, 4);
		    push @{$group->{rejected}{$rpm}}, [ "deps", $msg ]
		}
		$skip{$_} or push @{$revdeps[$_]}, $i
	    }
	}
    }
    return \@revdeps
}

# check if ONE require is satisfied
sub check_version {
    my ($d, $deps_d, $rpm, $special_require) = @_;
    my ($msg, $real_ok, $ok);
    $ok = 1;
    my $deps_v = sprintf "%s-%s", $deps_d->version, $deps_d->release;
    my $deps_rpm = sprintf "%s-%s.%s", $deps_d->name, $deps_v, $deps_d->arch;
    $rpm ||= sprintf "%s-%s-%s.%s", $d->name, $d->version, $d->release, $d->arch;
    foreach my $req ($d->requires) {
	my ($n, $s) = $req =~ /^([^\s\[]*)(?:\[\*\])?\[?([^\s\]]*\s*[^\s\]]*)/;
	# perl needs major checking
	$n eq 'perl' and next;
	# Some deps are broken and as a consequence using the major rejects some packages that shouldn't
	#$s and ($s =~ /:/ or $s =~ s/([>=<]+) /$1 0:/);
	$s =~ /:/ and $s =~ s/ \d+:/ /;
	if ($n && $s) {
	    # nok is usefull in case the package provides several times the require, each time with a different version
	    my $tok;
	    foreach my $prov ($deps_d->provides) {
		my ($deps_n, $deps_s) = $prov =~ /^([^\s\[]*)(?:\[\*\])?\[?([^\s\]]*\s*[^\s\]]*)/;
		#$deps_s and ($deps_s =~ /:/ or $deps_s =~ s/([>=<]+) /$1 0:/);
		$deps_s =~ /:/ and $deps_s =~ s/ \d+:/ /;
		$deps_s ||= ($s =~ /-/) ? "= $deps_v" : "= " . $deps_d->version;
		if ($deps_n && $deps_s) {
		    if ($deps_n eq $n) { 
			if (URPM::ranges_overlap($deps_s, $s)) {
			    $real_ok = 1 if !$special_require || $special_require eq $n;
			} else {
			    my $t = "$deps_rpm provides $deps_n $deps_s but $rpm needs $n $s";
			    $msg .= " $t";
			}
		    }
		}
	    }
	} else {
	    foreach my $prov ($deps_d->provides) {
		my ($deps_n) = $prov =~ /^([^\s\[]*)(?:\[\*\])?\[?([^\s\]]*\s*[^\s\]]*)/;
		if ($deps_n eq $n) { 
		    $real_ok = 1 if !$special_require || $special_require eq $n;
		}
	    }
	}
    }
    ($ok, $msg, $real_ok)
}

sub closeRpmsList {
    my ($group, $rpmfile) = @_;
    my $n = 1;
    my %done;
    my %doneName;
    my %alternatives;
    while ($n) {
	$n = 0;
	foreach my $listnumber (@{$group->{orderedlist}{rpm}}) {
	    foreach my $rpm (keys %{$rpmfile->{$listnumber}}) {
		# FIXME if the different packages have different deps, old packages may be better.
		# this should be done in buildDisc, or better packages have to be selected here.
		if (!$group->{options}{dup}) {
		    my ($name, $version, $release, $arch) = $rpm =~ /^(.*)-([^-]+)-([^-]+)\.([^.]+)$/;
		    if ($doneName{$name}) {
			if (!($doneName{$name}[0] eq "$version-$release.$arch")) {
			    log_("WARNING closeRpmsList: $name-$version-$release.$arch duplicated with $doneName{$name}[0]\n", $config->{verbose}, $config->{LOG}, 1);
			    my ($v, $r, $a) = @{$doneName{$name}[1]};	
			    my $todel;
			    my $vers;
			    my $ret = rpmVersionCompare($rpm, "$name-$v-$r.$a");
			    if ($ret < 0) {
				$todel = $rpm;
				$vers = [$v, $r, $a]
			    } elsif ($ret > 0) {
				$todel = "$name-$v-$r.$a";
				$vers = [$version, $release, $arch]
			    } else {
				log_("ERROR closeRpmsList: oops, something not possible happened in duplicate version comparaison ($rpm and $name-$v-$r.$a)\n",1, $config->{LOG});
			    }
			    if ($todel) {
				log_("closeRpmsList: deleting $todel\n", $config->{verbose}, $config->{LOG},2);
				$doneName{$name} = [ "$vers->[0]-$vers->[1].$vers->[2]", $vers ];
				#$group->{brokendeps}{$todel} = 3;
				#push @{$group->{rejected}{$todel}}, [ "old_version", "$name-$vers->[0]-$vers->[1].$vers->[2]"];
				delete $rpmfile->{$listnumber}{$todel};
				$todel eq $rpm and next	
			    }
			    $n = 1
			}
		    } else {
			$doneName{$name} = [ "$version-$release.$arch", [$version, $release, $arch] ]
		    }
		}
		if ($group->{brokendeps}{$rpm} == 2 || $group->{brokendeps}{$rpm} == 3) {
		    log_("closeRpmsList: deleting $rpm (list $listnumber)\n", $config->{verbose}, $config->{LOG},2);
		    delete $rpmfile->{$listnumber}{$rpm};
		    $n = 1;
		    next
		}
		$done{$rpm} and next;
		$rpmfile->{$listnumber}{$rpm}{nodeps} and next;
		my $needed;
		# FIXME the right thing to do would be to put the require in the rep just before the one of the $rpm, and not to force it.
		$needed = 1 if $rpmfile->{$listnumber}{$rpm}{force};
		$needed ||= $rpmfile->{$listnumber}{$rpm}{done};
		$needed ||= $rpmfile->{$listnumber}{$rpm}{needed};
		# $needed and log_("closeRpmsList: $rpm needed set to $needed (force $rpmfile->{$listnumber}{$rpm}{force} done $rpmfile->{$listnumber}{$rpm}{done} needed $rpmfile->{$listnumber}{$rpm}{needed})\n", $config->{verbose}, $config->{LOG});
		foreach (@{$group->{pkgdeps}{$rpm}}) {
		    m/NOTFOUND_(.*)/ and log_("ERROR closeRpmsList: $1 not provided\n", $config->{verbose}, $config->{LOG},1) and next;
		    my $rpmdep;
		    my $rpmdeplist;
		    my $specialrpmdep;
		    if (ref $_) {
			if ($alternatives{"@$_"}) {
			    ($rpmdep, $rpmdeplist) = @{$alternatives{"@$_"}};
			    log_("closeRpmsList: $rpm taking alternatives from already selected package ($rpmdep $rpmdeplist)\n", $config->{verbose}, $config->{LOG}, 4)
			}
			if (! ref $rpmfile->{$rpmdeplist}{$rpmdep}) {
			    foreach my $testalt (1,0) {
				($rpmdep, $rpmdeplist) = (undef,undef);
				# FIXME this is wrong, package can come from any list
				my @score = ($group->{maxlist}{rpm}, int @{$group->{list}{$listnumber}{rpm}}, $group->{maxsize});
				my @specialscore = (int @{$group->{list}{$listnumber}{rpm}}, $group->{maxsize});
				log_("closeRpmsList: $rpm @$_ (maxscore @score) alternative\n", $config->{verbose}, $config->{LOG}, 4);
				foreach (@$_) {
				    my $pkg = $group->{depslistid}[$_];
				    log_("closeRpmsList: trying $pkg (brokendeps $group->{brokendeps}{$pkg})\n", $config->{verbose}, $config->{LOG}, 4);
				    $group->{brokendeps}{$pkg} == 2 and next;
				    $group->{brokendeps}{$pkg} == 3 and next;
				    my $pkglist = find_list($group, $pkg, $listnumber);
				    $pkglist or log_("closeRpmsList: $pkg list could not be used for $rpm dependencies\n", $config->{verbose}, $config->{LOG}, 4) and next;
				    log_("closeRpmsList: list $pkglist\n", $config->{verbose}, $config->{LOG}, 4);

				    if ($rpmfile->{$pkglist}{$pkg}) {
					$rpmfile->{$pkglist}{$pkg}{limit} and next;
					$testalt and $rpmfile->{$pkglist}{$pkg}{noalternatives} and next;
				    }
				    my $rep = $group->{size}{$pkg}{$pkglist}[2];
				    my $s = $group->{size}{$pkg}{$pkglist}[0];
				    my $l = $group->{listsort}{$pkglist}{rpm};
				    log_("\t$pkg ($l, $rep, $s) (@score)\n", $config->{verbose}, $config->{LOG}, 5);
				    # also put an alternative from this list
				    if ($pkglist == $listnumber) {
					if ($rep < $specialscore[1]) {
					    @specialscore = ($rep, $s);
					    $specialrpmdep = $pkg;
					} elsif ($rep == $specialscore[1] && $s < $specialscore[2]) {
					    @specialscore = ($rep, $s);
					    $specialrpmdep = $pkg;
					}		
				    }
				    if ($l < $score[0]) {
					@score = ($l, $rep, $s);
					$rpmdep = $pkg;
					$rpmdeplist = $pkglist;
					log_("1 $rpmdep -- $rpmdeplist -- $l, $rep, $s\n", $config->{verbose}, $config->{LOG}, 6);
				    } elsif ($l == $score[0]) {
					if ($pkglist == $listnumber) {
					    if ($rep < $score[1]) {
						@score = ($l, $rep, $s);
						$rpmdep = $pkg;
						$rpmdeplist = $pkglist;
						log_("2 $rpmdep -- $rpmdeplist -- $l, $rep, $s\n", $config->{verbose}, $config->{LOG}, 6);
					    } elsif ($rep == $score[1] && $s < $score[2]) {
						@score = ($l, $rep, $s);
						$rpmdep = $pkg;
						$rpmdeplist = $pkglist;
						log_("3 $rpmdep -- $rpmdeplist -- $l, $rep, $s\n", $config->{verbose}, $config->{LOG}, 6);
					    }
					} elsif ($s < $score[2]) {
					    @score = ($l, $rep, $s);
					    $rpmdep = $pkg;
					    $rpmdeplist = $pkglist;
					    log_("4 $rpmdep -- $rpmdeplist -- $l, $rep, $s\n", $config->{verbose}, $config->{LOG}, 6);
					}

				    }
				}
				last if $rpmdep && $rpmdeplist
			    }
			    if ($rpmdep && $rpmdeplist) {
				log_("\tResult:\t$rpmdep\n", $config->{verbose}, $config->{LOG}, 4);
				$alternatives{"@$_"} = [ $rpmdep, $rpmdeplist ]
			    } else {
				log_("WARNING: $rpm has unresolved or excluded dependencies, removed\n", $config->{verbose}, $config->{LOG}, 1);
				log_("closeRpmsList: deleting $rpm (list $listnumber)\n", $config->{verbose}, $config->{LOG}, 2);
				delete $rpmfile->{$listnumber}{$rpm};
				$n = 1;
				$group->{brokendeps}{$rpm} = 2;
				push @{$group->{rejected}{$rpm}}, ["deps_rejected", (join ' ', map { $group->{depslistid}[$_] } @$_)];
			    }
			}
		    } else {	
			# TODO verify that there is no need to do $rpmfile->{$pkglist}{$rpmdep} or brokendeps;
			$rpmdep = $group->{depslistid}[$_];
			$rpmdeplist = find_list($group, $rpmdep, $listnumber);
		    }
		    # log_("rpmdep $rpmdep rpmdeplist $rpmdeplist rpm $rpm\n",1, $config->{LOG});
		    if ($rpmdep) {
			if (!$rpmdeplist || $group->{brokendeps}{$rpmdep} == 2 || $group->{brokendeps}{$rpmdep} == 3) {
			    $group->{brokendeps}{$rpm} = $group->{brokendeps}{$rpmdep};
			    push @{$group->{rejected}{$rpm}}, [ "deps_rejected", $rpmdep ];
			    $n = 1;
			    log_("WARNING closeRpmsList: $rpm has unresolved or excluded dependencies ($rpmdep list $rpmdeplist), removed\n", $config->{verbose}, $config->{LOG}, 1);
			    log_("closeRpmsList: deleting $rpm (list $listnumber)\n", $config->{verbose}, $config->{LOG}, 2);
			    delete $rpmfile->{$listnumber}{$rpm};
			    next
			}
			if (! ref $rpmfile->{$rpmdeplist}{$rpmdep}) {
			    $n = 1;
			    log_("closeRpmsList: ADDED $rpmdep (list $rpmdeplist) needed $needed\n", $config->{verbose}, $config->{LOG}, 3);
			    $rpmfile->{$rpmdeplist}{$rpmdep} = { needed => $needed }
			} elsif ($needed) {
			    my $n = $rpmfile->{$rpmdeplist}{$rpmdep}{needed};
			    $rpmfile->{$rpmdeplist}{$rpmdep}{needed} = !$n || $needed < $n ? $needed : $n;
			    log_("closeRpmsList: setting $rpmdep needed option to $rpmfile->{$rpmdeplist}{$rpmdep}{needed}\n", $config->{verbose}, $config->{LOG}, 4);
			}
		    }
		    if ($specialrpmdep) {
			if (! ref $rpmfile->{$listnumber}{$specialrpmdep}) {
			    $n = 1;
			    log_("closeRpmsList: ADDED $specialrpmdep (list $listnumber)\n", $config->{verbose}, $config->{LOG}, 3);
			    $rpmfile->{$listnumber}{$specialrpmdep} = { }
			}
		    }
		}
		$done{$rpm} = 1;
	    }
	    log_("closeRpmsList: $listnumber [$n]\n", $config->{verbose}, $config->{LOG}, 2);
	}
    }
}

sub addRPMToList {
    my ($group, $listnumber, $rpmfile, $done, $rpms, $fentry, $name) = @_;
    my $exclude = sub {
	my ($rpm, $name) = @_;
	log_("addRPMToList: excluding $_\n",1, $config->{LOG}, 1);
	$group->{brokendeps}{$rpm} = 3;
	push @{$group->{rejected}{$rpm}}, ["excluded", $name];
    };
    $name =~ s/\+/\\+/g;
    my @toadd;
    if ($fentry->{regexp}) { @toadd = grep { /$name/ } @$rpms } 
    else { @toadd = grep { /^$name-[^-]+-[^-]+\.[^.]*$/ } @$rpms }
    log_("addRPMToList: toadd $name (regexp $fentry->{regexp}) (@toadd)\n", $config->{verbose}, $config->{LOG}, 3);
    if ($fentry->{done}) {
	foreach (@toadd) {
	    my ($pkg) = /^(.*)-[^-]+-[^-]+\.[^.]*$/;
	    my %ht;
	    foreach my $k (keys %$fentry) { $ht{$k} = $fentry->{$k} }	
	    $rpmfile->{$listnumber}{$_} = \%ht;
	    $done->{$pkg} = [ $_, $group->{size}{$_}{$listnumber}[2], \%ht, $listnumber ];
	    log_("addRPMToList: ADDED done $_ (list $listnumber) options " . (join '', keys %ht) . "\n", $config->{verbose}, $config->{LOG}, 4)
	}
	return
    }
    my %pkg;
    if ($fentry->{regexp}) { 
	foreach (@toadd) {
	    $rpmfile->{$listnumber}{$_} and log_("WARNING addRPMToList: do not replace $_ entry in rpmfile\n", $config->{verbose}, $config->{LOG}, 1) and next;
	    $_ or log_("ERROR addRPMToList: empty rpm\n", $config->{verbose}, $config->{LOG}, 2) and next;
	    $group->{size}{$_}{$listnumber} or next;
	    $group->{brokendeps}{$_} == 2 and next;
	    $group->{brokendeps}{$_} == 3 and next;
	    my ($pkgname) = /^(.*)-[^-]+-[^-]+\.[^.]*$/;
	    $done->{$_} and next;
	    my $rep = $group->{size}{$_}{$listnumber}[2];
	    if ($fentry->{exclude}) {
		$exclude->($_, $name);
		next
	    }
	    if ($done->{$pkgname} && $done->{$pkgname}[3] == $listnumber) {
		if (!$fentry->{update} || !$done->{$pkgname}[2]{done}) {
		    if ($rep < $done->{$pkgname}[1]) {
			$pkg{$done->{$pkgname}[0]} = 0;
			log_("REPLACING $done->{$pkgname}[0] with $_ (list $listnumber)\n", $config->{verbose}, $config->{LOG}, 4);
			$pkg{$_} = 1;
			$done->{$pkgname} = [ $_, $rep, $fentry, $listnumber ];
			$done->{$_} = 1
		    } elsif ($done->{$pkgname}[1] == $rep) {
			if (rpmVersionCompare($done->{$pkgname}[0], $_) < 0) {
			    $pkg{$done->{$pkgname}[0]} = 0;
			    log_("REPLACING $done->{$pkgname}[0] with $_ (list $listnumber)\n", $config->{verbose}, $config->{LOG}, 4);
			    $pkg{$_} = 1;
			    $done->{$pkgname} = [ $_, $rep, $fentry, $listnumber ];
			    $done->{$_} = 1
			}
		    }
		}
	    } else { $pkg{$_} = 1; $done->{$pkgname} = [ $_, $rep, $fentry, $listnumber ]; $done->{$_} = 1 }
	}
    } else {
        my $rep;
	my $pkg;
	# FIXME present algorythm selects only one package per version, and choose the one in the list declared first. 
	# Maybe adding all the version and letting closeRRPMsList choose the right one is better.
	foreach (@toadd) {
	    $_ or log_("ERROR addRPMToList: empty rpm\n",1, $config->{LOG}, 2) and next;
	    $rpmfile->{$listnumber}{$_} and log_("WARNING addRPMToList: do not replace $_ entry in rpmfile\n", $config->{verbose}, $config->{LOG}, 1) and next;
	    $group->{size}{$_}{$listnumber} or next;
	    $group->{brokendeps}{$_} == 2 and next;
	    $group->{brokendeps}{$_} == 3 and next;
	    if ($fentry->{exclude}) {
		$exclude->($_, $name);
		next;
	    }
	    if ($group->{size}{$_}{$listnumber}[2] < $rep || !$rep)  {
		$rep = $group->{size}{$_}{$listnumber}[2];
		log_("addRPMToList: choosing $_ (rep $rep)\n", $config->{verbose}, $config->{LOG}, 2);
		$pkg = $_
	    } elsif ($group->{size}{$_}{$listnumber}[2] == $rep) {
		if (rpmVersionCompare($pkg, $_) < 0) {
		    $rep = $group->{size}{$_}{$listnumber}[2];
		    log_("addRPMToList: choosing $_ (rep $rep)\n", $config->{verbose}, $config->{LOG}, 2);
		    $pkg = $_
		}
	    }
	}
	my ($pkgname) = $pkg =~ /^(.*)-[^-]+-[^-]+\.[^.]*$/;
	if (!$done->{$pkgname}) { $pkg{$pkg} = 1; $done->{$pkgname} = [ $pkg, $rep, $fentry, $listnumber ]; $done->{$pkg} = 1 }
    }
    $fentry->{exclude} and return 1;
    foreach (keys %pkg) {
	$rpmfile->{$listnumber}{$_} and log_("WARNING addRPMToList: do not replace $_ entry in rpmfile\n", $config->{verbose}, $config->{LOG}, 1) and next;
	$pkg{$_} or next;
	defined $fentry->{rate} and $group->{pkgrate}{$_} = $fentry->{rate} and log_("addRPMToList: setting $_ rate to $fentry->{rate}\n",1, $config->{LOG}, 2);
	my %ht;
	foreach my $k (keys %$fentry) { $ht{$k} = $fentry->{$k} }
	$rpmfile->{$listnumber}{$_} = \%ht;	
	log_("addRPMToList: ADDED $_ (list $listnumber) options " . join(" ", keys %ht) . "\n", $config->{verbose}, $config->{LOG}, 4)
    }
}

sub build_list {
    my ($class, $group) = @_;
    my %rpmfile;
    my $filelist = $group->{filelist};
    my @fullrpm = keys %{$group->{urpm}{rpm}};
    my @section = keys %{$group->{rpmsrate}[1]};
    my %done;
    foreach my $listnumber (@{$group->{orderedlist}{rpm}}) {
	log_("build_list: list $listnumber ($group->{listrpm}{$listnumber})\n", $config->{verbose}, $config->{LOG}, 2);
	my $rpms = $group->{listrpm}{$listnumber};
	if (ref $rpms) {
	    log_("$listnumber -- $group->{filelist} -- " . keys(%{$group->{filelist}}) . "\n", $config->{verbose}, $config->{LOG}, 3);
	    ref $filelist->{$listnumber} or log_("WARNING: list $listnumber has an empty file list\n", $config->{verbose}, $config->{LOG}, 1) and next;
	    log_("build_list: FILE LIST $listnumber (" . int @{$filelist->{$listnumber}} . ")\n", $config->{verbose}, $config->{LOG}, 4);
	    foreach my $fentry (@{$filelist->{$listnumber}}) {
		my $name = $fentry->[0];
		my $opt = $fentry->[1];	
		log_("build_list: processing $name " . join(' ', keys %$opt) . "\n", $config->{verbose}, $config->{LOG}, 5);
		my @toadd;
		if ($opt->{section}) {
		    my $level = $opt->{section};
		    log_("build_list: selecting rpmsrate package of section $name with score higher than $level\n", $config->{verbose}, $config->{LOG}, 2);
		    $opt->{section} = 0;
		    if ($opt->{regexp}) {
			$opt->{regexp} = 0;
			@toadd = grep { /$name/ } @section;
			log_("$name (@section) -> @toadd\n", $config->{verbose}, $config->{LOG}, 4);
			foreach (@toadd) {
			    foreach (@{$group->{rpmsrate}[1]{$_}}) {
				log_("$_ -> $group->{rpmsrate}[0]{$_}\n", $config->{verbose}, $config->{LOG}, 4);
				if ($group->{rpmsrate}[0]{$_} >= $level) {
				    addRPMToList($group, $listnumber, \%rpmfile, \%done, $rpms, $opt, $_);
				}
			    }
			}
		    } else {
			my $rpmlist = $group->{rpmsrate}[1]{$name} or log_("ERROR build_list: $name unknown rpmsrate section\n", $config->{verbose}, $config->{LOG}, 0) and next;
			foreach (@$rpmlist) {
			    if ($group->{rpmsrate}[0]{$_} >= $level) {
				addRPMToList($group, $listnumber, \%rpmfile, \%done, $rpms, $opt, $_)
			    }
			}
		    }
		} else {
		    addRPMToList($group, $listnumber, \%rpmfile, \%done, $rpms, $opt, $name);
		}
	    }
	} else {
	    log_("WARNING: List $listnumber is empty, ignoring\n", $config->{verbose}, $config->{LOG}, 0);	
	    $class->{config}{list}[$listnumber]{empty} = 1;
	}
    }
    if (!$class->{config}{nodeps} && !$group->{options}{nodeps}) {
	my @toadd = grep { /^basesystem-[^-]+-[^-]+\.[^.]*$/ } @fullrpm; 
	my $pkg;
	my $listnumber;
	foreach (@toadd) {
	    my $l = find_list($group, $_);
	    if ($group->{listmatrix}{rpm}{$listnumber}{$l} || !$listnumber) {
		$pkg = $_;
		$listnumber = $l
	    }
	}
	if ($pkg) {
	    $rpmfile{$listnumber}{$pkg} = {};
	    log_("build_list ADDED $pkg (list $listnumber)\n", $config->{verbose}, $config->{LOG}, 4);
	} else { log_("ERROR: basesystem package is not available.\n",1, $config->{LOG}) }

	# add deps
	closeRpmsList($group, \%rpmfile)
    }
    \%rpmfile
}

sub addRPMToDiff {
    my ($rpm, $rpmd, $diff, $cdnum, $repnumber, $i, $list, $curdir, $size, $rpmsize, $totrpmsize, $repnum, $done, $packages) = @_;
    my @data;
    for (my $s; $s < @$rpm; $s++) {
	push @data, [$rpm->[$s],1, $rpmd->[$s], $rpmsize->[$s]];
	log_("addRPMToDiff: $rpm->[$s] put in rep $repnumber\n", $config->{verbose}, $config->{LOG}, 4);
	$done->{$rpm->[$s]} = $repnumber;
    }
    my $idx = push @{$diff->{data}}, [ $curdir, $i, $list, $repnum, 1, \@data, $rpmd, $totrpmsize ];
    push @{$diff->{idx}}, --$idx;
    $size->{disc}[$cdnum] += $totrpmsize;
    $size->{rep}{$cdnum}{$curdir->[1]}{$list} += $totrpmsize;
    log_("addRPMToDiff: SIZE disc $cdnum: $size->{disc}[$cdnum] (+ @$rpm $totrpmsize ID $idx) added rpmd $rpmd\n", $config->{verbose}, $config->{LOG}, 3);
    1
}

sub find_list {
    my ($group, $r, $list, $notdone) = @_;
    my $l;
    foreach (keys %{$group->{size}{$r}}) {
#	log_("find_list: for $r trying list $_ (listmatrix $l - $_ -> $group->{listmatrix}{rpm}{$l}{$_} listmatrix $list - $_ -> $group->{listmatrix}{rpm}{$list}{$_})\n",1, $config->{LOG});
	$l = $_ if ( ($l && $group->{listmatrix}{rpm}{$l}{$_} 
		       || 
		     (!$l && ($group->{listmatrix}{rpm}{$list}{$_} || !$list)))
		   && ($notdone && !$config->{list}[$_]{done} || !$notdone))
    }
    return $l
}

sub processDeps {
    my ($r, $group, $done, $rpmlist, $topush, $intopush, $depsdisc, $rpmd, $list, $loop, $i, $tobedone, $buildlist, $rpm, $needed) = @_;
    log_("processDeps: deps $r\n", $config->{verbose}, $config->{LOG}, 3);
    my $tcd = $done->{$r};
    my $l = find_list($group, $r, $list, !$tcd);
    if ($group->{rejected}{$r}) { 
	log_("ERROR processDeps: deps $r rejected, rejecting @$rpm\n",1, $config->{LOG}, 1);
	log_("Rejecting @$rpm $r\n", $config->{verbose}, $config->{LOG},1);
	foreach (@$rpm) { push @{$group->{rejected}{$_}}, ["deps_rejected", $r] }
	$$loop = 1; %$topush = (); return 0 
    }
    if ($tcd) {
	if ($tcd > $$depsdisc) { $$depsdisc = $tcd };
	log_("processDeps: deps done $r on rep $tcd ($$depsdisc)\n", $config->{verbose}, $config->{LOG}, 4);
	return 2 
    }
    if ($tobedone->{$r}) {
	if ($l == $list) {
	    log_("$r tobedone\n", $config->{verbose}, $config->{LOG},3);
	    $intopush->{$r} and log_("WARNING processDeps: $r added twice\n", $config->{verbose}, $config->{LOG}, 3) and return 1;
	    push @$rpmd, [$r, $rpmlist->[$i]{$l}{$r}];
	    $intopush->{$r} = 1;
	    push @{$topush->{$l}}, $rpmd; 
	    log_("processDeps: adding looping deps $r ($_ -- $l) with @$rpm\n", $config->{verbose}, $config->{LOG},3)
	} else {
		if ($group->{listmatrix}{rpm}{$list}{$l}) {
		    # FIXME tobedone may not mean dependencies loop in parallel mode for different list.
		    log_("processDeps: $r is already scheduled on list $l, waiting.\n", $config->{verbose}, $config->{LOG},4);
		    %$topush = ();
		    push @{$buildlist->[$i]{$list}}, @$rpmd > 1 ? $rpmd : $rpmd->[0];
		    return 3
		    #$intopush{$r} and log_("ERROR: $r added twice\n",1, $config->{LOG}) and return 0;
		    #$intopush{$r} = 1;
		    #push @{$topush{$l}}, [$r, $rpmlist->[$i]{$l}{$r}]; 
		    #log_("DEPS $r ($_ -- $l)\n", $config->{verbose}, $config->{LOG})
		} else {
		    log_("ERROR processDeps: deps $r could not be put in directory before packages @$rpm\n", $config->{verbose}, $config->{LOG},1);
		    log_("Rejecting @$rpm $r\n", $config->{verbose}, $config->{LOG},1);
		    reject_rpm($rpm, $group, 'order_pb', $r, \$loop, $topush);
		    return 0
	    }
	}
    } else {
	if ($l == $list) {
	    $intopush->{$r} and log_("WARNING processDeps: $r added twice\n",1, $config->{LOG},2) and return 1;
	    $intopush->{$r} = 1;
	    push @{$topush->{$l}}, [$r, $rpmlist->[$i]{$l}{$r}]; 
	    log_("processDeps: adding normal deps $r ($_ -- $l)\n", $config->{verbose}, $config->{LOG},3)
	} else {
	    if ($group->{options}{sequential}) {
		log_("WARNING processDeps: could not add interlist deps in sequential mode\n",1, $config->{LOG},2);
		reject_rpm($rpm, $group, 'sequential', $r, \$loop, $topush);
		return 0
	    } else {
		if (defined $needed->{$list}{asap} && grep { $l == $_->[0] } @{$needed->{$list}{asap}}) {
		    log_("ERROR processDeps: list of deps $r is already wainting for @$rpm list\n",1, $config->{LOG},1);
		    reject_rpm($rpm, $group, 'order_pb', $r, \$loop);
		    return 0
		} else {
		    if ($group->{listmatrix}{rpm}{$list}{$l}) {
			$intopush->{$r} and log_("WARNING processDeps: $r added twice\n",1, $config->{LOG},2) and return 1;
			$intopush->{$r} = 1;
			push @{$topush->{$l}}, [$r, $rpmlist->[$i]{$l}{$r}]; 
			log_("processDeps: adding normal deps $r ($_ -- $l)\n", $config->{verbose}, $config->{LOG},3)
		    } else {
			log_("ERROR processDeps: deps $r could not be put in directory before packages @$rpm\n",1, $config->{LOG},1);
			reject_rpm($rpm, $group, 'order_pb', $r, \$loop);
			return 0
		    } 
		}
	    }
	}
    }
}

sub reject_rpm {
    my ($rpm, $group, $reason, $r, $loop, $topush) = @_;
    log_("Rejecting @$rpm\n", $config->{verbose}, $config->{LOG},1);
    foreach (@$rpm) { push @{$group->{rejected}{$_}}, [ $reason, $r] }
    %$topush = ();
    $$loop = 1;
}

sub updateGenericLimit {
    my ($groups, $cdsize) = @_;
    log_("updateGenericLimit\n", $config->{verbose}, $config->{LOG},2);
    for (my $i; $i < @$groups; $i++) {
	foreach my $type (keys %{$groups->[$i]{orderedlist}}) {
	    foreach my $list (@{$groups->[$i]{orderedlist}{$type}}) {
		foreach my $r (@{$groups->[$i]{list}{$list}{$type}}) {
		    my ($cd, $rep, $repopt) = @$r;
		    #log_("trying to update disc $cd rep $rep list $list limit repopt $repopt (",1, $config->{LOG}),keys %$repopt,") opt $opt (",keys %$opt,")\n";
		    $config->{list}[$list]{disc}{$cd}{$rep}{done} and next;
		    $repopt->{limit} or next;
		    $repopt->{limit}{size} = $repopt->{limit}{value} * $cdsize->[$cd];
		    log_("updateGenericLimit: setting disc $cd rep $rep list $list limit to $repopt->{limit}{size} ($repopt->{limit}{value} * $cdsize->[$cd])\n", $config->{verbose}, $config->{LOG}, 3);
		}
	    }
	}
    }
}

sub testSoftLimit {
    my ($opt, $cd, $groups, $buildlist) = @_;
    log_("testSoftLimit\n", $config->{verbose}, $config->{LOG}, 2);
    my $softnok = 1;
    # FIXME this code must be tested
    if ($opt->{limit} && $opt->{limit}{soft}) {
	foreach my $l (@{$config->{disc}[$cd]{fastgeneric}}) {
	    my $lst = $l->[2]{list};
	    for (my $i; $i < @$groups; $i++) {
		$groups->[$i]{list}{$lst} or next;	
		$softnok = 0 if (@{$buildlist->[$i]{$lst}} && !($lst->{limit} && $lst->{limit}{soft}))   
	    }
	}
    }
    return $softnok;
}

sub add_one_disc {
    my ($cdlists, $group, $cdsize, $list, $cds, $sources, $size, $g) = @_;
    my $ncd;
    foreach (keys %$cdlists) {
	$ncd = $_ + 1 if $ncd <= $_
    }
    log_("add_one_disc: $config->{list}[$list]{cd} -- $ncd\n", $config->{verbose}, $config->{LOG}, 3);
    if (!$config->{list}[$list]{cd} || $config->{list}[$list]{cd} >= $ncd) {
	log_("add_one_disc: adding new disc $ncd\n", $config->{verbose}, $config->{LOG}, 4);
	$config->{disc}[$ncd]{size} = $config->{discsize};
	my $functions = $config->{group}{disc}{functions}{functions};
	$cdsize->[$ncd] = $config->{discsize};
	$config->{disc}[$ncd]{name} = $ncd;
	$size->{optimize_space}{disc}{$ncd} = $cdsize->[$ncd];
	$group->{disc_impacted}{$ncd} = 1;
	my ($curdir, $srpmcurdir);
	my $tmp = "$config->{tmp}/build/$config->{name}";
	my $f = "$tmp/$ncd.list";
	-f $f and unlink $f;
	if ($config->{nolive}) {
	    log_("makeDisc: removing $tmp/$ncd\n", $config->{verbose}, $config->{LOG}, 3);
	    rmtree "$tmp/$ncd";
	    mkpath "$tmp/$ncd";
	} else {
	    my $dir = "$config->{topdir}/build/$config->{name}";
	    rmtree "$dir/$ncd";
	    rmtree "$dir/first/$ncd";
	    mkpath "$dir/$ncd"
	}
    	my $instcd = $group->{installDisc};
	my ($rep, $src_rep);

	if ($sources && $config->{list}[$list]{sources} && $config->{list}[$list]{sources}{separate}) {
	    $config->{disc}[$ncd]{serial} = "$config->{name}-disc$ncd-sources";
	    $config->{disc}[$ncd]{longname} = "MandrakeLinux $config->{name} sources";
	    $config->{disc}[$ncd]{appname} = "MandrakeLinux $config->{name} sources disc $ncd";
	    $config->{disc}[$ncd]{label} = substr "MandrakeLinux-$config->{name}-$ncd.src", 0, 32;
	    $config->{disc}[$ncd]{group_list}{$g}{$list}{srpm} = 1;
	    &{$functions->{dir}[0][5]}($ncd, 3, "srpms", "Mandrake/SRPMS");
	    &{$functions->{generic}[0][5]}($ncd, 4, "srpms",1);
	    &{$functions->{generic}[1][5]}($ncd, 5, { source => 1 });
	    push @{$config->{disc}[$instcd]{function}{data}{installation}[1]{srpmsdir}}, [ 0, $ncd, "srpms" ];
	    $srpmcurdir = [ $ncd, "srpms" ];
	    push @{$group->{list}{$list}{srpm}}, $srpmcurdir;
	    $src_rep = $group->{maxrep}{srpm};
	    push @{$group->{replist}{srpm}}, [ $ncd, 'srpms', $group->{maxrep}{srpm}++, { $list => $srpmcurdir } ];
	} else {
	    $config->{disc}[$ncd]{serial} = "$config->{name}-disc$ncd";
	    $config->{disc}[$ncd]{longname} = "MandrakeLinux $config->{name}";
	    $config->{disc}[$ncd]{appname} = "MandrakeLinux $config->{name} disc $ncd";
	    $config->{disc}[$ncd]{label} = substr "MandrakeLinux-$config->{name}-$ncd.i586", 0, 32;
	    $config->{disc}[$ncd]{group_list}{$g}{$list}{rpm} = 1;
	    &{$functions->{dir}[0][5]}($ncd, 1, "rpms", "Mandrake/RPMS$ncd");
	    &{$functions->{generic}[0][5]}($ncd, 2, "rpms", 1);
	    $group->{orderedrep}{rpm}{"$ncd/rpms"} = $ncd;
	    #
	    # generic has no FIXED part, otherwize a call to generic with fixed=0 
	    # would have been needed
	    #
	    $curdir = [$ncd, "rpms"];
	    push @{$group->{list}{$list}{rpm}}, $curdir;
	    $rep = $group->{maxrep}{rpm};
	    if ($group->{replist}{rpm}[$group->{maxrep}{rpm}-1]) {
		die "FATAL add_one_disc: rep $group->{maxrep}{rpm} should not exist !\n"
	    } else {
		$group->{replist}{rpm}[$group->{maxrep}{rpm}-1], [ $ncd, 'rpms', $group->{maxrep}{rpm}++, { $list => $curdir } ];
	    }
	    push @{$config->{disc}[$instcd]{function}{data}{installation}[1]{rpmsdir}}, [ 0, $ncd, "rpms" ];
	    if ($config->{list}[$list]{sources}) {
		&{$functions->{dir}[0][5]}($ncd,3, "srpms", "Mandrake/SRPMS");
		&{$functions->{generic}[0][5]}($ncd,4, "srpms",1);
		&{$functions->{generic}[1][5]}($ncd,5, { source => 1 });
		push @{$config->{disc}[$instcd]{function}{data}{installation}[1]{srpmsdir}}, [ 0, $ncd, "srpms" ];
		$srpmcurdir = [ $ncd, "srpms" ];
		$src_rep = $group->{maxrep}{srpm};
		if ($group->{replist}{srpm}[$group->{maxrep}{srpm}-1]) {
		    die "FATAL add_one_disc: rep $group->{maxrep}{srpm} should not exist !\n"
		} else {
		    $group->{replist}{srpm}[$group->{maxrep}{srpm}-1], [ $ncd, 'srpms', $group->{maxrep}{srpm}++, { $list => $srpmcurdir } ];
		} 
		push @{$group->{list}{$list}{srpm}}, $srpmcurdir
	    }
	}
	push @$cds, $ncd;
	$cdlists->{$ncd} = 2;
	return ($curdir, $rep, $srpmcurdir, $src_rep)
    } else { return 0 }
}

sub addSRPMToDiff {
    my ($rpmd, $done, $diff, $size, $srpmrep, $srpmsize, $curdir, $srpm, $list, $i, $cdnum) = @_;
    for (my $s; $s < @$rpmd; $s++) {
	if (!$rpmd->[$s][1]{nosrc} && !$done->{$srpm->[$s]}) {
	    my $srep = $srpmrep->{$srpm->[$s]};
	    my $idx = push @{$diff->{data}}, [ $srep->[2], $i, $list, $srep->[1], 2, [[$srpm->[$s],1, $rpmd->[$s], $srpmsize->[$s]]], 0, $srpmsize->[$s] ];
	    push @{$diff->{idx}}, $idx - 1;
	    $size->{disc}[$srep->[0]] += $srpmsize->[$s];
	    $size->{rep}{$srep->[0]}{$srep->[2][1]}{$list} += $srpmsize->[$s];
	    log_("SIZE disc $srep->[0]: $size->{disc}[$srep->[0]] (+ $srpm->[$s] $srpmsize->[$s])\n", $config->{verbose}, $config->{LOG}, 2);
	}
	$done->{$srpm->[$s]}++;
    }
    1
}

sub sourcesSizeCheck {
    my ($done, $rpmd, $srpm, $group, $groups, $size, $cdsize, $list, $cdlists, $cdnum, $rpmsize, $buildlist, $cds, $i, $diff) = @_;
    my %srpmrep;   
    my $srpmok = 1;
    my @srpmsize;
    for (my $s; $s < @$srpm; $s++) {
	$done->{$srpm->[$s]} and next;
	$rpmd->[$s][1]{nosrc} and next;
	my $srpmsize = $group->{size}{$srpm->[$s]}{$list}[0];
	$srpmsize[$s] = $srpmsize;
	for (my $k; $k < @{$group->{list}{$list}{srpm}}; $k++) {
	    my $srpmdir = $group->{list}{$list}{srpm}[$k];
	    my ($srccd, $srcrepname, $srcopt) = @$srpmdir;
	    my $src_rep_num = $group->{orderedrep}{srpm}{"$srccd/$srcrepname"};
	    log_("trying source disc $srccd\n", $config->{verbose}, $config->{LOG}, 2);
	    $cdlists->{$srccd} > 1 or next;
	    my $currentrpm;
	    $cdnum == $srccd and $currentrpm = $rpmsize;
	    my $softnok = testSoftLimit($srcopt, $srccd, $groups, $buildlist);
	    my $gain = $size->{disc}[$srccd] + $srpmsize + $currentrpm - $cdsize->[$srccd];
	    # FIXME this need to be tested
	    if ($gain <= 0 && !($srcopt->{limit} && ($softnok || !$srcopt->{limit}{soft}) && $size->{rep}{$srccd}{$srcrepname}{$list} > $srcopt->{limit}{size})) {
		$srpmrep{$srpm->[$s]} = [$srccd, $src_rep_num, $srpmdir];
		last
	    } elsif ($k == $#{$group->{list}{$list}{srpm}}) {
		if (optimize_space($config, $groups, $diff, $size, $cdsize, $srccd, $gain, $cdlists,0, $i, $list, 'srpm', $srpmsize + $currentrpm) < $gain) {
		    $srpmok = 0
		} else {
		    $srpmrep{$srpm->[$s]} = [$srccd, $src_rep_num, $srpmdir];
		}
	    }
	}
	if (!$srpmrep{$srpm->[$s]}) {
	    $srpmok = 0
	    # no last here because if in autoMode a CD will be added after and we will not retest for each srpm if it could be put on an existing CD.
	}
    }
    if (!$srpmok && $config->{list}[$list]{auto}) {
	my (undef,undef, $srpmdir, $repnum) = add_one_disc($cdlists, $group, $cdsize, $list, $cds,1, $size, $i);
	if ($srpmdir) {
	    for (my $s; $s < @$srpm; $s++) {
		if (!$srpmrep{$srpm->[$s]}) {
		    $srpmrep{$srpm->[$s]} = [$srpmdir->[0], $repnum, $srpmdir];
		}
	    }
	    $srpmok = 1
	}
    }
    return (\%srpmrep, \@srpmsize, $srpmok)
}

sub choose_alt {
    my ($deps, $rpmlist, $group, $cdnum, $repname, $list, $buildlist, $intopush, $tobedone, $needed) = @_;
    my $r = -1;
    my $score = [ 0, $group->{maxlist} ];
    my $done = $group->{done};
    foreach my $testalt (1,0) {
	foreach (@$deps) {
	    # FIXME it may have a problem here, as depslistid are not erased when the 
	    # package is removed, that is to say that if the previous deps failed for 
	    # any reason, alternates deps may be added, although excluded before
	    # however this _must_ not happen, and signify a bug somewhere else.
	    my $pkg = $group->{depslistid}[$_];

	    my $l = find_list($group, $pkg, $list);
	    $testalt and $rpmlist->{$l}{$pkg}{noalternatives} and log_("choose_alt: $pkg is not selected in first pass for alternatives\n", $config->{verbose}, $config->{LOG},6) and next;
	    $intopush->{$pkg} and $r = $pkg and last;
	    log_("choose_alt: alternatives deps $pkg\n", $config->{verbose}, $config->{LOG}, 6);
	    $group->{rejected}{$pkg} and log_("choose_alt: $pkg is rejected, ignoring\n", $config->{verbose}, $config->{LOG}, 6) and next;
	    my $tcd = $done->{$pkg};
	    if ($tcd && $tcd <= $group->{orderedrep}{rpm}{"$cdnum/$repname"}) {
		log_("$pkg ($tcd) done\n", $config->{verbose}, $config->{LOG}, 6);
		$r = 0;
		last
	    } 
	    my $s = $group->{scorelist}{$pkg};
	    my $pkgList = find_list($group, $pkg, $list, !$tcd);
	    if ($list != $pkgList && ((defined $needed->{$pkgList}{asap} && grep { $list == $_->[0] } @{$needed->{$pkgList}{asap}}) || ($group->{options}{sequential} && !$config->{list}[$pkgList]{done} && @{$buildlist->{$pkgList}}))) { next }
	    log_("choose_alt: $pkg list $pkgList\n", $config->{verbose}, $config->{LOG}, 6);
	    if (!$tcd && $group->{listmatrix}{rpm}{$list}{$pkgList}) {
		if ($group->{listsort}{$pkgList}{rpm} < $score->[1] || $group->{listsort}{$pkgList}{rpm} == $score->[1] && $s > $score->[0]) {
		    log_("choose_alt: choosing $pkg ($s, $group->{listsort}{$pkgList}{rpm})\n", $config->{verbose}, $config->{LOG}, 6);
		    $score = [ $s, $group->{listsort}{$pkgList}{rpm} ];
		    $r = $pkg;
		    last if $tobedone->{$r} 
		}
	    }
	}
	$r and last
    }
    return $r
}

sub check_deps {
    my ($rpmd, $group, $done, $rpmlist, $list, $i, $tobedone, $buildlist, $rpm, $cdnum, $repname, $needed, $thisorderrep) = @_;
    my $deps = get_pkgs_deps($rpmd, $group);
    my $loop;
    if (@$deps) {
	my ($waiting, %topush, %intopush, $depsdisc);
	foreach (@$deps) {
	    if (!ref $_) {
		my $a = processDeps($group->{depslistid}[$_], $group, $done, $rpmlist, \%topush, \%intopush, \$depsdisc, $rpmd, $list, \$loop, $i, $tobedone, $buildlist, $rpm, $needed->[$i]);
		if ($a < 0) { return 0 } elsif ($a == 0) { last } elsif ($a == 2) { next } elsif ($a == 3) { $waiting = 1; last  }
	    } else {
		# must create a virtual package that install all of them in one loop
		log_("check_deps: alternatives deps @$_\n", $config->{verbose}, $config->{LOG}, 5);
		my $r = choose_alt($_, $rpmlist->[$i], $group, $cdnum, $repname, $list, $buildlist->[$i], \%intopush, $tobedone, $needed->[$i]);
		$intopush{$r} and next;
		if ($r == -1) {
		    log_("ERROR check_deps: alternatives deps (@$_) could not be put in directory before packages @$rpm\n", $config->{verbose}, $config->{LOG}, 2);
		    log_("Rejecting @$rpm\n", $config->{verbose}, $config->{LOG},2);
		    foreach my $p (@$rpm) { push @{$group->{rejected}{$p}}, ["order_pb", "@$_"] }
		    %topush = ();
		    $loop = 1;
		    last
		}
		if ($r) { 
		    my $a = processDeps($r, $group, $done, $rpmlist, \%topush, \%intopush, \$depsdisc, $rpmd, $list, \$loop, $i, $tobedone, $buildlist, $rpm, $needed->[$i]);
		    if ($a < 0) { return 0 } elsif ($a == 0) { last } elsif ($a == 2) { next } elsif ($a == 3) { $waiting = 1; last  }
		} else {
		    log_("Finding better alternatives rep (@$_ - $depsdisc)\n", $config->{verbose}, $config->{LOG}, 4);
		    my $bestdisc = keys %{$group->{orderedrep}{rpm}};
		    if ($bestdisc >= $depsdisc) {
			foreach (@$_) {
			    my $pkg = $group->{depslistid}[$_];
			    $group->{rejected}{$pkg} and log_("$pkg rejected\n", $config->{verbose}, $config->{LOG}, 2) and next;
			    my $tcd = $done->{$pkg} or next; 
			    log_("$pkg => rep $tcd\n", $config->{verbose}, $config->{LOG}, 4);
			    if ($tcd < $bestdisc) { $bestdisc = $tcd }
			}
			$bestdisc > $depsdisc and $depsdisc = $bestdisc
		    }
		    log_("Finding better alternatives rep result $depsdisc\n", $config->{verbose}, $config->{LOG}, 4);
		}
	    }
	}
	$waiting and next;
	if (keys %topush) {
	    log_("Adding dependencies, looping\n", $config->{verbose}, $config->{LOG}, 3);
	    $loop = 1;
	    my $test = @$rpmd > 1 ? $rpmd : $rpmd->[0];
	    push @{$buildlist->[$i]{$list}}, @$rpmd > 1 ? $rpmd : $rpmd->[0];
	    foreach (keys %topush) {
		$list != $_ and push @{$needed->[$i]{$list}{asap}}, [ $_, int @{$buildlist->[$i]{$_}} ];
		push @{$buildlist->[$i]{$_}}, @{$topush{$_}}
	    }
	} elsif ($thisorderrep < $depsdisc) {
	    if ($group->{listmaxrep}{rpm}{$list} >= $depsdisc) {
		# has a chance to put it after depsdic
		log_("Dependencies on further directories ($depsdisc)\n", $config->{verbose}, $config->{LOG}, 3);
		next
	    } else {
		log_("check_deps: dependances are in further directories, rejecting @$rpm\n", $config->{verbose}, $config->{LOG}, 2);
		foreach (@$rpm) { push @{$group->{rejected}{$_}}, ["order_pb", ""] }
		$loop = 1
	    }
	}
    }
    return $loop;
}

sub put_in_rep {
    my ($i, $groups, $group, $size, $rpmsize, $all_rpmsize, $cdsize, $needed, $rpm, $rpmd, $list, $cdlists, $buildlist, $diff, $cds, $done, $tobedone, $rpmlist, $nosrcfit) = @_; 
    my $loop;
    my $dn;
    log_("put_in_rep: @$rpm\n", $config->{verbose}, $config->{LOG}, 3);
    for (my $j; !$loop && !$dn && $j < @{$group->{list}{$list}{rpm}}; $j++) {
	$loop = 0;
	my $curdir = $group->{list}{$list}{rpm}[$j];
	$config->{list}[$list]{disc}{$curdir->[0]}{$curdir->[1]}{done} and next;
	my ($cdnum, $repname, $repopt) = @$curdir;
	my $rep_num = $group->{orderedrep}{rpm}{"$cdnum/$repname"};
	$cdlists->{$cdnum} > 1 or next;
	my $thisorderrep = $group->{orderedrep}{rpm}{"$cdnum/$repname"};
	my $softnok = testSoftLimit($repopt, $cdnum, $groups, $buildlist);
	my $gain = $size->{disc}[$cdnum] + $all_rpmsize - $cdsize->[$cdnum];
	log_("put_in_rep: curdir cd $cdnum (space $gain rpm size $all_rpmsize) rep $repname rep_num $rep_num softnok $softnok\n", $config->{verbose}, $config->{LOG}, 4);
	if ($gain > 0 || $repopt->{limit} && ($softnok || !$repopt->{limit}{soft}) && $size->{rep}{$cdnum}{$repname}{$list} + $all_rpmsize > $repopt->{limit}{size}) {
	    if ($j == $#{$group->{list}{$list}{rpm}}) {
		if (!($repopt->{limit} && !$softnok && $repopt->{limit}{soft})) {
		    if (optimize_space($config, $groups, $diff, $size, $cdsize, $cdnum, $gain, $cdlists,0, $i, $list, 'rpm', $all_rpmsize) < $gain) {
			if ($config->{list}[$list]{auto}) {
			    ($curdir, $rep_num) = add_one_disc($cdlists, $group, $cdsize, $list, $cds,0, $size, $i);
			    if ($curdir) {
				$cdnum = $curdir->[0]
			    } else {
				log_("Could not add more disc, rejecting @$rpm\n", $config->{verbose}, $config->{LOG}, 2);
				foreach my $p (@$rpm) { push @{$group->{rejected}{$p}}, [ "no_disc", "" ] }
				next
			    }
			} else {
			    log_("Rejecting @$rpm\n", $config->{verbose}, $config->{LOG}, 2);
			    foreach my $p (@$rpm) { push @{$group->{rejected}{$p}}, ["no_space", ""] }
			    next
			}
		    }
		} else { 
		    foreach my $l (@{$config->{disc}[$cdnum]{fastgeneric}}) {
			my $lst = $l->[2]{list};
			$list == $lst and next;
			for (my $i; $i < @$groups; $i++) {
			    $groups->[$i]{list}{$lst}{rpm} or next;	
			    push @{$needed->[$i]{$list}{asap}}, [ $lst, 0 ] if (!($lst->{limit} && $lst->{limit}{soft}))
			}
		    }
		}
	    } else { next }
	}
	if (!$config->{nodeps} && !$group->{options}{nodeps}) {
	   $loop = check_deps($rpmd, $group, $done, $rpmlist, $list, $i, $tobedone->[$i], $buildlist, $rpm, $cdnum, $repname, $needed, $thisorderrep)
	}
	$loop and next;	
	log_("@$rpm deps ok\n", $config->{verbose}, $config->{LOG}, 4);
	my $nosrc = 1;
	my @srpm;
	my $donesrpm = 1;
	if (!$group->{options}{nosources} && @{$group->{list}{$list}{srpm}}) {
	    for (my $s; $s < @$rpmd; $s++) {
		my $srpm = $group->{urpm}{sourcerpm}{$rpm->[$s]}; 
		$srpm =~ s/\.rpm$//;
		if (!$group->{size}{$srpm}{$list}) {
		    log_("put_in_rep ERROR: $srpm not available, trying alternatives => ", $config->{verbose}, $config->{LOG}, 5);
		    my ($srpmname) = $srpm =~ /(.*)-[^-]+-[^-]+\.src/;
		    $srpm = $group->{srpmname}{$srpmname};
		    if ($group->{size}{$srpm}{$list}) { 
			log_(" $srpm\n", $config->{verbose}, $config->{LOG}) 
		    } else {
			if ($srpm) {
			    log_("not found (but a srpm $srpm exist in another list)\n", $config->{verbose}, $config->{LOG}, 5);
			    $srpm = 0
			} else {
			    log_("not found\n", $config->{verbose}, $config->{LOG}, 5)  
			}
		    }
		}
		if ($srpm) { 
		    $done->{$srpm} or $donesrpm = 0;
		    $srpm[$s] = $srpm;
		    $rpmd->[$s][1]{nosrc} or $nosrc = 0 
		}
	    }
	}
	log_("put_in_rep: group $i list $list: @$rpm (@srpm) -- $curdir->[0] -- $curdir->[1] -- disc $cdnum\n", $config->{verbose}, $config->{LOG}, 4);
	if ($config->{nosrc} || $group->{options}{nosources} || !@{$group->{list}{$list}{srpm}} || $nosrc || $donesrpm) {
	    ($dn) = addRPMToDiff($rpm, $rpmd, $diff, $cdnum, $group->{orderedrep}{rpm}{"$cdnum/$repname"}, $i, $list, $curdir, $size, $rpmsize, $all_rpmsize, $rep_num, $done)
	} else {
	    if ($config->{nosrcfit} || $group->{options}{nosrcfit}) {
		$dn = addRPMToDiff($rpm, $rpmd, $diff, $cdnum, $group->{orderedrep}{rpm}{"$cdnum/$repname"}, $i, $list, $curdir, $size, $rpmsize, $all_rpmsize, $rep_num, $done);
		push @$nosrcfit, [$rpmd, \@srpm, $list, $i, $curdir, $cdnum]
	    } else {
		my ($srpmrep, $srpmsize, $srpmok) = sourcesSizeCheck($done, $rpmd, \@srpm, $group, $groups, $size, $cdsize, $list, $cdlists, $cdnum, $all_rpmsize, $buildlist, $cds, $i, $diff);
		if ($srpmok) {
		    addRPMToDiff($rpm, $rpmd, $diff, $cdnum, $group->{orderedrep}{rpm}{"$cdnum/$repname"}, $i, $list, $curdir, $size, $rpmsize, $all_rpmsize, $rep_num, $done);
		    $dn = addSRPMToDiff($rpmd, $done, $diff, $size, $srpmrep, $srpmsize, $curdir, \@srpm, $list, $i, $cdnum);
		} else {
		    log_("WARNING: @srpm does not fit on the discs\n",1, $config->{LOG}, 2)
		}
	    }
	    if (!$dn) {
		foreach my $p (@$rpm) { push @{$group->{rejected}{$p}}, ["no_space", ""] }
		log_("WARNING: @$rpm does not fit on the disc ($size->{disc}[$cdnum] + $all_rpmsize > $cdsize->[$cdnum]) \n", $config->{verbose}, $config->{LOG}, 1)
	    }
	}
    }
    return $dn
}

sub loop_on_lists {
    my ($i, $groups, $group, $groupok, $needed, $buildlist, $tobedone, $diff, $nosrcfit, $size, $cdsize, $cds, $rpmlist, $cdlists, $ok) = @_; 
    #
    # FIXME source rpms are not shared between group, it may be usefull for mutilple installation
    # with common source dir, so that the same source rpm is shared (but this is not so common).
    #

    my $done = $group->{done};
    my $dn;
    log_("loop_on_lists: group $i (@{$group->{orderedlist}{rpm}})\n", $config->{verbose}, $config->{LOG}, 2);
    while (!$dn) {
	$groupok->[$i] = 1;
	foreach my $list (@{$group->{orderedlist}{rpm}}) {
	    log_("loop_on_lists: list $list (empty $config->{list}[$list]{empty} done $config->{list}[$list]{done})\n", $config->{verbose}, $config->{LOG}, 3);
	    do {
		$config->{list}[$list]{done} and goto end;
		$config->{list}[$list]{empty} and goto end;
		my $next;
		foreach (@{$needed->[$i]{$list}{asap}}) {
		    my $nb_elt = @{$buildlist->[$i]{$_->[0]}};
		    log_("List $list need list $_->[0] to be <= $_->[1] ($nb_elt)\n", $config->{verbose}, $config->{LOG}, 4);
		    $nb_elt <= $_->[1] or $next = 1
		}
		$next and log_("List $list waiting\n",1, $config->{LOG},4) and goto end;
		$needed->[$i]{$list}{asap} = [];
		my ($trpmd, $k, $goon, @rpmd);
		do { 
		    $trpmd = pop @{$buildlist->[$i]{$list}} or goto end;
		    if (ref $trpmd->[0]) {
			foreach (@$trpmd) {
			    !$done->{$_->[0]} and push @rpmd, $_
			}
		    } else { !$done->{$trpmd->[0]} and push @rpmd, $trpmd }
		} until @rpmd;
		$groupok->[$i] = 0;
		$ok = 0;
		my @rpm;
		my $all_rpmsize;
		my @rpmsize;
		foreach (@rpmd) {
		    my $r = $_->[0];
		    !$r and log_("ERROR loop_on_lists: empty package @$_\n", $config->{verbose}, $config->{LOG}, 2);
		    push @rpm, $r;
		    log_("RPM $r (group $i list $list)\n", $config->{verbose}, $config->{LOG},6);
		    $tobedone->[$i]{$r} = 1;
		    $all_rpmsize += $group->{size}{$r}{$list}[0];
		    push @rpmsize, $group->{size}{$r}{$list}[0]
		}
		$dn = put_in_rep($i, $groups, $group, $size, \@rpmsize, $all_rpmsize, $cdsize, $needed, \@rpm, \@rpmd, $list, $cdlists, $buildlist, $diff, $cds, $done, $tobedone, $rpmlist, $nosrcfit); 
	    } while $group->{options}{sequential} && @{$buildlist->[$i]{$list}};
	    end:
	    last if $group->{options}{sequential} && @{$buildlist->[$i]{$list}} && !$config->{list}[$list]{done}
	} 
	$groupok->[$i] and $dn = 1
    }
    return $ok
}

sub calc_needed_size {
    my ($group, $i, $needed, $needed_size, $buildlist, $rpmlist, $tobedone) = @_;
    my ($msg, %local_done);
    $msg = "calc_needed_size\n";
    my $done = $group->{done};
    foreach my $rep (@{$group->{replist}{rpm}}) {
	my ($cd, $repname, $num, $l) = @$rep;
	$msg .= "calc_needed_size: rep $num\n";
	foreach my $list (keys %$needed) {
	    $l->{$list} or next;
	    $config->{list}[$list]{disc}{$cd}{$repname}{done} and log_("calc_needed_size: rep $cd/$repname for list $list is done, ignoring\n", $config->{verbose}, $config->{LOG}, 5) and next;
	    foreach my $elt (@{$needed->{$list}{alap}[$num]}) {
		my $rpm = $elt->[0];
		if ($done->{$rpm} || $local_done{$rpm}) {
		    next
		}
		if ($group->{rejected}{$rpm}) {
		    $msg .= "ERROR: $rpm is rejected, ignoring\n";
		    next
		}
		$msg .= "calc_needed_size: list $list rpm $rpm size $group->{size}{$rpm}{$list}[0]\n";
		$needed_size->[$num]{fix} += $group->{size}{$rpm}{$list}[0];
		$local_done{$rpm} = 1;
		# FIXME This following code is a simplified version of check_deps. It may be overkill to use
		# full check_deps as anyway check_deps will be used to put the package at the end.
		foreach (@{$group->{pkgdeps}{$rpm}}) {
		    if (ref $_) {
			$local_done{"@$_"} and next;
			$local_done{"@$_"} = 1;
			my $r = choose_alt($_, $rpmlist->[$i], $group, $cd, $num, $list, $buildlist, $tobedone, $needed);
			if ($r != -1 && $r != 0) {
			    next if $local_done{$r};
			    $needed_size->[$num]{var} += $group->{size}{$r}{$list}[0];
			}
		    } else {
			my $pkg = $group->{depslistid}[$_];
			next if $done->{$pkg} || $local_done{$pkg};
			$local_done{$pkg} = 1;
			$needed_size->[$num]{var} += $group->{size}{$pkg}{$list}[0];
		    }
		}
	    }
	}
    }
    log_($msg, $config->{verbose}, $config->{LOG}, 3)
}

sub revert_to {
    my ($groups, $i, $p2r, $diff, $size, $buildlist) = @_;
    log_("revert_to: try to find $p2r\n", $config->{verbose}, $config->{LOG}, 3);
    foreach (@{$diff->{data}}) {
	$_ or next; 
	foreach (@{$_->[5]}) {
	    goto revert_to_ok if $_->[0] eq $p2r
	} 
    }
    log_("ERROR revert_to: $p2r is not present in movement history\n", $config->{verbose}, $config->{LOG}, 2);
    return 0;
    revert_to_ok:
    log_("revert_to: $p2r found\n", $config->{verbose}, $config->{LOG}, 5);
    my @keep;
    my $idx;
    do {
	$idx = pop @{$diff->{idx}};
	goto revert_to_endloop if grep { $_->[0] eq $p2r } @{$diff->{data}[$idx][5]}; 
	my $step = $diff->{data}[$idx];
	if ($groups->[$i]{conflict}{$step->[1]}) {
	    my ($curdir, $g, $list) = @$step;
	    my $cdnum = $curdir->[0];
	    foreach (@{$step->[5]}) { 
		my ($rpm,undef,undef, $rpmsize) = @$_; 
		delete $groups->[$g]{done}{$rpm}; 
		$diff->{data}[$idx] = 0;
		$size->{disc}[$cdnum] -= $rpmsize; 
		$size->{rep}{$cdnum}{$curdir->[1]}{$list} -= $rpmsize;
		log_("revert_to: reverting $rpm ID $idx for $p2r on cd $cdnum group $g list $list (cd size $size->{disc}[$cdnum])\n", $config->{verbose}, $config->{LOG}, 3)
	    }
	    push @{$buildlist->{$list}}, $step->[6] if $step->[4] == 1
	} else {
	    unshift @keep, $idx;
	}
    } while @{$diff->{idx}};
revert_to_endloop:
    die "FATAL revert_to: diff data are empty\n" if ! @{$diff->{data}};
    push @{$diff->{idx}}, $idx, @keep;
    1
}

sub mark_and_check_lists {
    my ($groups, $i, $needed, $diff, $buildlist, $rpmlist, $mark, $size, $cdsize, $ok, $tobedone) = @_;
    my @needed_size;
    my $group = $groups->[$i];
    my $need_to_calc;
    foreach my $list (@{$group->{orderedlist}{rpm}}) {
	ref $buildlist->{$list} or next;
	log_("mark_and_check_list: list $list\n", $config->{verbose}, $config->{LOG}, 3);
	$config->{list}[$list]{done} and log_("mark_and_check_lists: list $list is done, ignoring\n", $config->{verbose}, $config->{LOG}, 5) and next;
	if (defined $mark->{cur}{$list}) {
	    $need_to_calc = 1;
	    my $m = $mark->{cur}{$list};
	    my $m_rpm = $m->[0];
	    if ($group->{done}{$m_rpm}) {
		log_("mark_and_check_list: $m_rpm done, deleting mark for list $list\n", $config->{verbose}, $config->{LOG},4);
		push @{$mark->{his}}, $m;
		delete $mark->{cur}{$list}
	    } elsif ($group->{rejected}{$m_rpm}) {
		log_("mark_and_check_list: $m_rpm rejected, deleting mark for list $list\n", $config->{verbose}, $config->{LOG},4);
		delete $mark->{cur}{$list}
	    } elsif (!@{$buildlist->{$list}}) {
		log_("mark_and_check_list: list $list finished and $m_rpm not done or rejected\n",1, $config->{LOG},4)
	    }
	}
	if (!defined $mark->{cur}{$list} && @{$buildlist->{$list}}) {
	    my $rpm;
	    for (my $i = $#{$buildlist->{$list}}; $i >= 0; $i--) {
		my $t = $buildlist->{$list}[$i];
		$rpm = ref $t->[0] ? $t->[0] : $t;
		if (!$group->{done}{$rpm->[0]}) {
		    last
		} 
		# this is not necessary, but when we are at it...
		pop @{$buildlist->{$list}}
	    }
	    $mark->{cur}{$list} = $rpm;
	    log_("mark_and_check_list: marking $rpm->[0] for $list\n", $config->{verbose}, $config->{LOG},3);
	}
    }
    if ($need_to_calc || $ok) {
	calc_needed_size($group, $i, $needed, \@needed_size, $buildlist, $rpmlist, $tobedone);
	my ($need_in_rep, $av_in_rep, %done_disc);
	#
	# First impression would have been to check needed in reverse order, because we could imagine
	# that, in the current configuration, if needed 2 does not fit, for exemple, one package
	# is removed, needed 2 is put. But if needed does not fit at this moment, needed 2 is removed,
	# and needed 3 put, then needed 2 does not fit, and it is needed to revert more to make both of
	# them fit.
	#
	# If fact this could not happen, because if needed 3 does not fit when needed 2 has just been
	# put, this mean that the calc_needed_size if bogus.
	#
	foreach my $rep (@{$group->{replist}{rpm}}) {
	    my ($cd,undef, $num) = @$rep;
	    $need_in_rep += $needed_size[$num]{var} + $needed_size[$num]{fix};
	    if (! $done_disc{$num}) {
		$av_in_rep += $cdsize->[$cd] - $size->{disc}[$cd];
		$done_disc{$num} = 1
	    }
	    log_("mark_and_check_list: rep $num need_in_rep $need_in_rep av_in_rep $av_in_rep needed_size $needed_size[$num]{fix} disc_av_space ($cdsize->[$cd] - $size->{disc}[$cd])\n", $config->{verbose}, $config->{LOG}, 4);
	    if ($need_in_rep && $need_in_rep > $av_in_rep || $needed_size[$num]{fix} > $cdsize->[$cd] - $size->{disc}[$cd]) {
		log_("mark_and_check_list: not enough space for needed in rep $num on disc $cd\n", $config->{verbose}, $config->{LOG}, 3);
		pop @{$mark->{his}};
		my $p2r = pop @{$mark->{his}};
		log_("mark_and_check_list: trying to revert $p2r->[0]\n", $config->{verbose}, $config->{LOG}, 4);
		if (revert_to($groups, $i, $p2r->[0], $diff, $size, $buildlist)) {
		    log_("mark_and_check_list: $p2r->[0] reverted\n", $config->{verbose}, $config->{LOG}, 3);
		    foreach my $idx (0 .. @{$group->{orderedlist}{rpm}}) {
			my $list = $group->{orderedlist}{rpm}[$idx];
			$needed->{$list} or next;
			my $elt;
			foreach my $tr (1 .. $num) {
			    foreach my $elt (@{$needed->{$list}{alap}[$tr]}) {
				my $rpm = $elt->[0];
				if ($group->{rejected}{$rpm}) {
				    log_("ERROR: $rpm is rejected, ignoring\n", $config->{verbose}, $config->{LOG}, 3);
				    next
				}
				push @{$buildlist->{$list}}, $elt;
			    }
			}
			$mark->{cur}{$list} = $elt->[0];
			my $l_idx = $#{$buildlist->{$list}};
			next if $l_idx < 0;
			foreach my $tidx ($idx + 1 .. @{$group->{orderedlist}{rpm}}) {
			    my $l = $group->{orderedlist}{rpm}[$tidx];
			    push @{$needed->{$l}{asap}}, [ $list, $l_idx ]
			}
		    }
		} else {
		    log_("ERROR mark_and_check_list: reverting to $p2r->[0] failed\n", $config->{verbose}, $config->{LOG}, 4)
		}
		
	    }
	}
    }
    return $ok
}

# TODO the algo is not as beautiful as it should be
# ... but it is getting better
# ... and better
sub buildDiscs {
    my ($class, $groups, $buildlist, $rpmlist, $groupok, $size, $cdsize, $cdlists, $cds, $needed, $diff, $n) = @_;
    log_("buildDiscs\n", $config->{verbose}, $config->{LOG}, 3);
    $config = $class->{config};
    if ($n > 1) {
	foreach my $i (reverse @$cds) {
	    $size->{optimize_space}{disc}{$i} = $size->{disc}[$i];
	    if ($size->{disc}[$i] > $cdsize->[$i]) { 
		my $gain = ($size->{disc}[$i] - $cdsize->[$i])/2;
		next if $gain < 0;
		optimize_space($config, $groups, $diff, $size, $cdsize, $i, $gain, $cdlists,1)
	    } else {
		log_("buildDiscs: disc $i size OK $size->{disc}[$i] ($cdsize->[$i])\n", $config->{verbose}, $config->{LOG},2)
	    }
	}
    }
    my ($ok, $iti);
    my @groupok;
    my (@tobedone, @nosrcfit);
    my @mark = ({}) x @$groups;
    updateGenericLimit($groups, $cdsize);
    while (!$ok) {
	log_("iti: " . $iti++ . "\n", $config->{verbose}, $config->{LOG},4);
	$ok = 1;
	for (my $i = 0; $i < @$groups; $i++) {
	    my $group = $groups->[$i];
	    if (!$config->{fast}) {
		$groupok[$i] = mark_and_check_lists($groups, $i, $needed->[$i], $diff, $buildlist->[$i], $rpmlist, $mark[$i], $size, $cdsize, $groupok[$i], $tobedone[$i]);
		$groupok[$i] and next;
	    }
	    $ok = loop_on_lists($i, $groups, $group, \@groupok, $needed, $buildlist, \@tobedone, $diff, \@nosrcfit, $size, $cdsize, $cds, $rpmlist, $cdlists, $ok); 
	}
    }
    foreach (@nosrcfit) {
	my ($rpmd, $srpm, $list, $i, $curdir, $cdnum) = @$_;
	my $group = $groups->[$i];
	my $done = $group->{done};
	my ($srpmrep, $srpmsize, $srpmok) = sourcesSizeCheck($done, $rpmd, $srpm, $group, $groups, $size, $cdsize, $list, $cdlists,0,0, $buildlist, $cds, $i, $diff);
	if ($srpmok) {
	    addSRPMToDiff($rpmd, $done, $diff, $size, $srpmrep, $srpmsize, $curdir, $srpm, $list, $i, $cdnum);
	} else {
	    log_("WARNING: @$srpm does not fit on the discs\n",1, $config->{LOG},2)
	}
    }
    my $is_rejected;
    log_("buildDiscs: rejected packages\n", $config->{verbose}, $config->{LOG},2);
    for (my $i; $i < @$groups; $i++) {
	$groups->[$i]{rejected} or next;
	my $gh = $groups->[$i]{rejected};
	foreach (keys %$gh) {
	    if (!$is_rejected) {
		$is_rejected = 1 if grep { $_->[0] =~ /no_disc/ || $_->[0] =~ /no_space/ } @{$gh->{$_}};
	    }
	    log_("WARNING buildDisc: group $i REJECTED $_ (",1, $config->{LOG},2);
	    ref $groups->[$i]{rejected}{$_} and log_((join ',', map { "$config->{rejected_options}{$_->[0]}: $_->[1]" } @{$groups->[$i]{rejected}{$_}}),1, $config->{LOG},2);
	    log_(")\n",1, $config->{LOG});
	}
    }
    ($is_rejected)
}
1

# Changelog 
#
# 2002 02 21
# change false $j comparaison to $depsdisc in buildDisc to new $thisorderrep value.
#
# 2002 03 03
# new limit option handling.
# add updateGenericSoft function 
# add testSoftLimit function
# update size to check rep size
#
# 2002 03 08
# fix autoMode CD adding
#
# 2002 03 13
# better selection of alternatives in multi-list to take the one in the first lists.
#
# 2002 03 14
# add sources new sources handling method
# in nosrcfit mode sources are added afterwards
#
# 2002 03 19
# add prelist in geList for cdcom, will be useful for oem too I guess.
#
# 2002 05 02 
# add_one_disc: add separate mode for sources mode
#
# 2002 05 09
# add graft structure for md5 and graft point handling
#
# 2002 05 13 
# fix a tricky bugs in build_list about fentry shared and not recreated for each packages.
#
# 2002 06 01
# use perl-URPM
#
# 2002 06 15
# new diff mode, global, shared between disc and group, only one table.
#
# 2002 08 16
# new diff_idx table to sort diff data
#
# 2002 08 24
# optimize_space first version, still need to handle correctly needed and more advanced optimization methods.
#
# 2002 09 18
# optimize_space work, fixes and updates.
#
# 2002 10 25
# fix needed assignation pb in closeRPMslist
