#!/usr/bin/env perl

$VERBOSE = 0;

if ($ARGV[0] eq "-v") { $VERBOSE = 1; shift @ARGV; }
if (@ARGV != 3) { die "generate (schema) (template) (output)\n"; }

$schema   = $ARGV[0];
$template = $ARGV[1];
$output   = $ARGV[2];

# global variables:
# @key, @value                         : paired list of keys and values
# @headcode, @tailcode                 : additional HTML or PHP code before or after table
# @field, @width, @name, @show, @link, @extras : properties of each table column
# @imagedefs, %image: array of image defines

&parse_schema;
&parse_template;
exit 0;

sub parse_schema {
    open (FILE, "$schema");
    @list = <FILE>;
    close (FILE);

    &init_key ("MENU");
    &init_key ("STYLE");
    &init_key ("MODE");
    &init_key ("TABLE");
    &init_key ("TITLE");
    &init_key ("FILE");

    &init_key ("FIELDS");
    &init_key ("GROUPS");

    foreach $line (@list) {
	chop $line;
	($key, $value) = split (" ", $line, 2);
	
	# strip leading and trailing white space from the following
	if ($key eq "TABLE") { ($value) = $value =~ m|^\s*(.+)\s*$|; }
	if ($key eq "TYPE")  { ($value) = $value =~ m|^\s*(.+)\s*$|; }

	&set_keypair ($key, $value);

	# list of the table fields
 	if ($key eq "FIELD") {
	    ($field, $width, $format, $name, $show, $link, $extras) = split (/,\s+/, $value, 7);
	    if (! $name) { die "table $ARGV[0] missing required elements\n"; }
	    if ($field =~ m|\S+\s+as\s+\S+|) {
		($fieldreal) = $field =~ m|\S+\s+as\s+(\S+)|;
	    } else {
		$fieldreal = $field;
	    }
	    
	    if ($VERBOSE) { printf "%-20s %-20s %-20s %-20s %-20s %-20s\n", $field, $name, $format, $show, $link, $extras; }

	    if ($show eq "") {$show = "value";}
	    push @field,     $field;
	    push @fieldreal, $fieldreal;
	    push @width,     $width;
	    push @format,    $format;
	    push @name,      $name;
	    push @show,      $show;
	    push @link,      $link;
	    push @extras,    $extras;
	}

	if ($key eq "FILE") {
	    $myFile = $value;
	}

	# list of the table fields
 	if ($key eq "HEAD") {
	    push @headcode, $value;
	}

	# the IMAGE commands sets up a reference name
	if ($key eq "IMAGE") {
	    push @imagedefs, $value;
	}

	# the GROUP entries are used in summary tables as arguments to the GROUP BY sql statement
	if ($key eq "GROUP") {
	    push @group, $value;
	}

	# the ARGS commands define arguments to FIELDS commands
	if ($key eq "ARGS") {
	    push @linkargs, $value;
	}

	# the OP commands define field operations
	if ($key eq "OP") {
	    push @opwords, $value;
	}

	# the TD_CLASS command sets up an alternate TD class
	if ($key eq "TD_CLASS") {
	    push @tdClasses, $value;
	    # print STDERR "found TD_CLASS\n";
	}

	# list of the table fields
 	if ($key eq "TAIL") {
	    push @tailcode, $value;
	}

	# list of the table restrictions
	# replace this with a flexible where statement?
 	if ($key eq "WHERE") {
	    ($field) = split (/,\s+/, $value, 1);
	    if ($VERBOSE) { printf "%-20s\n", $field; }
	    push @where,   $field;
	}
    }

    # check on keys
    &check_key ("MENU", "ipp.menu.dat");
    &check_key ("STYLE", "ipp.css");
    &check_key ("MODE", "basic");
    &check_key ("TABLE", "");
    &check_key ("TITLE", "");
    &check_key ("FILE", "");
    
    # these are used internally (not just a replacement)
    for ($i = 0; $i < @key; $i++) {
	if ($key[$i] eq "MENU") { $MENU = $value[$i]; }
	if ($key[$i] eq "MODE") { 
	    if ($value[$i] eq "summary") {
		if (@group == 0) {
		    print STDERR "a summary table must have at least one group\n";
		    exit 1;
		}
		&define_groups_string (@group);
	    }
	}
    }

    &check_key ("GROUPS", "none");

    # define FIELDS and WHERE strings, add to keypairs
    &define_fields_count;
    &define_fields_string (@field);
    &define_where_string (@where);

    foreach $linkarg (@linkargs) {
	&parse_linkarg ($linkarg);
    }

    foreach $imagedef (@imagedefs) {
	&parse_imagedef ($imagedef);
    }

    foreach $opword (@opwords) {
	&parse_opwords ($opword);
    }

    if (0) {
	print STDERR "show: $show[0]\n";
	($a, $b, $c) = &parse_label ($show[0]);
	print STDERR "a: $a, b: $b, c: $c\n";

	print STDERR "extras: $extras[0]\n";
	($a, $b, $c) = &parse_label ($extras[0]);
	print STDERR "a: $a, b: $b, c: $c\n";

	print STDERR "show: $show[1]\n";
	($a, $b, $c) = &parse_label ($show[1]);
	print STDERR "a: $a, b: $b, c: $c\n";

	print STDERR "extras: $extras[1]\n";
	($a, $b, $c) = &parse_label ($extras[1]);
	print STDERR "a: $a, b: $b, c: $c\n";
	exit 0;
    }
}

