#
# Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
# All rights reserved.
# This component and the accompanying materials are made available
# under the terms of the License "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: 
#

package spitool;

use strict;
use Exporter;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
use romutl;

$VERSION     = 1.00;
@ISA         = qw(Exporter);
@EXPORT      = ();
@EXPORT_OK   = qw(createSpi);

sub binarize { #converts decimal number to 4 byte litte endian format
	my $value = shift;
	my $remainder;
	my $returnValue;
	for(my $i=0;$i<4;$i++) {
		$remainder=$value % 256;
		$returnValue.=chr($remainder);
		$value = ($value-$remainder)/256;
	}
	return $returnValue;
}

sub convertUidFromText { #converts UID from hexadeciaml text value to decimal value, passes decimal value unchanged, returns -1 if invalid UID
	my $value = shift;
	if($value =~ /^0x([\da-fA-F]{1,8})$/i) {
		return hex $1;
	} elsif ($value =~ /^\d*$/) {
		return $value;
	} else {
		return -1;
	}
}

sub bin2hex { #converts 4 byte little endian value to 0x... hex text value
	my $value=shift;
	my $byte;
	my $quotient;
	my $remainder;
	my $hexValue="";
	for(my $i=0;$i<4;$i++) {
		$byte=ord(substr($value,$i,1));
		$remainder=$byte%16;
		$quotient=($byte-$remainder)/16;
		if($remainder>9) {
			$remainder= chr($remainder+55);
		}
		if($quotient>9) {
			$quotient= chr($quotient+55);
		}		
		$hexValue=$quotient . $remainder . $hexValue;
	}
	return "0x" . $hexValue;
}

sub uidcrc { #returns decimal UID checksum value for the three inputs
	my $output = `uidcrc $_[0] $_[1] $_[2]`;
	if($output =~ /([^ ]*)$/i) {
		$output =$1;
		chomp $output;
		return hex($output);
	}	
}

sub printZeroes { #prints as many hexadecimal zeroes to OUTPUTFILE as specified by input
	my $numberOfZeroes=shift;
	for(my $i=0;$i<$numberOfZeroes;$i++) {
		print OUTPUTFILE chr(0);
	}
}

sub bytes2dec { #calculates decimal value from inputted 4 byte little endian value 
	my $bytes=shift;
	my @byteArray;
	for(my $i=0;$i<length $bytes;$i++) {
		$byteArray[$i]=ord(substr($bytes,$i,1));
	}
	my $decValue;
	for(my $i=0;$i<scalar @byteArray;$i++) {
		$decValue+=($byteArray[$i]*(2**($i*8)));
	}
	return $decValue;
}

sub print_usage
	{
#........1.........2.........3.........4.........5.........6.........7.....
	print <<USAGE_EOF;

Usage:
  spitool.pl [options] files directories   

Create an SPI file by concatenating the files and contents of directories,
based on the options passed. 

The available options are:

-tSPIFileName       -- SPIFileName is the name the produced SPI file will 
                       have (i.e. ecom-0-0.spi). If not specified, the name 
                       will be ecom-0-0.spi by default
-dTargetDir         -- TargetDir is the directory where the SPI file should
                       be created, ending with a \
-iExisting          -- Existing is address and name of existing SPI file to
                       concatenate specified files to 
-uid<x>=<y>         -- <x> has value 1, 2 or 3, <y> is an UID value in
                       decimal or 0x... hexadecimal
-existinguid<x>=<y> -- <x> has value 1, 2 or 3, <y> is an UID value in 
                       decimal or 0x... hexadecimal
-existinguidcrc=<x> -- <x> is an UID value in decimal or 0x.. hexadecimal
-hide<ResourceFileNames> -- <ResourceFileNames> is the list of the resource files
			    that are to be hidden in the SPI file separated by
			    space or comma.
  
If an existing SPI file is specified with the -i option then this file is
used as a base and other data files are added to the end of this file,
otherwise a new SPI file is created. In either case the produced SPI file
is placed in the directory specified by the -d option and given the name 
specified with the -t option.

Files which are to be concatenated into the SPI file should be specified
on the command line by either specifying the file's name (and location), or
by including a directory name, in which case all files from that directory
will be included.

The -uid options can be used to filter files for inclusion in the SPI file.
This option can be included multiple times, so a list of UID1 values can be
built up, and the same for UID2 and UID3 values. Each file on the command
line is compared with this list, and if any of its UID values match a
relevant value in the UID lists then that file will be included in the SPI
file. If the file does not match any values it will be excluded.

The -existinguid options allow the UID values that an existing SPI file
should have to be specified. This will allow the possibility of checking
that the correct type of files are being concatenated together.

The -hide option can be used to mark a resource file as hidden in the SPI file.
To mark a resource file as a hidden entry in the SPI file, the resource data 
length will be written as 0.

USAGE_EOF
	}

