#!/usr/bin/perl
# Copyright (c) 1998-2009 Nokia Corporation and/or its subsidiary(-ies).
# All rights reserved.
# This component and the accompanying materials are made available
# under the terms of "Eclipse Public License v1.0"
# which accompanies this distribution, and is available
# at the URL "http://www.eclipse.org/legal/epl-v10.html".
#
# Initial Contributors:
# Nokia Corporation - initial contribution.
#
# Contributors:
#
# Description:
# all variables called *Path* are set up to end with a backslash
# all variables called *Path or *File are stored as absolute (file)paths
# all variables called UpPath* are stored as relative paths
# 
#


use FindBin;		# for FindBin::Bin
use Getopt::Long;

my $PerlLibPath;    # fully qualified pathname of the directory containing our Perl modules

BEGIN {
# check user has a version of perl that will cope
	require 5.005_03;
# establish the path to the Perl libraries: currently the same directory as this script
	$PerlLibPath = $FindBin::Bin;	# X:/epoc32/tools
# do not convert slashes for linux
	if ($^O eq "MSWin32") {
		$PerlLibPath =~ s/\//\\/g;	# X:\epoc32\tools
		$PerlLibPath .= "\\";
	}
}

# Version
my $MajorVersion = 1;
my $MinorVersion = 1;
my $PatchVersion = 1;

use lib $PerlLibPath;
use Defutl;

# THE MAIN PROGRAM SECTION
##########################

{
	my %Options;

	# process the command-line
	unless (GetOptions(\%Options, 'compare', 'remove')) {
		exit 1;
	}
	unless (@ARGV==2) {
		&Usage;
	}

	my $FRZFILE;
	my $GENFILE;

	($FRZFILE,$GENFILE)=(@ARGV);
	
#	check the file exists
	unless (-e $FRZFILE) {
		warn "WARNING: $FRZFILE: File not found - OK if freezing for first time\n";
	}
	unless (-e $GENFILE) {
		die "EFREEZE ERROR: $GENFILE: File not found\n";
	}

#	Read the Frozen .DEF file if it exists
	my @FrzDataStruct;
	my $FrzExportsOn=0;
	if (-e $FRZFILE) {
		eval { &Def_ReadFileL(\@FrzDataStruct, $FRZFILE, $FrzExportsOn); };
		die $@ if $@;
	}

#	Read the New .DEF file
	my @GenDataStruct;
	my $Dummy;
	if ($GENFILE) {
		eval { &Def_ReadFileL(\@GenDataStruct, $GENFILE, $Dummy, ($Options{compare} or $Options{remove})); };
	}
	die $@ if $@;

#	Compare the frozen .DEF data with the new .DEF file
	my (@NewDataStruct, @MissingDataStruct, @BadDataStruct);
	eval { &CompareFrzGenL (\@NewDataStruct, \@MissingDataStruct, \@BadDataStruct, \@FrzDataStruct, \@GenDataStruct, $Options{remove}); };
	die $@ if $@;

#	check for errors
	my $NumRemoved;
	my $Ref;
	my @Errors;
	my $Title='EFREEZE ERROR:';
	if ($Options{compare}) {
		$Title='EFREEZE:';
	}
	if (@MissingDataStruct and $Options{remove}) {
#		Mark missing exports as ABSENT in DEF file
		$NumRemoved=@MissingDataStruct;
		if ($Options{compare}) {
			print "EFREEZE: Marking $NumRemoved Export(s) as ABSENT :\n";
		} else {
			print "EFREEZE: Marking $NumRemoved Export(s) as ABSENT in $FRZFILE :\n";
		}
		foreach (@MissingDataStruct) {
			$$_{Absent} = 1;
			my $Comment='';
			if ($$_{Comment}) {
				$Comment=" ; $$_{Comment}";
			}
			my $r3unused = $$_{R3Unused} ? " R3UNUSED" : "";
			print "  $$_{Name} \@ $$_{Ordinal} NONAME$r3unused$Comment\n";
		}
	}
	elsif (@MissingDataStruct) {
		my $Num=@MissingDataStruct;
		push @Errors, "$Title $FRZFILE: $Num Frozen Export(s) missing from $GENFILE (POSSIBLE COMPATIBILITY BREAK):\n"; 
		foreach $Ref (@MissingDataStruct) {
			my $r3unused = $$Ref{R3Unused} ? " R3UNUSED" : "";
			push @Errors, "  $$Ref{LineNum}: $$Ref{Name} \@ $$Ref{Ordinal} NONAME$r3unused\n";
		}
		push @Errors, "\n";
	}
	if (@BadDataStruct) {
		my $Num=@BadDataStruct;
		push @Errors, "$Title $GENFILE: $Num function(s) exported at wrong ordinal:\n";
		foreach $Ref (@BadDataStruct) {
			my $r3unused = $$Ref{R3Unused} ? " R3UNUSED" : "";
			push @Errors, "  $$Ref{LineNum}: $$Ref{Name} \@ $$Ref{Ordinal} NONAME$r3unused\n";
		}
	}
	if (@Errors) {
		unless ($Options{compare}) {
			die @Errors;
		}
		else {
			print @Errors;
		}
	}

#	Update the frozen .DEF file
	eval { &UpdateFrzFileL(\@NewDataStruct, \@FrzDataStruct, $FRZFILE, $FrzExportsOn, $Options{compare}, $NumRemoved); };
	die $@ if $@;
	

	exit;
}

