##!/usr/bonsaitools/bin/perl -w
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Rick Dean <software@fdd.com>
#

use diagnostics;
use strict;

require "CGI.pl";
use Date::Parse;

#
#  Sadly this report is going to be ugly (computationally expensive) to
#  compute, but the data is important and MIPS are cheap.
#
#  We will run through every bug in order, simultaneously pulling from
#  the bugs and bugs_activity tables.   From bugs we will only pull the
#  fields we are screening on.  We will do the selection in
#  perl not by the SQL database.  i.e. maybe no "where" clauses.  Yuck!
#
#


ConnectToDatabase();

$::FORM{'debug'} = 0; # Handy for debugging.


#  Load the fielddefs table because we will use it a lot
#  It is easier to load the whole darn thing even though we don't use it all.
#  The "sql" here is the name column.  The table default is bugs.
my(%field_id_to_fname,%field_fname_to_id,%field_fname_to_desc);
SendSQL("SELECT fieldid, name, description FROM fielddefs");
while(1) {
   my($id,$fname,$descrip) = FetchSQLData();
   last if ! defined $id;
   $field_id_to_fname{$id} = $fname;
   $field_fname_to_id{$fname} = $id;
   $field_fname_to_desc{$fname} = $descrip;
}
my($statfid,$resfid) = ($field_fname_to_id{'bug_status'},$field_fname_to_id{'resolution'});  # cause we use 'em a lot


# Now build some data structure about our legal vertical axes.
# The "nick" here should match the @axis_types of bug_count_trends.cgi that show in the form.
# "sql" Is the query we make so it looks pretty.
# "fname" matches the name column of the fielddefs table (which are the SQL field names of the bugs table).
my(%vert_nick_to_sql,%vert_nick_to_rownames);  # key is "nick"
sub DefVert
{
   my($nick,$sql,$fname,$rownames) = @_;
   if(! defined $field_fname_to_id{$fname} && $fname ne 'who' && $fname ne 'nothing') {
      print STDERR "### fielddefs missing fname=$fname nick=$nick\n";
   }
   $vert_nick_to_sql{$nick} = $sql;
   $vert_nick_to_rownames{$nick} = $rownames;
}
#         nick                sql                        fname          rowname-order
DefVert("severity"        ,"bug_severity"             ,"bug_severity",\@::severities);
DefVert("priority"        ,"priority"                 ,"priority"    ,\@::priorities);
DefVert("who"             ,"map_reporter.login_name"  ,"who"         ,undef);  # This one cheats  
DefVert("nothing"         ,"' '"                      ,"nothing"     ,undef);  # This one cheats  
DefVert("assigned_to"     ,"map_assigned_to.login_name","assigned_to" ,undef);
DefVert("reporter"        ,"map_reporter.login_name"  ,"reporter"    ,undef);
DefVert("status"          ,"bug_status"               ,"bug_status"  ,['UNCONFIRMED','NEW','ASSIGNED','REOPENED','RESOLVED','VERIFIED','CLOSED']);
DefVert("resolution"      ,"resolution"               ,"resolution"  ,undef);
DefVert("component"       ,"component"                ,"component"   ,undef);
DefVert("product"         ,"product"                  ,"product"     ,undef);
DefVert("version"         ,"version"                  ,"version"     ,undef);
DefVert("platform"        ,"rep_platform"             ,"rep_platform",undef);
DefVert("os"              ,"op_sys"                   ,"op_sys"      ,undef);
DefVert("qa_contact"      ,"map_qa_contact.login_name","qa_contact"  ,undef); # if Param("useqacontact");
DefVert("target_milestone","target_milestone"         ,"target_milestone",undef); # if Param("usetargetmilestone");
DefVert("short_desc"      ,"short_desc"               ,"short_desc"  ,undef);
DefVert("bug_file_loc"    ,"bug_file_loc"             ,"bug_file_loc",undef);