sub createSpi 
	{
	my @resourceFiles=();
	my @hideresourceFiles=();
	my $spiFileName;
	my $targetDirectory;
	my $existingSpiFileName;
	my @uid;
	my @uidLengths = (0, 0, 0, 0);
	my @uid1;
	my @uid2;
	my @uid3;
	my @existingUid = (-1,-1,-1,-1);
	my $uidNumber;
	my $defaultSpiFileName = "ecom-0-0.spi";
	my $defaultTargetDirectory = &get_epocroot."epoc32\/tools\/";
	my @defaultUid = (-1,-1,-1,-1);
	
##########################################################################################
# Reading arguments phase
##########################################################################################

	foreach my $arg (@_) {
		if ($arg =~ /^-t(.*)/i) { # set target SPI file
			$spiFileName = $1;
			next;
			}
		if ($arg =~ /^-d(.*)/i) { # set target ouput directory
			my $tempDirectory=$1;
			if((-d $tempDirectory) ) {
				$targetDirectory = $tempDirectory;
				next;
				}
				else
				{
				 print "Output directory \'",$tempDirectory,"\' does not exist.\n";
				 exit(1);
				 }				
			}
		if ($arg =~ /^-i(.*)/i) { # existing SPI file to use as a base
			my $tempFileName = $1;
			if((-e $tempFileName) && (!(-d $tempFileName))) {
				$existingSpiFileName = $tempFileName;
				next;
				}
			}
		if ($arg =~ /^-uid([1-3])\=(.*)/i) {
			$uid[$1-1][$uidLengths[$1-1]++] = $2;
			next;
			}
		if($arg=~/^-existinguidcrc\=(.*)/i) {
			$existingUid[3]=$1;
			next;
			}
		if ($arg =~ /^-existinguid([1-3])\=(.*)/i) {
			$existingUid[$1-1]=$2;
			next;
			}
		if ($arg =~ /^-hide(.*)/i) { # Collect the files to be hidden
			my $line = $1;
			$line =~ s/,/ /g;
			my @files = split(' ' , $line);
			foreach my $file (@files)
			{
				push @hideresourceFiles, $file;
			}
			next;
			}
		if (-d $arg) {
			if(($arg =~ m-^.:-) && ($arg =~ m-[\\\/]$-)) {
				unless(opendir(DIRECTORY, $arg)) { print "Exiting: $arg"; exit; }
				while (my $file=readdir(DIRECTORY)) {
					my $newfile = $arg.$file;
					if(!(-d $newfile)) {
						push @resourceFiles, $newfile;
					}
				}
				close(DIRECTORY);
				next;
				}
			}
		if ((-e $arg) && (!(-d $arg))) {
			push @resourceFiles, $arg;
			next;
			}
		if ($arg eq "-h") {
			print_usage;
			exit(1);
		}	
		print "Unknown command: $arg\n";
	}

#####################################################################################
# UID phase
#####################################################################################
		
	if(!(defined $spiFileName)) { #use default file name if none passed on command line
		$spiFileName = $defaultSpiFileName;
	}
	if(!(defined $targetDirectory)) { #use default target dir if none passed on command line
		$targetDirectory = $defaultTargetDirectory;
	}
	for(my $i=0;$i<3;$i++) { #if default UIDs specified then added to UID match lists
		if($defaultUid[$i]>=0) {
			$uid[$i][$uidLengths[$i]++] = $defaultUid[$i];
		}
	}
	for(my $i=0;$i<3;$i++) { #makes sure UIDs are valid UIDs
		my @tempUidArray;
		my $iterator=0;
		while(defined $uid[$i][$iterator]) {
			my $convertedUid=convertUidFromText($uid[$i][$iterator]);
			if ($convertedUid != -1) {
				push @tempUidArray, binarize($convertedUid);
			} else {
				print "Invalid UID: $uid[$i][$iterator]\n";
			}
			$iterator++;
		}
		for(my $j=0;$i<scalar @tempUidArray;$j++) {
			$uid[$i][$j]=$tempUidArray[$j];
		}
		for(my $j=scalar@tempUidArray;defined $uid[$i][$j];$j++) {
			undef $uid[$i][$j];
		}
	}
#####################################################################################
# Phase to split up resource names
#####################################################################################

	my @resourceFilePaths;
	my @resourceFileNames;
	my @resourceExtensions;
	my @filestobehidden;

# To mark the resource files as hidden in the SPI file by writing the data length as zero
	foreach my $file (@hideresourceFiles)
	{
		my $matchfound =0;
		my $i=0;
		for(;$i<scalar @resourceFiles && !$matchfound;$i++)
		{
			if (lc($file) eq lc($resourceFiles[$i]))
			{
				$filestobehidden[$i] = 1;
				$matchfound =1;
			}
		}
		if (!$matchfound)
		{
# Those files that are to be hidden in the SPI file but not existing in the SPI
			if (! -e $file)
			{
				print "Warning: Hiding non-existent file $file\n";
			}
			push @resourceFiles,$file;
			$filestobehidden[$i] = 1;
		}
	}
	
	for(my $i=0;$i<scalar @resourceFiles;$i++) {
		if($resourceFiles[$i]=~m|\/|) {
			if($resourceFiles[$i]=~m|(.*)\/([^\/]*)$|) {
				$resourceFilePaths[$i]=$1;
				$resourceFileNames[$i]=$2;
			}
			if($resourceFileNames[$i]=~m|(.*)\.([^\.]*)|) {
				$resourceFileNames[$i]= $1;
				$resourceExtensions[$i]=$2;
			}
		} else {
			$resourceFilePaths[$i]="";
			if($resourceFiles[$i]=~m|(.*)\.([^\.]*)|) {
				$resourceFileNames[$i]= $1;
				$resourceExtensions[$i]=$2;
			}
		}
	}
	my %uid2values; #hash to hold UID2 values for each type of SPI file
	$uid2values{"ecom"} = 270556204;

##########################################################
# Existing file stage
##########################################################

	my @spiUid = (270556203, 0, 0); #holds spi values (including CRC value)
	foreach my $key (keys(%uid2values)) { #searches through SPI types to match UID2 value
		if($spiFileName =~/^$key/) {
			$spiUid[1]=$uid2values{$key};
		}
	}
	$spiUid[3] = uidcrc($spiUid[0], $spiUid[1], $spiUid[2]);
	my $total=0; #used to keep track of position in SPI file
	my $buffer;
	my $spifile=File::Spec->catpath( "", $targetDirectory, $spiFileName );
 	open OUTPUTFILE, ">$spifile" or die $!;
	binmode (OUTPUTFILE);
	if($existingSpiFileName) {
		open EXISTINGFILE, "$existingSpiFileName" or die $!;
		binmode (EXISTINGFILE);

		my @fileNameLengths;
		my @fileLengths;
		my @fileNames;
		my @fileContents;

		read(EXISTINGFILE,$buffer,4);
		read(EXISTINGFILE,$buffer,4);
		if(bytes2dec($buffer)!=$spiUid[1]) {
			print "Incompatible SPI files.\n";
		}
		read(EXISTINGFILE,$buffer,24);
		$total=32;
		my $existingSpiFileSize = (stat(EXISTINGFILE))[7];
		while($total<$existingSpiFileSize) { #loop to store information from files which are not being replaced
			read(EXISTINGFILE,$buffer,4);
			push @fileNameLengths, bytes2dec($buffer);
			read(EXISTINGFILE,$buffer,4);
			push @fileLengths, bytes2dec($buffer);
			read(EXISTINGFILE,$buffer,$fileNameLengths[$#fileNameLengths]);
			push @fileNames, $buffer;
			$total=$total+8+$fileNameLengths[$#fileNameLengths]+$fileLengths[$#fileLengths];
			my $padding = (4-(($fileNameLengths[$#fileNameLengths]+$fileLengths[$#fileLengths])%4))%4;
			read(EXISTINGFILE,$buffer,$fileLengths[$#fileLengths]+$padding);
			push @fileContents, $buffer;
			$total += (4-($total%4))%4;			
		}
		close EXISTINGFILE;	
		#next part prints to OUTPUTFILE the header and files which are not being replaced
		print OUTPUTFILE binarize($spiUid[0]) . binarize($spiUid[1]) . binarize($spiUid[2]) . binarize($spiUid[3]);
		printZeroes(16);
		$total=32;
		for(my $i=0; $i<scalar @fileNames; $i++) {
			my $flag=1;
			for(my $j=0; $j<scalar @resourceFileNames && $flag==1; $j++) {
				if($fileNames[$i] eq $resourceFileNames[$j]) {
					$flag=0;
				}
			}
			if($flag) {
				print OUTPUTFILE binarize($fileNameLengths[$i]) . binarize($fileLengths[$i]) . $fileNames[$i] . $fileContents[$i];
				$total=$total+8+length($fileNames[$i])+length($fileContents[$i]);
			}
		}
	} else { #prints header for target SPI file if there is no existing SPI file
		print OUTPUTFILE binarize($spiUid[0]) . binarize($spiUid[1]) . binarize($spiUid[2]) . binarize($spiUid[3]);
		printZeroes(16);
		$total=32;
	}

####################################################################
# Appending new data files to the SPI file
####################################################################

	my $resourceFileSize;
	my $resourceFileSizeInBinary;
	my $resourceFileNameSize;
	my $resourceFileNameSizeInBinary;
	for(my $i=0; $i<scalar @resourceExtensions;$i++) {
# To mark the resource files as hidden in the SPI file by writing the data length as zero
	   if ($filestobehidden[$i] == 1)
	   {
		$resourceFileNameSize = length($resourceFileNames[$i]);
		$resourceFileNameSizeInBinary = binarize($resourceFileNameSize);
		$resourceFileSize = 0;
		$resourceFileSizeInBinary = binarize($resourceFileSize);
		print OUTPUTFILE $resourceFileNameSizeInBinary . $resourceFileSizeInBinary . $resourceFileNames[$i];
		$total+=$resourceFileNameSize;
		my $padding = (4-(($resourceFileSize + $resourceFileNameSize)%4))%4;
		printZeroes($padding);
		$total+=$padding;
	   }
	   else
	   {
		open RESOURCEFILE, "<$resourceFiles[$i]";
		binmode(RESOURCEFILE);
		my @fileUid; #stores UIDs from particular data file
		my $fileUid1;
		my $fileUid2;
		my $fileUid3;
		read(RESOURCEFILE,$fileUid[0],4);
		read(RESOURCEFILE,$fileUid[1],4);
		read(RESOURCEFILE,$fileUid[2],4);
		my $uidFlag=0; #changes to 1 if a UID value in data file matches a specified UID value
		my $uidExists=1; #changes to 1 if there are specified UIDs to match to
		for(my $j=0;$j<3 && (!$uidFlag);$j++) {
			my $k=0;
			while(defined $uid[$j][$k] && (!$uidFlag)) {
				$uidExists=0;
				if($uid[$j][$k] eq bin2hex($fileUid[$j])) {
					$uidFlag=1;
				}
				$k++;
			}
		}	
		if(($uidFlag) || ($uidExists)) { #if suitable UIDs writes data file to SPI file
			$resourceFileSize = (stat(RESOURCEFILE))[7];
			$resourceFileNameSize = length($resourceFileNames[$i]);
			$resourceFileNameSizeInBinary = binarize($resourceFileNameSize);
			$resourceFileSizeInBinary = binarize($resourceFileSize);
			print OUTPUTFILE $resourceFileNameSizeInBinary . $resourceFileSizeInBinary . $resourceFileNames[$i];
			print OUTPUTFILE "$fileUid[0]$fileUid[1]$fileUid[2]";
			while(read(RESOURCEFILE,$buffer,1)) {
				print OUTPUTFILE $buffer;
			}
			$total+=$resourceFileSize;
			$total+=8;
			$total+=$resourceFileNameSize;
			my $padding = (4-(($resourceFileSize + $resourceFileNameSize)%4))%4;
			printZeroes($padding);
			$total+=$padding;
		}
	   }
	}
	print "Created $spiFileName\n";
}


1;
