#!/usr/bin/perl

eval 'exec /usr/bin/perl  -S $0 ${1+"$@"}'
    if 0; # not running under some shell
#
# $Id: distlint,v 1.39 2004/02/09 05:28:43 othauvin Exp $

use strict;
use URPM;
use URPM::Build;
use MDK::Common;
use urpmchecker;
use urpmchecker::query;
use Getopt::Long qw(:config prefix_pattern=(--|-));
use Term::ReadLine;

sub get_maintener { undef };
my %maint;

my ($name) = $0 =~ m!.*/(.*)!;
my $urpmchecker = urpmchecker->new;

my (%flag, $to_add, %path, @list, @output, $std);
$to_add = {};
sub help() {
	print urpmchecker::version();
    print 
"Usage: $0 [options] [--] [+]arg1 [[+]arg2 ... [+]argn]
Check missing dependances for each arg passed.

arg can be:
 -  a hdlist (not synthesis)
 -  dir, it look for dir/*.rpm
 -  a rpm

-p packager show error if changelog text contain 'packager'
-n name     show error if package name contain 'name'
-a arch     set system architecture to arch, then check only compatible rpms
-i          start interactif mode

--          set or unset report flag (start to off)
/           up to next level of rpms, check order dependancies
--src       set or unset check link src <=> binary
--reb       check if src need to be built
--nodep     set or unset non-checking dependencies
--nocf      do not check for conflict files
--update    set or unset update type for repository

-h          print this help
--ho [TYPE] print output explanations

Each arg can be setup in ~/.chkdep, to refer to a macros, call the macros name
with a '+' as prefix. ~/.chkdep format is:
macros = arg

ex: $0 Mandrake/RPMS +macros / -- *.rpm
"; 
    exit 0;
}

sub help_output {
    print "$0 output description:
	
packager name in changelog <packager\@email> 
 packagename (source_package)
    TYPE : description 2nd_package_concerned

If too many problem are found for a package, description is the number of problem.

";
	@_ ? print join("\n", urpmchecker::explanation_error(@_)) : print join(" ", urpmchecker::explanation_error(@_))."\n";
    exit 0;
}

sub interactive_help {
    my ($item)=@_;
    my %help=(
        listrep => "\nList repository actually setup for checking",
        listpath => "\nList know path in your ~/.chkdep",
        listurpmi => "\nList hdlist in /var/lib/urpmi",
        add => "[flag] /path/[rpm|hdlist]\nadd a repository entry (hdlist, rpm or dir)",
        batch => "\nload a batch file",
        'arch' => "ARCH\nSet system arch to ARCH",
        flag => 
"available option when adding a repository:
--nochk
--src       set or unset check link src <=> binary
--reb       check if src need to be built
--nodep     set or unset non-checking dependencies
--nocf      do not check for conflict files
--update    set or unset update type for repository",
        '--' => "\nSet or unset checking option
--nochk   global checking
--update  source is update
--src     check SRPMS
--reb     find package need to be reuild
--nodep   do not check dependancies
--nocf    do not check files",
        '/' => "\nSet or unset increase level of repository",
        parse => "\nRead rpm header and store information in memory",
        search => 
"[options] regexp
-n search for name
-p search for packager
-s search for package build from package named
--nonoarch do not show noarch rpms
--nosrc do not show src rpms",
        qi => "[search options]\nDo something like rpm -qi",
        ql => "[search options]\nList file in the package",
        findfile => "[search options] regex\nFind a file",
        dep => "[search options]\nShow dependencies, see search",
        pkgneed =>  "[search options]\nDump dependancies tree of packages",
        req => "[search options]\nShow package which need it, see search",
        analyze => "\nFind error a write list of package concerned",
        listerror => "[search options]\nList package which contain error",
        noerror => "[search option]\nShow packages without error, see search",
        error => "[search option]\nShow Error, see search",
        'reset' => "\nReset all data",
        quit => "\nLeave this stupid tools :)",
    );
    print($item ? "  $item $help{$item}\n" : "Availlable help topics:\n  " . join(" ", sort keys %help) . "\n"); 
}

# print output, permit a callback
# 

sub echo {
    my @out = @_;
    printf(@out);
}

sub search {
    my %rpmmatch = @_;
    my @chmatch=qw(name fullname sourcerpm packager);
    my @r;
    $rpmmatch{list} or @{$rpmmatch{list}} = (0 .. $#{$urpmchecker->{urpm}{depslist}});
    any { defined $rpmmatch{$_} && !/^list$/ and $_ } (@chmatch, 'sname') or return $rpmmatch{allowall} ? @{$rpmmatch{list}} : ();
    foreach my $id (@{$rpmmatch{list}}) {
        my $pkg = $urpmchecker->{urpm}{depslist}[$id];
        defined $rpmmatch{arch} && $pkg->arch ne $rpmmatch{arch} ||
          ($rpmmatch{src} == 0 && $pkg->arch eq 'src' ||
           $rpmmatch{noarch} == 0 && $pkg->arch eq 'noarch') and next;
#        my $sname = $pkg->arch eq 'src' ? $pkg->name : ($pkg->sourcerpm =~ m/(.*)-[^\-]*-.*\.src\.rpm$/)[0];
        $rpmmatch{sname} && eval("\$sname =~ /$rpmmatch{sname}/") and do {
            push @r,$id;
            next;
        };
        push @r, map { if_($rpmmatch{$_} && eval ("\$pkg->$_ =~ /$rpmmatch{$_}/"), $id) } @chmatch;
    }
    uniq(@r)
}

sub show_error {
    foreach my $u_rpm (@_) {
        my $pkg = $urpmchecker->{urpm}{depslist}[$u_rpm];
        defined $pkg or next;
        defined $urpmchecker->{error}{$u_rpm} or next;
        echo "\n* " . $urpmchecker->pkgname($u_rpm) . "\n";
        my @err = @{$urpmchecker->{error}{$u_rpm}{err}};
        my %tmp;
        foreach my $err (@err) {
            push @{$tmp{$err->{type}}{sprintf("%s %s", $err->{msgsum}, $err->{fullname})}}, $err->{msg};
        }
        foreach my $type (keys %tmp) {
			foreach my $fullname (keys %{$tmp{$type}}) {
				my $n = scalar @{$tmp{$type}{$fullname}};
				if ($n > 3) {
					echo "    $type: (x$n) $fullname\n";
				} else {
					echo join("", map { "    $type: $_ $fullname\n" } @{$tmp{$type}{$fullname}});
			    }
		    }
		}
    }
}

if (-f "$ENV{HOME}/.chkdep") {
    local $_;
    foreach (cat_("$ENV{HOME}/.chkdep")) {
        s/#.*//;         # no comments
        if (/^\s*(\S+)\s*=\s*(\S+)\s*$/) {
            my ($name, $value) = ($1, $2);
            $path{"+$name"} = $value;
        }
    }
}

