use strict;
use warnings;
use JSON;
use File::Find qw(finddepth);
use Encode qw(decode);

#### START OF GLOBAL VARS

my $fh;  # Output file handle
my $imgDirectory = 'C:/Users/Owner/My Website/img/covers';

#### END OF GLOBAL VARS

#### START OF MAIN PROGRAM ####

my $respJsonData = loadJson("responses.json.txt");
my $nworks = keys %{ $respJsonData->{'works'} };
foreach my $commandKey (keys %{$respJsonData->{"commands"}}) {
   makeResponsePage($commandKey);
}
makeIndexPage();

#### END OF MAIN PROGRAM ####

sub loadJson {
   my $inname = $_[0];
   my $jsondata = '';
   open(my $jinfh, "<", $inname) || die("can't open $inname: $!");
   print "*** reading $inname ... ";
   while ( <$jinfh> ) {
     $jsondata .= $_; 
   }
   close $jinfh;
   print "DONE!\n";

   print "*** parsing $inname ... ";
   $jsondata =~ s/^\x{ef}\x{bb}\x{bf}//;  # decode_json doesn't like these leading bytes, the "Byte Order Mark" aka BOM.
   my $hashref = decode_json $jsondata;
   print "DONE!\n";
   return $hashref;
}

sub makeIndexPage {
   open($fh, ">:encoding(UTF-8)", "index.html");
   print $fh "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n";
   print $fh "<meta charset=\"UTF-8\">\n";
   print $fh "<meta name=\"Author\" content=\"David Welbourn\">\n";
   print $fh "<title>IF Responses</title>\n";
   print $fh "</head>\n\n";
   print $fh "<body>\n<h1><img src=\"../img/kaclogo.svg\" width=\"50\" height=\"50\" alt=\"\" style=\"vertical-align:middle\"> <span id=\"h1\">IF Responses</span></h1>\n\n";
   print $fh "<p>Datamining $nworks works of interactive fiction:</p>\n";
   print $fh "<ul>\n";
   foreach my $k (sort keys %{$respJsonData->{'commands'}}) {
      my $v = $respJsonData->{'commands'}->{$k};
      print $fh "<li>&gt; <a href=\"$k.html\">$k</a>.";
      print $fh " " . $v->{'nresp'} . " " . $v->{'sdesc'} if exists($v->{'sdesc'});
      print $fh "</li>\n";
   }
   print $fh "</ul>\n\n";

   print $fh "<p>Other versions of these pages will be kept around until all their data has been absorbed into the new versions:</p>\n";
   print $fh "<ul>\n";
   print $fh "<li>&gt; <a href=\"notunderstood_old.html\">notunderstood</a> (older version)</li>\n";
   print $fh "<li>&gt; <a href=\"plugh_old.html\">plugh</a> (older version)</li>\n";
   print $fh "<li>&gt; <a href=\"sing_old.html\">sing</a> (older version)</li>\n";
   print $fh "<li>&gt; <a href=\"xyzzy_old.html\">xyzzy</a> (older version)</li>\n";
   print $fh "</ul>\n\n";

   print $fh "<hr><h2>About this Responser project</h2>\n";
   print $fh "<p>I don't expect to stay around forever, so I'm also publishing the raw data and program that I used to create these pages.";
   print $fh " If you wish to continue (update, modify, expand) this project or make a similar project and not want to start from scratch, please feel free to use my materials.";
   print $fh " Crediting me would be nice, please and thank you.";
   print $fh " In the meantime, I retain the right to maintain my own version of this project as long as I'm willing and able to do it.</p>\n";
   print $fh "<ul>\n";
   print $fh "<li><code><a href=\"responses.json.txt\">responses.json.txt</a></code> contains the raw data in JSON format, UTF-8 encoding.</li>\n";
   print $fh "<li><code><a href=\"Responser.pl\">Responser.pl</a></code> is the Perl program that reads the JSON file and spits out the webpages.";
   print $fh " It also peeks at an image directory on my computer to see what cover art images are available.</li>\n";
   print $fh "<li><code><a href=\"About Responser.html\">About Responser.html</a></code> is my documentation page for this project.</li>\n";
   print $fh "</ul>\n";
   print $fh "<p>Note that prior to the creation of the Responser program in January 2018, all the response pages were marked up manually in Notepad.</p>\n";
   print $fh "<p>And last but not least, a HUGE thank you to all you authors of interactive fiction. You are all amazing. Thank you.</p>\n";
   print $fh "<p style=\"text-align:right\">&mdash; David Welbourn</p>\n";
   doFooter('index');
   print $fh "</body>\n</html>\n";
   close $fh;
}