# This defines some Horizontal axis (time) times.
my(%time_nick_to_secs,%time_nick_to_format);
sub DefTime
{
   my($nick,$secs,$format) = @_;
   $time_nick_to_secs{$nick} = $secs;
   $time_nick_to_format{$nick} = $format;
}
DefTime("months",3600*24*30,"%b %e");
DefTime("2weeks",3600*24*14,"%b %e");
DefTime("weekly"  ,3600*24*7,"%b %e");
DefTime("3days" ,3600*24*3,"%b %e");
DefTime("daily"   ,3600*24,"%b %e");
DefTime("12hours",3600*12,"%l%P %b %e");
DefTime("4hours",3600,"%l%P %b %e");

my $vertnick = $::FORM{'vertical'} || "priority";  # use default if not specified
$vertnick = "priority" if ! defined $vert_nick_to_sql{$vertnick};  # must be a legal value
my $horiznick = $::FORM{'horizontal'} || "weekly"; # use default if not specified
$horiznick = "weekly" if ! defined $time_nick_to_secs{$horiznick};  # must be a legal value
my $interval_secs = $time_nick_to_secs{$horiznick};  
my $num_intervals = $::FORM{'num_intervals'} || 10;  # use defaul if not specified
$num_intervals = 2 if $num_intervals < 2;  # disallow too small
$num_intervals = 100 if $num_intervals > 100;  # disallow too large

#  These are the fields we will fetch from the bugs table and track from bugs_activity.
#  We index it several different ways.  Yeah, kind of ugly.
my(@attribsql,%attrib_sql_to_index,%attrib_fieldid_to_index,@attriballowed,@substringy);  
sub AddAttrib
{
   my($sql,$substry,@allowed) = @_;
   print("Noticing attrib sql=$sql ___ &lt;&lt;". join("&gt;&gt; &lt;&lt;",@allowed)  ."&gt;&gt;<br>\n") if $::FORM{'debug'};
   if(! defined $attrib_sql_to_index{$sql}) {  # if not already search
      push(@attribsql,$sql);
      $attrib_sql_to_index{$sql}                       = $#attribsql;  # index to find stuff in @attribsql and friends.
      if($field_fname_to_id{$sql}) {
         $attrib_fieldid_to_index{$field_fname_to_id{$sql}} = $#attribsql;  # index to find stuff in @attribsql and friends.
      };
   };
   my $index = $attrib_sql_to_index{$sql};
   if($substry) {
      $substringy[$index] = 1;
      foreach (@allowed) { 
         $_ =~ s/(\W)/\\$1/g;  # escape non word characters for regexp pattern
         $attriballowed[$index]{$_} = 1 
      };
   } else { # else not substring 
      foreach (@allowed) { $attriballowed[$index]{$_} = 1 };
   };
}
AddAttrib($vert_nick_to_sql{$vertnick},0);  # must be first
AddAttrib("bug_status",0);  
AddAttrib("resolution",0); 
# For every enum attribute that can be filtered on
foreach my $fname ("bug_status","resolution") {
   next unless $::FORM{$fname};
   AddAttrib($fname,0,split(/\|/,$::default{$fname}));   # add attrib with constraint
}
my @wheres;
foreach my $fname ("priority","bug_severity","rep_platform","op_sys",
                  "product","version","component","target_milestone") {
   next unless $::FORM{$fname};
   my @values = split(/\|/,$::default{$fname});
   if($::FORM{'retroactive'}) {
      push(@wheres,"(" . join(" OR ",map {"$fname = ". SqlQuote($_)} @values) .")");
   } else {
      AddAttrib($fname,0,@values);   # add attrib with constraint
   };
}
foreach my $fname ("reporter","assigned_to","qa_contact") {   
   next unless $::FORM{$fname};
   $::FORM{$fname} =~ tr/,/ /;  # map commas to spaces  (email addresses don't have commas)
   AddAttrib($vert_nick_to_sql{$fname},1,split(/\s+/,$::FORM{$fname}));   # add attrib with constraint
}
foreach my $fname ("short_desc","bug_file_loc") {
   next unless $::FORM{$fname};
   AddAttrib($vert_nick_to_sql{$fname},1,split(/\s+/,$::FORM{$fname}));   # add attrib with constraint
}