sub parse_template {
    open (FILE, $template);
    @list = <FILE>;
    close (FILE);

    open (FILE, ">$output");

    foreach $line (@list) {
	
	&check_keypairs;

	print FILE $line;

	# fill in table header
	if ($line =~ m|// \*\* TABLE HEADER \*\*|) {
	    &write_table_header;
	}

	# fill in TD_CLASS test
	if ($line =~ m|// \*\* TD CLASS \*\*|) {
	    &write_td_class;
	}

	# fill in table data
	if ($line =~ m|// \*\* TABLE DATA \*\*|) {
	    &write_table_data;
	}

	# fill in table query
	if ($line =~ m|// \*\* TABLE QUERY \*\*|) {
	    &write_table_query;
	}

	# fill in table restricts
	if ($line =~ m|// \*\* TABLE RESTRICTIONS \*\*|) {
	    &write_table_restrict;
	}

	# fill in table restricts
	if ($line =~ m|// \*\* BUTTON RESTRICTIONS \*\*|) {
	    &write_button_restrict;
	}

	# fill in head HTML or PHP code
	if ($line =~ m|// \*\* HEAD CODE \*\*|) {
	    &write_inline_code (@headcode);
	}

	# fill in tail HTML or PHP code
	if ($line =~ m|// \*\* TAIL CODE \*\*|) {
	    &write_inline_code (@tailcode);
	}
    }
    close (FILE);
}

sub write_inline_code {
    my (@code) = @_;
    my ($i, $code, $type);
    
    for ($i = 0; $i < @code; $i++) {
	($type, $line) = split (" ", $code[$i], 2);
	if ($type eq "PHP") {
	    print FILE "$line\n";
	}
	if ($type eq "HTML") {
	    print FILE "?> $line <?\n";
	}
    }
}

# generate a table header cell (<th> </th>) for each field (show != none)
sub write_table_header {

    # print the table header (field labels)
    print FILE "echo \"<tr><td></td>\\n\";\n"; 
    for ($i = 0; $i < @field; $i++) {
	($label, $value, $string) = &parse_label ($show[$i]);
	if ($show[$i] eq "none")  { next; }
	print FILE "write_header_cell (\"list\", \"$name[$i]\");\n";
    }
    print FILE "echo \"</tr>\\n\";\n";

    # print the column sort cells
    print FILE "echo \"<tr><td></td>\\n\";\n"; 
    for ($i = 0; $i < @field; $i++) {
	($label, $value, $string) = &parse_label ($show[$i]);
	if ($show[$i] eq "none")  { next; }
	if ($field[$i] eq "*") { print FILE "echo \"<td></td>\\n\";\n"; next; } # empty cell (no sort for such fields)
	if ($label eq "op") {
	    print FILE "write_sort_cell (\"list\", \"$opf{$value}\", \$buttonLink, \$ID, '$myFile');\n";
	} else {
	    print FILE "write_sort_cell (\"list\", \"$fieldreal[$i]\", \$buttonLink, \$ID, '$myFile');\n";
	}
    }
    print FILE "echo \"</tr>\\n\";\n";
}

sub write_td_class {
    my ($field, $testline);

    if (@tdClasses == 0) { return; }
    foreach $tdClass (@tdClasses) {

	# TD_CLASS class field expression
	($class, $testline) = split (" ", $tdClass, 2);

	$testlineFixed = "";
	@testlineBits = split (" ", $testline);
	foreach my $bit (@testlineBits) {
	    $newbit = &parse_fieldname ($bit);
	    $testlineFixed = "$testlineFixed $newbit";
	}

	print FILE "  if ($testlineFixed) {\n";
	print FILE "    \$class = \"$class\";\n";
	print FILE "  }\n";
    }
}