sub makeResponsePage {
   my $commandKey = $_[0];
   my $respFilename = $commandKey . ".html";
   open($fh, ">:encoding(UTF-8)", $respFilename);
   print $fh "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n";
   print $fh "<meta charset=\"UTF-8\">\n";
   print $fh "<meta name=\"Author\" content=\"David Welbourn\">\n";
   print $fh "<title>" . uc($commandKey) . " Responses</title>\n";
   print $fh "<link rel=\"stylesheet\" type=\"text/css\" href=\"resp.css\">\n";
   insertJS();
   print $fh "</head>\n\n";
   print $fh "<body id=\"_top\">\n<h1><img src=\"../img/kaclogo.svg\" width=\"32\" height=\"32\" alt=\"\" style=\"vertical-align:middle;margin-left:3px;margin-right:0.5em;border-style:none\">" . uc($commandKey) . " Responses";
   print $fh "<span style=\"float:right;\">";
   print $fh "<a href=\"#_top\"><img src=\"../img/top20x20.png\" height=\"20\" width=\"20\" alt=\"[top]\" title=\"top\" style=\"vertical-align:middle;\"></a>";
   print $fh " <img src=\"../img/menu20x20.png\" height=\"20\" width=\"20\" alt=\"[menu]\" title=\"menu\" style=\"vertical-align:middle;margin-right:0.5em;\" onclick=\"toggle_vmenu()\">";
   print $fh "</span></h1>\n\n";
   print $fh "<div style=\"height:36px;\"><!-- spacer under the h1 --></div>\n\n";
   print_vmenu();   # The vertical menu with the sort menuitems.

   my $v = $respJsonData->{'commands'}->{$commandKey};

   # Intro
   if (exists($v->{'intro'})) {
      my $s = $v->{'intro'};
      print $fh "<p>" if (substr($s,0,1) ne '<');
      print $fh $s;
      print $fh "</p>" if (substr($s,0,1) ne '<');
      print $fh "\n<hr>\n";
   }

   # Temporary buttons
#   print $fh "\n<p>";
#   print $fh "<button onclick=\"sortList('data-s1')\">Sort by title</button> ";
#   print $fh "<button onclick=\"sortList('data-s2')\">Sort by response</button> ";
#   print $fh "<button onclick=\"sortList('data-s3')\">Sort by year</button> ";
#   print $fh "<button onclick=\"sortList('data-s4')\">Sort by reverse year</button>";
#   print $fh "</p>\n\n";

   # Main: Gather info
   my @rawInfo = ();
   foreach my $k (keys %{$respJsonData->{'works'}}) {
      my $v = $respJsonData->{'works'}{$k};
      if (exists($v->{$commandKey})) { # Process normal response:
         my $resp = $v->{$commandKey};
         if ($resp !~ /^!!/ and $resp ne '-') {
            my $relyear = calcReleaseYear($k, $v);
            my $revyear = 9999 - $relyear;
            my $r = { 'id' => $k, 'title' => $v->{'title'}, 'grp' => '1',
                      's1' => '1 ' . title2sort($v->{'title'}),
                      's2' => '1 ' . resp2sort($resp),
                      's3' => '1 ' . $relyear . ' ' . title2sort($v->{'title'}),
                      's4' => '1 ' . $revyear . ' ' . title2sort($v->{'title'}),
                      'resp' => $resp };
            if (exists($v->{'sfx'})) {
               $r->{'sfx'} = $v->{'sfx'};
            }
            $r->{'year'} = $relyear;
            if (exists($v->{$commandKey . '_nb'})) {
               $r->{'nb'} = $v->{$commandKey . '_nb'};
            }
            # Either push the record or merge it with an existing record:
            insertRecordIntoRaws( $r, \@rawInfo );
         }
         else {  # Process special response:
            my ($grp, $h2, $id, $title);
            if ($resp =~ /^(\-|!! >notunderstood)$/) {
               $h2 = 'notunderstood'; $grp = '5'; $id = 'see_notunderstood';
               $title =  uc($commandKey) . ' is not understood';
            }
            elsif ($resp =~ /^!! >(.*)$/) {
               $h2 = $1; $grp = '2'; $id = 'see_' . $h2;
               $title = uc($commandKey) . ' acts like ' . uc($h2);
            }
            elsif ($resp =~ /^!! \~(.*)$/) {
               $h2 = $1; $grp = '2'; $id = 'sim_' . $h2;
               $title = uc($commandKey) . ' acts like ' . uc($h2) . ' (but substitutes ' . uc($commandKey) . ' for ' . uc($h2) . ' in the response)';
            }
            elsif ($resp =~ /^!! @(.*)$/) {
               $h2 = $1; $grp = '3'; $id = 'default_' . s2id($h2);
               $title = uc($commandKey) . ' default in ' . $h2;
            }
            elsif ($resp =~ /^!! n\/a$/) {
               $h2 = 'n/a'; $grp = '6'; $id = 'n_a';
               $title = uc($commandKey) . ' is not applicable';
            }
            else { # Unexpected special response!
               $h2 = 'zzz'; $grp = '7'; $id = 'zzz';
               $title = uc($commandKey) . ' is in error [BUG]';
            }
            # Find or create the record.
            my ($r) = grep {$_->{'id'} eq $id} @rawInfo;
            if (!defined($r)) {
               $r = { 'id' => $id, 'title' => $title,  'grp' => $grp,
                      's1' => $grp . ' ' . lc($h2),
                      's2' => $grp . ' ' . lc($h2),
                      's3' => $grp . ' ' . lc($h2),
                      's4' => $grp . ' ' . lc($h2) };
            }
            # Add default system response and its nb, if applicable.
            if ($grp eq '3'
            and exists($respJsonData->{'systems'}{$h2})) {
               my $sysrec = $respJsonData->{'systems'}{$h2};
               if (exists($sysrec->{$commandKey})) {
                  $r->{'resp'} = $sysrec->{$commandKey};
                  $r->{'s2'} = '1 ' . resp2sort($r->{'resp'});
               }
               if (exists($sysrec->{$commandKey . '_nb'})) {
                  my $nb = $sysrec->{$commandKey . '_nb'};
                  if ($nb !~ /^</) { $nb = '<p>' . $nb . '</p>'; }
                  $r->{'nb'} = $nb;
               }
            }
            # Add work title to titles
            my $worktitle = $v->{'title'};
            $worktitle .= ' (' . $v->{'sfx'} . ')' if defined($v->{'sfx'});
            if (exists($r->{'titles'})) {
               push @{$r->{'titles'}}, $worktitle;
            }
            else {
               $r->{'titles'} = [ $worktitle ];
            }
            # Add work's resp_nb, if any.
            if (exists($v->{$commandKey . '_nb'})) {
               my $nb = $v->{$commandKey . '_nb'};
               if ($nb !~ /^</) { $nb = '<p>' . $nb . '</p>'; }
               if (exists($r->{'nb'})) {
                  $r->{'nb'} .= $nb;
               }
               else {
                  $r->{'nb'} = $nb;
               }
            }
            push @rawInfo, $r unless grep {$_->{'id'} eq $id} @rawInfo;            
         }
      }
   }

   # Sorting
   my @sortedInfo = sort { $a->{'s2'} cmp $b->{'s2'} } @rawInfo;
   my $counter = 1000;
   foreach my $r (@sortedInfo) {
      $r->{'s2'} = $counter++;  # Replace s2 field with a 4-digit number to sort by.
   }
   @sortedInfo = sort { $a->{'s3'} cmp $b->{'s3'} } @sortedInfo;
   $counter = 1000;
   foreach my $r (@sortedInfo) {
      $r->{'s3'} = $counter++;  # Replace s3 field with a 4-digit number to sort by.
   }
   @sortedInfo = sort { $a->{'s4'} cmp $b->{'s4'} } @sortedInfo;
   $counter = 1000;
   foreach my $r (@sortedInfo) {
      $r->{'s4'} = $counter++;  # Replace s4 field with a 4-digit number to sort by.
   }
   @sortedInfo = sort { $a->{'s1'} cmp $b->{'s1'} } @sortedInfo;
   $counter = 1000;
   foreach my $r (@sortedInfo) {
      $r->{'s1'} = $counter++;  # Replace s1 field with a 4-digit number to sort by.
   }

   # Main: Output
   my $nresp = 0;
   print $fh "<ul id=\"respList\">\n";
   foreach my $r (@sortedInfo) {
      # Possibly increment $nresp.
      if ($r->{'grp'} eq '1' || $r->{'grp'} eq '3') {
         $nresp++;
      }

      # Begin the article.
      my $id = $r->{'id'};
      print $fh "<li id=\"$id\"";
      print $fh " data-s1=\"" . $r->{'s1'} . "\"" if exists($r->{'s1'});
      print $fh " data-s2=\"" . $r->{'s2'} . "\"" if exists($r->{'s2'});
      print $fh " data-s3=\"" . $r->{'s3'} . "\"" if exists($r->{'s3'});
      print $fh " data-s4=\"" . $r->{'s4'} . "\"" if exists($r->{'s4'});
      print $fh ">\n";

      # Optional cover art.
      my $img = '';   
      if (-e "$imgDirectory/$id.png") { $img = 'png'; }
      elsif (-e "$imgDirectory/$id.jpg") { $img = 'jpg'; }
      elsif (-e "$imgDirectory/$id.gif") { $img = 'gif'; }
      if ($img ne '') {
         print $fh "<img class=\"cover80\" src=\"../img/covers/$id.$img\" alt=\"\">\n";
      }

      # Begin the entry. Statusline or Groupline?
      print $fh "<div class=\"entry\">\n";
      if ($r->{'grp'} eq '1') {
         print $fh "<p class=\"statusline\">" . title2html($r->{'title'});
         print $fh " <i>(" . $r->{'sfx'} . ")</i>" if defined($r->{'sfx'});
         print $fh " (" . $r->{'year'} . ")" if exists($r->{'year'});
         print $fh "</p>\n";
      }
      else {
         print $fh "<p class=\"groupline\">" . title2html($r->{'title'}) . "</p>\n";
         print $fh "<div style=\"max-height:200px; overflow:auto;\">\n";
      }

      # DEBUG s2
#      if (exists($r->{'s2'})) {
#         print $fh "<p class=\"debug\">" . $r->{'s2'} . "</p>\n";
#      }

      # Print the response(s).
      if (!exists($r->{'resp'})) {
         # Do nothing in this case.
      }
      elsif (substr($r->{'resp'},0,1) eq '<') {
         print $fh $r->{'resp'} . "\n";
      }
      else {
         print $fh "<p>" . $r->{'resp'} . "</p>\n";
      }

      # Print the note(s).
      if (exists($r->{'nb'})) {
         my $nb = $r->{'nb'};
         if ($nb !~ /^</) { $nb = "<p>" . $nb . "</p>"; }
         $nb =~ s/^<p>/<p class="nb lead">/ if exists($r->{'resp'});
         $nb =~ s/<p>/<p class="nb">/g;
         print $fh $nb . "\n";
      }

      # Sort and print the titles, if any.
      if (exists($r->{'titles'})) {
         my @sortedTitles = sort {title2sort($a) cmp title2sort($b)} @{$r->{'titles'}};
         print $fh "<p class=\"nb";
         print $fh " lead" if exists($r->{'resp'}) or exists($r->{'nb'});
         print $fh "\">";
         print $fh "Also: " if $r->{'grp'} eq '1';
         my $ntitles = 0;
         foreach my $title (@sortedTitles) {
            print $fh "; " if $ntitles > 0;
            print $fh title2html($title); $ntitles++;
         }
         print $fh ".</p>\n";
      }

      # End the entry and list item.
      print $fh "</div>" if $r->{'grp'} ne '1';
      print $fh "</div>\n</li>\n\n";
   }
   print $fh "</ul>\n\n";   # End of respList.

   # Remember the number of responses.
   $respJsonData->{'commands'}{$commandKey}{'nresp'} = $nresp;

   # Outro
   print $fh "<hr>\n";
   if (exists($v->{'outro'})) {
      my $s = $v->{'outro'};
      print $fh "<p>" if (substr($s,0,1) ne '<');
      print $fh $s;
      print $fh "</p>" if (substr($s,0,1) ne '<');
      print $fh "\n";
   }
   print $fh "<p>Do you want to see the ";
   my @otherCommands = ();
   foreach my $command (sort keys %{$respJsonData->{"commands"}}) {
      push @otherCommands, $command if $command ne $commandKey;
   }
   my $n = 1; my $nCommands = scalar(@otherCommands);
   foreach my $command (@otherCommands) {
      print $fh "or " if $n == $nCommands;
      print $fh "<a href=\"$command.html\">". uc($command) . "</a>";
      print $fh "," if $n != $nCommands;
      print $fh " ";
      $n++;
   }
   print $fh " page next?</p>";

   doFooter($commandKey);
   print $fh "</body>\n</html>\n";
   close $fh;
}

