# 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::RevisionCollector;
use base qw(ArchWay::Util::RevisionNodes);

use Arch::Tree;
use Arch::Storage;
use Arch::Session;
use Arch::Registry;

# no init
sub new {
	my $class = shift;
	
	my $self = $class->SUPER::new();
	
	$self->{"version_log_origin"} = {};
	# 0 = unknown/Tree
	# 1 = local Archive (possibly not up-to-date Mirror)
	# 2 = remote Archive (not implemented) ###
	
	$self->{"known_archives"} = {};
	
	my $register = Arch::Registry->new();
	$self->{"known_archives"} = $register->registered_archives();
	
	return $self;
}


# link the given version--revision with
#  its predecessor.
# !!! does not (berücksichtigen) tags.
sub _find_link_pre_rev {
	my $self = shift;
	my $version = shift;
	my $revision = shift;
	
	my $nodes = $self->{"nodes"};
	my $rrevision = undef;
	my $revision2 = $self->_find_next_pre_rev($version, $revision);
	if ( !defined $revision2 ) {
		return $rrevision;
	}
	if ( defined $nodes->{$version}->{$revision2}->{"next"} ) {
		print "Error linking Nodes in RevisionCollector:\n";
		print "	 Trying to link ";
		print $version . "--" . $revision . "\n";
		print "	 but found " . $version . "--" . $revision2 . "\n" ;
		print "	 is linked to " . $nodes->{$version}->{$revision2}->{"next"};
		print "\n ABORTING linking\n";
		return $rrevision;
	}
	### possibly it might be enough to link via "from" "to"
	### / moeglicherweise ist es besser via "from"/"to" zu verbinden
	### / da hierueber die Linien im Renderer erstellt werden sollen.
	$nodes->{$version}->{$revision}->{"pre"}=$version . "--" . $revision2;
	$nodes->{$version}->{$revision2}->{"next"}=$version . "--" . $revision;
	$rrevision = $version . "--" . $revision2;
	return $rrevision;
}


# returns undef or tagged-of - revision
sub _link_until_tag {
	my $self = shift;
	my $frevision = shift;
	
	my $version;
	my $revision;
	my $nodes = $self->{"nodes"};
	
	while (defined $frevision) {
		
		($version, $revision) = $self->_zerlege_vers_rev($frevision);
		
		if ( defined $nodes->{$version}->{$revision}->{"tagged-of"}	 ) {
			$frevision = $nodes->{$version}->{$revision}->{"tagged-of"};
			return $frevision;
		}
		if ( defined $nodes->{$version}->{$revision}->{"pre"} ) {
			$frevision = $nodes->{$version}->{$revision}->{"pre"};
			next;
		}
		$frevision = $self->_find_link_pre_rev($version, $revision);
	}
	return undef;
}


# take a version and link all revision as far as possible
sub _link_version {
	my $self = shift;
	my $version = shift;
	
	my $revision;
	my $nodes = $self->{"nodes"};
	
	foreach $revision ( keys %{$nodes->{$version}} ) {
		if ( defined $nodes->{$version}->{$revision}->{"tagged-of"}	 ) {
			next;
		}
		if ( defined $nodes->{$version}->{$revision}->{"pre"} ) {
			next;
		}
		$self->_find_link_pre_rev($version, $revision);
	}
}


# start linking with given revision
# and follow tags.
sub _link_follow_tags {
	my $self = shift;
	my $frevision = shift;
	
	while (defined $frevision) {
		$frevision = $self->_link_until_tag($frevision);
	}
	return;	
}


sub _link_after_read_tree {
	my $self = shift;
	my $tree = shift;
	
	my $frevision = $tree->get_revision();
	
	$self->_link_follow_tags($frevision);
	return;	
}


