# vim: syntax=perl

# Author:  Chao-Kuei Hung
# For more info, including license, please see doc/index.html

use strict;

sub pfs {
    my ($gr, $pqcan, %opts) = @_;
    my ($prio_name, $prio_func, %to_do, $n, $v, %stat, %value,
	%incoming, $discovery_order, $visit_order);
    my ($prio_table) = {
	bfs	=> '$discovery_order',			# Breadth First Search
	sbs	=> '$n - $discovery_order',		# Stack Based Search
	prim    => '$e->cget(-weight)',		# Prim's spanning tree
	dijk    => '$value{$v} + $e->cget(-weight)',
						# Dijkstra's shorstest path
    };
    $prio_name = ($opts{-priority} or "prim");
    croak "unkown priority '$prio_name'" unless exists $prio_table->{$prio_name};
    $prio_func = $prio_table->{$prio_name};
    %to_do = %{ $gr->cget(-vertices) };
    $v = (delete $opts{-start} or (sort keys %to_do)[0]);
    $v = $to_do{$v};
#    my ($print_func) = sub {
#	my ($x)=@_;
#	return "$x(" . $self->v_get($x, -value) . " " . $self->v_get($x, -parent) . ")";
#    };
    $n = [ %to_do ];
    $n = ($#$n + 1) / 2;
#    $self->v_set($v, -value=>0);
    require Heap;
    my ($seen) = Heap->new(-canvas=>$pqcan, -compare => sub {
	    my ($v, $w) = @_;
	    return $value{$v} <=> $value{$w};
	},
	-node_opts=>{
	#    -shape => "rectangle", -size => [70,50],
	    -display => sub {
		my ($v) = $_[0]->cget(-content);
		return "$v\n$value{$v}";
	    }
	},
    );
    $visit_order = $discovery_order = 1;
    do {
	$v = $to_do{$v};
	$value{$v} = 0;
	$incoming{$v} = "";
	$seen->insert($v);
	while (not $seen->is_empty()) {
	    $v = $seen->remove();
	    delete $to_do{$v};
	    $stat{$v} = "done";
	    $incoming{$v}->configure(-status=>"done") if ref $incoming{$v};
	    $opts{-on_vertex}->($v, $value{$v})
		if ref $opts{-on_vertex} eq "CODE";
	    # see comment in graph/dfs
	    $v->configure(-status=>"done");
	    $v->configure(-text=>"$v\n$visit_order") if $prio_name eq "sbs";
	    ++$visit_order;
	    $gr->cget(-canvas)->set_mark(0);
	    my ($e, $w);
	    foreach $e ($gr->edges_around($v)) {
#		if ($incoming{$v} eq $e->twin()) {
#		    # avoid examining the edge pointing back to the parent
#		    $e->configure(-status=>"discard") if $e->cget(-directed);
#		    next;
#		}
		$w = $e->target();
		my ($new_prio) = eval $prio_func;
		if (not defined $stat{$w}) {
		    $value{$w} = $new_prio;
		    ++$discovery_order;
		    $stat{$w} = "fringe";
		    $incoming{$w} = $e;
		    $seen->insert($w);
		    $w->configure(-status=>"pending");
		    $e->configure(-status=>"pending");
		} elsif ($stat{$w} eq "fringe" and $prio_name ne 'sbs'
		    and $new_prio < $value{$w}) {
		    $value{$w} = $new_prio;
		    $incoming{$w}->configure(-status=>"discard");
		    $incoming{$w} = $e;
		    # fix me! it's O(n) slow...
		    $seen->up(search_heap_for($seen, $w));
		    $w->configure(-status=>"pending");
		    $e->configure(-status=>"pending");
		} else {	# $stat{$w} eq "done"
		    $e->configure(-status=>"discard")
			unless ($incoming{$v} eq $e->twin() and not $e->cget(-directed));
			# avoid painting as back edge the edge pointing
			# back to the parent
		}
		$gr->cget(-canvas)->set_mark(0);
	    }	# foreach $e ($gr->edges_around($v)) ...
	    $gr->cget(-canvas)->set_mark(1);
	}	    # while (not $seen->is_empty()) ...
	$v = (keys %to_do)[0];
    } while ($v);
}

sub search_heap_for {
    my ($h, $v) = @_;
    my ($i, $n, $t);
    $n = $h->size();
    for ($i=1; $i<=$n; ++$i) {
	$t = $h->vc($i);
	return $i if $t eq $v;
    }
    croak "internal error: can't find vertex $v";
    return 1;
}

1;