# generate a WHERE test for each field (field != *)
sub write_table_restrict {

    if ($WHERE) {
	print FILE "\$WHERE = \"WHERE $WHERE\";\n";
    }	
    for ($i = 0; $i < @field; $i++) {
	$value = $fieldreal[$i];
	if ($value eq "*") { next; }
	if ($format[$i] eq "%s") {
	    print FILE "\$WHERE = check_restrict ('$value', \$WHERE, 'string');\n";
	} else {
	    print FILE "\$WHERE = check_restrict ('$value', \$WHERE, 'string');\n";
	    print FILE "\$WHERE = check_restrict ('$value', \$WHERE, 'min');\n";
	    print FILE "\$WHERE = check_restrict ('$value', \$WHERE, 'max');\n";
	}
    }
    print FILE "\$WHERE = check_ordering ('$GROUPS', \$WHERE);\n";
}

# generate a button link entry for each field (field != *)
sub write_button_restrict {

    for ($i = 0; $i < @field; $i++) {
	$value = $fieldreal[$i];
	if ($value eq "*") { next; }
	if ($format[$i] eq "%s") {
	    print FILE "\$buttonLink = button_restrict_string ('$value', \$buttonLink);\n";
	} else {
	    print FILE "\$buttonLink = button_restrict_string ('$value', \$buttonLink);\n";
	    print FILE "\$buttonLink = button_restrict_min ('$value', \$buttonLink);\n";
	    print FILE "\$buttonLink = button_restrict_max ('$value', \$buttonLink);\n";
	}
    }
}

# there are two query rows: the first is populated for both strings
# and numbers, the second only for numbers
sub write_table_query {
    my ($i);

    print FILE "echo \"<tr><td>&ge;</td>\\n\";\n";
    for ($i = 0; $i < @field; $i++) {
	if ($show[$i] eq "none")  { next; }
	if ($field[$i] eq "*")  { 
	    # * fields create an empty cell 
	    print FILE "echo \"<td> &nbsp; </td>\\n\";\n";
	} else {
	    if ($format[$i] eq "%s") {
		print FILE "write_query_row ('$fieldreal[$i]', $width[$i], 'string');\n";
	    } else {
		print FILE "write_query_row ('$fieldreal[$i]', $width[$i], 'min');\n";
	    }
	}
    }
    print FILE "echo \"</tr><tr><td>&le;</td>\\n\";\n";
    for ($i = 0; $i < @field; $i++) {
	if ($show[$i] eq "none")  { next; }
	if (($field[$i] eq "*") || ($format[$i] eq "%s")) { 
	    # * fields create an empty cell 
	    print FILE "echo \"<td> &nbsp; </td>\\n\";\n";
	} else {
	    print FILE "write_query_row ('$fieldreal[$i]', $width[$i], 'max');\n";
	}
    }
    print FILE "echo \"</tr>\\n\";\n";
}

sub write_table_data {
    my ($i, $Nrow);
    my ($label, $value, $string);

    for ($i = 0; $i < @field; $i++) {
	($label, $value, $string) = &parse_label ($show[$i]);
	$Nrow = $count[$i];

	# create the link variable if this entry should be linked
	if ($label eq "none")  { next; }

	if ($label eq "value") {
	    # create the basic link variable
	    if ($link[$i]) {
		print FILE "  \$link = \"$link[$i]\" . \"?menu=$MENU&\" . \$ID['link'];\n";
		# add extra GET data to target
		if ($extras[$i]) { &parse_extras ($linkarg{$extras[$i]}); } 
		$myLink = "\$link";
	    } else {
		$myLink = "\"\"";
	    }
	    # print the actual table cell line with the link...
	    if (! $value && ($field[$i] ne "*")) { $value = "\$row[$Nrow]"; }

	    # add special format elements
	    # $realFormat = $format[$i];
	    # if ($format[$i] eq '%T') {
	    # 	$realFormat = '%s';
	    # }
	    print FILE "  write_table_cell (\$class, '$format[$i]', $myLink, $value);\n";
	    next;
	}

	if ($label eq "op") {
	    # create the basic link variable
	    if ($link[$i]) {
		print FILE "  \$link = \"$link[$i]\" . \"?menu=$MENU&\" . \$ID['link'];\n";
		# add extra GET data to target
		if ($extras[$i]) { &parse_extras ($linkarg{$extras[$i]}); } 
		$myLink = "\$link";
	    } else {
		$myLink = "\"\"";
	    }
	    print FILE "  write_table_cell (\$class, '$format[$i]', $myLink, $ops{$value});\n";
	    next;
	}

	if (($label eq "image") && !$link[$i]) {
	    # print the actual table cell line with the image...
	    print FILE "  echo \"<td class=\\\"\$class\\\">\n";
	    print FILE "  echo \"              <img src=\\\"$image{$value}\\\"></td>\\n\";\n";
	    next;
	}

	if (($label eq "image") && $link[$i]) {
	    # create the basic link 
	    print FILE "  \$link = \"$link[$i]\" . \"?menu=$MENU&\" . \$ID['link'];\n";
	    # add extra php data to target
	    if ($extras[$i]) { &parse_extras ($linkarg{$extras[$i]}); } 
	    # print the actual table cell line with the link...
	    print FILE "  echo \"<td class=\\\"\$class\\\"><a href=\\\"\$link\\\"> <img src=\\\"$image{$value}\\\"> </a></td>\\n\";\n";
	    next;
	}
    }
}