sub doFooter {
   my $commandKey = $_[0];
   my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
   my $dd = substr('0' . $mday,-2);
   my $mmm = ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec')[$mon];
   my $yyyy = 1900 + $year;
   print $fh "<footer>\n";
   print $fh "<hr>\n";
   print $fh "<p style=\"font-size:small;text-align:center;\">\n";
   if ($commandKey eq 'index') {
      print $fh " <a href=\"../ifindex.html\">Interactive Fiction</a>\n";
   }
   else {
      print $fh " <a href=\"index.html\">IF Responses</a>\n";
   }
   print $fh " <span style=\"float:left;\">Last updated: $dd $mmm $yyyy</span>\n";
   print $fh " <span style=\"float:right;\">davidwelbourn(a)hotmail*com</span>\n";
   print $fh "</p>\n";
   print $fh "</footer>\n";
}

sub print_vmenu {
   my $code = <<'END_VMENU';
<div class="menupopup">
<ul class="vmenu">
<li><a href="javascript:sortList('data-s1');">Sort by Title</a></li>
<li><a href="javascript:sortList('data-s2');">Sort by Response</a></li>
<li><a href="javascript:sortList('data-s3');">Sort by Year</a></li>
<li><a href="javascript:sortList('data-s4');">Sort by Reverse Year</a></li>  
</ul>
</div>
END_VMENU
   print $fh $code;
}

