# Graph for tla
# Copyright (C) 2005 Thomas Gerigk
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
# or look under http://www.gnu.org/licenses/licenses.html#GPL

use strict;

package ArchWay::Util::RenderNodes;
use base qw(ArchWay::Util::RevisionNodes);

use ArchWay::Util::RevisionCollector;

# RenderNodes->new($rev_nodes)
#
sub new {
	my $class = shift;
	my $RevCol = shift;

	my $self = $class->SUPER::new();

	$self->{"RevCol"} = $RevCol;

	# how of has which version which version merged
	$self->{"relation"} = {};
	# in which row to place a (rendered) Node
	$self->{"y"} = {};
	# additional construction data
	$self->{"kon"} = {};
	$self->{"maxpos"} = 0;
	$self->{"letter"} = {};

	$self->init();

	return $self;
}


# pre and post define order, in which to render
#  revisions of a version
# from and to in RevNode defines where to draw lines
#
#


# copy render-data from RevNodes
### possibly place here summarizing:
### "patch-x" to "patch-y" => "patch-x ... patch-y"
sub _copy {
	my $self = shift;
	
	my $revnodes = $self->{"RevCol"};
	my $revision_nodes = $revnodes->{"nodes"};
	my $render_nodes = $self->{"nodes"};
	
	foreach my $t1v (keys %{$revision_nodes} ) {
		foreach my $t1r (keys %{$revision_nodes->{$t1v}} ) {
			$render_nodes->{$t1v}->{$t1r}->{"defined"} = 1;
###		print "$t1v -- $t1r\n";
		}
	}
}

sub _link_by_name {
	my $self = shift;
	
	my $nodes = $self->{"nodes"};
	
	for my $t1v (sort keys %{$nodes} ) {
		for my $t1r (sort keys %{$nodes->{$t1v}} ) {
			
			if ( defined $nodes->{$t1v}->{$t1r}->{"pre"} ) {
				print "Error linking RenderNodes: \n";
				print "	 Trying to link " . $t1v . "--" . $t1r . "\n" ;
				print "	 but it is linked\n";
				next;
			}
			
			my $t2r = $self->_find_next_pre_rev($t1v, $t1r);
			
			if ( !defined $t2r ) {
				next;
			}
			$nodes->{$t1v}->{$t1r}->{"pre"}=$t1v . "--" . $t2r;
			$nodes->{$t1v}->{$t2r}->{"next"}=$t1v . "--" . $t1r;
###		print "$t1v -- $t2r .. $t1r\n";
		}
	}
}

# int to letter
# minimum int is 1. below breaks
sub i2l {
	my $self = shift;
	my $i = shift;

	my $c = "";
	my $t = 0;

	do {
		$t = $i % 26;
		$i = ($i - $t) / 26;
		if ( ($t == 0) && ($i > 0) ) {
			$t = 26;
			$i--;
		};
		$c = chr(ord("A") + $t - 1) . $c;
	} until ($i <= 0);
	return $c;
}



sub _assign_letter {
	my $self = shift;
	
	my $nodes = $self->{"nodes"};
	my $letter = $self->{"letter"};
	my $kon = $self->{"kon"};

	my $t1v;
	my $tl;

	my $curcounter = 1;

	for $t1v ( sort keys %{$nodes} ) {
		$tl = $self->i2l($curcounter);
		$curcounter++;
		$letter->{$tl} = $t1v;
		$kon->{$t1v}->{"letter"} = $tl;
###	print "$tl = $t1v\n";
	}
}


