# MakeByteArray
#
#
use strict;
use warnings;
use Cwd qw(cwd);

my $maxCols = 16;
my $maxLines = 0;
my $ESP = 0;
my $DIR = 0;
my $ROOT = 0;
my $UINT8 = 0;
my $file = "";
my $ctrlC = 0;		# User induced termination
my $lineT = 0;		# Linecount induced termination

$SIG{INT}  = \&CtrlCHandler;
$SIG{QUIT} = \&CtrlCHandler;

my %DirInfo;		# Name and FileSize

my $prg = $0;
$prg =~ s/^.*[\\\/](.*)/$1/;
$prg =~  s/(.*)\..*/$1/;

my $VERSION = 1.0;

# Extension in this list must be lower case.
my %FileType = (
	"htm"	=> "text/html",
	"html"	=> "text/html",
	"gif"	=> "image/gif",
	"svg"	=> "image/svg+xml",
	"css"	=> "text/css",
	"ico"	=> "image/x-icon",
	"png"	=> "image/png",
	"jpg"	=> "image/jpeg",
	"js"	=> "text/javascript",
);


if (!@ARGV)
	{
	print <<EOM;

$prg [file|<wildcard> [...]] [-n=##] [-l=##] [-ESP] [-DIR] [-o=outfile]
                                                   version $VERSION

    Dumps out the contents of the [file(s)] as a c array.

      If the file is binary, it emits a hex char-array using
        the -n and -l settings.
      If the file is text, it emits as an ascii char-array
        where each source line is one ascii quoted line.

    -ESP adds the PROGMEM attribute to the byte array data.

    -DIR adds a 'directory' at the end of the output, providing
       a reference to each input file. Most useful for a set
       of files.

    -ROOT prefixes each entry in the 'directory' with '/',
       so "index.htm" becomes 

    -o=outfile Writes the output to outfile.

    -u    overrides the default 'char' array to be 'uint8_t'
          NOTE that the filename and type info remains 'char'.
    -n=## sets the byte count for each line, defaults 32.
    -l=## sets a termination after ## lines have been printed.

    Example: MakeByteArray -ESP srcpath\\*.* -o=dstpath\\Web_Resources.h

EOM
	exit;
	}

my @files;
my $outfile = "";

#printf("ARGS: '%s'\n", join("|", @ARGV));
foreach (@ARGV)
	{
	if (/-n=(\d+)/)
		{ $maxCols = $1; }
	elsif (/-l=(\d+)/)
		{ $maxLines = $1; }
	elsif (/-o=(.*)/)
		{ $outfile = $1; }
	elsif (/-ESP/)
		{ $ESP = 1; }
	elsif (/-ROOT/)
		{ $ROOT = 1; }
	elsif (/-DIR/)
		{ $DIR = 1; }
	elsif (/-u/)
		{ $UINT8 = 1; }
	elsif (-e $_ )
		{ push @files, $_; }
	elsif (/[\*\?]/)
		{
		my @gFiles = glob($_);
		foreach (@gFiles) 
			{
			push @files, $_ if (-e $_);
			}
		}
	else
		{ print "unrecognized command.\n"; exit; }
	}

if (!@files)
	{
	print "No files to process.\n";
	exit;
	}

my $oldHandle;
if ($outfile ne "") {
	#printf("Open for write '%s'\n", $outfile);
	open(FO, ">$outfile") || die("Can't write to $outfile");
	$oldHandle = select FO;
} else {
	printf("No outfile, emitting to console.\n");
}
printf("//\n");
printf("//   Command: %s %s\n", $prg, join(" ", @ARGV));
printf("//   version: %2.1f\n", $VERSION);
printf("//  From cwd: %s\n", cwd);
printf("// Generated: %s\n", scalar localtime());
printf("//      NOTE: Run script in command shell, not PowerShell\n");
printf("//\n");

if ($ESP) {
	print <<EOM;
// PROGMEM is not recognized by Win32 tools...
#ifdef WIN32
#define PROGMEM
#endif
EOM
}

my @summary;
foreach my $file (@files) {
	push @summary, Process($file);
	last if ($ctrlC);
}

print "\n// Prototypes:\n// " . join("\n// ", @summary) . "\n";

if ($DIR) {
	my $type = "char   "; #($UINT8) ? "uint8_t" : "char   ";
	print <<EOM;

// Directory of Files
//
// This typedef is used to iterate through the directory listing, until
// an empty "" Filename is reached.
//
typedef struct {
	const char    * Filename;
	const $type * Filedata;
	const char    * Filetype;
	uint16_t        Filesize;
} DirEntry;

EOM
	
	printf("const DirEntry Directory[] %s= {\n", ($ESP) ? "PROGMEM " : "");
	foreach my $file (sort keys %DirInfo) {
		#printf(STDERR "file %s, title %s, size %d\n", $file, $DirInfo{$file}{'reference'}, $DirInfo{$file}{'size'});
		my $title = $DirInfo{$file}{'reference'};
		my $fn = sprintf("\"%s%s\"", $ROOT ? "/" : "", $file);
		my $ext = "";
		$ext = $1 if ($file =~ /.*\.(.*)/);
		my $ftype = sprintf("\"%s\"", $FileType{$ext});
		#printf(STDERR "ext: '%s' => '%s'\n", $ext, $FileType{lc($ext)});
		printf("\t{ %-20s, %-20s, %24s, %6d },\n", $fn, $title, $ftype, $DirInfo{$file}{'size'});
	}
	printf("\t{ %-20s, %-20s, %24s, %6d }\n", "NULL", "NULL", "NULL", 0);
	printf("};\n\n");
}

if ($outfile) {
	close FO;
	select($oldHandle);
}
print "\nTerminated by operator\n" if ($ctrlC == 1);
exit;

# ####################################

sub Process {
	my $file = shift;
	my $title = $file;
	my $isText = 0;
	my $prototype;
	
	$title =~ s/\./_/g;
	$isText = 1 if (-T $file);
	open(FH, "<$file") || die("Can't read $file.\n");
	binmode FH if ($isText);
	my $cols = 0;
	my $c;
	my $ascii = "";
	my $address = 0;
	my $lineCount = 0;
	printf("\n");
	printf("// File: %s\n", $file);
	printf("//\n");
	$prototype = sprintf("const %s %s[]%s", ($UINT8) ? "uint8_t" : "char", $title, ($ESP) ? " PROGMEM" : "");
	printf("%s = {\n", $prototype);
	
	my $size = 0;
	if ($isText) {
		while (<FH>) {
			my $line = $_;
			chomp $line;
			$line =~ s/\t/  /g;
			$line =~ s/\\"/"/g;
			$line =~ s/\r//g;
			$line =~ s/"/\\"/g;
			printf("\t\"%s\\n\"\n", $line);
			$size += length($line) + 1;		# +1 for the \n line termination.
		}
	} else {
		printf("\t");
		while (sysread(FH, $c, 1) && !$ctrlC && !$lineT) {
			my $b = ord($c);
			printf("0x%02X,", $b);
			if ($b >= 0x20 && $b <= 0x7F)
				{ $ascii .= chr($b); }
			else
				{ $ascii .= "."; }
			++$cols;
			++$address;
			$cols %= $maxCols;
			if ($cols == 0) {
				#print " // $ascii";
				print "\n";
				$ascii = "";
				$lineCount++;
				$lineT = 1 if ($maxLines && $lineCount >= $maxLines);
				if (!$lineT) {
					printf("\t");
				}
			}
			$size += 1;
		}
		if ($ascii ne "") { 
			print "   " x ($maxCols - $cols);
		}
		printf("\n");
	}
	printf("}; // %s, %d bytes\n", $title, $size);
	close FH;
	$DirInfo{$file}{'reference'} = $title;
	$DirInfo{$file}{'size'} = $size;
	return $prototype;
}

# If the user presses Control-C, signal it.
#
sub CtrlCHandler {
	$ctrlC = 1;
}

