#!/usr/bin/perl

use strict;
use warnings;
use Carp;

use Data::Dumper;

use constant DEBUG => 1;

# Character definitions consist of a character header followed by
# lines containing data for a single row in the matrix. Each line
# consists of a series of non-space characters and spaces. An
# non-space character indicates the should be 'on', a space indicates
# 'off'.

# The following parameters can be set in the configuration file using
# NAME = VALUE lines ...

# c_file ........ Name of the .c file to generate
# h_file ........ Name of the .h file to generate
# dir ........... Directory to place the .c and .h files
# num_col ....... Number of columns in the LED matrix
# num_row ....... Number of rows in the LED matrix

my %Cfg;  # all of the date read from the configuration file

my $Char; # reference to the current character array

@ARGV = qw(pico1tr_led_s.cfg) if $#ARGV == -1;

while (<>) {
    s/\s+$//; # remove trailing spaces
    next if /^\s*\#.*/;  # Skip comment lines
    last if /^__END__$/; # Skip lines after the end marker
    if (/^\s*\[(.)\]$/) {
	#
	# Start a new character array
	#
	$Cfg{_offs} = ord $1 unless defined $Cfg{_offs};
	my $char = [];
	push @ { $Cfg{chars} }, $char;
	$Char = $char;
    } elsif (s/\s*=\s*/ /) {
	#
	# key-value pair
	#
	s/^\s+//;
	s/\s+$//;
	my ($k,$v) = split;
	$Cfg{$k} = $v;
	$Cfg{last_row} = $Cfg{num_row} - 1 if $k eq 'num_row';
	$Cfg{last_col} = $Cfg{num_col} - 1 if $k eq 'num_col';
	$Cfg{_num_bits} = $Cfg{num_row} * $Cfg{num_col}
  	  if defined $Cfg{num_row} && defined $Cfg{num_col};
    } elsif (defined $Char) {
	#
	# change all non-space character to 1's
	# change all spaces to 0's
	#
	s/\S/1/g;
	s/\s/0/g;
	#
	# split the string into characters
	# and pad the right side with 0's
	# 
	my @bits = split //;
	push @bits, '0' while $#bits < $Cfg{last_col};
	#
	# @bits now contains the proper number of
	# binary digits. Add the bits to the current
	# chracter array.
	#
	push @$Char, @bits;
	#
	# if all the LED row data has been generated
	# than save the character and reset
	# 
	$Char = undef if $#$Char == $Cfg{_num_bits} - 1;
    }
}

#
# output the C file
#
open(OUT, ">$Cfg{c_file}") || die "(ascii2led) Could not open $Cfg{c_file} for output: $!";
print OUT << 'END';
///
/// generated by ascii2led
///

#include <avr/pgmspace.h>

END


my @Chars = @ { $Cfg{chars} };
$Cfg{_last_ord}  = $#Chars;
$Cfg{_num_bytes} = int(($Cfg{_num_bits} + 4) / 8);

printf(OUT "uint8_t led_char[][%i] = \n{\n", $Cfg{_num_bytes});
while (@Chars) {
    my @bits = @ { shift @Chars };
    push @bits, 0 while $#bits < $Cfg{_num_bits} - 1;
    push @bits, 0 while ($#bits + 1) % 8 != 0;
    my $line = "   {";
    while (@bits) {
	my @b = splice @bits, 0, 8;
	$line .= sprintf("0b%s%s", join('', @b), $#bits == -1 ? '}' : ', ');
    }
    printf(OUT "%s%s\n", $line, $#Chars == -1 ? "\n};" : ",");
}
close(OUT) || die "(ascii2led) Could not close output file $Cfg{c_file}: $!";


#
# output the header file
#
my $__H__ = sprintf("__%s__", $Cfg{h_file});
$__H__ =~ tr/[a-z]/[A-Z]/;
$__H__ =~ s/\./_/g;
open(OUT, ">$Cfg{h_file}") || die "(ascii2led) Could not open $Cfg{h_file} for output: $!";

printf(OUT "#ifndef %s\n", $__H__);
printf(OUT "#define %s\n", $__H__);

print OUT << 'END';
///
/// generated by ascii2led
///

END

printf(OUT "extern const uint8_t led_char[][%i];\n", $Cfg{_num_bytes});

my @def = qw(LED_CHAR_OFFS _offs LED_LAST_ORD _last_ord LED_NUM_ROWS num_row LED_NUM_COLS num_col);
while (@def) {
    my ($def, $key) = splice @def, 0, 2;
    printf(OUT "#define %s %i\n", $def, $Cfg{$key});
}
printf(OUT "\n\n#endif\n");

close(OUT) || die "(ascii2led) Could not close output file $Cfg{h_file}: $!";

#
# Copy the files to the Arduino directory
#
foreach (map { $Cfg{$_} } qw(c_file h_file)) {
    my $cmd = sprintf("mv %s %s", $_, $Cfg{dir});
    printf("(ascii2led) $cmd\n");
    next if DEBUG;
    system($cmd);
}

# Style (adapted from the Perl Cookbook, First Edition, Recipe 12.4)

# 1. Names of functions and local variables are all lowercase.
# 2. The program's persistent variables (either file lexicals
#    or package globals) are capitalized.
# 3. Identifiers with multiple words have each of these
#    separated by an underscore for readability.
# 4. Constants are all uppercase.
# 5. If the arrow operator (->) is followed by either a
#    method name or a variable containing a method name then
#    there is a space before and after the operator.