# show form stuff (if debug mode)
print "<table bgcolor=dddddd>", map { "<tr><Td>$_</td><td>$::FORM{$_}</td><td>". $::default{$_} ."</td></tr>\n" if $::FORM{$_} } keys %::FORM if $::FORM{'debug'}; 

# We will do all our time calcualtions as unix-timestamps (i.e. integer seconds since epoch of Jan 1, 1970)
SendSQL("SELECT UNIX_TIMESTAMP()");
my $now = FetchOneColumn();
if($::FORM{'csv'}) { # if we are producing CSV (comma separated variables)
   print(time2str("%C",$now) ."\n");
} else {   # else we are spitting HTML
   print("<center><b>". time2str("%C",$now) ."</b></center><br>\n");
}

# Query the database
my $query = "SELECT bugs.bug_id, UNIX_TIMESTAMP(creation_ts), ". join(",\n       ",@attribsql) ." FROM bugs\n";
if(defined $attrib_sql_to_index{$vert_nick_to_sql{"qa_contact"}}) {
   $query .= "LEFT JOIN profiles map_qa_contact ON bugs.qa_contact = map_qa_contact.userid\n";
};
if(defined $attrib_sql_to_index{$vert_nick_to_sql{"reporter"}} || defined $attrib_sql_to_index{'who'}) {
   $query .= "LEFT JOIN profiles map_reporter ON bugs.reporter = map_reporter.userid\n";
}
if(defined $attrib_sql_to_index{$vert_nick_to_sql{"assigned_to"}}) {
   $query .= "LEFT JOIN profiles map_assigned_to ON bugs.assigned_to = map_assigned_to.userid\n";
}
if($::FORM{"cc"}) {  
   $query .= "LEFT JOIN cc ON bugs.bug_id = cc.bug_id LEFT JOIN profiles map_cc2 ON cc.who = map_cc2.userid\n";
   push(@wheres,"INSTR(LOWER(map_cc2.login_name),". SqlQuote(lc($::FORM{'cc'})) .")");
}
if($::FORM{'comment_add'}) {
   $query .= "LEFT JOIN longdescs ld ON bugs.bug_id = ld.bug_id LEFT JOIN profiles map_ld2 ON ld.who = map_ld2.userid\n";
   push(@wheres,"INSTR(LOWER(map_ld2.login_name),". SqlQuote(lc($::FORM{'comment_add'})) .")");
}
#push(@wheres,"bugs.bug_id = 958\n");
$query .= "WHERE ". join(" AND\n      ",@wheres) ."\n" if $#wheres >= 0;
$query .= "ORDER BY bug_id DESC";
print "<pre>$query</pre>\n" if $::FORM{'debug'};
SendSQL($query);
# Second (concurrent) query is not so pretty.  
# We need to pull from these concurrently so Push-pop of bugzilla 2.14 would not work.
my $actquerystr = "SELECT bug_id, UNIX_TIMESTAMP(bug_when), fieldid, oldvalue, login_name\n".
                  "FROM bugs_activity LEFT JOIN profiles ON who = userid\n";
$actquerystr .= "WHERE bug_when >= FROM_UNIXTIME(". ($now - $num_intervals * $interval_secs) .")\n";
$actquerystr .= "AND (fieldid = $field_fname_to_id{'bug_status'} OR fieldid = $field_fname_to_id{'resolution'})\n" if $::FORM{'retroactive'};
#$actquerystr .= "AND bug_id = 958\n"; 
$actquerystr .= "ORDER BY bug_id DESC, bug_when DESC";
my $actquery = $::db->submit($actquerystr) || die "$actquerystr: " . $::db->errstr;
$actquery->execute || die "$actquerystr: " . $::db->errstr;
my($act_bug_id,$act_when,$act_field_id,$act_old_value,$act_who) = $actquery->fetchrow();

# "row" and "col" refer to the final output table not the SQL database.
# The horizontal axis will be time.  Time flows to the right.
my @bcount; # THIS IS WHERE THE MAGIC HAPPENS
            # It is a list (by time) of hash refs (by vert metric) containing bug counts.