sub _calc_y {
	my $self = shift;
	
	my $revnodes = $self->{"RevCol"}->{"nodes"};
	my $level = $self->{"y"};
	my $nodes = $self->{"nodes"};
	my $kon = $self->{"kon"};

	my $t1v;
	my $t1r;
	my $t2v;
	my $t2r;
	my $tag;
	my $cl;
	my $ct;

	foreach $t1v ( keys %{$nodes} ) {
		foreach $t1r (keys %{$nodes->{$t1v} } ){
			$level->{$t1v}->{$t1r} = 0;
###		print "$t1v--$t1r set to 0\n";
		}
	}
	
	
	# the lower level the older the revision

	# level 0 is topmost, build down from there
	do {
		$tag = 0;
		foreach $t1v (keys %{$nodes}) {
			foreach $t1r (sort keys %{$nodes->{$t1v}}) {
				$cl = -1;
				# get level of pre-revision
				if (defined $nodes->{$t1v}->{$t1r}->{"pre"}) {
					($t2v, $t2r) = $self->_zerlege_vers_rev( $nodes->{$t1v}->{$t1r}->{"pre"} );
					$ct = $level->{$t2v}->{$t2r} + 0;
					if ($ct > $cl) {
						$cl = $ct;
###			print "$t1v--$t1r :pre : $t2v--$t2r :: $ct\n";
					}
				}
				
				# get merge-ins from revnodes
				if ( defined $revnodes->{$t1v}->{$t1r}->{"from"} ) {
					foreach $t2v ( keys %{$revnodes->{$t1v}->{$t1r}->{"from"}} ) {
						foreach $t2r ( keys %{$revnodes->{$t1v}->{$t1r}->{"from"}->{$t2v}} ) {
							$ct = $level->{$t2v}->{$t2r} + 0;
							if ($ct > $cl) {
								$cl = $ct;
###				print "$t1v--$t1r :from: $t2v--$t2r :: $ct\n";
							}
						}
					}
				}
				
				# current level
				$ct = $level->{$t1v}->{$t1r};
				if ( $cl != -1 
					 && $ct <= $cl ) {
###			print "$t1v--$t1r : new " . ($cl + 1) . " old $ct\n";
					$level->{$t1v}->{$t1r} = $cl + 1;
					$tag = 1;
				}
			}
		}
	} until ( $tag == 0);
	
	# let free-revs fall
	do {
		$tag = 0;
		foreach $t1v (keys %{$nodes}) {
			foreach $t1r (sort keys %{$nodes->{$t1v}}) {
				$cl = undef;
				# get level of post-revision
				if (defined $nodes->{$t1v}->{$t1r}->{"next"}) {
					($t2v, $t2r) = $self->_zerlege_vers_rev( $nodes->{$t1v}->{$t1r}->{"next"} );
					$ct = $level->{$t2v}->{$t2r} + 0;
					if (!defined $cl
						|| $ct < $cl) {
						$cl = $ct;
###			print "$t1v--$t1r :next: $t2v--$t2r :: $ct\n";
					}
				}
				
				# get merge-tos from revnodes
				if ( defined $revnodes->{$t1v}->{$t1r}->{"to"} ) {
					foreach $t2v ( keys %{$revnodes->{$t1v}->{$t1r}->{"to"}} ) {
						foreach $t2r ( keys %{$revnodes->{$t1v}->{$t1r}->{"to"}->{$t2v}} ) {
							$ct = $level->{$t2v}->{$t2r} + 0;
							if (!defined $cl
								|| $ct < $cl) {
								$cl = $ct;
###				print "$t1v--$t1r :to  : $t2v--$t2r :: $ct\n";
							}
						}
					}
				}
				
				# current level
				$ct = $level->{$t1v}->{$t1r};
				if ( defined $cl 
					 && ($ct + 1) < $cl ) {
###			print "$t1v--$t1r : new " . ($cl - 1) . " old $ct\n";
					$level->{$t1v}->{$t1r} = $cl - 1;
					$tag = 1;
				}
			}
		}
	} until ( $tag == 0);
	
	my $min_level;
	my $max_level;
	my $clevel;
	foreach $t1v (keys %{$level}) {
		$min_level = undef;
		$max_level = undef;
		foreach $t1r (sort keys %{$level->{$t1v}}) {
			$clevel = $level->{$t1v}->{$t1r};
###		print "$t1v--$t1r at $clevel\n";
			if ( !defined $min_level
				 || $clevel < $min_level ) {
				$min_level = $clevel;
			}
			if ( !defined $max_level
				 || $clevel > $max_level ) {
				$max_level = $clevel;
			}
		}
		$kon->{$t1v}->{"minlevel"} = $min_level;
		$kon->{$t1v}->{"maxlevel"} = $max_level;
		$kon->{$t1v}->{"height"} = $max_level - $min_level + 1;
	}
}


# _calc_y MUST be called before this
sub _build_relation {
	my $self = shift;
	
	my $revnodes = $self->{"RevCol"}->{"nodes"};
	my $relation = $self->{"relation"};
	my $kon = $self->{"kon"};
	
	foreach my $t1v ( sort keys %{$revnodes} ) {
		$kon->{$t1v}->{"sum"} = 0;
		foreach my $t1r (sort keys %{$revnodes->{$t1v} } ){
			if ( !defined $revnodes->{$t1v}->{$t1r}->{"from"} ) {
				next;
			}
			foreach my $t2v ( sort keys %{$revnodes->{$t1v}->{$t1r}->{"from"} } ) {
				if ( $t1v ne $t2v ) {
					$relation->{$t1v}->{$t2v}+=1;
					$relation->{$t2v}->{$t1v}+=1;
				}
			}
		}
	}
	
	foreach my $t1v ( keys %{$relation} ) {
		foreach my $t2v ( keys %{$relation->{$t1v} } ) {
			$relation->{$t1v}->{$t2v} = $relation->{$t1v}->{$t2v} *
				$kon->{$t1v}->{"height"} * $kon->{$t2v}->{"height"};
			$kon->{$t1v}->{"sum"} = $kon->{$t1v}->{"sum"} + $relation->{$t1v}->{$t2v};
		}
	}
}

sub _dump_relation {
	my $self = shift;
	
	my $relation = $self->{"relation"};
	print "RELATION :\n";
	foreach my $t1v ( keys %{$relation} ) {
		foreach my $t2v ( keys %{$relation->{$t1v}} ) {
			print "  $t1v - $t2v  : " . $relation->{$t1v}->{$t2v} . "\n";
		}
	}
}