sub define_fields_string {
    my (@array) = @_;
    my ($i, $FIELDS);

    $FIELDS = "$array[0]";
    for ($i = 1; $i < @array; $i++) {
	# skip * fields (not a valid query element
	if ($array[$i] eq "*") { next; }
	$FIELDS = "$FIELDS,$array[$i]";
    }
    set_keypair ("FIELDS", $FIELDS);
}

sub define_groups_string {
    my (@array) = @_;
    my ($i);

    $GROUPS = "$array[0]";
    for ($i = 1; $i < @array; $i++) {
	# skip * fields (not a valid query element
	if ($array[$i] eq "*") { next; }
	$GROUPS = "$GROUPS,$array[$i]";
    }
    set_keypair ("GROUPS", $GROUPS);
}

sub define_fields_count {
    my ($i, $Nrow);

    @count = ();

    $Nrow = 0;
    for ($i = 0; $i < @field; $i++) {
	# skip * fields (not a valid query element
	if ($field[$i] eq "*") { 
	    $count[$i] = -1;
	} else {
	    $count[$i] = $Nrow;
	    $Nrow ++;
	}
    }
}

sub define_where_string {
    my (@array) = @_;
    my ($i, $FIELDS);

    if (@array == 0) { return; }

    $WHERE = "$array[0]";
    for ($i = 1; $i < @array; $i++) {
	$WHERE = "$WHERE AND $array[$i]";
    }
    
    set_keypair ("WHERE", $WHERE);
}

sub parse_extras {
    my ($extras) = $_[0];
    my (@extfields);
    my ($label, $value, $outline);
    my ($i);

    # examine the extras and parse the embedded fields (comma-separated)
    @extfields = split (/,/, $extras);
    for ($i = 0; $i < @extfields; $i++) {
	($label, $value, $outline) = &parse_label ($extfields[$i]);
	print FILE "  \$link = \$link . \"&$outline\";\n";
    }
}

# each linkarg is an entry of the form "word name=value"
# the entries for matching words are concatenated together
# separated by commas to create the value $linkarg{word}
sub parse_linkarg {
    my ($word, $value);

    ($word, $value) = split (" ", $_[0]);

    if ($linkarg{$word}) {
	$linkarg{$word} = "$linkarg{$word},$value";
    } else {
	$linkarg{$word} = $value;
    }	
}

# parse strings of the form label=value
# if 'value' is of the form $field, try to match with 
# a supplied DB field, and replace with $row[$n] if found
sub parse_label {
    my ($string) = $_[0];
    my ($label, $value, $outline);
    my ($Nrow, $i);

    # unless we replace the value with the db row element, return the original string
    $outline = $string;
    $label = $string;
    $value = "";

    # search for embedded table fields and replace with row[N]
    if ($VERBOSE) { print STDERR "string: $string\n"; }
    if ($string =~ m|\S+=\S+|) {
	($label, $value) = $string =~ m|(\S+)=(\S+)|;
	if ($value =~ m|^\$|) {
	  MATCH_LABEL:
	    for ($i = 0; $i < @field; $i++) {
		if ($field[$i] eq "*") { next; }
		if ($value =~ m|^\$$fieldreal[$i]|) {
		    # print "field: $fieldreal[$i]\n";
		    ($name, $extra) = $value =~ m|^\$($fieldreal[$i])\@(\S+)|;
		    # print "name: $name\n";
		    # print "extra: $extra\n";
		    # print "value: $value\n";

		    $Nrow = $count[$i];
		    if ($extra) {
			$value = "\$row[$Nrow]\@$extra";
		    } else {
			$value = "\$row[$Nrow]";
		    }
		    # print "outval: $value\n";
		    last MATCH_LABEL;
		}
	    }
	    $outline = "$label=$value";
	} 
    }
    return ($label, $value, $outline);
}