my @blist;  # THIS IS WHERE THE MAGIC HAPPENS
            # It is a list (by time) of hash refs (by vert metric) containing a string of bug_id's.
my %coltotal; # We use this to loop over the column names
my %rowtotal; # We use this to loop over the row names

# Here we know for sure which row name we want it counted as.
# We are still going to check the time range here.
sub count_bug($$$)
{
   my($timestamp,$vertval,$bug_id) = @_;
   return if $timestamp > $now;  # What the fuck?  bug in future.
   my $xoffset = $num_intervals - int( ($now - $timestamp) / $interval_secs ) - 1;
   #print("count_bug($timestamp,$vertval) now=$now xoffset=$xoffset<br>\n");
   return if $xoffset < 0;  # if it is too old, return
   # book it!
   $bcount[$xoffset]{$vertval} += 1;
   $blist[$xoffset]{$vertval} .= ",$bug_id" if $bcount[$xoffset]{$vertval} <= 100; # only the first 100
   $rowtotal{$vertval} += 1;
   $coltotal{$xoffset} += 1;
}
sub count_bug_since($$$$)
{
   my($timestamp,$vertval,$bug_id,$since) = @_;
   my $censusTime;
   if($timestamp < $now - ($num_intervals * $interval_secs)) {  # if timestamp is older than our table (e.g. creation_ts)
      $censusTime = $now - ($num_intervals * $interval_secs);  # use the table starting time
   } else {  # else the timestamp is during our table
      $censusTime = $timestamp + (($now - $timestamp) % $interval_secs);  # round forward to next census interval
   };
   for(;$censusTime <= $since;$censusTime += $interval_secs) {  # for every census where...  timestamp < censusTime <= since 
      count_bug($censusTime,$vertval,$bug_id);
   }
}

# Count the metric so we know how descriptive to be on the vertical axis labels
my $num_metrics = grep { /^metric_/ } keys %::FORM;
if($num_metrics == 0) {  # if no metric
   print("<br><font color=red>(No metric was selected.)</font><br>\n");
   return 1;
}

