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

package Graph;
# Graph

use strict;
use Carp;
use vars qw(@ISA);
@ISA = qw(Collection);

use Collection;
use Vertex;
use DCEdge;
# { package main; require "graph/dfs"; }

# use Data::Dumper;

# We don't distinguish abstract graphs from geometric graphs.
# Every graph is given the DCEL structure. This makes the logic
# of coding easier (at the cost of slight increase in running time).
# Every graph is directed, and every edge is paired. Artificial
# edges are hidden.

sub new {
    my ($class, %opts) = @_;
    $class = ref($class) if ref($class);
    my ($init_data) = delete $opts{-init_data};
    my ($operation) = delete $opts{-operation};
    my ($t) = delete $opts{-type};
    croak "'Graph' code does not know how to process '$t' data\n"
	unless ($t eq 'graph' or $t eq 'points');
    my ($self) = $class->SUPER::new(%opts);

    my (@v_names, $k, $s);

    if ($t eq 'points') {
	my ($n, $i, $name) = $#$init_data;
	$name =
	($n < 26) ?
	    sub { return chr(ord('A')+$_[0]); }
	:
	    sub { 'V' . $_[0] }
	;
	$k = 0;
	$init_data = { map { $name->($k++) => {-pos=>$_} } @$init_data };
    }
    # When vertices and edges are refered to in a text file, names are
    # used. Inside a program, however, they seem to be more naturally
    # refered to as objects (blessed references). But don't go too far
    # and use objects as hash keys. You would get strings as return
    # values when using operator "keys" on such a hash. It does not help
    # reducing indirection (even if one manages to get eval work) and it
    # makes debugging less convenient. So we decide to make -name an
    # option instead of making it the key, and create a temporary table
    # mapping names to objects during Graph creation.

    # create vertices
    my ($n2V);	# table of (vertex) names to vertex objects
    @v_names = sort keys %{ $init_data };
#    $self->{"#Vertices"} = [];
    foreach $s (@v_names) {
	my (%v_opt) = map { /^-\w+$/ ? ($_=>$init_data->{$s}{$_}) : () }
	    keys %{$init_data->{$s}};
	$n2V->{$s} = $self->v_new($s, %v_opt);
#	push @{ $self->{"#Vertices"} }, $n2V->{$s};
    }
    $self->{-vertices} = $n2V;	# convenience, read-only configuration variable

    # create edges
    my ($n2E);	# table of (vertex) names to edge objects
    my (@nbr);
    foreach $s (@v_names) {
	@nbr = sort grep { not /\W/ } keys %{$init_data->{$s}};
	foreach $t (@nbr) {
	    $n2E->{$s}{$t} = DCEdge->new($n2V->{$s}, $n2V->{$t},
		-weight=>$init_data->{$s}{$t}, -text=>$init_data->{$s}{$t},
		-directed=>$self->cget(-directed)
	    );
	}
	$self->{"#OneEdge"} = $n2V->{$s}->{"#OneEdge"} =
	    $n2E->{$s}{$nbr[0]} if $#nbr>=0;
    }

    $self->dcelify($n2V, $n2E);

    # End of naming scheme conversion. From now on, vertices and
    # edges are refered to as objects (blessed references). Code
    # readers are reminded to think in object terms.

    return $self;
}

sub dcelify {
# build Doubly Connected Edge List pointers
    my ($self, $n2V, $n2E) = @_;
    my ($s, $t);

    # pass one: verify symmetry and identify twins
    foreach $s (keys %$n2E) {
	foreach $t (keys %{ $n2E->{$s} }) {
	    if (not exists $n2E->{$t}{$s}) {
		$n2E->{$t}{$s} = DCEdge->new($n2V->{$t}, $n2V->{$s}, -arrow=>"last");
		if ($self->cget(-directed)) {
		    $n2E->{$t}{$s}->phantomize();
		} else {
		    warn "one way edge detected";
		    $n2E->{$t}{$s}->configure(-status=>"alert", -directed=>1);
		}
	    }
	    $n2E->{$s}{$t}->twin($n2E->{$t}{$s});
	    $n2E->{$t}{$s}->twin($n2E->{$s}{$t});
	    my ($w) = $n2E->{$t}{$s}->cget(-weight);
	    if (not $self->cget(-directed)) {
		if (not defined $w or $w != $n2E->{$s}{$t}->cget(-weight)) {
		    warn "$s-$t is different from $t-$s in an undirected graph";
		    $n2E->{$t}{$s}->configure(-status=>"alert",
			-arrow=>"last", -directed=>1);
		}
	    }
	}
    }

    # pass two: sort edges around each vertex
    foreach $s (keys %$n2V) {
	my ($neighbor);
	my ($src_pos) = $n2V->{$s}->pos();
	foreach $t (keys %{ $n2E->{$s} }) {
	    my ($tgt_pos) = $n2V->{$t}->pos();
	    my ($a) = $tgt_pos - $src_pos;
	    push @$neighbor, {name=>$t, angle=>atan2($a->[1], $a->[0])};
	}
	next unless $#$neighbor >= 0;
	$neighbor = [ map { $_->{name} }
	    sort { $a->{angle} <=> $b->{angle} } @$neighbor
	];
	push @$neighbor, $neighbor->[0];
	my ($i);
	for ($i=0; $i<$#$neighbor; ++$i) {
	    my ($edge) = $n2E->{$s}{$neighbor->[$i]};
	    my ($prev) = $n2E->{$neighbor->[$i+1]}{$s};
	    $edge->prev($prev);
	    $prev->next($edge);
	}
    }

#my ($e, $k);
#foreach $e ( @{ $self->{"#EdgeList"} } ) {
#    print "[$e]\n";
#    foreach $k (keys %{ $e->{adj} }) {
#	print "    $k: $e->{adj}{$k}\n";
#    }
#}
}

