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

package TreeNode;
# Node of a Tree

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

use Vertex;
use Edge;
use overload
    '""' => 'stringify',
    'fallback' => 1
#    'eq' => '()',
#    'fallback' => undef
;

sub new {
    my ($class, $parent, $rank, %opts) = @_;
    $class = ref($class) if ref($class);
    # $parent is either (normally) the parent of this TreeNode
    # or (when this TreeNode is the dummy node) the host

    # NOTE BELOW: Because of the chicken-egg problem, it's easier
    # not to call moveto() and set_child()
    # First figure out row and column
    my ($host, $rc, $self);
    if ($parent->isa("TreeNode")) { # ordinary node
	$host = $parent->host();
	$rc = $parent->{rowcol};
	$rc = [$rc->[0]+1, $rc->[1]*2+$rank];
	$self = $class->SUPER::new($host,
	    ::rc2xy($host, "TreeNode", @$rc), %opts);
	$self->{parent} = $parent;
	$parent->{child}[$rank] = $self;
	$opts{-status} = "hidden" if $rc->[0] < 1;
	delete $opts{-content};
	$self->{edge} = Edge->new($parent, $self, %opts);
    } else { # the dummy node
	$host = $parent;
	$rc = [-1, 0];
	$self = $class->SUPER::new($host,
	    ::rc2xy($host, "TreeNode", @$rc), %opts);
	$self->{parent} = $self;
    }
    # NOTE ABOVE: Because of the chicken-egg problem, it's easier
    # not to call moveto() and set_child()
    $self->{child} = [];
    $self->{rowcol} = $rc;
    return $self;
}

sub stringify {
    # In accordance with the semantics of Vertex::stringify,
    # the stringify method of every child class of Vertex
    # should return a unique string for identifying $self.
    my ($self) = @_;
    my ($r) = $self->SUPER::stringify();
    return $r ? $r : "(" . join(",", @{ $self->rowcol() }) . ")";
}

sub parent {
    my ($self) = @_;
    return $self->{parent};
}

sub level {
    my ($self) = @_;
    return $self->rowcol()->[0];
}

#sub is_dummy {
#    my ($self) = @_;
#    return $self->parent() eq $self;
#}
#
#sub is_root {
#    my ($self) = @_;
#    return $self->parent()->is_dummy();
#}
#
sub child {
    my ($self, $index, $child) = @_;
    if ($#_ >= 2) {
	croak "you probably wanted to call set_child()?";
    } elsif ($#_ >= 1) {
	# retrieve the $index-th child
	return $self->{child}[$index];
    } else {
	# retrieve all children (or # of children)
	return wantarray ? @{ $self->{child} } : $#{$self->{child}}+1;
    }
}

sub moveto {
    my ($self, $r, $c) = @_;
    $self->{rowcol} = [$r, $c];
    $self->set_pos(::rc2xy($self->host(), "TreeNode", $r, $c));
    $self->{edge}->set_ends($self->parent(), $self);
    $self->{edge}->configure(-status=>
	$self->level()>=1 ? $self->cget(-status) : "hidden"
    );
}

sub set_child {
    my ($self, $index, $child, %opts) = @_;
    $self->{child}[$index] = $child;
    return unless ref $child;
    $child->{parent} = $self;
    my ($r, $c) = @{ $self->rowcol() };
    $child->moveto($r+1, $c*2+$index) # <-- problematic for non-binary trees
	unless $opts{-nomove};
}

sub rank {
    my ($self) = @_;
    my ($i, $parent);
    $parent = $self->parent();
    return undef unless defined $parent;
    my ($c);
    for ($i=0; $i<=$#{$parent->{child}}; ++$i) {
	if (defined $parent->{child}[$i]) {
	    return $i if $parent->{child}[$i] eq $self;
	    $c .= ",$parent->{child}[$i]";
	} else {
	    $c .= ",undef";
	}
    }
    croak "internal error: inconsistent parent link and child link:" .
	" parent of $self is $parent but children of $parent are $c";
}

sub rowcol {
    my ($self) = @_;
    die "call moveto instead!" if $#_ >= 1;
    return $self->{rowcol};
}

sub configure {
    my ($self, %opts) = @_;
    $self->SUPER::configure(%opts);
#    my ($r, $c) = @{ $self->rowcol() };
    return if not ref $self->{edge};
#    delete @opts{ qw(-shape -size -text -display -content) };
    $opts{-status} = "hidden" if $self->level() < 1;
    delete $opts{-content};
    $self->{edge}->configure(%opts);
}
#
#sub cget {
#    my ($self, $opt_name) = @_;
#    return $self->SUPER::cget($opt_name)->[0]
#	if ($opt_name eq "-sorting_key");
#    return exists $self->{$opt_name} ? $self->{$opt_name} : $self->SUPER::cget($opt_name);
#}