sub _calc_x {
	my $self = shift;
	
	my $kon = $self->{"kon"};
	my $relation = $self->{"relation"};

	my $t1v;
	my $t1r;
	my $t2v;
	my $t2r;
	my $t3v;
	my $t3r;
	
	my $max_pos;
	my $min_pos;
	my $maxpos;
	my %curpos;
	my %v_int;
	my %v_ext;
	my $curv;
	my $temp;
	my $pos;
	my $bestval;
	my $bestpos;
	
	
	# init int/ext
	foreach $t1v (keys %{$kon} ) {
		$v_int{$t1v} = 0;
		$v_ext{$t1v} = $kon->{$t1v}->{"sum"};
	}
	
	$min_pos = 0;
	$max_pos = 0;
	
	do {
		$temp = -1;
		$t3v = undef;
		# first search version with strongest binding
		# to already positioned versions
		foreach $t1v ( sort keys %v_ext ) {
			if ( $v_int{$t1v} > $temp ) {
				$temp = $v_int{$t1v};
				$t3v = $t1v;
			}
		}
		# alternatively search version with max binding
		# to not yet positioned versions
		if (!defined $t3v) {
			foreach $t1v ( sort keys %v_ext ) {
				if ( $v_ext{$t1v} > $temp ) {
					$temp = $v_int{$t1v};
					$t3v = $t1v;
				}
			}
		}
		
		# at this point $t3v should be defined
		
		# initialize position-array (ok, it's a hash)
		undef %curpos;
		foreach $temp ( $min_pos..$max_pos ) {
			$curpos{$temp} = 0;
		}
		# throw away all positions in use within level-range of
		#  current version
		foreach $t1v (keys %{$kon} ) {
			if ( !defined $kon->{$t1v}->{"pos"} ) {
				next;
			}
			if ( $kon->{$t1v}->{"minlevel"} > $kon->{$t3v}->{"maxlevel"} ) {
				next;
			}
			if ( $kon->{$t1v}->{"maxlevel"} < $kon->{$t3v}->{"minlevel"} ) {
				next;
			}
			delete $curpos{ $kon->{$t1v}->{"pos"} };
		}

		# evaluate positions
		foreach $t1v (keys %{$relation->{$t3v}} ) {
			if ( !defined $kon->{$t1v}->{"pos"} ) {
				next;
			}
			foreach $pos (keys %curpos ) {
				$curpos{$pos} = $curpos{$pos} + ( abs ( $pos - ( $kon->{$t1v}->{"pos"} ) ) - 1 ) * $relation->{$t3v}->{$t1v};
			}
		}
		
		undef $bestval;
		
		# value of best position
		foreach $pos (keys %curpos) {
			if ( !defined $bestval
				 || $curpos{$pos} < $bestval ) {
				$bestval = $curpos{$pos};
			}
		}
		# delete all worse position
		foreach $pos (keys %curpos) {
			if ( $curpos{$pos} > $bestval ) {
				delete $curpos{$pos};
			}
		}
		
		# select best position
		# if more than one has bestval, then
		# select position next to zero and positive
		undef $bestval;
		undef $bestpos;
		foreach $pos (reverse $min_pos..$max_pos) {
			if (!defined $curpos{$pos}) {
				next;
			}
			if ( !defined $bestval
				 || abs($pos) < $bestval ) {
				$bestval = abs ($pos);
				$bestpos = $pos;
			}
		}
		
		# assign position
		$kon->{$t3v}->{"pos"} = $bestpos;
		
		if ($bestpos == $min_pos) {
			$min_pos = $bestpos - 1;
		}
		if ($bestpos == $max_pos) {
			$max_pos = $bestpos + 1;
		}
		# update v_ext and v_int
		foreach $t1v (keys %v_ext) {
			if (defined $relation->{$t3v}
				&& defined $relation->{$t3v}->{$t1v}) {
				$temp = $relation->{$t3v}->{$t1v};
			} else {
				$temp = 0;
			}
			$v_ext{$t1v} = $v_ext{$t1v} - $temp;
			$v_int{$t1v} = $v_int{$t1v} + $temp;
		}
		delete $v_ext{$t3v};
		delete $v_int{$t3v};
	} until ( ((keys %v_ext) + 0) == 0 );
	
	# adjust positions to range from 0 to max
	if ($min_pos < 0) {
		$min_pos++;
	}
	if ($max_pos > 0) {
		$max_pos--;
	}
	
	$maxpos = $max_pos - $min_pos;
	
	# normalize positions to 0..maxpos
	foreach $t1v ( keys %$kon ) {
		$kon->{$t1v}->{"pos"} = $kon->{$t1v}->{"pos"} - $min_pos;
###	print "$t1v :pos: " . $kon->{$t1v}->{"pos"} . "\n";
	}
	
	$self->{"maxpos"} = $maxpos;
###	   print "maxpos = $maxpos\n";
}


sub init {
	my $self = shift;
#	 print "init begin\n";
#	 print "copying next\n";
	$self->_copy();
#	 print "linking next\n";
	$self->_link_by_name();
#	 print "letters next\n";
	$self->_assign_letter();
#	 print "y next\n";
	$self->_calc_y();
#	 print "relations next\n";
	$self->_build_relation();
#	 print "x next\n";
	$self->_calc_x();
#	 print "init end\n";
}

1;

__END__