sub v_new {
    my ($self, $name, %opts) = @_;
    %opts = ( %{ $self->cget(-node_opts) }, %opts );
    # as always, the host should take care of prepending %opts with -node_opts
    my ($pos) = Vector2->new(@{ delete $opts{-pos} });
    my ($v) = Vertex->new($self, $pos, -name=>$name, %opts);
#    $v->configure(-text=>$v->cget(-display)->($v)) unless defined $opts{-text};
    return $v;
}

# sub e_new {
#     my ($self, $src, $tgt, %opts) = @_;
#     my ($e1, $e2);
#     $e1 = DCEdge->new($src, $tgt, -host=>$self, -arrow=>"last", %opts);
#     return $e1 if $self->{-directed};
#     $e2 = DCEdge->new($tgt, $src, -host=>$self, -arrow=>"last", %opts);
#     return wantarray ? ($e1, $e2) : $e1;
# }

sub one_edge {
    my ($self, $v) = @_;
    return $#_ >= 1 ? $v->{"#OneEdge"} : $self->{"#OneEdge"};
}

sub edges_around {
    my ($self, $v) = @_;
    my ($e, @s, $start);
    $e = $self->one_edge($v);
    return () unless $e;
    croak "broken 'OneEdge' on vertex $v" unless $e->source() eq $v;
    $start = $e;
    do {
	push @s, $e unless $e->is_phantom();
	$e = $e->prev()->twin();
    } while ($e ne $start);
    return @s;
}

sub destroy {
    my ($self) = @_;
    ::pfs($self, $self->cget(-canvas),
	-priority=>"sbs",
	-on_vertex=>sub { $_[0]->destroy(); },
	-on_edge=>sub { $_[0]->destroy(); },
    );
    undef $self;
}

if ($0 =~ /Graph.pm$/) {
# being tested as a stand-alone program, so run test code.

require "utilalgo";
my ($mw, $ctrl, $can);
$mw = MainWindow->new(-title=>"main_test");
# $can->{main} = gen_can($mw, undef, -elevation=>1, -maxlevel=>3);
$can->{main} = gen_can($mw, undef, -elevation=>2, -maxlevel=>3);
$can->{pq} = gen_can($mw, "Fringe (Frontier)",  -elevation=>1, -maxlevel=>3);
$ctrl = gen_ctrl($mw, $can);
my ($g) = Graph->new(-canvas=>$can->{main}, %{ do "data/trc.gr" });

sub disp_vert_val {
    my ($v, $val) = @_;
    $v->configure(-text=>"$v\n$val");
}

$::Config->{Graph} = {
};

#::dfs($g);

# Theoretically, Dijkstra's single-source-shortest path algorithm should
# not be applied to graphs like trc.gr which has edges with negative weights.
# But we are lucky with this particular example :-)
{ package main; require "graph/pfs"; }
::pfs($g, $can->{pq}, -start=>"lin", -priority=>"dijk", -on_vertex=>\&disp_vert_val);

#{ package main; require "graph/flwa"; }
#::flwa($g);

$ctrl->configure(-recorder=>0);

my ($v);
$v = $g->one_edge->source();
print "$v: ", join(",", map {$_->target()} $g->edges_around($v)), "\n";

#my ($rc, $s);
#$rc = $can->{pq}{SubWidget}{scrolled};
#foreach $s (@{ $rc->{"#history"} }) {
#    foreach my $l (@{ $s->{mark} }) {
#	printf "%4d", $l;
#    }
#    print " | ";
#    foreach my $l ($rc->relative_mark($s->{mark})) {
#	printf "%4d", $l;
#    }
#    print "\n";
#}

# $can->{main}->set_mark(1);
$ctrl->configure(-recorder=>0);
# If the canvas refuses to show any change, remember to verify that:
# - set_mark() was called at least once
# - -recorder is set to zero before entering MainLoop
# Failing to do either of the above will result in a mysterious bug
# that takes days to figure out !@#$%
Tk::MainLoop();

}

1;