# returns 1 if new revision were added, otherwise 0.
sub _add_version_from_log {
	my $self = shift;
	my $log = shift;
	my $version = shift;
	my $revision = shift;
	
	my $version2;
	my $revision2;
	
	my $tag = 0;
	my $nodes = $self->{"nodes"};
	my $origin = $self->{"version_log_origin"};
	my $frevision = $version . "--" . $revision;
	
	my $header = $log->header("continuation_of");
	if ( defined $header )
	{
		($version2, $revision2) = $self->_zerlege_vers_rev($header);
		if ( !defined $nodes->{$version2}
			 || !defined $nodes->{$version2}->{$revision2}
			 || !defined $nodes->{$version2}->{$revision2}->{"defined"} ) {
			$nodes->{$version2}->{$revision2}->{"defined"} = 1;
			if ( !defined $origin->{$version2}
				 || $origin->{$version2} < 1) {
				$origin->{$version2} = undef;
			}
			$tag = 1;
		}
		
		# revision is tagged from header
		$nodes->{$version}->{$revision}->{"from"}->{$version2}->{$revision2} = 1;
		# (to follow the linear history)
		$nodes->{$version}->{$revision}->{"tagged-of"} = "$header";
		$nodes->{$version2}->{$revision2}->{"to"}->{$version}->{$revision} = 1;
	} else {
		my $header = $log->header("new_patches");
		
		foreach my $elem ( @{$header}) {
			if ($elem ne $frevision) {
				($version2, $revision2) = $self->_zerlege_vers_rev($elem);
				if ( !defined $nodes->{$version2}
					 || !defined $nodes->{$version2}->{$revision2}
					 || !defined $nodes->{$version2}->{$revision2}->{"defined"} ) {
					$nodes->{$version2}->{$revision2}->{"defined"} = 1;
					if ( !defined $origin->{$version2}
						 || $origin->{$version2} < 1) {
						$origin->{$version2} = undef;
					}
					$tag = 1;
				}
				
				# revision merged from elem etc.
				$nodes->{$version}->{$revision}->{"from"}->{$version2}->{$revision2} = 1;
				$nodes->{$version2}->{$revision2}->{"to"}->{$version}->{$revision} = 1;
			}
		}
	}
	$nodes->{$version}->{$revision}->{"defined"} = 2;
	return $tag;
}


#
# collect revisions from TREE.
#
### doesn't care about previous read log
### must be called first (at the moment)

sub read_tree ($;$) {
	my $self = shift;
	my $dir = shift || ".";
	
	my $nodes = $self->{"nodes"};
	my $origin = $self->{"version_log_origin"};
	
	my $t1v;
	my $t1r;
	my $t2v;
	my $t2r;
	
	my $tree = Arch::Tree->new($dir, own_logs => 1);
	my @tree_versions = $tree->get_log_versions;
	
	foreach my $version (@tree_versions) {
		if ( defined $origin->{$version}
			 && $origin->{$version} > 0 ) {
			next;
		}
		$origin->{$version} = 0;
		foreach my $revision ( $tree->get_log_revisions($version) ) {
			
			($t1v, $t1r) = $self->_zerlege_vers_rev($revision);
			
			my $log = $tree->get_log($revision);
			
			$self->_add_version_from_log($log, $t1v, $t1r);
		}
	}
	$self->_link_after_read_tree($tree);
}


#
# dump for test purposes
#
sub _dump {
	my $self = shift;
	
	my $revision_nodes = $self ->{"nodes"};
	foreach my $version ( sort keys %{$revision_nodes} ) {
		foreach my $revision ( sort keys %{$revision_nodes->{$version}} ) {
			print "${version}--${revision}\n";
			
			if (defined $revision_nodes->{$version}->{$revision}->{"pre"}) {
				print "	 previous\n";
				print "		" . $revision_nodes->{$version}->{$revision}->{"pre"} . "\n";
			}
			
			foreach my $version2 ( sort keys %{$revision_nodes->{$version}->{$revision}->{"from"}} ) {
				foreach my $revision2 ( sort keys %{$revision_nodes->{$version}->{$revision}->{"from"}->{$version2}} ) {
					my $temp = $version2 . "--" . $revision2;
					if ( defined $revision_nodes->{$version}->{$revision}->{"tagged-of"}
						 && $revision_nodes->{$version}->{$revision}->{"tagged-of"} == $temp ) {
						print "	 tag-of\n  ";
					}
					print "	  ${version2}--${revision2}\n";
				}
			}
			print "\n";
		}
	}
}