#######################################################################
# SUBROUTINES
#######################################################################

sub Usage () {

	print(
		"\n",
		"EFREEZE - .DEF file maintenance utility V$MajorVersion.$MinorVersion.$PatchVersion\n",
		"\n",
		"EFREEZE {options} [frozen .DEF file] [new .DEF file]\n",
		"\n",
		"options:   (case-insensitive)\n",
		"  -Compare\n",
		"  -Remove\n",
		"\n"
	);
	exit 1;
}

sub CompareFrzGenL ($$$$$$) {
	my ($NewStructRef, $MissingStructRef, $BadStructRef, $FrzStructRef, $GenStructRef, $remove)=@_;

#	compare the input export data with the frozen data

#	take a copy of the frozen .DEF file structure that we can trash
	my @TmpStruct=@$FrzStructRef;

#	remove any entries not containing export data and get the highest ordinal value used
	my $LastOrdinal=0;
	foreach (@TmpStruct) {
		if ($$_{Name}) {
			if ($LastOrdinal<$$_{Ordinal}) {
				$LastOrdinal=$$_{Ordinal};
			}
			next;
		}
		undef $_;
	}

	my $GenRef;
	my $TmpRef;
	GENLOOP: foreach $GenRef (@$GenStructRef) {
		next unless $$GenRef{Name};		# ignore lines in the .DEF file not containing an export
		foreach $TmpRef (@TmpStruct) {
			next unless defined $TmpRef; # ignore nullified entries in the temporary array
#			does the function name match?
			if ($$GenRef{Name} eq $$TmpRef{Name}) {
#				check the names have the same ordinals
				if ($remove or $$GenRef{Ordinal}==$$TmpRef{Ordinal}) {
					undef $TmpRef;
					next GENLOOP;
				}
#				store exports with the wrong ordinals
				push @$BadStructRef, $GenRef;
				undef $TmpRef;
				next GENLOOP;
			}
#			Absent export?
			if ($$TmpRef{Absent} and $$TmpRef{Ordinal}==$$GenRef{Ordinal}) {
				next GENLOOP;
			}
		}
#		store new exports not found in the frozen .DEF file with the right ordinal value
		$LastOrdinal++;
		$$GenRef{Ordinal}=$LastOrdinal;
		push @$NewStructRef, $GenRef;
	}

#	all the exports left in the frozen .DEF file weren't found
	foreach $TmpRef (@TmpStruct) {
		next unless defined $TmpRef; # ignore nullified entries in the temporary array
		next if $$TmpRef{Absent};	# skip entries marked as ABSENT in the DEF file
		push @$MissingStructRef, $TmpRef;
	}
}

