#!/usr/bin/perl
# usage: tb-list [<patch-spec>]
#  lists all patches matching <patch-spec> and other criteria

xxx needs updating for new metadata and new theory

use warnings;
use strict;

use Getopt::Long;
use Topbloke;

#----- option parsing -----

Getopt::Long::Configure(qw(bundling));

our $deleted=0;
our $deleted_only=0;
our $current=0;
our $relatedto;
our $leaves=0;
our $sort='';

GetOptions("d|deleted!" => \$deleted,         # including deleted patches
	   "deleted-only!" => \$deleted_only, # only deleted patches
	   "r|related=s" => sub {             # only patches related to that
	       $relatedto = parse_patch_spec($_[1]);
	   },
	   "c|related-current" => sub {      # only patches related to current
	       my $current_branch = current_branch();
	       die "-c only permitted when on a topbloke branch\n"
		   unless $current_branch->{Kind} =~ m/^(?:tip|base)$/;
	       $relatedto = $current_branch;
	   },
	   "l|last|leaf|leaves" => \$leaves, # only leaf patches
	   "sort=s" => \$sort,
    ) or die "bad options\n";

our $spec;

if (@ARGV==1) {
    $spec = parse_patch_spec($ARGV[0]);
} elsif (!@ARGV) {
} else {
    die "too many arguments\n";
}

our @sort = grep { /./ } split m/,/, $sort;
push @sort, 'topo' if !$spec;
push @sort, 'created';
my $toposort = 0;
foreach $sort (@sort) {
    die "bad sort $sort\n" unless grep { $_ eq $sort } 
        qw(fullname created nick topo);
    $toposort=1 if $sort eq $toposort;
}

#----- list patches -----

our %patches;

foreach_patch($relatedto || $leaves || !$spec ? { } : $spec, 
xxx new api for foreach_patch
	      $deleted || $deleted_only, 
	      [0, !!$leaves, 0, $toposort || !!$relatedto],
	      sub { 
		  my ($patch,$parsedname,@info) = @_;
		  $patches{$patch}{Info} = \@info;
		  $patches{$patch}{ParsedName} = $parsedname;
	      });

#----- selection -----

if ($leaves) {
    debug("leaves");
    foreach my $p (keys %patches) {
	debug("leaves $p");
	my $v = $patches{$p};
	next if $v->{Info}[0]{Deleted};
	foreach my $dep (keys %{ $v->{Info}[1] }) {
	    debug("leaves $p $dep");
	    next unless exists $patches{$dep};
	    $patches{$dep}{NotLeaf} = 1;
	}
    }
}

if ($relatedto) {
    foreach my $p (keys %patches) {
	my $v = $patches{$p};
	# mark Related=1 if any patch matching $relatedto includes us
	foreach my $dep (keys %{ $v->{Info}[3] }) {
	    next unless exists $patches{$dep};
	    my $depv = $patches{$dep};
	    next unless patch_matches_spec($depv->{ParsedName}, $relatedto);
	    $v->{Related} = 1;
	    last;
	}
	if (patch_matches_spec($v->{ParsedName}, $relatedto)) {
	    # if we match $relatedto, mark all our inclusions as Related=1
	    foreach my $dep (keys %{ $v->{Info}[3] }) {
		next unless exists $patches{$dep};
		$patches{$dep}{Related} = 1;
	    }
	    # oh, and mark ourselves as Related=1 too!
	    $v->{Related} = 1;
	}
    }
}

our @output;

foreach my $p (keys %patches) {
    my $v = $patches{$p};
    next if !$deleted && $v->{Info}[0]{Deleted};
    next if $deleted_only && !$v->{Info}[0]{Deleted};
    next if $leaves && $v->{NotLeaf};
    next if $relatedto && !$v->{Related};
    next if $spec && !patch_matches_spec($v->{ParsedName}, $spec);
    push @output, $p;
}

#----- sorting -----

sub sortsub () {
    my $txt = "sub sort_cmp {\n    my \$r;\n";
    debug("@sort");
    my $def_vab;
    $def_vab = sub {
	foreach my $ab (qw(a b)) {
	    $txt .= "    my \$v$ab = \$patches{\$$ab};\n";
	}
	$def_vab = sub { };
    };
    my $by_r = sub {
	$txt .= "    \$r = $_[0];\n    return \$r if \$r;\n";
    };
    my $by_parsed = sub {
	$def_vab->();
	$by_r->("\$va->{ParsedName}{$_[0]} cmp \$vb->{ParsedName}{$_[0]}");
    };
    my %done;
    foreach my $sort (@sort) {
	next if $done{$sort}++;
	if ($sort eq 'fullname') {
	    $by_r->('$a cmp $b');
	} elsif ($sort eq 'created') {
	    $by_parsed->('Date');
	} elsif ($sort eq 'nick') {
	    $by_parsed->('Nick');
	} elsif ($sort eq 'topo') {
	    $def_vab->();
	    foreach my $ix (qw(0 1)) {
		my $ab = (qw(a b))[$ix];
		my $ba = (qw(b a))[$ix];
		my $r = (qw(1 -1))[$ix];
		$txt .= "    return $r if \$v${ab}->{Info}[3]{\$$ba};\n";
	    }
        } else {
	    die $sort;
	}
    }
    $txt .= "    return 0;\n}\n";
    debug("sortsub | $_") foreach split /\n/, $txt;
    return $txt;
}
eval sortsub()." 1;" or die "$@ ?";

@output = sort sort_cmp @output;

#----- printing -----

my $current_branch = current_branch();
my $current_patch = '';
my $ifcurrent;
if ($current_branch->{Kind} eq 'tip') {
    $current_patch = $current_branch->{Fullname};
    $ifcurrent = '>';
} elsif ($current_branch->{Kind} eq 'base') {
    $current_patch = $current_branch->{Fullname};
    $ifcurrent = '#';
}

foreach my $p (@output) {
    my $v = $patches{$p};
    my $pa = $v->{ParsedName};
    my ($msgkind, $msg) = git_get_object("$pa->{Ref}:.topbloke/msg");
    die "$p $msgkind ?" unless $msgkind eq 'blob';
    my $subject =
	$msg =~ m/^Subject:\s*(?:\[[^][]*\]\s*)?(.*\S)\s*$/mi
	? $1 : "[no subject]";	
    printf("%1s%1s %s\@%s/%s/%-20s %s\n",
	   $p eq $current_patch ? $ifcurrent : '',
	   $v->{Info}[0]{Deleted} ? 'D' : '',
	   $pa->{Email}, $pa->{Domain}, $pa->{Date}, $pa->{Nick},
	   $subject)
	or die $!;
}

closeout();