sub _dump_to {
	my $self = shift;
	
	my $revision_nodes = $self ->{"nodes"};
	foreach my $version ( sort keys %{$revision_nodes} ) {
		foreach my $revision ( sort keys %{$revision_nodes->{$version}} ) {
			print "${version}--${revision}\n";
			
			if (defined $revision_nodes->{$version}->{$revision}->{"next"}) {
				print "	 next\n";
				print "		" . $revision_nodes->{$version}->{$revision}->{"next"} . "\n";
			}
			
			foreach my $version2 ( sort keys %{$revision_nodes->{$version}->{$revision}->{"to"}} ) {
				foreach my $revision2 ( sort keys %{$revision_nodes->{$version}->{$revision}->{"to"}->{$version2}} ) {
					my $temp = $version2 . "--" . $revision2;
#					if ( defined $revision_nodes->{$version}->{$revision}->{"tagged-of"}
#						 && $revision_nodes->{$version}->{$revision}->{"tagged-of"} == $temp ) {
#						print "	 tag-of\n  ";
#					}
					print "	  ${version2}--${revision2}\n";
				}
			}
			print "\n";
		}
	}
}


#e.g.:
# A was merged to B, B was merged to C
# This would generate (unnecessary) lines from A to C
# These are removed here.
sub reduce_merges {
	my $self = shift;
	
	my $nodes = $self->{"nodes"};
	
	my $t1v;
	my $t1r;
	my $t2v;
	my $t2r;
	my $t3v;
	my $t3r;
	
	## mark for deletion
	# t1 merged from t2
	foreach $t1v (keys %{$nodes} ) {
		foreach $t1r (keys %{$nodes->{$t1v}} ) {
			if ( !defined $nodes->{$t1v}->{$t1r}->{"from"} ) {
				next;
			}
			
			foreach $t2v (keys %{$nodes->{$t1v}->{$t1r}->{"from"}} ) {
				foreach $t2r (keys %{$nodes->{$t1v}->{$t1r}->{"from"}->{$t2v}} ) {
					if ( $nodes->{$t1v}->{$t1r}->{"from"}->{$t2v}->{$t2r} == 0 ) {
						# link is invalid/marked for deletion
						next;
					}
					if ( !defined $nodes->{$t2v}->{$t2r}->{"from"} ) {
						next;
					}
					if ( defined $nodes->{$t1v}->{$t1r}->{"tagged-of"}
						 && $nodes->{$t1v}->{$t1r}->{"tagged-of"} eq ( $t2v . "--" . $t2r ) ) {
						next;
					}
					
					# t2 merged from t3
					foreach $t3v (keys %{$nodes->{$t2v}->{$t2r}->{"from"}} ) {
						foreach $t3r (keys %{$nodes->{$t2v}->{$t2r}->{"from"}->{$t3v}} ) {
							if ( $nodes->{$t2v}->{$t2r}->{"from"}->{$t3v}->{$t3r} == 0) {
								# invalid
								next;
							}
							if ( defined $nodes->{$t2v}->{$t2r}->{"tagged-of"}
								 && $nodes->{$t2v}->{$t2r}->{"tagged-of"} eq ( $t3v . "--" . $t3r ) ) {
								next;
							}
							
							# has t1 merged from t3 ?
							if ( defined $nodes->{$t1v}->{$t1r}->{"from"}->{$t3v}
								 && defined $nodes->{$t1v}->{$t1r}->{"from"}->{$t3v}->{$t3r} ) {
								$nodes->{$t1v}->{$t1r}->{"from"}->{$t3v}->{$t3r} = 0;
							}
						}
					}
				}
			}
		}
	}
	
	# delete
	foreach $t1v (keys %{$nodes} ) {
		foreach $t1r (keys %{$nodes->{$t1v}} ) {
			if ( !defined $nodes->{$t1v}->{$t1r}->{"from"} ) {
				next;
			}
			foreach $t2v (keys %{$nodes->{$t1v}->{$t1r}->{"from"}} ) {
				foreach $t2r (keys %{$nodes->{$t1v}->{$t1r}->{"from"}->{$t2v}} ) {
					if ( $nodes->{$t1v}->{$t1r}->{"from"}->{$t2v}->{$t2r} == 0 ) {
						delete $nodes->{$t1v}->{$t1r}->{"from"}->{$t2v}->{$t2r};
						delete $nodes->{$t2v}->{$t2r}->{"to"}->{$t1v}->{$t1r};
					}
				}
### this causes bug in _dump()
### commented out it can cause bugs otherwise?
				if ( (( keys %{$nodes->{$t1v}->{$t1r}->{"from"}->{$t2v}} ) +0) == 0 ) {
					delete $nodes->{$t1v}->{$t1r}->{"from"}->{$t2v};
				}
				if ( (( keys %{$nodes->{$t1v}->{$t1r}->{"to"}->{$t2v}} ) +0) == 0 ) {
					delete $nodes->{$t1v}->{$t1r}->{"to"}->{$t2v};
				}
			}
			if ( (( keys %{$nodes->{$t1v}->{$t1r}->{"from"}} ) +0) == 0 ) {
				delete $nodes->{$t1v}->{$t1r}->{"from"};
			}
			if ( (( keys %{$nodes->{$t1v}->{$t1r}->{"to"}} ) +0) == 0 ) {
				delete $nodes->{$t1v}->{$t1r}->{"to"};
			}
		}
	}
}


