#!/usr/bin/perl
#
# (C) Copyright Markus Weber, 1994. All rights reserved.
#     Permission is granted to use, copy, and modify for non-commercial use.
#

# usage: check-consistency.pl [options]...
# Options:
# archdb=pathname-of-archetype-database		*** not used ***
#	default	./ARCHDB .{dir,pag}
# archetypes=pathname-of-archetypes-file
#	default	$cfdir/share/crossfire/archetypes
# cfdir=pathname-to-crossfire-installation
#	default /opt/cf0901	(hardcoded)
# mapdir=pathname-of-map-directory
#	default $cfdir/share/crossfire/maps
# start-map=map-path-of-starting map
#	default (init in archetypes)

# %% make it a command line option
$debug = 1;

#
#	ARGUMENT PROCESSING
#
# preset options
$cfdir = "/export/home/crossfire/cf-installroot";

# loop thru arg vector
while (@ARGV) {
	$_ = @ARGV[0];
	if (/^archdb=/) {
		($junk,$archdb) = split(/=/,$ARGV[0]);
		shift;
	}
	elsif (/^archetypes=/) {
		($junk,$archetypes) = split(/=/,$ARGV[0]);
		shift;
	}
	elsif (/^cfdir=/) {
		($junk,$cfdir) = split(/=/,$ARGV[0]);
		shift;
	}
	elsif (/^mapdir=/) {
		($junk,$mapdir) = split(/=/,$ARGV[0]);
		shift;
	}
	elsif (/^start-map=/) {
		($junk,$start_map) = split(/=/,$ARGV[0]);
		shift;
	}
	else {
		print "Unknown option $ARGV[0]\n";
		exit;
	}
}

# post-process
$mapdir = "$cfdir/share/crossfire/maps" unless defined($mapdir);
$archetypes = "$cfdir/share/crossfire/archetypes" unless defined($archetypes);
print STDERR "DBG: archetypes=$archetypes\n" if $debug > 5;
print STDERR "DBG: archdb=$archdb\n" if $debug > 5;
print STDERR "DBG: mapdir=$mapdir\n" if $debug > 5;


#
#	INIT ARCHETYPES DATABASE
#
print STDERR "DBG: initializing archetype database...\n" if $debug;
&init_archetypes_database;	# unless $skip_db_init;
print STDERR "DBG: ...done\n" if $debug;

defined($start_map) || die "FATAL: no starting map";
print STDERR "DBG: start_map=$start_map\n" if $debug;


print STDERR "DBG: scanning for archetypes of special interest...\n" if $debug;

while ( ($arch,$type) = each(%ARCHDB) ) {

	next if !defined($type);	# skip if not special

	$_ = $type;			# see below

	if ($type == 41 || $type == 66 || $type == 94) {
		# EXITS: archetypes with exits to other maps
		$EXITS{$arch} = 1;
	}
		# Bad Programming Style Alert. Don't try this at home!
	elsif (/^1[78]$/ || /^2[679]$/ || /^3[012]$/ || /^9[123]$/) {
		# CONNECT: "connected" archetypes,
		# e.g. buttons, handles, gates, ...
		$CONNECT{$arch} = 1;
	}
	if ($type == 85) {
		$SB{$arch} = 1;
	}

}

print STDERR "DBG: ...done.\n" if $debug;

#
#	MAIN LOOP
#

# pathname of start_map is assumed to be absolute (e.g. /village/village
push(@MAPS,$start_map);