# given a variable of the form $fieldname, return the corresponding
# row entry in the form $row[N], otherwise return $fieldname
# globals: field (list of field names), count (list of sequence)
sub parse_fieldname {
    my ($variable) = $_[0];
    my ($i);
    
    # print STDERR "variable: $variable\n";

    unless ($variable =~ m|^\$|) {
	return $variable;
    }

    $fieldname = substr ($variable, 1);

    # print STDERR "fieldname: $fieldname\n";

    for ($i = 0; $i < @field; $i++) {
	# print STDERR "$i: $fieldreal[$i] : $fieldname\n";

	if ($field[$i] eq "*") { next; }
	if ($fieldname ne $fieldreal[$i]) { next; }


	$Nrow = $count[$i];

	$value = "\$row[$Nrow]";

	# print STDERR "found : $i : $count[$i] : $Nrow : $value\n";
	return $value;
    }
    return $variable;
}

sub parse_imagedef {
    my ($value) = $_[0];

    ($var, $name, $rule, $camera, $class) = split (" ", $value);
    $name   = &parse_fieldname ($name);
    $camera = &parse_fieldname ($camera);
    $class  = &parse_fieldname ($class);
    $image{$var} = "getimage.php?name=$name&rule=$rule&camera=$camera&class_id=$class";
    return 1;
}

sub parse_opwords {
    my ($value) = $_[0];

    @words = split (" ", $value);
    $name = shift (@words);

    $opline = "";
    @outwords = ();
    @outfields = ();
    foreach $word (@words) {
	$fword = &parse_fieldname ($word);
	# print "field: $word -> $fword\n";
	push @outwords, $fword;
	if ($word =~ m|^\$|) {
	    $word = substr ($word, 1);
	}
	push @outfields, $word;
    }
    $outline = join (" ", @outwords);
    $ops{$name} = $outline;
    $outline = join (" ", @outfields);
    $opf{$name} = $outline;

    # print "ops: $name : $ops{$name}\n";
    # print "opf: $name : $opf{$name}\n";

    return 1;
}

sub init_key {
    my ($key)   = $_[0];

    push @key, $key;
    push @value, "";
}

sub set_keypair {
    my ($i);
    my ($key)   = $_[0];
    my ($value) = $_[1];

    for ($i = 0; $i < @key; $i++) {
	if ($key eq $key[$i]) {
	    if ($value[$i] ne "") { die "key is multiply defined\n"; }
	    $value[$i] = $value;
	    if ($VERBOSE) { print STDERR "setting $key = $value\n"; }
	    return;
	}
    }
}

sub check_key {
    my ($i);
    my ($key)     = $_[0];
    my ($default) = $_[1];

    for ($i = 0; $i < @key; $i++) {
	if ($key eq $key[$i]) {
	    if ($VERBOSE) { print "found $key: $key[$i]  -- $value[$i] (def: $default)\n"; }
	    if (($default eq "") && ($value[$i] eq "")) { die "missing value for required key $key[$i]\n"; }
	    if ($value[$i] eq "") { $value[$i] = $default; }
	    return;
	}
    }
    die "unknown key $key\n";
}

sub check_keypairs {
    my ($i);
    for ($i = 0; $i < @key; $i++) {
	if ($VERBOSE) { print "$key[$i]  -- $value[$i]\n"; }
	if ($line =~ m|\$$key[$i]|) {
	    if ($value[$i] eq "") { die "missing value for required key $key[$i]\n"; }
	    $line =~ s|\$$key[$i]|$value[$i]|g;
	}
    }
}

# we need to find the structure size, including padding 
# i'm not sure I know the answer to this: it is probably 
# the total number of bytes rounded up to the largest 
# data item in the structure (ie, 8 for a double, etc)
# if we have the size, then we can double check the structure
# against the expectation at runtime.  for the moment,
# calculate by hand and add to def.d file 