sub UpdateFrzFileL ($$$$$$) {
	my ($NewStructRef, $FrzStructRef, $FILE, $ExportsOn, $Compare, $NumRemoved)=@_;

#	add the exports to the frozen .DEF file text
	unless (@$NewStructRef or $NumRemoved) {
		print "EFREEZE: DEF file up to date\n";
		return;
	}

	my $NumNewExports=@$NewStructRef;
	unless ($Compare) {
# 		abort the operation unless the frozen .DEF file is writeable
		if (-e $FILE and not -w $FILE) {
			die
				"EFREEZE ERROR: Can't append $NumNewExports New Export(s) to $FILE\n",
				"  as file is not writeable.  Check source code control system.\n"
			;
		}
		print "EFREEZE: Appending $NumNewExports New Export(s) to $FILE:\n" if ($NumNewExports);
	}
	else {
		print "EFREEZE: $NumNewExports New Export(s):\n" if ($NumNewExports);
	}

	my @Text;
	my $ExportsDeclared;

#	Process the frozen .DEF file
	if (@$FrzStructRef) { # there is already a frozen .DEF file
		my $FrzRef;

#		get the lines of text from the frozen .DEF file
		foreach $FrzRef (@$FrzStructRef) {
			next if (!$FrzRef);
			if (!defined($$FrzRef{Ordinal})) {
				push @Text, $$FrzRef{Line};
				$ExportsDeclared = 1 if ($$FrzRef{Line} =~ /^\s*EXPORTS\s*(\s+\S+.*)?$/io);
				next;
			}
			my $Comment='';
			if ($$FrzRef{Comment}) {
				$Comment=" ; $$FrzRef{Comment}";
			}
			my $r3unused = $$FrzRef{R3Unused} ? " R3UNUSED" : "";
			my $absent = $$FrzRef{Absent} ? " ABSENT" : "";

			my $data = "";
			if( !($$FrzRef{Name} =~ /^(_ZTI|_ZTV|_ZTT)/))
			{
#				A symbol name with the above pattern indicates that it is a Data symbol.
#				Mark symbols as DATA only when it cannot be found from the name alone (i.e.,
#				explicitly exported data symbols).

				if(($$FrzRef{Data}) and ($$FrzRef{Size}) ){
				$data = " DATA $$FrzRef{Size}";
				}
			}
			push @Text, "\t$$FrzRef{Name} \@ $$FrzRef{Ordinal} NONAME$data$r3unused$absent$Comment\n";
		}

#		strip any blank lines at the end of the frozen .DEF file text
		foreach (reverse @Text) {
			if (/^\s*$/o) {
				$_='';
				next;
			}
			last;
		}

	}

#	Add an EXPORTS section header if there aren't already exports
	unshift @Text, "EXPORTS\n" unless ($ExportsDeclared);

	my $NewRef;
	foreach $NewRef (@$NewStructRef) {
		my $Comment='';
		if ($$NewRef{Comment}) {
			$Comment=" ; $$NewRef{Comment}";
		}
		my $r3unused = $$NewRef{R3Unused} ? " R3UNUSED" : "";
		my $absent = $$NewRef{Absent} ? " ABSENT" : "";

		my $data = "";
		if( !($$NewRef{Name} =~ /^(_ZTV|_ZTI|_ZTT)/)) {
#				A symbol name with the above pattern indicates that it is a Data symbol.
#				Mark symbols as DATA only when it cannot be found from the name alone (i.e.,
#				explicitly exported data symbols).
			if(($$NewRef{Data}) and ($$NewRef{Size}) ){
			$data = " DATA $$NewRef{Size}";
			}
		}
		print "  $$NewRef{Name} \@ $$NewRef{Ordinal} NONAME$r3unused$Comment\n";
		push @Text, "\t$$NewRef{Name} \@ $$NewRef{Ordinal} NONAME$data$r3unused$absent$Comment\n";
	}

#	add a terminating newline
	push @Text, "\n";

	unless ($Compare) {
#		write the new frozen .DEF file
		eval { &Def_WriteFileL(\@Text, $FILE); };
		die $@ if $@;
	}
}