while ($map = pop(@MAPS)) {

#	print STDERR "array stack size is $#MAPS\n";
	next if $visited{$map};		# skip if been here before
	$visited{$map} = 1;		# flag it if not

	# skip random maps
	next if ($map =~ m#/!#);

	print STDERR "DBG: visiting $map\n" if $debug;
	#print "visiting $map\n" if $debug;

	#
	# side effect: check_map pushes any (legal) exits found on stack
	#
	&check_map($map);
}

print "Unused archetypes:\n";
foreach $key (sort(keys %ARCHDB)) {
	print "$key\n" if (!defined($USED{$key}))
}



exit;

#
#	++++++++++++++++++++ END OF MAIN ++++++++++++++++++
#

#
# INIT ARCHETYPES DATABASE
#
# store (archname,type) pairs
#
sub init_archetypes_database {
	local($arch_lines,$arches);	# counters
	local($arch,$type,$slaying);	# values
	local($junk);

print STDERR "DBG: opening archetypes: $archetypes\n" if $debug > 5;
	open(ARCHETYPES,$archetypes) || die "can't open $archetypes";

	$arch_lines = 0;
	$arches = 0;
	$type = 0;

	while ( <ARCHETYPES> ) {
		$arch_lines++;
		if (/^Object\s/) {
			($junk,$arch) = split;
			if (!defined($arch)) {
		print STDERR "$archetypes: bad Object, line $arch_lines\n";
			}
		}
		elsif (/^type\s/) {
			($junk,$type) = split;
			if (!defined($type)) {
		print STDERR "$archetypes: bad type, line $arch_lines\n";
			}
		}
		elsif (/^slaying\s/ && $arch eq "map") {
			($junk,$slaying) = split;
			# don't care if defined or not (yet)
		}
		elsif (/^end$/) {
print STDERR "DBG: entered arch=$arch, optional type=$type\n" if $debug > 10;
			next if (!defined($arch));
			# don't care whether $type defined or not
			$ARCHDB{$arch} = $type;
			$arches++;
			$type = 0;
		}
		elsif (/^end\s*$/) {
			print STDERR "DBG: arch $arch is using end with space before newline\n";
			next if (!defined($arch));
			# don't care whether $type defined or not
			$ARCHDB{$arch} = $type;
			$arches++;
			$type = 0;
		}
	}

	#
	# find start map
	# print error message iff "map" arch not found or missing path
	# assign start map (unless pre-defined on cmd line)
	#
	if (!defined($slaying)) {
		print STDERR "***ERROR*** no map object or map path missing\n";
	}
	elsif (!defined($start_map)) {
		$start_map = $slaying;
	}
#print STDERR "DBG: start_map=$start_map\n";

	close(ARCHETYPES);
print STDERR "DBG: closed $archetypes, $arch_lines lines, $arches arches\n"
		if $debug > 5;
}

#
# CHECK MAP FOR ELEMENTARY CONSISTENCY
#

sub check_map {
	local($map) = @_;
	local($arch,$connected,$slaying,$exit,$x,$y, $rx, $ry);
	local($lines,$fullmap);
	local($junk);
	$depth=0;

	# build full pathname (nb: map path starts with /) and open map file
	$fullmap = "$mapdir$map";
	open(MAP,$fullmap) || die "can't open $fullmap";
	print STDERR "DBG: opened $map\n" if $debug > 5;

	$lines = 0;

	while ( <MAP> ) {
		if (/^tile_path_/) {
			($junk,$slaying) = split;
			$_ = "$map $slaying";	# easy matching :-)
			s@^(/.*/)([^/]*)\s([^\./].*)$@\1\2 \1\3@;
			s@^(/.*/)([^/]*)\s(\.\./.*)$@\1\2 \1\3@;
			s@/[^/]*/\.\./@/@g;
			($junk,$exit) = split;
			next if $visited{$exit};

			if ( (! -r "$mapdir$exit")  && ( $exit ne "/!") ) {
				print "ERROR: map $map, arch $arch, line $lines, no such exit $exit ($rx, $ry, to $x, $y)\n";
				next;
			}
			push(@MAPS,$exit);
		}


		$lines++;
		if (/^arch\s/) {
			# Note we have to do some checks here - that is because
			# if an object is inside an object, the value of $arch
			# is clobbered.
			($junk,$arch) = split;
#			if ($SB{$arch}) {
#				print "WARNING: spellbook found at map $map, line $lines, arch $arch\n";
#			}
			if (!defined($ARCHDB{$arch})) {
				print "FATAL: map $map, line $lines, bad archetype: $arch ($rx, $ry)\n";
			}
			$USED{$arch}=1;
			undef($slaying);
			undef($x);
			undef($y);
			undef($rx);
			undef($ry);
			undef($connected);
			$depth++;
		}
		elsif (/^connected\s/) {
			($junk,$connected) = split;
		}
		elsif (/^slaying\s/) {
			($junk,$slaying) = split;
		}
		elsif (/^hp\s/) {
			($junk,$x) = split;
		}
		elsif (/^sp\s/) {
			($junk,$y) = split;
		}
		elsif (/^x\s/) {
			($junk, $rx) = split;
		}
		elsif (/^y\s/) {
			($junk, $ry) = split;
		}
		elsif (/^anim$/) {
			print "Map $fullmap has an anim command in it\n";
		}

		next if !/^end$/;	# continue iff not end of arch
		$depth--;

		#
		# CHECK 2: connect-arch actually connected?
		#	NB: if not, that's perfectly legal, but suspicious
		#
#		if ($CONNECT{$arch}) {
#			if (!$connected) {
#print STDERR "WARNING: map $map, line $lines, arch $arch, not connected\n";
#print "WARNING: map $map, line $lines, arch $arch, not connected\n";
#			}
#			next;
#		}

		next if !$EXITS{$arch};	# continue if not an exit


		#
		# CHECK 3: exit-type arch, but no path given
		#	Presumably the path defaults to the local map,
		#	but in all probability this is an error
		#
		if (!defined($slaying)) {
			if ($x || $y) {
#print STDERR "ERROR: map $map, line $lines, arch $arch, exit defaults\n";
#print "ERROR: map $map, line $lines, arch $arch, exit defaults\n";
			}
			else {
#print STDERR "INFO: map $map, line $lines, arch $arch, no exit defined\n";
#print "INFO: map $map, line $lines, arch $arch, no exit defined\n";
			}
			next;
		}

		#
		# CHECK 4: verify that exit map exists
		#	if not, the game (hopefully!) won't crash, but
		#	chances are this _is_ an error
		#

		#
		# normalize exit path	(FullyQualifiedPathName :-)))
		# (i.e. construct absolute pathname, rooted in CLibDir/maps)
		# E.g.:
		# current map: /village/somewhere
		#	EXIT PATH		YIELDS
		#	/village/building	/village/building
		#	townhouse		/village/townhouse
		#	../island		/island
		#
		$_ = "$map $slaying";	# easy matching :-)
		#	/path/map exit		--> /path/map /path/exit
		s@^(/.*/)([^/]*)\s([^\./].*)$@\1\2 \1\3@;
		#	/path/map ../exit	--> /path/map /path/../exit
		s@^(/.*/)([^/]*)\s(\.\./.*)$@\1\2 \1\3@;
		#	/dir/../		--> /	(all occurances)
		s@/[^/]*/\.\./@/@g;

		($junk,$exit) = split;
#print STDERR "DBG: exit $map $exit\n" if $debug > 5;
#print "exit $map $exit\n";

		#
		# shortcut: if the exit map was already checked, don't bother
		#	stacking it again.
		# %% if a map is never pushed twice in the first place,
		#    the corresponding test in the main loop is probably
		#    in vain.
		#
		next if $visited{$exit};

		#
		# this is check 4, finally.
		# if exit map can't be opened, complain and continue
		#
		if ( (! -r "$mapdir$exit")  && ( $exit ne "/!") ) {
#print STDERR "ERROR: map $map, arch $arch, line $lines, no such exit $exit\n";
print "ERROR: map $map, arch $arch, line $lines, no such exit $exit ($rx, $ry, to $x, $y)\n";
			next;
		}

		#
		# the exit map looks good; push it and continue
		push(@MAPS,$exit);
	}

	close(MAP);
	if ($depth != 0) {
		print "ERROR: map $map, mismatched arch/end, $depth\n";
	}
}