# We have done no screening at this point.
sub log_change_event
{
   my($bug_id,$timestamp,$bug_status_changed,$bug_resolution_changed,$since,@attribs) = @_;
   #print("log_change_event() bug_id=$bug_id timestamp=$timestamp (".time2str("%b %e",$timestamp).") stat=".
   #      ($bug_status_changed||"") ." res=".($bug_resolution_changed||"") ." || ". join(" - ",@attribs) ."<br>\n");
   # Screen the bug...
   foreach my $index (0..$#attribsql) {
      next if ! defined $attriballowed[$index];    # if not screening on this one
      if($substringy[$index]) { 
         my $good;
         foreach (keys %{$attriballowed[$index]}) {
            $good = 1 if $attribs[$index] =~ /$_/; # attrib value must match at least one pattern 
         }
         return if ! $good;
      } else {  # else requires exact match
         return if ! defined $attriballowed[$index]{$attribs[$index]};  # attrib value must be in allowed hash
      }
   }

   # Log the bug for the chosen metrics...
   my $vertvalue = $attribs[0];
   $vertvalue = "(empty)" if ! defined $vertvalue || $vertvalue eq "";
   my $status =  $attribs[$attrib_sql_to_index{'bug_status'}];
   my $resolution =  $attribs[$attrib_sql_to_index{'resolution'}];
   if($bug_status_changed) {
      if($::FORM{'metric_openings'} && ($status eq "NEW" || $status eq "REOPENED")) {
         count_bug($timestamp,$num_metrics <= 1 ? $vertvalue : "$vertvalue - openings",$bug_id);
      }
      if($::FORM{'metric_resolves'} && $status eq "RESOLVED") {
         count_bug($timestamp,$num_metrics <= 1 ? $vertvalue : "$vertvalue - resolves",$bug_id);
      }
      if($::FORM{'metric_verifies'} && $status eq "VERIFIED") {
         count_bug($timestamp,$num_metrics <= 1 ? $vertvalue : "$vertvalue - verifies",$bug_id);
      }
      if($::FORM{'metric_closings'} && $status eq "CLOSED") {
         count_bug($timestamp,$num_metrics <= 1 ? $vertvalue : "$vertvalue - closings",$bug_id);
      }
   }
   if($bug_status_changed || $bug_resolution_changed) {
      if($::FORM{'metric_fixes'} && $status eq "RESOLVED" && $resolution eq "FIXED") {
         count_bug($timestamp,$num_metrics <= 1 ? $vertvalue : "$vertvalue - fixes",$bug_id);
      }
      if($::FORM{'metric_nonfixes'} && $status eq "RESOLVED" && $resolution ne "FIXED") {
         count_bug($timestamp,$num_metrics <= 1 ? $vertvalue : "$vertvalue - non-fixes",$bug_id);
      }
   };
   if($::FORM{'metric_census'}) {
      count_bug_since($timestamp,$num_metrics <= 1 ? $vertvalue : "$vertvalue - census",$bug_id,$since);
   }
   if($::FORM{'metric_open'} && ($status eq "NEW" || $status eq "ASSIGNED" || $status eq "REOPENED")) {
      count_bug_since($timestamp,$num_metrics <= 1 ? $vertvalue : "$vertvalue - open",$bug_id,$since);
   }
   if($::FORM{'metric_resolved'} && $status eq "RESOLVED") {
      count_bug_since($timestamp,$num_metrics <= 1 ? $vertvalue : "$vertvalue - resolved",$bug_id,$since);
   }
   if($::FORM{'metric_fixed'} && $status eq "RESOLVED" && $resolution eq "FIXED") {
      count_bug_since($timestamp,$num_metrics <= 1 ? $vertvalue : "$vertvalue  - fixed",$bug_id,$since);
   }
   if($::FORM{'metric_verified'} && $status eq "VERIFIED") {
      count_bug_since($timestamp,$num_metrics <= 1 ? $vertvalue : "$vertvalue - verified",$bug_id,$since);
   }
   if($::FORM{'metric_closed'} && $status eq "CLOSED") {
      count_bug_since($timestamp,$num_metrics <= 1 ? $vertvalue : "$vertvalue - closed",$bug_id,$since);
   }
}

# Now we shall compute the totals.
my $prev_bug_id = -1;
while(1) {  # For every bug
   my($bug_id,$creation_ts,@attribs) = FetchSQLData();  # fetch from bugs table
   last if ! defined $bug_id;
   next if $prev_bug_id == $bug_id;  # if bug repeat (e.g. because of longdescs join and repeated comment)
   $prev_bug_id = $bug_id;
   #print("bug start bug_id=$bug_id creation_ts=$creation_ts act_bug_id=$act_bug_id act_when=$act_when ___ ". join(" - ",@attribs) ."<br>\n");
   next if $creation_ts > $now;  # What the fuck?  bug in future.  Skip it.
   my $since = $now;  # keep track of last change time for bug count, so we know which columns to score it on
   # for each change time which belongs to the current bug (may be several bugs_activity rows with same time stamp)
   for(;defined $act_bug_id && $act_bug_id >= $bug_id;) {
      my($bug_status_changed,$bug_resolution_changed,$when);  # start with our flags false
      #print("___ event start act_bug_id=$act_bug_id act_when=$act_when since=$since<br>\n");
      my @attribs2 = @attribs;  # newer? older?  Before processing event, thus for later in time.
      $attribs2[0] = $act_who if $vertnick eq "who";
      # for each bugs_activity row for this change time
      for(;defined $act_bug_id && $act_bug_id >= $bug_id && (! defined $when || $when < $act_when + 3);
           ($act_bug_id,$act_when,$act_field_id,$act_old_value,$act_who) = $actquery->fetchrow()) {
         #print("______ attirb change act_bug_id=$act_bug_id act_when=$act_when $field_id_to_fname{$act_field_id} -> $act_old_value<br>\n");
         next if $act_bug_id > $bug_id;    # if bugs_activity has data on bug missing from bugs table 
         next if $act_when > $now;  #  What the fuck? activity in the future?  Skip it.
         $when = $act_when;
         $bug_status_changed = 1 if $act_field_id == $statfid;  # $field_sql_to_id{"bug_status"};
         $bug_resolution_changed = 1 if $act_field_id == $resfid; # $field_sql_to_id{"resolution"};
         my $index = $attrib_fieldid_to_index{$act_field_id};
         $attribs[$index] = $act_old_value if defined $index && (!$::FORM{'retroactive'} || $statfid == $act_field_id || $resfid == $act_field_id);
      }
      log_change_event($bug_id,$when,$bug_status_changed,$bug_resolution_changed,$since,@attribs2); # log changes at this time
      $since = $when-1;
   }
   log_change_event($bug_id,$creation_ts,1,1,$since,@attribs);  # log bug creation
}