sub add_version_for_read {
	my $self = shift;
	my $version = shift;
	### ensure that version is valid ###
	
	my $nodes = $self->{"nodes"};
	my $origin = $self->{"version_log_origin"};
	
	if ( !defined $origin->{$version} ) {
		$origin->{$version} = undef;
	}
}


# take all versions with origin > 0
# and do a full link for these
# and follow tags
sub _link_full_versions {
	my $self = shift;
	
	my $nodes = $self->{"nodes"};
	my $origin = $self->{"version_log_origin"};
	
	foreach my $version ( keys %{$origin} ) {
		# sort out all possible incomplete version
		if ($origin->{$version} < 1 ) {
			next;
		}
		# link all revisions of this version (as far as possible)
		$self->_link_version($version);
		
		# now follow tags into this version
		foreach my $revision ( keys %{$nodes->{$version}} ) {
			if ( defined $nodes->{$version}->{$revision}->{"tagged-of"} ) {
				$self->_link_follow_tags( $nodes->{$version}->{$revision}->{"tagged-of"} );
			}
		}
	}
}

sub _read_version {
	my $self = shift;
	my $version = shift;

	my $nodes = $self->{"nodes"};
	my $origin = $self->{"version_log_origin"};
	my $tag;
	my $rtag = 0;
	
	my $archive_access = Arch::Session->new();
	$archive_access->working_name($version);
	
	do {
		$tag = 0;
		foreach my $revision ( @{$archive_access->revisions()} ) {
			if (defined $nodes->{$version}
				&& defined $nodes->{$version}->{$revision}
				&& defined $nodes->{$version}->{$revision}->{"defined"}
				&& defined $nodes->{$version}->{$revision}->{"defined"} == 2) {
				# is read.
				next;
			}
			my $frevision = $version . "--" . $revision;
			my $log = $archive_access->get_revision_log($frevision);
			$tag += $self->_add_version_from_log($log, $version, $revision);
			$rtag = 1;
		}
	} while ($tag > 1);
	# repeat until no new versions get added.
	$origin->{$version} = 1;
	return $rtag;
}

sub reread_from_local_archives {
	my $self = shift;
	
	my $known = $self->{"known_archives"};
	my $origin = $self->{"version_log_origin"};
	
	my %local_archives = {};
	my $location;
	my $tag;
	
	do {
		$tag = 0;
		# build reduced hash: keys are LOCAL archives.
		# this does not support stuff like tla: archive-MIRROR or archive-SOURCE
		foreach my $archive (keys %{$known} ) {
			$location = $known->{$archive};
			### This won't work using Windows.
			### Any ideas to make this portable?
			if ( $location =~ /^\// ) {
				$local_archives{$archive} = 1;
			}
		}
		
		# read data 
		foreach my $version (keys %{$origin} ) {
			# are better data loaded yet?
			if ( defined $origin->{$version}
				 && $origin->{$version} > 0) {
				next;
			}
			$version =~ /^(.+?)\/(.*)$/;
			my $archive = $1;
			# is archive local?
			if ( !defined $local_archives{$archive} ) {
				next;
			}
			
			$tag += $self->_read_version($version);
		}
	} while ($tag > 0);
	$self->_link_full_versions();
}


1;

__END__