sub search {
    my ($self, $sk_cont, %opts) = @_;
    # $sk_cont is the the search key, not in the form of a node,
    # but in the same form as -content=>...
    my ($c, $h, $focus, $rank, $foc_cont, $focus_status);
    $h = $self->host();
    $focus = $self;
#    $row = $col = 0;
    while (1) {
	$foc_cont = $focus->cget(-content);
	$focus_status = $focus->cget(-status);
	$focus->configure(-status=>"focus", -content=>$sk_cont);
	$h->cget(-canvas)->set_mark(0);
# this code only works for _binary_ trees
	$c = $h->cget(-compare)->($sk_cont, $foc_cont);
	$rank = ($c<=0) ? 0 : 1;
	$focus->configure(-status=>$focus_status, -content=>$foc_cont);
	# termination conditions are different:
	# locating an existing item stops upon finding the key;
	# locating for insertion falls through all the way to a leaf.
	last if $c == 0 and not $opts{-to_leaf};
	last if not ref $focus->child($rank);
	$focus = $focus->child($rank);
#	++$row;
#	$col = $col * $h->cget(-ary) + $rank;
    }
    return ($opts{-to_leaf} or $c == 0) ? $focus : undef;
}

sub adopt_subtree {
    my ($self, $rank, $child) = @_;
    $self->set_child($rank, $child, -nomove=>1);
    # $child will move itself later during traverse()
    return unless ref $child;
    my ($r0, $c0) = @{ $self->rowcol()  };	# 0-th generation
    my ($r1, $c1) = @{ $child->rowcol() };	# new 1-st generation
    my ($r_ofs, $c_ofs) = ($r0+1-$r1, $c0*2+$rank - $c1);
    $child->traverse(sub {
	my ($node) = @_;
	my ($r, $c) = @{ $node->rowcol() };
	($r, $c) = ($r+$r_ofs, $c+$c_ofs*::po2($r-$r1));
	$node->moveto($r, $c);
    }, "pre");
}

sub rotate_cw {
    my ($self) = @_;
    my ($rank, $parent, $promoted, $cn) = (
	$self->rank(), $self->parent(), $self->child(0), $self->host()->cget(-canvas)
    );
    if (not ref $promoted) {
	carp "rotate_cw requires the pivot node to have a left child\n";
	return;
    }
    my ($self_status, $promoted_status) = (
	$self->cget(-status), $promoted->cget(-status)
    );
    $self->configure(-status=>"focus");
    $promoted->configure(-status=>"focus");
    $cn->set_mark(0);
    my ($L, $M, $R) = (
	$promoted->child(0), $promoted->child(1), $self->child(1)
    );
    # note: each of $L, $M, $R could be undef
    my ($row, $col) = @{ $self->rowcol() };
    $parent->set_child($rank, $promoted);
#    $promoted->moveto($row, $col);
    $promoted->set_child(1, $self);
#    $self->moveto($row+1, $col*2+1);
    $promoted->adopt_subtree(0, $L);
    $self->adopt_subtree(0, $M);
    $self->adopt_subtree(1, $R);
    $cn->set_mark(0);
    $self->configure(-status=>$self_status);
    $promoted->configure(-status=>$promoted_status);
    $cn->set_mark(0);
}

sub rotate_ccw {
    my ($self) = @_;
    my ($rank, $parent, $promoted, $cn) = (
	$self->rank(), $self->parent(), $self->child(1), $self->host()->cget(-canvas)
    );
    if (not ref $promoted) {
	carp "rotate_ccw requires the pivot node to have a right child\n";
	return;
    }
    my ($self_status, $promoted_status) = (
	$self->cget(-status), $promoted->cget(-status)
    );
    $self->configure(-status=>"focus");
    $promoted->configure(-status=>"focus");
    $cn->set_mark(0);
    my ($L, $M, $R) = (
	$self->child(0), $promoted->child(0), $promoted->child(1)
    );
    # note: each of $L, $M, $R could be undef
    my ($row, $col) = @{ $self->rowcol() };
    $parent->set_child($rank, $promoted);
#    $promoted->moveto($row, $col);
    $promoted->set_child(0, $self);
#    $self->moveto($row+1, $col*2);
    $self->adopt_subtree(0, $L);
    $self->adopt_subtree(1, $M);
    $promoted->adopt_subtree(1, $R);
    $cn->set_mark(0);
    $self->configure(-status=>$self_status);
    $promoted->configure(-status=>$promoted_status);
    $cn->set_mark(0);
}

sub findmax {
    my ($self) = @_;
    my ($t) = $self;
    my ($c);
    while (ref ($c = $t->child(1))) { $t = $c; }
    return $t;
}

sub traverse {
    my ($self, $func, $order) = @_;

    $order = "in" unless defined $order;
    $func->($self) if $order eq "pre";
    $self->child(0)->traverse($func, $order) if (ref $self->child(0));
    $func->($self) if $order eq "in";
    $self->child(1)->traverse($func, $order) if (ref $self->child(1));
    $func->($self) if $order eq "post";
}

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

1;

