#!/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 ("UNRESTRICTED");
    &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 ("UNRESTRICTED", "none");
    &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) {
        # print STDERR "linkarg: $linkarg\n";
        &parse_linkarg ($linkarg);
    }

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

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

    if ($VERBOSE) {
        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";
    }
}

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', 1.0);\n";
            next;
        }
        if ($format[$i] eq "%C") {
            # convert the RA & DEC limits to radians for comparison
            print FILE "\$WHERE = check_restrict ('$value', \$WHERE, 'min', 0.017453);\n";
            print FILE "\$WHERE = check_restrict ('$value', \$WHERE, 'max', 0.017453);\n";
            next;
        }
        print FILE "\$WHERE = check_restrict ('$value', \$WHERE, 'string', 1.0);\n";
        print FILE "\$WHERE = check_restrict ('$value', \$WHERE, 'min', 1.0);\n";
        print FILE "\$WHERE = check_restrict ('$value', \$WHERE, 'max', 1.0);\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
            # print STDERR "value: $link[$i]\n";
            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]);

    # print STDERR "0: $_[0], $word, $value\n";

    if ($linkarg{$word}) {
        $linkarg{$word} = "$linkarg{$word},$value";
    } else {
        $linkarg{$word} = $value;
    }
    # print STDERR "linkarg($word) : $linkarg{$word}\n";
}

# 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; }
                while ($value =~ m|\$$fieldreal[$i]|) {
                    # print "field: $fieldreal[$i]\n";
                    # ($part1, $name, $part2) = $value =~ m|\$($fieldreal[$i])\@(\S+)|;
                    ($part1, $name, $part2) = $value =~ m|(\S*)\$($fieldreal[$i])(\S*)|;
                    # print "name: $name\n";
                    # print "part1: $part1\n";
                    # print "part2: $part2\n";
                    # print "value: $value\n";

                    $Nrow = $count[$i];
                    $value = $part1 . "\$row[$Nrow]" . $part2;

                    # if ($part1 || $part2) {
                    # } 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