sub parse_cmdline {
    #my @arg;
    local $_;
    local @ARGV;
    ($_, @ARGV) = @_;

    /^--$/  and do { 
        $to_add->{check} = $to_add->{check} ? 0 : 1;
        $flag{yc} = 1;
        $flag{v} and echo "checking flag set to $to_add->{check}\n";
    };
    
    m!^/$! and do { 
        $to_add->{nextlevel} = $to_add->{nextlevel} ? 0 : 1;
        $flag{v} and echo "Increase level set to $to_add->{nextlevel}\n";
    };
    
    /^--(.+)/ and do {
        my $k = $1;
        $k =~ s/^nocf$/nochkcf/;
        $k =~ s/^nodep$/nochkdep/;
        $k =~ s/^src$/chksrc/;
        $k =~ s/^reb$/chkreb/;
        any { $_ eq $k } qw(nochkcf chksrc chkreb nochkdep update) or next;
        $to_add->{$k} = $to_add->{$k} ? 0 : 1;
        $flag{v} and echo "$k set to $to_add->{$k}\n";
    };
    
    /^listpath$/ and do {
        foreach my $i (sort keys  %path) {
            echo("%-20s = %s\n", $i, $path{$i});
        }
    };

    /^listrep$/ and do {
        foreach my $rep (@{$urpmchecker->{repository}}) {
            echo "$rep->{origin} [$rep->{level}] ";
            if ($rep->{check}) {
                foreach my $f (qw(update nochkdep nochkcf chksrc chkreb)) {
                    echo "$f=$rep->{$f} ";
                }
            } else {
                echo "nocheck";
            };
            echo "\n";
        }
    };
       
    /^listurpmi$/ and do {
        foreach my $i (glob("/var/lib/urpmi/hdlist*.cz")) { echo "$i\n" };
    };

    /^-a$|^arch$/  and do {
        @ARGV and $urpmchecker->{arch} = shift @ARGV;
        $flag{v} and echo "Arch is now $urpmchecker->{arch}\n";
    };
    
    /^-b$|^batch$/ and do {
        my $file = shift @ARGV;
        our $o;
        $flag{v} and echo "Read batch $file\n";
        eval(cat_($file));
        %maint = get_maintener();
        $urpmchecker->{arch} = $o->{arch};	
        foreach my $r (@{$o->{repository}}) {
            my $nextlevel = 1;
    	    foreach my $i (@{$r->{list}}) {
	    	    $urpmchecker->add($i, { nochkdep => $r->{nochkdep}, chksrc => $r->{chksrc},
		                                nochkcf => $r->{nochkcf},  check => $r->{check},
                                        nextlevel => $nextlevel, update => $r->{update},
                                        chkreb => $r->{chkreb}, }
                                        ) or warn "can't add $i, aborting\n";
    	        $nextlevel = 0;
            }
	    }
    };

    /^add$/ and do {
        my $new_add = { %$to_add };
        GetOptions(
            'check!'             => \$new_add->{check},
            'nodep|nochkdep!'    => \$new_add->{nochkdep},
            'src|chksrc!'        => \$new_add->{chksrc},
            'reb|chkreb!'        => \$new_add->{chkreb},
            'nocf|nochkcf!'      => \$new_add->{nochkcf},
            'update!'            => \$new_add->{update},
        );
        foreach my $i (@ARGV) {
            $flag{v} and echo "Add $i\n";
            $path{$i} and $i = $path{$i};
            $urpmchecker->add($i, $new_add) or print STDERR "Can't add '$i'\n";
            $new_add->{nextlevel} = 0;
        }
    };

    /^set$/ and do {
        my $rep;
        GetOptions(
            'check!'             => \$rep->{check},
            'nodep|nochkdep!'    => \$rep->{nochkdep},
            'src|chksrc!'        => \$rep->{chksrc},
            'reb|chkreb!'        => \$rep->{chkreb},
            'nocf|nochkcf!'      => \$rep->{nochkcf},
            'update!'            => \$rep->{update},
        );
        foreach my $repository (@{$urpmchecker->{repository}}) {
            $repository->{origin} =~ /$ARGV[0]/ or next; 
            foreach my $i (keys(%$rep)) {
                defined $rep->{$i} and $repository->{$i} = $rep->{$i}; 
            }
        }
    };

    /^parse$/ and do {
        $flag{v} and echo "Parsing rpms headers\n";
        my $repository = $urpmchecker->{repository};
        my $arch = $urpmchecker->{arch};
        $urpmchecker = urpmchecker->new;
        $urpmchecker->{repository} = $repository;
        $urpmchecker->{arch} = $arch;
        $urpmchecker->parse_list;
    };

    /^reset$/ and do {
        $urpmchecker = urpmchecker->new;
        $flag{v} and echo "Reset done\n";
    };

    #defined $urpmchecker or "you should parse before\n";
    /^analyze$/ and do {
        $flag{v} and echo "Analizing...\n";
        $urpmchecker->{error} = undef;
        $urpmchecker->analyze;
        $_ = "listerror";
    };

    # parse search option
    my %search_options = (
        src => 1,
        noarch => 1,
        ch => 'name',
    );
    GetOptions(
        'src!' => \$search_options{src},
        'noarch!' => \$search_options{noarch},
        'a|arch=s' => \$search_options{arch},

        'all!' => \$search_options{allowall},
        
        's=s' => \$search_options{sourcerpm},
        'p=s' => \$search_options{packager},
        'f=s' => \$search_options{fullname},
        'n=s' => \$search_options{name},
        'sname=s' => \$search_options{sname},
    );

    
    /^listerror$/ and do {
        $search_options{allowall}=1;
        @{$search_options{list}}=keys %{$urpmchecker->{error}};
        echo "Have error:\n";
        foreach my $i (search(%search_options)) {
            defined $urpmchecker->{error}{$i} or next;
            echo $urpmchecker->pkgname($i)."\n";
        } 
    };

    /^noerrors$/ and do {
        $search_options{allowall}=1;
        my @list=(0 .. $#{$urpmchecker->{urpm}{depslist}});
        @ARGV and @list=search(@ARGV);
        foreach my $i (search(%search_options)) {
            $urpmchecker->{error}{$i} or echo $urpmchecker->pkgname($i)."\n";
        }
    };

    /^finderror$/ and do {
        $search_options{allowall}=1;
        my @list=search(%search_options);
        foreach my $id (@list) {
		    exists $urpmchecker->{error}{$id} or next;
            any { $_->{type} =~ /^$ARGV[0]$/ } @{$urpmchecker->{error}{$id}{err}} and show_error($id);
        }
    };
        
    /^error$/ and do {
        $search_options{allowall}=1;
        show_error(search(%search_options));
    };
        
    /^search$/ and do {
        foreach my $i (search(%search_options)) { echo "$i ".$urpmchecker->pkgname($i)."\n" };
    };

    /^pkgneed$/ and do {
        echo "\n";
        foreach (search(%search_options)) {
            my @res;
            $urpmchecker->pkg_need({ rpm => $_ },
            sub { my ($o) = @_;
                #any { $o->{rpm} eq $_ } @{$o->{o_select}} and return;
                push @res, '| ' x $o->{level} . ' \\_';
                push @res, defined($o->{rpm}) ? $urpmchecker->pkgname($o->{rpm}) : "Not Found:";
                push @res, " {$o->{dep}}\n";
                push @res, ('  ' x $o->{level} . '   From: '.join('|', map { $urpmchecker->pkgname($_) } @{$o->{possible}})."\n") if defined($o->{possible});
            }    
            );
            print @res;
        }
    };

    /^move$/ and do {
        my @l=search(%search_options);
        foreach my $i (@l) { 
            echo '* ' . $urpmchecker->pkgname($i) . "\n";
        }
        echo "Will break:\n";
        foreach my $i ($urpmchecker->try_move(@l, $ARGV[0])) {
            echo '- ' . $urpmchecker->pkgname($i) . "\n";
        }
    };
    
    /^qi$/ and do {
            echo join("\n", map { $urpmchecker->rpmqi($_) } search(%search_options));
    };
    
    /^ql$/ and do {
        foreach (search(%search_options)) {
            echo '* '.$urpmchecker->pkgname($_)."\n";
            echo "$_\n" foreach $urpmchecker->rpmql($_);
        }
    };

    /^req$/ and do {
        foreach my $i (search(%search_options)) {
            echo '* '.$urpmchecker->pkgname($i)."\n";
            foreach my $j ($urpmchecker->rpmreq($i)) {
                echo ' - '.$urpmchecker->pkgname($j)."\n";
            }
        }
    };

    /^dep$/ and do {
        foreach my $u_rpm (search(%search_options)) {
            echo '* '.$urpmchecker->pkgname($u_rpm)."\n";
            foreach my $i ($urpmchecker->rpmdep($u_rpm)) { echo " - $i\n" };
        }
    };

    /^findloop$/ and do {
        foreach my $u_rpm (search(%search_options)) {
            my @loop = $urpmchecker->find_loop_req($u_rpm);
            @loop or next; 
            echo '* '.$urpmchecker->pkgname($u_rpm)."\n";
            foreach my $i (@loop) {
                echo ' - '.$urpmchecker->pkgname($i)."\n";
            }
        }
    };
    
    /^findfile$/ and do {
        $search_options{allowall}=1;
        my %l=$urpmchecker->find_file(@ARGV);
        @{$search_options{list}} = keys(%l);
        foreach my $u_rpm (search(%search_options)) {
            echo '* '.$urpmchecker->pkgname($u_rpm)."\n";
            foreach my $i (@{$l{$u_rpm}}) { echo "$i\n" };
        }
    };
 
    /^help$/ and interactive_help(@ARGV);
    /^quit$/ and exit();

}

while (local $_ = shift @ARGV) {
    /^-p$/ and do { $flag{p} = shift @ARGV; next };
    /^-n$/ and do { $flag{n} = shift @ARGV; next };
    /^-v$/ and do { $flag{v} = 1; next };
    /^-output$/ and do { push @output, shift(@ARGV); next }; 
        
    /^-h$|^--help$/  and help();
    /^--ho$/ and help_output(@ARGV);

    /^-i$/ and do { $flag{interactif} = 1; next };
    m!^/$! and do { $to_add->{nextlevel}=1; next };
    /^--/ and do { parse_cmdline($_); next };  
    /^-/ and do { parse_cmdline($_, shift(@ARGV)); next };
    parse_cmdline('add', $_);
    $to_add->{nextlevel}=0;
}

# Nothing to check, checking all
any { $_->{check} and 1 } @{$urpmchecker->{repository}} or do {
    foreach my $repos (@{$urpmchecker->{repository}}) { $repos->{check} = 1 };
};

$flag{interactif} and do {
    use Term::ReadLine;
    Getopt::Long::Configure("pass_through");
    $to_add = { 
        check => 1,
        nextlevel => 1,
        update => 0,
        chkreb => 0,
        chksrc => 0,
        nochkcf => 0,
        nochkdep => 0,
    };
    $flag{v} = 1;
    my $term =  Term::ReadLine->new('distlint');
    while (1) {
        $term->addhistory(local $_ = $term->readline('> '));
        foreach (split(';')) {
            my @arg = split;
            parse_cmdline(@arg);
        }
    }
};

# Nothing on list, hehe read this !
scalar @{$urpmchecker->{repository}} or help();

$flag{v} and echo STDERR "[parsing]\n";
$urpmchecker->parse_list;

$flag{v} and echo STDERR "[chk dep]\n";
$urpmchecker->analyze_dep;
$flag{v} and echo STDERR "[chk cfs]\n";
$urpmchecker->analyze_files;
$flag{v} and echo STDERR "[chk src]\n";
$urpmchecker->analyze_src;

my @date = localtime(time);

@output or @output = qw/std/;
local $_;
foreach (@output) {
printf("Output at %02d/%02d/%04d %02d:%02d:%02d\n\n",
	$date[4] + 1,
	$date[3],
	$date[5] + 1900,
	$date[2],
	$date[1],
	$date[0]
	);
/^std$/ and do {
    my %report;
	foreach my $id (keys %{$urpmchecker->{error}}) {
		exists $urpmchecker->{error}{$id} or next;
		my $idinfo = $urpmchecker->{error}{$id};
        my $packager = $maint{$idinfo->{sname}} ? $maint{$idinfo->{sname}} : $idinfo->{packager}; 
		
		$flag{p} && $packager !~ /$flag{p}/ and next;
		$flag{n} && $idinfo->{fullname} !~ /$flag{n}/ && $idinfo->{sourcerpm} !~ /$flag{n}/ and next;
		my $msg;
		my %tmp;
		$msg .= " $idinfo->{fullname} ($idinfo->{sourcerpm}: $idinfo->{sname}) [$idinfo->{level}]\n";
		foreach my $i (@{$idinfo->{err}}) {
			push @{$tmp{$i->{type}}{sprintf("%s %s", $i->{msgsum}, $i->{fullname})}}, $i->{msg};
		}	
		foreach my $type (keys %tmp) {
			foreach my $fullname (keys %{$tmp{$type}}) {
				my $n = scalar @{$tmp{$type}{$fullname}};
				if ($n > 3) {
					$msg .= "    $type: (x$n) $fullname\n";
				} else {
					$msg .= join("", map { "    $type: $_ $fullname\n" } @{$tmp{$type}{$fullname}});
				}
			}
		}
		push @{$report{$packager}}, $msg;
    }
    foreach my $name (sort keys %report) {
        echo "$name:\n";
        echo join("", @{$report{$name}}) . "\n";
    }
    };
    
/^count$/ and do {
    foreach my $id (keys %{$urpmchecker->{error}}) {
		exists $urpmchecker->{error}{$id} or next;
		my $idinfo = $urpmchecker->{error}{$id};
          my %report;
          foreach my $i (@{$idinfo->{err}}) {
              $report{$i->{type}}++;
          }
        echo "$idinfo->{fullname} ";
        echo join('; ', map { "$_:$report{$_}" } (keys(%report)));
        echo "\n";
    }
    };
}