sub title2sort {
   my $s = $_[0];
   $s = lc($s);
   if ($s =~ /^(.*)\|(.*)$/) {
      $s = $2 . ' (' . $1 .')';
   }
   # TODO: replace accented characters with unaccented ones.
   return $s;
}

sub title2html {
   my $s = $_[0];
   $s =~ s/\|//g;
   $s =~ s/&/&amp;/g;
   $s =~ s/</&lt;/g;
   $s =~ s/>/&gt;/g;
   $s =~ s/\[or\]/|/g;
   return $s;
}

sub s2id {
   my $s = $_[0];
   $s = lc($s);
   $s =~ s/ /_/g;
   $s =~ s/\//_/g;
   return $s;
}

sub calcReleaseYear {
   my ($k, $v) = @_;
   if (exists($v->{'year'})) {
      return $v->{'year'};
   }
   else {
      my $yy = substr($k, 5, 2);  # scrape the 2-digit year from the id.
      if ($yy eq 'xx' || $yy eq '') { return '0000'; }
      elsif ($yy ge '70') { return '19' . $yy; }
      else                { return '20' . $yy; }
   }
}

sub insertJS {
   my $jsCode = <<'END_JSCODE';
<script>
function toggle_vmenu() {
   var e = document.getElementsByClassName("menupopup")[0];
   if (e.style.visibility === 'visible') { e.style.visibility = 'hidden'; }
   else { e.style.visibility = 'visible'; }
}

function sortList(key) {
   var list = document.getElementById('respList');
   var nodes = list.children;
   // Bubble sort
   for (var i = 0; i < nodes.length - 1; i++) {
      for (var j = i + 1; j < nodes.length; j++) {
         var iNode = nodes[i];
         var jNode = nodes[j];
         if (iNode.getAttribute(key) > jNode.getAttribute(key)) {
            if (iNode.swapNode) {
                iNode.swapNode(jNode);
            }
            else {
                var kNode = iNode.nextSibling;
                list.replaceChild(iNode, jNode);
                list.insertBefore(jNode, kNode);
            }
         }
      }
   }
   // Hide the vmenu
   document.getElementsByClassName("menupopup")[0].style.visibility = 'hidden';
}
</script>

END_JSCODE
   print $fh $jsCode;
}