my @rownames;
if(defined $vert_nick_to_rownames{$vertnick} && $num_metrics <= 1) {  # if the order of the rownames is forced
   @rownames = @{$vert_nick_to_rownames{$vertnick}};
} else { # else use the rows we found in ascii-sort order
   @rownames = sort keys %rowtotal;
}

if($::FORM{"csv"}) {  # if comma separated variable format
   # description 
   my $descript = $::buffer;  
   $descript =~ s/[^&]*=&//g;  # remove parameters set to nothing
   $descript =~ s/[^&]*=substring&//g;  # remove anything "=subsstring"
   $descript =~ s/csv=[^&]&//g;  # remove csv= (first param)
   $descript =~ s/\&/\n/g;  # convert "&" to newlines

   # top row
   print("$vertnick,");
   foreach my $xtime (0..$num_intervals-1) {
      print(scalar(time2str($time_nick_to_format{$horiznick},($now - ($num_intervals-$xtime-1)*$interval_secs))) .",");
   };
   print("totals\n");
   # middle rows
   foreach my $rowname (@rownames) {
      # row title on left side
      print("\"$rowname\",");
      # center of table
      foreach my $xtime (0..$num_intervals-1) {
         my $val = $bcount[$xtime]{$rowname} || "0";
         print("$val,");
      };
      # row total on right side
      my $val = $rowtotal{$rowname} || "0";
      print("$val\n");
   }
   # bottom row
   my $tottot = 0;
   print("totals,");
   foreach my $xtime (0..$num_intervals-1) {
      my $val = $coltotal{$xtime} || "0";
      $tottot += $val;
      print("$val,");
   };
   print("$tottot\n\n" . scalar(time2str("%C",time())) . "\n\n$descript");
   return 1;
};

# define color matrix for HTML
my @colors = ("c3d3ed","dddddd",
              "dfefff","ffffff");
my $totcolor = "bgcolor=ffcccc";

# Now we shall print out the HTML results
print("<table align=center>\n");
# top row
print("<tr align=right><td align=center>$vertnick</td>\n");
foreach my $xtime (0..$num_intervals-1) {
   my $color = $colors[2 + ($xtime & 1)];
   print("  <td bgcolor=$color>". scalar(time2str($time_nick_to_format{$horiznick},($now - ($num_intervals-$xtime-1)*$interval_secs))) ."</td>\n");
};
print("  <td>total</td></tr>\n");
# middle rows
my $row=0;
foreach my $rowname (@rownames) {
   print(" <tr align=right>\n");
   # row title on left side
   my $color = $colors[2 * ($row & 1) + 1];
   print("  <td bgcolor=$color>$rowname</td>\n");
   # center of table
   foreach my $xtime (0..$num_intervals-1) {
      my $color = $colors[2 * ($row & 1) + ($xtime & 1)];
      if($bcount[$xtime]{$rowname}) {
         my $val = $bcount[$xtime]{$rowname};
         my $url = "buglist.cgi?bug_id=". $blist[$xtime]{$rowname}; # don't bother trimming leading comma
         print("  <td bgcolor=$color><a href=$url>$val</a></td>\n");
      } else {
         print("  <td bgcolor=$color>.</td>\n");
      };
   };
   # row total on right side
   my $val = $rowtotal{$rowname} || "0";
   print("  <td $totcolor>$val</td>\n");
   print(" </tr>\n");
   $row++;
}
# bottom row
my $tottot = 0;
print("<tr align=right><td $totcolor>totals</td>\n");
foreach my $xtime (0..$num_intervals-1) {
   my $val = $coltotal{$xtime} || "0";
   $tottot += $val;
   print("  <td $totcolor>$val</td>\n");
};
print("<td $totcolor>$tottot</td></tr>\n");
print("</table>\n");


