package A2B::Tools;

use A2B::Table;
use Heap::Fibonacci;
use A2B::Path;
use A2B::PartialPath;
use A2B::Tool;

use strict;
use vars qw(@ISA $ELEMENT_TYPE $DEBUG);

@ISA = qw(A2B::Table);

$ELEMENT_TYPE = "A2B::Tool";

$DEBUG = 0;

#my $c = 0; # this is an `efficiency measure'

sub add_row {
	my $self = shift;
	my $row = shift;
	$self->SUPER::add_row($row);
	my ($from, $to) = ($row->from, $row->to); # this should happen before `bind foreign keys' - but doesn't matter?

	# all this hashing is going to hurt a bit! TODO should probably use
	# some sensible bitmapped method? or at least sets of numbers instead
	# of mime types!

	# update transitive closure of from -> to
	# This is better than the stupid V^3 Floyd-Warshall algorithm!
	# (well, for sparse graphs, e.g. with disjoint sub-graphs, it is)

	# this `transitive closure' does not include A->A mappings at the moment.

	if (!$self->{from_to_closure}{$from}{$to}) {
		for my $f ($from, keys %{$self->{to_from_closure}{$from}}) {
			for my $t ($to, keys %{$self->{from_to_closure}{$to}}) {
	#			$c++;
				$self->{from_to_closure}{$f}{$t} = 1;
				$self->{to_from_closure}{$t}{$f} = 1;
			}
		}
	}

	# keep hashes/sets of direct maps also.  not used yet?

	$self->{from_to_direct}{$from}{$to} = 1;
	$self->{to_from_direct}{$to}{$from} = 1;
}

# efficiency measure getter

#sub c {
#	print $c;
#}

# get all the paths from A to B
# this includes a different paths for each tool that can do the job, and an
# overall `goodness' factor for each path.  The paths are returned sorted by
# the `goodness' factor.  You can specify a limit to the number of paths to be
# returned, which should make things a bit faster.

# it would be nice to abstract this algorithm... not now (or in Perl)

# note: $to is now an array ref

sub grep_paths {
	my ($self, $paths, $grep, $nogrep) = @_;
	$grep ||= [];
	$nogrep ||= [];
	my @out;
	for my $p (@$paths) {
		my $s = $p->as_string(2);
		my $ok = 1;
		for my $g (@$grep) {
			if ($s !~ $g) {
				$ok = 0;
				last;
			}
		}
		if ($ok) {
			for my $g (@$nogrep) {
				if ($s =~ $g) {
					$ok = 0;
					last;
				}
			}
		}
		if ($ok) {
			push @out, $p;
		}
	}
	$paths = \@out;
	return $paths;
}

sub paths {
	my ($self, $from, $to, $number, $grep, $nogrep) = @_;

	my %to_set = map {$_->type, 1} @$to;

	my $from_to_closure = $self->{from_to_closure};

	# < "can we get from here to there?" worker outer and cacher!
	my %cgfhtt_cache;
	my $can_get_from_here_to_there = sub {
		my $from = $_[0]->type;
		$cgfhtt_cache{$from} ||= do {
			my $ans = 0;
			for my $t (@$to) {
				if ($from_to_closure->{$from}{$t->type}) {
					$ans = 1;
					last;
				}
			}
			$ans;
		}
	};
	# >

	unless (&$can_get_from_here_to_there($from)) {
		return [];
	}

	# TODO make the path computer a separate object?  only if going to generalise it

	my $to_types = join "\0", map {$_->type} @$to;

	my $path_computer = $self->{path_computers_from_to}{$from->type}{$to_types} ||= do {
		# this is a priority queue for incomplete paths
		my $pq = Heap::Fibonacci->new;
		$pq->add(A2B::PartialPath->new($from));
		{ paths => [],
		  priority_queue => $pq	# remaining incomplete paths
		};
	};

	# methinks this should be written in Lisp!

	my $pq = $path_computer->{priority_queue};

	my $paths = $path_computer->{paths};
	my $grepped_paths;

	while (1) {
		# get the next partial path off the priority queue¸
		# and deal with it
		my $ppath = $pq->extract_minimum;
		if (!defined $ppath || defined $number) {
			$grepped_paths = $self->grep_paths($paths, $grep, $nogrep);
		}
		if (!defined $ppath || defined $number && @$grepped_paths >= $number) {
			last;
		}
#		print "popped ", $ppath->goodness, "\n" if $DEBUG;

		# is this path at the goal already?

		if ($to_set{$ppath->node->type}) {
#			print "done\n" if $DEBUG;
			# we made it!
			push @$paths, $ppath->path;
		} else {
#			print "not done\n" if $DEBUG;
			# not there yet...
			# get all steps from this node

			my $steps = $self->query(from => $ppath->node);

			# exclude steps that go back to a previous node, and steps that
			# won't lead to the goal (thanks, mr. closure!)

			for my $step (@$steps) {
				my $step_to = $step->to;
				if (! $ppath->contains_node($step_to) and
					$to_set{$step_to->[0]} || &$can_get_from_here_to_there($step_to)) {
						# the closure excludes A->A (null paths), hence the first clause

					# the step is okay - make a new path for it
					my $new_ppath = $ppath->new_step($step);

#					warn "adding ", $step->tool, " ", $new_ppath->goodness, "\n" if $DEBUG;
					$pq->add($new_ppath);
				}
			}
		}
	}

	$paths = $grepped_paths;

	if (!defined $number || @$paths < $number) {
		return $paths;
	}

	return [ @{$paths}[0..$number-1] ];
}

1