# Convert a response string into something I can sort by.
sub resp2sort {
   my $s = $_[0];
   $s = lc($s);
   $s =~ s/\n/ /gs;
#   $s =~ s/<i>\[.*?\]<\/i>//g;  # remove my embedded comments. (old markup!)
   $s =~ s/<ins>.*?<\/ins>//g;   # remove my embedded comments. (new markup!) 
   $s =~ s/<.*?>//g;   # remove all HTML tags.
   $s =~ s/&[a-z]+;/ /g;   # remove &nbsp; etc.
   # TODO: remove accents on characters.
   $s =~ s/\s+/ /g;    # replace whitespace sequences with single space.
   $s =~ s/^[^a-z0-9]+//;   # remove leading non-alphanumerics.
   if (length($s) > 50) {   # chop down to 50 characters if longer.
      $s = substr($s,0,50);
   }
   return $s;
}

sub insertRecordIntoRaws {
   my ($r1, $raws) = @_;

   # Find out if there's already a record with the new record's response.
   my ($r2) = grep {$_->{'grp'} eq '1' and $_->{'resp'} eq $r1->{'resp'}} @{$raws};
   if (!defined($r2)) {
      # It's a new response. Add the new record and we're done!
      push @{$raws}, $r1;
      return;
   }

   # Ugh. We now want to merge $r1 into $r2.
   # So we now need to figure out which work is older, more or less.
   # Use the third sort field (Sort by Year) for this.
   if ($r1->{'s3'} lt $r2->{'s3'}) {
      # Case 1: $r1 is older.

      # First, add $r2's title to $r2's titles array.
      my $r2Title = $r2->{'title'};
      $r2Title .= ' (' . $r2->{'sfx'} . ')' if defined($r2->{'sfx'});
      if (exists($r2->{'titles'})) {
         push @{$r2->{'titles'}}, $r2Title;
      }
      else {
         $r2->{'titles'} = [ $r2Title ];
      }

      # Second, subplant $r2's other fields with $r1's.
      $r2->{'id'} = $r1->{'id'};  $r2->{'title'} = $r1->{'title'};
      $r2->{'s1'} = $r1->{'s1'};  $r2->{'s2'} = $r1->{'s2'};
      $r2->{'s3'} = $r1->{'s3'};  $r2->{'s4'} = $r1->{'s4'};
      if (defined($r1->{'sfx'})) {
         $r2->{'sfx'} = $r1->{'sfx'};
      }
      elsif (defined($r2->{'sfx'})) {
         undef $r2->{'sfx'};
      }
      if (exists($r1->{'year'})) {
         $r2->{'year'} = $r1->{'year'};
      }
      elsif (exists($r2->{'year'})) {
         undef $r2->{'year'};
      }
      if (exists($r1->{'nb'})) {
         $r2->{'nb'} = $r1->{'nb'};
      }
      elsif (exists($r2->{'nb'})) {
         undef $r2->{'nb'};
      }

      # Note that we might want to combine nb fields in the future.
      # Note that the resp field can be left alone since it's the same.
      # Note that the img field doesn't exist at all yet; that's later.

   }
   else {
      # Case 2: $r2 is older.

      # So just add $r1's title to $r2's titles array.
      my $r1Title = $r1->{'title'};
      $r1Title .= ' (' . $r1->{'sfx'} . ')' if defined($r1->{'sfx'});
      if (exists($r2->{'titles'})) {
         push @{$r2->{'titles'}}, $r1Title;
      }
      else {
         $r2->{'titles'} = [ $r1Title ];
      }

   }
}

#### NOTES ####
#
# Regarding $grp in the "special response" processing, its value is one of:
#  '1' means a work has its own response for this VERB.
#  '2' means a "VERB acts like VERB2" group.
#  '3' means a "VERB default in System" group.
#  '4' means a "VERB is handled oddly" group.
#  '5' means a "VERB is not understood" group.
#  '6' means a "VERB is not applicable" group.
# The values are the way they are just so I can sort the groups in this order.