# give some friendly warnings 
if($vertnick eq "resolution" and $::buffer =~ /status=[^&]&?/ &&
   ! ($::buffer =~ /status=RESOLVED/ or $::buffer =~ /status=VERIFIED/ or $::buffer =~ /status=CLOSED/)) {
    print("<br><font color=red>\"Resolution\" is pretty borring when you specify " .
          "bugs not of RESOLVED, VERIFIED, or CLOSED, huh?</font>");
};
if($::buffer =~ /resolution=[^&]+&?/ and $::buffer =~ /status=[^&]&?/ and
   ! ($::buffer =~ /status=RESOLVED/ or $::buffer =~ /status=VERIFIED/ or $::buffer =~ /status=CLOSED/)) {
    print("<br><font color=red>Did you really mean to specify a " .
          "resolution but not bugs of RESOLVED, VERIFIED, or CLOSED?</font>");
};
if(($::FORM{'metric_resolved'} || $::FORM{'metric_fixed'} || $::FORM{'metric_resolves'} || $::FORM{'metric_fixes'} || $::FORM{'metric_nonfixes'}) && 
    $::FORM{'bug_status'} && $::FORM{'bug_status'} !~ /RESOLVED/) {
    print("<br><font color=red>Did you really mean to count resolves/resolved, but exclude the RESOLVED state?</font>\n");
}
if(($::FORM{'metric_openings'} || $::FORM{'metric_open'}) && $::FORM{'bug_status'} && $::FORM{'bug_status'} !~ /NEW/) {
    print("<br><font color=red>Did you really mean to count open/openings, but exclude the NEW state?</font>\n");
}
if(($::FORM{'metric_openings'} || $::FORM{'metric_open'}) && $::FORM{'bug_status'} && $::FORM{'bug_status'} !~ /REOPENED/) {
    print("<br><font color=red>Did you really mean to count open/openings, but exclude the REOPENED state?</font>\n");
}
if(($::FORM{'metric_verify'} || $::FORM{'metric_verified'}) && $::FORM{'bug_status'} && $::FORM{'bug_status'} !~ /VERIFIED/) {
    print("<br><font color=red>Did you really mean to count verified/verifies, but exclude the VERIFY state?</font>\n");
}
if(($::FORM{'metric_closings'} || $::FORM{'metric_closed'}) && $::FORM{'bug_status'} && $::FORM{'bug_status'} !~ /CLOSED/) {
    print("<br><font color=red>Did you really mean to count closed/closings, but exclude the CLOSED state?</font>\n");
}
if(($::FORM{'metric_resolved'} || $::FORM{'metric_resolves'}) && $::FORM{'resolution'}) {
    print("<br><font color=red>Did you really mean to count resolves, and filter based on resolution?</font>\n");
}
if(($::FORM{'metric_fixed'} || $::FORM{'metric_fixes'}) && $::FORM{'resolution'} && $::FORM{'resolution'} !~ /FIXED/) {
    print("<br><font color=red>Did you really mean to count fixed/fixes, but exclude resolution FIXED?</font>\n");
}
if(($::FORM{'metric_resolved'} || $::FORM{'metric_resolves'}) && $::FORM{'resolution'}) {
    print("<br><font color=red>Did you really mean to count resolved/resolves non-fixed, and filter based on resolution?</font>\n");
}


1;  # needed last for return value

