reporter.h

/* This is the header file for the Reporter module.  It is also included by
   the Cases module. */

/* external function */

extern void
Write_Report(
      file report_stream,
      file log_stream,
      area *area_pointer,
      vector_element *facts_head,
      vector_element *original_facts,
      boolean verbose,
      boolean hypothetical,
      boolean same_result,
      cardinal number,
      cardinal level);

reporter.c

/* This is the implementation file for the Reporter module. */

#include <stdio.h>
#include "shyster.h"
#include "cases.h"
#include "reporter.h"
#include "dumper.h"
#include "odometer.h"

static void
warning(
      file stream,
      const string message,
      cardinal level)
{
   Write_Warning_Message(stream, "Reporter", message, level);
}

static void
list_facts(
      file report_stream,
      vector_element *vector_pointer,
      attribute *attribute_pointer,
      cardinal count)

/* Lists the facts pointed to by vector_pointer by writing the appropriate
   string (YES, NO, or UNKNOWN) for each attribute. attribute_pointer is the
   head of the list of attributes for this area.  count is the number of
   attributes. */

{
   /* while there are still facts to list ... */

   while ((attribute_pointer != NULL) && (count != 0)) {

      if (count == 1)
         Write(report_stream, vector_pointer->attribute_value == YES ?
               attribute_pointer->yes : vector_pointer->attribute_value == NO ?
               attribute_pointer->no : attribute_pointer->unknown, ".", 1, Hang);
      else if (count == 2)
         Write(report_stream, vector_pointer->attribute_value == YES ?
               attribute_pointer->yes : vector_pointer->attribute_value == NO ?
               attribute_pointer->no : attribute_pointer->unknown, "; and", 1, Hang);
      else
         Write(report_stream, vector_pointer->attribute_value == YES ?
               attribute_pointer->yes : vector_pointer->attribute_value == NO ?
               attribute_pointer->no : attribute_pointer->unknown, ";", 1, Hang);

      vector_pointer = vector_pointer->next;
      attribute_pointer = attribute_pointer->next;
      count--;
   }
}

static void
list_equidistant_cases_known_first(
      file report_stream,
      kase *known_case_pointer,
      kase *unknown_case_pointer,
      boolean short_names,
      boolean and)

/* Lists the case pointed to by known_case_pointer (and any known equidistant
   cases), then the case pointed to by unknown_case_pointer (and any unknown
   equidistant cases).  Writes short case names, if short_names is TRUE.
   Writes "and" between the last two cases in the list, if and is TRUE;
   writes "or", otherwise. */

{
   /* while there are still known cases to list ... */

   while (known_case_pointer != NULL) {

      /* write the case name with appropriate trailing characters */

      fprintf(report_stream, "{\\it %s", short_names ?
            known_case_pointer->short_name : known_case_pointer->name);
      if ((known_case_pointer->equidistant_known_next == NULL) &&
            (unknown_case_pointer == NULL))

         /* this is the last case to list */

         fprintf(report_stream, "\\/}");

      else if (((known_case_pointer->equidistant_known_next != NULL) &&
                     (known_case_pointer->equidistant_known_next->
                           equidistant_known_next == NULL) &&
                     (unknown_case_pointer == NULL)) ||
               ((known_case_pointer->equidistant_known_next == NULL) &&
                     (unknown_case_pointer != NULL) &&
                  (unknown_case_pointer->equidistant_unknown_next == NULL)))

         /* this is the penultimate case to list */

         fprintf(report_stream, "\\/} %s\n", and ? "and" : "or");

      else
         fprintf(report_stream, "},\n");

      known_case_pointer = known_case_pointer->equidistant_known_next;
   }

   /* while there are still unknown cases to list ... */

   while (unknown_case_pointer != NULL) {

      /* write the case name with appropriate trailing characters */

      fprintf(report_stream, "{\\it %s", short_names ?
            unknown_case_pointer->short_name : unknown_case_pointer->name);
      if (unknown_case_pointer->equidistant_unknown_next == NULL)

         /* this is the last case to list */

         fprintf(report_stream, "\\/}");

      else if (unknown_case_pointer->equidistant_unknown_next->
            equidistant_unknown_next == NULL)

         /* this is the penultimate case to list */

         fprintf(report_stream, "\\/} %s\n", and ? "and" : "or");

      else
         fprintf(report_stream, "},\n");

      unknown_case_pointer = unknown_case_pointer->equidistant_unknown_next;
   }
}

static void
list_equidistant_cases_unknown_first(
      file report_stream,
      kase *known_case_pointer,
      kase *unknown_case_pointer,
      boolean short_names,
      boolean and)

/* Lists the case pointed to by unknown_case_pointer (and any unknown
   equidistant cases), then the case pointed to by known_case_pointer (and
   any known equidistant cases).  Writes short case names, if short_names is
   TRUE.  Writes "and" between the last two cases in the list, if and is
   TRUE; writes "or", otherwise. */

{
   /* while there are still unknown cases to list ... */

   while (unknown_case_pointer != NULL) {

      /* write the case name with appropriate trailing characters */

      fprintf(report_stream, "{\\it %s", short_names ?
            unknown_case_pointer->short_name : unknown_case_pointer->name);
      if ((unknown_case_pointer->equidistant_unknown_next == NULL) &&
            (known_case_pointer == NULL))

         /* this is the last case to list */

         fprintf(report_stream, "\\/}");

      else if (((unknown_case_pointer->equidistant_unknown_next != NULL) &&
                     (unknown_case_pointer->equidistant_unknown_next->
                           equidistant_unknown_next == NULL) &&
                     (known_case_pointer == NULL)) ||
               ((unknown_case_pointer->equidistant_unknown_next == NULL) &&
                     (known_case_pointer != NULL) &&
                  (known_case_pointer->equidistant_known_next == NULL)))

         /* this is the penultimate case to list */

         fprintf(report_stream, "\\/} %s\n", and ? "and" : "or");

      else
         fprintf(report_stream, "},\n");

      unknown_case_pointer = unknown_case_pointer->equidistant_unknown_next;
   }

   /* while there are still known cases to list ... */

   while (known_case_pointer != NULL) {

      /* write the case name with appropriate trailing characters */

      fprintf(report_stream, "{\\it %s", short_names ?
            known_case_pointer->short_name : known_case_pointer->name);
      if (known_case_pointer->equidistant_known_next == NULL)

         /* this is the last case to list */

         fprintf(report_stream, "\\/}");

      else if (known_case_pointer->equidistant_known_next->equidistant_known_next == NULL)

         /* this is the penultimate case to list */

         fprintf(report_stream, "\\/} %s\n", and ? "and" : "or");

      else
         fprintf(report_stream, "},\n");

      known_case_pointer = known_case_pointer->equidistant_known_next;
   }
}

static void
list_equidistant_cases(
      file report_stream,
      kase *known_case_pointer,
      kase *unknown_case_pointer,
      boolean unknown_first,
      boolean short_names,
      boolean and)

/* Lists the case pointed to by known_case_pointer (and any known equidistant
   cases) and the case pointed to by unknown_case_pointer (and any unknown
   equidistant cases).  Lists the unknown cases first, if unknown_first is
   TRUE.  Writes short case names, if short_names is TRUE.  Writes "and"
   between the last two cases in the list, if and is TRUE; writes "or",
   otherwise. */

{
   if (unknown_first)
      list_equidistant_cases_unknown_first(report_stream, known_case_pointer,
            unknown_case_pointer, short_names, and);
   else
      list_equidistant_cases_known_first(report_stream, known_case_pointer,
            unknown_case_pointer, short_names, and);
}

static void
state_opinion(
      file report_stream,
      result *result_pointer,
      kase *known_case_pointer,
      kase *unknown_case_pointer,
      boolean unknown_first)

/* States its opinion: that, following the cases pointed to by
   known_case_pointer and unknown_case_pointer, the result will be that which
   is pointed to by result_pointer.  Lists the unknown cases first, if
   unknown_first is TRUE. */

{
   fprintf(report_stream, "---following \\frenchspacing\n");

   list_equidistant_cases(report_stream, known_case_pointer, unknown_case_pointer,
         unknown_first, FALSE, TRUE);

   fprintf(report_stream, "\\nonfrenchspacing---%%\n"
         "%s.\n\n", result_pointer->string);
}

static void
state_counter_opinion(
      file report_stream,
      result *result_pointer,
      kase *known_case_pointer,
      kase *unknown_case_pointer,
      boolean unknown_first)

/* States a counter opinion: that, if the cases pointed to by
   known_case_pointer and unknown_case_pointer are followed, the result will
   be that which is pointed to by result_pointer.  Lists the unknown cases
   first, if unknown_first is TRUE. */

{
   boolean plural = FALSE;

   fprintf(report_stream, "%s If \\frenchspacing\n", Skip);

   list_equidistant_cases(report_stream, known_case_pointer, unknown_case_pointer,
         unknown_first, FALSE, FALSE);

   if (known_case_pointer != NULL) {
      if (unknown_case_pointer != NULL)
         plural = TRUE;
      else if (known_case_pointer->equidistant_known_next != NULL)
         plural = TRUE;
   } else if (unknown_case_pointer != NULL)
      if (unknown_case_pointer->equidistant_unknown_next != NULL)
         plural = TRUE;

   fprintf(report_stream, " \\nonfrenchspacing\n"
         "%s followed then %s.\n\n",
         plural ? "are" : "is", result_pointer->string);
}

static cardinal
number_of_similarities(
      matrix_element *matrix_pointer,
      vector_element *vector_pointer)

/* Returns the number of similarities in attribute value pairs between a
   leading case and the instant case (their attribute values are pointed to
   by matrix_pointer and vector_pointer, respectively), where both cases have
   known attribute values. */

{
   cardinal count = 0;

   /* while there are still attribute values to compare ... */

   while (matrix_pointer != NULL) {

      if ((matrix_pointer->attribute_value != UNKNOWN) &&
            (matrix_pointer->attribute_value == vector_pointer->attribute_value))

         /* the corresponding attribute values are identical and known */

         count++;

      matrix_pointer = matrix_pointer->case_next;
      vector_pointer = vector_pointer->next;
   }
   return count;
}

static void
list_similarities(
      file report_stream,
      matrix_element *matrix_pointer,
      vector_element *vector_pointer,
      attribute *attribute_pointer,
      cardinal count)

/* Lists the similarities between a leading case and the instant case (their
   attribute values are pointed to by matrix_pointer and vector_pointer,
   respectively), where both cases have known attribute values, by writing
   the appropriate string (YES or NO) for the similar attributes.
   *attribute_pointer is the head of the list of attributes for this area.
   count is the number of similarities. */

{
   /* while there are still attribute values to compare, and not all of the
      similarities have been listed ... */

   while ((attribute_pointer != NULL) && (count != 0)) {

      if ((matrix_pointer->attribute_value != UNKNOWN) &&
            (matrix_pointer->attribute_value == vector_pointer->attribute_value)) {

         /* the corresponding attribute values are identical and known, so
            write the relevant string with appropriate trailing characters */

         if (count == 1)
            Write(report_stream, matrix_pointer->attribute_value == YES ?
                  attribute_pointer->yes : attribute_pointer->no, ".\n", 1, Hang);
         else if (count == 2)
            Write(report_stream, matrix_pointer->attribute_value == YES ?
                  attribute_pointer->yes : attribute_pointer->no, "; and", 1, Hang);
         else
            Write(report_stream, matrix_pointer->attribute_value == YES ?
                  attribute_pointer->yes : attribute_pointer->no, ";", 1, Hang);

         count--;
      }
      matrix_pointer = matrix_pointer->case_next;
      vector_pointer = vector_pointer->next;
      attribute_pointer = attribute_pointer->next;
   }
}

static cardinal
number_of_known_differences(
      matrix_element *matrix_pointer,
      vector_element *vector_pointer)

/* Returns the number of differences in attribute value pairs between a
   leading case and the instant case (their attribute values are pointed to
   by matrix_pointer and vector_pointer, respectively), where the leading
   case has a known attribute value. */

{
   cardinal count = 0;

   /* while there are still attribute values to compare ... */

   while (matrix_pointer != NULL) {

      if ((matrix_pointer->attribute_value != UNKNOWN) &&
            (matrix_pointer->attribute_value != vector_pointer->attribute_value))

         /* the corresponding attribute values are different, and the leading
            case's value is known, so increment the count */

         count++;

      matrix_pointer = matrix_pointer->case_next;
      vector_pointer = vector_pointer->next;
   }
   return count;
}

static void
list_known_differences(
      file report_stream,
      matrix_element *matrix_pointer,
      vector_element *vector_pointer,
      attribute *attribute_pointer,
      cardinal count)

/* Lists the differences between a leading case and the instant case (their
   attribute values are pointed to by matrix_pointer and vector_pointer,
   respectively), where the leading case has a known attribute value, by
   writing the appropriate string (YES or NO) for the different attributes.
   *attribute_pointer is the head of the list of attributes for this area.
   count is the number of differences. */

{
   /* while there are still attribute values to compare, and not all of the
      differences have been listed ... */

   while ((attribute_pointer != NULL) && (count != 0)) {

      if ((matrix_pointer->attribute_value != UNKNOWN) &&
            (matrix_pointer->attribute_value != vector_pointer->attribute_value)) {

         /* the corresponding attribute values are different, and the leading
            case's value is known, so write the relevant string with
            appropriate trailing characters */

         if (count == 1)
            Write(report_stream, matrix_pointer->attribute_value == YES ?
                  attribute_pointer->yes : attribute_pointer->no, ".", 1, Hang);
         else if (count == 2)
            Write(report_stream, matrix_pointer->attribute_value == YES ?
                  attribute_pointer->yes : attribute_pointer->no, "; and", 1, Hang);
         else
            Write(report_stream, matrix_pointer->attribute_value == YES ?
                  attribute_pointer->yes : attribute_pointer->no, ";", 1, Hang);
         count--;
      }
      matrix_pointer = matrix_pointer->case_next;
      vector_pointer = vector_pointer->next;
      attribute_pointer = attribute_pointer->next;
   }
}

static cardinal
number_of_unknowns(
      matrix_element *matrix_pointer)

/* Returns the number of UNKNOWNs in the leading case whose attribute values
   are pointed to by matrix_pointer. */

{
   cardinal count = 0;

   /* while there are still attribute values to check ... */

   while (matrix_pointer != NULL) {
      if (matrix_pointer->attribute_value == UNKNOWN)
         count++;
      matrix_pointer = matrix_pointer->case_next;
   }
   return count;
}

static void
list_unknowns(
      file report_stream,
      matrix_element *matrix_pointer,
      attribute *attribute_pointer,
      cardinal count)

/* Lists the UNKNOWN string for each unknown attribute in the leading case
   whose attribute values are pointed to by matrix_pointer. attribute_pointer
   is the head of the list of attributes for this area.  count is the number
   of UNKNOWNs. */

{
   /* while there are still attribute values to compare, and not all of the
      UNKNOWNs have been listed ... */

   while ((attribute_pointer != NULL) && (count != 0)) {

      if (matrix_pointer->attribute_value == UNKNOWN) {
         if (count == 1)
            Write(report_stream, attribute_pointer->unknown, ".", 1, Hang);
         else if (count == 2)
            Write(report_stream, attribute_pointer->unknown, "; and", 1, Hang);
         else
            Write(report_stream, attribute_pointer->unknown, ";", 1, Hang);
         count--;
      }
      matrix_pointer = matrix_pointer->case_next;
      attribute_pointer = attribute_pointer->next;
   }
}

static cardinal
number_of_differences(
      vector_element *vector_pointer_X,
      vector_element *vector_pointer_Y)

/* Returns the number of differences in attribute value pairs between two
   fact vectors. */

{
   cardinal count = 0;

   /* while there are still attribute values to compare ... */

   while (vector_pointer_X != NULL) {
      if (vector_pointer_X->attribute_value != vector_pointer_Y->attribute_value)
         count++;
      vector_pointer_X = vector_pointer_X->next;
      vector_pointer_Y = vector_pointer_Y->next;
   }
   return count;
}

static void
list_new_differences(
      file report_stream,
      vector_element *vector_pointer,
      vector_element *original_vector_pointer,
      attribute *attribute_pointer,
      cardinal count)

/* Lists the differences between an instantiation or hypothetical and the
   uninstantiated and unhypothesized instant case (their attribute values are
   pointed to by vector_pointer and original_vector_pointer, respectively),
   by writing the appropriate string (YES, NO or UNKNOWN) for the different
   attributes.  *attribute_pointer is the head of the list of attributes for
   this area.  count is the number of differences. */

{
   /* while there are still attribute values to compare, and not all of the
      differences have been listed ... */

   while ((attribute_pointer != NULL) && (count != 0)) {

      if (vector_pointer->attribute_value != original_vector_pointer->attribute_value) {

         /* the corresponding attribute values are different, so write the
            relevant string with appropriate trailing characters */

         if (count == 1)
            Write(report_stream, vector_pointer->attribute_value == YES ?
                  attribute_pointer->yes : vector_pointer->attribute_value == NO ?
                  attribute_pointer->no : attribute_pointer->unknown, ".", 1, Hang);
         else if (count == 2)
            Write(report_stream, vector_pointer->attribute_value == YES ?
                  attribute_pointer->yes : vector_pointer->attribute_value == NO ?
                  attribute_pointer->no : attribute_pointer->unknown, "; and", 1, Hang);
         else
            Write(report_stream, vector_pointer->attribute_value == YES ?
                  attribute_pointer->yes : vector_pointer->attribute_value == NO ?
                  attribute_pointer->no : attribute_pointer->unknown, ";", 1, Hang);
         count--;
      }
      vector_pointer = vector_pointer->next;
      original_vector_pointer = original_vector_pointer->next;
      attribute_pointer = attribute_pointer->next;
   }
}

static void
summarize_case(
      file report_stream,
      kase *case_pointer,
      boolean written_linking_paragraph,
      boolean verbose)

/* Writes the name of the case pointed to by case_pointer with its citation
   in a footnote.  Summarizes the case, if verbose is TRUE.  If
   written_linking_paragraph is TRUE, a brief paragraph was just written
   linking the previous case with this case (the two cases are equidistant). */

{
   if (!case_pointer->summarized) {

      /* the case has not been summarized yet */

      if (written_linking_paragraph)

         /* the case's citation has already been footnoted in the linking
            paragraph */

         fprintf(report_stream, "In \\frenchspacing\n"
               "{\\it %s}\\nonfrenchspacing,\n",
               case_pointer->short_name);

      else {

         fprintf(report_stream, "In \\frenchspacing\n"
               "{\\it %s}\\nonfrenchspacing,%%\n"
               "\\footnote{%s.}\n",
               case_pointer->name, case_pointer->citation);
         Write_Year_and_Court(report_stream, case_pointer, 1);
         fprintf(report_stream, ",\n");
      }

      if (verbose) {
         if (case_pointer->summary != NULL) {
            Write(report_stream, case_pointer->summary, "\n", 1, Hang);
            case_pointer->summarized = TRUE;
         } else
            fprintf(report_stream, "\n");
      } else
         Write(report_stream, "[summary].\n", Empty_String, 1, Hang);

   } else

      /* the case has already been summarized */

      fprintf(report_stream, "Details of \\frenchspacing\n"
            "{\\it %s\\/} \\nonfrenchspacing\n"
            "are summarized above.\n",
            case_pointer->short_name);
}

static void
list_similarities_and_differences(
      file report_stream,
      kase *case_pointer,
      attribute *attribute_head,
      vector_element *facts_head,
      string instant_case_type,
      boolean neighbour,
      boolean written_linking_paragraph,
      boolean unknown_list_to_follow,
      boolean verbose)

/* Cites the case pointed to by case_pointer, and lists the similarities
   between that case and the instant case (whose attribute values are pointed
   to by facts_head). Summarizes the case, if verbose is TRUE.
   instant_case_type is either "instant", "instantiated" or "hypothetical".
   If neighbour is TRUE, this case is a nearest neighbour; otherwise, it is a
   nearest other.  If written_linking_paragraph is TRUE, a brief paragraph
   was just written linking the previous case with this case (the two cases
   are equidistant).  If unknown_list_to_follow is TRUE, an invocation of
   list_unknowns() will immediately follow this invocation of
   list_similarities_and_differences(). */

{
   cardinal count;

   if (!written_linking_paragraph)

      /* a linking paragraph has not just been written */

      fprintf(report_stream, "%s ", Skip);

   if (case_pointer->summary != NULL)

      /* this case has a summary, so write the case name (with its citation
         in a footnote) and the summary */

      summarize_case(report_stream, case_pointer, written_linking_paragraph, verbose);

   if (Is_Zero_Subdistance(case_pointer->metrics.distance.known))

      /* there is no known distance between this case and the instant case */

      if (Is_Zero_Subdistance(case_pointer->metrics.distance.unknown)) {

         /* there is no unknown distance between this case and the instant
            case */

         fprintf(report_stream,
               "The %s case is on all fours with \\frenchspacing\n",
               instant_case_type);
         if (case_pointer->summary != NULL)
            fprintf(report_stream,
                  "{\\it %s}\\null\\nonfrenchspacing.\n",
                  case_pointer->short_name);
         else

            /* the case has no summary, so its citation has not yet been
               footnoted */

            fprintf(report_stream,
                  "{\\it %s}\\null\\nonfrenchspacing.%%\n"
                  "\\footnote{%s.}\n",
                  case_pointer->name, case_pointer->citation);
      } else {

         /* there is some unknown distance between this case and the instant
            case */

         fprintf(report_stream, "The %s case {\\it may\\/} be "
               "on all fours with \\frenchspacing\n", instant_case_type);
         if (case_pointer->summary != NULL)
            fprintf(report_stream, "{\\it %s\\/}\\nonfrenchspacing",
                  case_pointer->short_name);
         else

            /* the case has no summary, so its citation has not yet been
               footnoted */

            fprintf(report_stream, "{\\it %s\\/}\\nonfrenchspacing%%\n"
                  "\\footnote{%s.}",
                  case_pointer->name, case_pointer->citation);

         if (unknown_list_to_follow)

            /* a list of unknown differences follows immediately */

            fprintf(report_stream, "---but\n");

         else

            /* a statement that this case would have been followed (instead
               of the nearest neighbour) follows - and a list of unknown
               differences follows that */

            fprintf(report_stream, " and");

   } else {

      /* there is some known and/or unknown distance between this case and
         the instant case */

      count = number_of_similarities(case_pointer->matrix_head, facts_head);
      if (count != 0) {
         fprintf(report_stream, "There ");

         if (neighbour)

            /* characterize the similarities as "extremely significant"
               (one), "very significant" (two), or just "significant" (three
               or more) */

            switch (count) {
               case 1:
                  fprintf(report_stream,
                        "is one extremely significant similarity\n");
                  break;
               case 2:
                  fprintf(report_stream,
                        "are two very significant similarities\n");
                  break;
               default:
                  fprintf(report_stream,
                        "are several significant similarities\n");
                  break;
            }
         else
            switch (count) {
               case 1:
                  fprintf(report_stream,
                        "is one similarity\n");
                  break;
               case 2:
                  fprintf(report_stream,
                        "are two similarities\n");
                  break;
               default:
                  fprintf(report_stream,
                        "are several similarities\n");
                  break;
            }
         fprintf(report_stream, "between the %s case and \\frenchspacing\n",
               instant_case_type);

         if (case_pointer->summary != NULL)
            fprintf(report_stream, "{\\it %s\\/}\\null\\nonfrenchspacing:\n",
                  case_pointer->short_name);
         else

            /* the case has no summary, so its citation has not yet been
               footnoted */

            fprintf(report_stream, "{\\it %s\\/}\\null\\nonfrenchspacing:%%\n"
                  "\\footnote{%s.}\n",
                  case_pointer->name, case_pointer->citation);

         /* list the similarities between this case and the instant case */

         list_similarities(report_stream, case_pointer->matrix_head, facts_head,
               attribute_head, count);
      }
      count = number_of_known_differences(case_pointer->matrix_head, facts_head);

      if (neighbour)
         fprintf(report_stream,
               "However, the %s case is not on all fours "
               "with \\frenchspacing\n"
               "{\\it %s}\\null\\nonfrenchspacing.\n",
               instant_case_type, case_pointer->short_name);
      else {

         if (count != 0) {

            /* characterize the differences as "extremely significant" (one),
               "very significant" (two), or just "significant" (three or
               more) */

            fprintf(report_stream, "However, there ");
            switch (count) {
               case 1:
                  fprintf(report_stream,
                        "is one extremely significant difference\n");
                  break;
               case 2:
                  fprintf(report_stream,
                        "are two very significant differences\n");
                  break;
               default:
                  fprintf(report_stream,
                        "are several significant differences\n");
                  break;
            }
            fprintf(report_stream, "between the %s case and \\frenchspacing\n",
                  instant_case_type);
            fprintf(report_stream,
                  "{\\it %s}\\null\\nonfrenchspacing.\n",
                  case_pointer->short_name);
         }
      }
      fprintf(report_stream, "In that case\n");

      /* list the differences between this case and the instant case */

      list_known_differences(report_stream, case_pointer->matrix_head, facts_head,
            attribute_head, count);
   }
}

static boolean
has_less_known_distance(
      kase *case_pointer_X,
      kase *case_pointer_Y)

/* Returns TRUE, iff the case pointed to by case_pointer_X has less known
   distance than does that pointed to by case_pointer_Y. */

{
   return ((case_pointer_X != NULL) && (case_pointer_Y != NULL) &&
         ((case_pointer_X->metrics.distance.known.infinite <
                     case_pointer_Y->metrics.distance.known.infinite) ||
               ((case_pointer_X->metrics.distance.known.infinite ==
                           case_pointer_Y->metrics.distance.known.infinite) &&
                     Is_Less(case_pointer_X->metrics.distance.known.finite,
                           case_pointer_Y->metrics.distance.known.finite,
                           Distance_Precision))));
}

static void
state_confidence(
      file report_stream,
      string short_name)

/* States its confidence that the case called short_name should still be
   followed. */

{
   fprintf(report_stream, "Nevertheless, I believe that \\frenchspacing\n"
         "{\\it %s\\/} \\nonfrenchspacing\n"
         "should be followed.\n\n", short_name);
}

static boolean
write_number_as_word(
      file report_stream,
      cardinal number)

/* Writes number: as a word, if number <= 10; as a number, otherwise.
   Returns TRUE, iff number is not 1: i.e. if the noun to follow should be
   plural. */

{
   switch (number) {
      case 1:
         fprintf(report_stream, "one");
         break;
      case 2:
         fprintf(report_stream, "two");
         break;
      case 3:
         fprintf(report_stream, "three");
         break;
      case 4:
         fprintf(report_stream, "four");
         break;
      case 5:
         fprintf(report_stream, "five");
         break;
      case 6:
         fprintf(report_stream, "six");
         break;
      case 7:
         fprintf(report_stream, "seven");
         break;
      case 8:
         fprintf(report_stream, "eight");
         break;
      case 9:
         fprintf(report_stream, "nine");
         break;
      case 10:
         fprintf(report_stream, "ten");
         break;
      default:
         fprintf(report_stream, "%u", number);
         break;
   }
   return (number != 1);
}

static void
state_intransigence(
      file report_stream,
      kase *nearest_neighbour,
      kase *nearest_other)

/* Restates its opinion that the case pointed to by nearest_neighbour should
   be followed, and compares the relative importance of the courts that
   decided that case and the case pointed to by nearest_other. */

{
   if ((nearest_neighbour->court_string == NULL) || (nearest_other->court_string == NULL))

      /* the nearest neighbour or the nearest other has no court, so make no
         comment about each case's relative importance */

      fprintf(report_stream, "\nConsequently, ");

   else if (nearest_neighbour->court_rank < nearest_other->court_rank)

      /* the nearest neighbour was decided by a more important court than was
         the nearest other */

      fprintf(report_stream, "Note%s that \\frenchspacing\n"
            "{\\it %s\\/} \\nonfrenchspacing\n"
            "is only a decision of\n"
            "%s\n"
            "and not as good authority as a case decided by\n"
            "%s%%\n"
            "---like \\frenchspacing\n"
            "{\\it %s}\\null\\nonfrenchspacing.\n\n"
            "Consequently, ",
            Is_Zero_Subdistance(nearest_other->metrics.distance.known) &&
            Is_Zero_Subdistance(nearest_other->metrics.distance.unknown) ?
            ", however," : " also",
            nearest_other->short_name, nearest_other->court_string,
            nearest_neighbour->court_string, nearest_neighbour->short_name);

   else if (nearest_neighbour->court_rank > nearest_other->court_rank)

      /* the nearest other was decided by a more important court than was the
         nearest neighbour */

      fprintf(report_stream, "\nDespite the fact that \\frenchspacing\n"
            "{\\it %s\\/} \\nonfrenchspacing\n"
            "is a decision of\n"
            "%s\n"
            "(and better authority than a case decided by\n"
            "%s%%\n"
            "---like \\frenchspacing\n"
            "{\\it %s\\/}\\nonfrenchspacing),\n",
            nearest_other->short_name, nearest_other->court_string,
            nearest_neighbour->court_string, nearest_neighbour->short_name);

   else if (nearest_neighbour->court_string == nearest_other->court_string)

      /* the nearest neighbour and the nearest other were decided by the same
         court */

      fprintf(report_stream, "\nDespite the fact that \\frenchspacing\n"
            "{\\it %s\\/} \\nonfrenchspacing\n"
            "and \\frenchspacing\n"
            "{\\it %s\\/} \\nonfrenchspacing\n"
            "are both decisions of\n"
            "%s,\n", nearest_other->short_name,
            nearest_neighbour->short_name, nearest_other->court_string);

   else

      /* the nearest neighbour and the nearest other were decided by
         different courts of the same rank */

      fprintf(report_stream, "\nDespite the fact that \\frenchspacing\n"
            "{\\it %s\\/} \\nonfrenchspacing\n"
            "is a decision of\n"
            "%s\n"
            "(and as good authority as a case decided by\n"
            "%s%%\n"
            "---like \\frenchspacing\n"
            "{\\it %s\\/}\\nonfrenchspacing),\n",
            nearest_other->short_name, nearest_other->court_string,
            nearest_neighbour->court_string, nearest_neighbour->short_name);

   fprintf(report_stream, "there is nothing in \\frenchspacing\n"
         "{\\it %s\\/} \\nonfrenchspacing\n"
         "to warrant any change in my conclusion.\n\n",
         nearest_other->short_name);
}

static boolean
write_linking_paragraph(
      file report_stream,
      kase *previous_case_pointer,
      kase *next_case_pointer)

/* Writes a brief paragraph linking the previous case with the next case (the
   two cases are equidistant), if there is a next case.  Puts the two cases
   into context (i.e. explains which is more important and why).  Returns
   TRUE, if a paragraph is written, which means that the next paragraph
   should not include year and court information - information included in
   this linking paragraph. */

{
   if ((next_case_pointer != NULL) &&
         (previous_case_pointer->court_string != NULL) &&
         (next_case_pointer->court_string != NULL)) {

      /* the previous case and the next case are equidistant and both have a
         court string, so write a linking paragraph */

      fprintf(report_stream, "%s In %u,\n", Skip, next_case_pointer->year);

      if (previous_case_pointer->court_string == next_case_pointer->court_string) {

         /* the previous case and the next case were decided by the same
            court */

         if (previous_case_pointer->year == next_case_pointer->year)

            /* the previous case and the next case were decided in the same
               year */

            fprintf(report_stream, "the same year in which \\frenchspacing\n"
                  "{\\it %s\\/} \\nonfrenchspacing\n"
                  "was decided,\n",
                  previous_case_pointer->short_name);

         fprintf(report_stream, "%s\n"
               "also decided \\frenchspacing\n"
               "{\\it %s}\\null\\nonfrenchspacing.%%\n"
               "\\footnote{%s.}\n",
               next_case_pointer->court_string, next_case_pointer->name,
               next_case_pointer->citation);

         if (previous_case_pointer->year != next_case_pointer->year) {

            /* the previous case and the next case were decided in a
               different year; the previous case must be more recent than the
               next case (otherwise the next case would be earlier in the
               list than the previous case) */

            fprintf(report_stream, "(Note, however, that \\frenchspacing\n"
                  "{\\it %s\\/} \\nonfrenchspacing\n"
                  "is ", previous_case_pointer->short_name);

            if (write_number_as_word(report_stream,
                        previous_case_pointer->year - next_case_pointer->year))
               fprintf(report_stream, " years");
            else
               fprintf(report_stream, " year");

            fprintf(report_stream, " more recent than \\frenchspacing\n"
                  "{\\it %s}\\null\\nonfrenchspacing.)\n",
                  next_case_pointer->short_name);
         }
      } else if (previous_case_pointer->court_rank == next_case_pointer->court_rank) {

         /* the previous case and the next case were decided by different
            courts of the same rank */

         if (previous_case_pointer->year == next_case_pointer->year)

            /* the previous case and the next case were decided in the same
               year */

            fprintf(report_stream, "the same year in which \\frenchspacing\n"
                  "{\\it %s\\/} \\nonfrenchspacing\n"
                  "was decided by\n"
                  "%s, ",
                  previous_case_pointer->short_name,
                  previous_case_pointer->court_string);

         fprintf(report_stream, "\\frenchspacing\n"
               "{\\it %s\\/}\\nonfrenchspacing%%\n"
               "\\footnote{%s.}\n"
               "was decided by\n"
               "%s.\n"
               "(A case decided by\n"
               "%s\n"
               "is as good authority as a case decided by\n"
               "%s", next_case_pointer->name, next_case_pointer->citation,
               next_case_pointer->court_string, next_case_pointer->court_string,
               previous_case_pointer->court_string);

         if (previous_case_pointer->year == next_case_pointer->year)

            /* the previous case and the next case were decided in the same
               year */

            fprintf(report_stream, ".)\n");

         else {

            /* the previous case and the next case were decided in a
               different year; the previous case must be more recent than the
               next case (otherwise the next case would be earlier in the
               list than the previous case) */

            fprintf(report_stream, "%%\n"
                  "---like \\frenchspacing\n"
                  "{\\it %s\\/}\\nonfrenchspacing;\n"
                  "note, however, that \\frenchspacing\n"
                  "{\\it %s\\/} \\nonfrenchspacing\n"
                  "is ",
                  previous_case_pointer->short_name,
                  previous_case_pointer->short_name);

            if (write_number_as_word(report_stream,
                        previous_case_pointer->year - next_case_pointer->year))
               fprintf(report_stream, " years");
            else
               fprintf(report_stream, " year");

            fprintf(report_stream, " more recent than \\frenchspacing\n"
                  "{\\it %s}\\null\\nonfrenchspacing.)\n",
                  next_case_pointer->short_name);
         }

      } else {

         /* the previous case and the next case were decided by different
            courts of different ranks; the previous case must be more
            important than the next case, otherwise the next case would be
            earlier in the list than the previous case) */

         if (previous_case_pointer->year == next_case_pointer->year)

            /* the previous case and the next case were decided in the same
               year */

            fprintf(report_stream, "the same year in which \\frenchspacing\n"
                  "{\\it %s\\/} \\nonfrenchspacing\n"
                  "was decided by\n"
                  "%s, ",
                  previous_case_pointer->short_name,
                  previous_case_pointer->court_string);

         fprintf(report_stream, "\\frenchspacing\n"
               "{\\it %s\\/}\\nonfrenchspacing%%\n"
               "\\footnote{%s.}\n"
               "was decided by\n"
               "%s.\n"
               "(A case decided by\n"
               "%s\n"
               "is not as good authority as a case decided by\n"
               "%s", next_case_pointer->name, next_case_pointer->citation,
               next_case_pointer->court_string, next_case_pointer->court_string,
               previous_case_pointer->court_string);

         if (previous_case_pointer->year == next_case_pointer->year)

            /* the previous case and the next case were decided in the same
               year */

            fprintf(report_stream, ".)\n");

         else
            fprintf(report_stream, "%%\n"
                  "---like \\frenchspacing\n"
                  "{\\it %s\\/}\\nonfrenchspacing;\n",
                  previous_case_pointer->short_name);

         if (previous_case_pointer->year > next_case_pointer->year) {

            /* the previous case is more recent than the next case */

            fprintf(report_stream, "furthermore \\frenchspacing\n"
                  "{\\it %s\\/} \\nonfrenchspacing\n"
                  "is ", next_case_pointer->short_name);

            if (write_number_as_word(report_stream,
                        previous_case_pointer->year - next_case_pointer->year))
               fprintf(report_stream, " years");
            else
               fprintf(report_stream, " year");

            fprintf(report_stream, " older than \\frenchspacing\n"
                  "{\\it %s}\\null\\nonfrenchspacing.)\n",
                  previous_case_pointer->short_name);

         } else if (previous_case_pointer->year < next_case_pointer->year) {

            /* the next case is more recent than the previous case */

            fprintf(report_stream, "though \\frenchspacing\n"
                  "{\\it %s\\/} \\nonfrenchspacing\n"
                  "is ", next_case_pointer->short_name);

            if (write_number_as_word(report_stream,
                        next_case_pointer->year - previous_case_pointer->year))
               fprintf(report_stream, " years");
            else
               fprintf(report_stream, " year");

            fprintf(report_stream, " more recent than \\frenchspacing\n"
                  "{\\it %s}\\null\\nonfrenchspacing.)\n",
                  previous_case_pointer->short_name);
         }
      }

      fprintf(report_stream, "\n");

      return TRUE;

   } else

      /* no linking paragraph has been written */

      return FALSE;
}

static void
handle_near_unknown(
      file report_stream,
      result *result_pointer,
      result *nearest_result_pointer,
      kase *nearest_neighbour,
      boolean nearest_neighbour_is_known,
      area *area_pointer,
      vector_element *facts_head,
      string instant_case_type,
      boolean neighbour,
      boolean verbose)

/* Argues that the nearest unknown case of the result pointed to by
   result_pointer would have been followed but for its unknown distance. */

{
   kase *case_pointer;
   boolean written_linking_paragraph = FALSE;

   if (result_pointer == nearest_result_pointer) {

      fprintf(report_stream, "%s\\frenchspacing\n", Skip);

      list_equidistant_cases(report_stream, NULL, result_pointer->nearest_unknown_case,
            FALSE, FALSE, TRUE);

      fprintf(report_stream, " \\nonfrenchspacing\n"
            "%s in which %s.\n\n",
            result_pointer->nearest_unknown_case->equidistant_unknown_next ==
            NULL ? "is another case" : "are other cases", result_pointer->string);

   }

   /* for each nearest unknown case ... */

   for (case_pointer = result_pointer->nearest_unknown_case; case_pointer != NULL;
         case_pointer = case_pointer->equidistant_unknown_next) {

      list_similarities_and_differences(report_stream, case_pointer,
            area_pointer->attribute_head, facts_head, instant_case_type, neighbour,
            written_linking_paragraph, FALSE, verbose);

      fprintf(report_stream, "\n"
            "I would have suggested that \\frenchspacing\n"
            "{\\it %s\\/} \\nonfrenchspacing\n"
            "be followed (instead of \\frenchspacing\n",
            case_pointer->short_name);

      if (nearest_neighbour_is_known)
         list_equidistant_cases(report_stream, nearest_neighbour, NULL,
               FALSE, TRUE, TRUE);

      else
         list_equidistant_cases(report_stream, NULL, nearest_neighbour,
               FALSE, TRUE, TRUE);

      fprintf(report_stream, "\\nonfrenchspacing)\n"
            "except that\n");

      list_unknowns(report_stream, case_pointer->matrix_head,
            area_pointer->attribute_head, number_of_unknowns(case_pointer->matrix_head));

      if (!neighbour)
         state_intransigence(report_stream, nearest_neighbour, case_pointer);

      written_linking_paragraph = write_linking_paragraph(report_stream, case_pointer,
            case_pointer->equidistant_unknown_next);
   }
}

static void
handle_nearest_known(
      file report_stream,
      result *result_pointer,
      result *nearest_result_pointer,
      kase *nearest_neighbour,
      area *area_pointer,
      vector_element *facts_head,
      string instant_case_type,
      boolean verbose)

/* Argues that the result should be same as that of the nearest known
   neighbour.  Uses the nearest unknown neighbour too, if (but for its
   unknown distance) it would be the nearest neighbour. */

{
   kase *case_pointer;
   boolean written_linking_paragraph = FALSE;

   if (has_less_known_distance(result_pointer->nearest_unknown_case,
               result_pointer->nearest_known_case))

      /* if not for its unknown distance, the nearest unknown neighbour would
         be the nearest neighbour, so base the argument on the nearest known
         and the nearest unknown neighbours */

      state_opinion(report_stream, result_pointer,
            result_pointer->nearest_known_case,
            result_pointer->nearest_unknown_case, FALSE);

   else

      /* base the argument only on the nearest known neighbours */

      state_opinion(report_stream, result_pointer,
            result_pointer->nearest_known_case, NULL, FALSE);

   /* for each nearest known neighbour ... */

   for (case_pointer = result_pointer->nearest_known_case; case_pointer != NULL;
         case_pointer = case_pointer->equidistant_known_next) {

      list_similarities_and_differences(report_stream, case_pointer,
            area_pointer->attribute_head, facts_head, instant_case_type, TRUE,
            written_linking_paragraph, FALSE, verbose);

      fprintf(report_stream, "\n");

      if (!Is_Zero_Distance(case_pointer->metrics.distance))

         /* the instant case is not on all fours with the case */

         state_confidence(report_stream, case_pointer->short_name);

      written_linking_paragraph = write_linking_paragraph(report_stream, case_pointer,
            case_pointer->equidistant_known_next);
   }

   if (has_less_known_distance(result_pointer->nearest_unknown_case,
               result_pointer->nearest_known_case)) {

      /* if not for its unknown distance, the nearest unknown neighbour would
         be the nearest neighbour */

      handle_near_unknown(report_stream, result_pointer, nearest_result_pointer,
            nearest_neighbour, TRUE, area_pointer,
            facts_head, instant_case_type, TRUE, verbose);
      fprintf(report_stream, "\n");
   }
}

static void
handle_nearest_unknown(
      file report_stream,
      result *result_pointer,
      area *area_pointer,
      vector_element *facts_head,
      string instant_case_type,
      boolean verbose)

/* Argues that the result should be same as that in the nearest unknown
   neighbour.  Uses the nearest known neighbour too. */

{
   kase *case_pointer;
   boolean written_linking_paragraph = FALSE;
   cardinal count;

   /* base the argument on the nearest unknown and the nearest known
      neighbours */

   state_opinion(report_stream, result_pointer,
         result_pointer->nearest_known_case,
         result_pointer->nearest_unknown_case, TRUE);

   /* for every nearest unknown neighbour ... */

   for (case_pointer = result_pointer->nearest_unknown_case; case_pointer != NULL;
         case_pointer = case_pointer->equidistant_unknown_next) {

      list_similarities_and_differences(report_stream, case_pointer,
            area_pointer->attribute_head, facts_head, instant_case_type, TRUE,
            written_linking_paragraph, TRUE, verbose);

      count = number_of_unknowns(case_pointer->matrix_head);

      if (count != 0) {

         fprintf(report_stream, "Furthermore, \n");

         list_unknowns(report_stream, case_pointer->matrix_head,
               area_pointer->attribute_head, count);
      }
      fprintf(report_stream, "\n");

      state_confidence(report_stream, case_pointer->short_name);

      written_linking_paragraph = write_linking_paragraph(report_stream, case_pointer,
            case_pointer->equidistant_unknown_next);
   }
   /* for every nearest known neighbour ... */

   written_linking_paragraph = FALSE;

   for (case_pointer = result_pointer->nearest_known_case; case_pointer != NULL;
         case_pointer = case_pointer->equidistant_known_next) {

      list_similarities_and_differences(report_stream, case_pointer,
            area_pointer->attribute_head, facts_head, instant_case_type, TRUE,
            written_linking_paragraph, FALSE, verbose);

      fprintf(report_stream, "\n");

      if (!Is_Zero_Distance(case_pointer->metrics.distance))
         state_confidence(report_stream, case_pointer->short_name);

      written_linking_paragraph = write_linking_paragraph(report_stream, case_pointer,
            case_pointer->equidistant_known_next);
   }
}

static void
handle_nearest_others(
      file report_stream,
      result *result_pointer,
      result *nearest_result_pointer,
      kase *nearest_other,
      boolean nearest_other_is_known,
      area *area_pointer,
      vector_element *facts_head,
      string instant_case_type,
      boolean verbose)

/* Make a counter argument that the result should be same as that of the
   nearest known other.  Uses this result's nearest unknown other too, if
   (but for its unknown distance) it would be the nearest neighbour. */

{
   kase *case_pointer;
   boolean written_linking_paragraph = FALSE;
   cardinal count;

   if ((result_pointer->nearest_known_compared_with_unknown == FURTHER) ||
         has_less_known_distance(result_pointer->nearest_unknown_case,
               nearest_other)) {

      /* the nearest unknown other is the nearest other or if not for its
         unknown distance, the nearest unknown other would be the nearest
         neighbour */

      state_counter_opinion(report_stream, result_pointer,
            result_pointer->nearest_known_case,
            result_pointer->nearest_unknown_case, TRUE);

      if (has_less_known_distance(result_pointer->nearest_unknown_case,
                  nearest_other)) {

         /* if not for its unknown distance, the nearest unknown other would
            be the nearest neighbour */

         handle_near_unknown(report_stream, result_pointer, nearest_result_pointer,
               nearest_other, nearest_other_is_known, area_pointer,
               facts_head, instant_case_type, FALSE, verbose);

      } else

         /* for every nearest unknown other ... */

         for (case_pointer = result_pointer->nearest_unknown_case;
               case_pointer != NULL;
               case_pointer = case_pointer->equidistant_unknown_next) {

            list_similarities_and_differences(report_stream, case_pointer,
                  area_pointer->attribute_head, facts_head, instant_case_type, FALSE,
                  written_linking_paragraph, FALSE, verbose);

            count = number_of_unknowns(case_pointer->matrix_head);

            if (count != 0) {

               fprintf(report_stream, "Furthermore, \n");

               list_unknowns(report_stream, case_pointer->matrix_head,
                     area_pointer->attribute_head, count);
            }
            state_intransigence(report_stream, nearest_other, case_pointer);

            written_linking_paragraph = write_linking_paragraph(report_stream, case_pointer,
                  case_pointer->equidistant_unknown_next);
         }

   } else

      /* the nearest known other is the nearest other, so base the
         counter-opinion only on the nearest known other */

      state_counter_opinion(report_stream, result_pointer,
            result_pointer->nearest_known_case, NULL, FALSE);

   written_linking_paragraph = FALSE;

   for (case_pointer = result_pointer->nearest_known_case; case_pointer != NULL;
         case_pointer = case_pointer->equidistant_known_next) {

      /* for every nearest known other ... */

      list_similarities_and_differences(report_stream, case_pointer,
            area_pointer->attribute_head, facts_head, instant_case_type, FALSE,
            written_linking_paragraph, FALSE, verbose);

      state_intransigence(report_stream, nearest_other, case_pointer);

      written_linking_paragraph = write_linking_paragraph(report_stream, case_pointer,
            case_pointer->equidistant_known_next);
   }
}

static void
initialize_nearest_metrics(
      cardinal *minimum_known_differences,
      floating_point *minimum_association_coefficient,
      floating_point *minimum_weighted_association_coefficient,
      floating_point *max_correlation_coefficient,
      floating_point *max_weighted_correlation_coefficient,
      cardinal number_of_attributes)

/* Initializes the minimum and maximum metric variables. */

{
   *minimum_known_differences = number_of_attributes;
   *minimum_association_coefficient = 1.0;
   *minimum_weighted_association_coefficient = 1.0;
   *max_correlation_coefficient = -1.0;
   *max_weighted_correlation_coefficient = -1.0;
}

static void
find_nearest_metrics(
      metrics_type metrics,
      cardinal *minimum_known_differences,
      floating_point *minimum_association_coefficient,
      floating_point *minimum_weighted_association_coefficient,
      floating_point *max_correlation_coefficient,
      floating_point *max_weighted_correlation_coefficient,
      boolean weighted_association_coefficient)

/* Checks metrics against the various minimum and maximum values found so
   far, and changes each minimum/maximum if the relevant metric is less/more. */

{
   if (metrics.number_of_known_differences < *minimum_known_differences)
      *minimum_known_differences = metrics.number_of_known_differences;

   if (metrics.number_of_known_pairs == 0) {

      *minimum_known_differences = 0;
      *minimum_association_coefficient = 0.0;
      *minimum_weighted_association_coefficient = 0.0;

   } else {

      if (Is_Less((floating_point) metrics.number_of_known_differences /
                  metrics.number_of_known_pairs,
                  *minimum_association_coefficient, Precision))
         *minimum_association_coefficient =
               (floating_point) metrics.number_of_known_differences /
               metrics.number_of_known_pairs;

      if (weighted_association_coefficient)
         if (Is_Less(metrics.weighted_association_coefficient,
                     *minimum_weighted_association_coefficient, Precision))
            *minimum_weighted_association_coefficient =
                  metrics.weighted_association_coefficient;

      if (!metrics.correlation_coefficient.meaningless) {

         if (Is_Less(*max_correlation_coefficient,
                     metrics.correlation_coefficient.unweighted, Precision))
            *max_correlation_coefficient =
                  metrics.correlation_coefficient.unweighted;

         if (Is_Less(*max_weighted_correlation_coefficient,
                     metrics.correlation_coefficient.weighted, Precision))
            *max_weighted_correlation_coefficient =
                  metrics.correlation_coefficient.weighted;
      }
   }
}

static boolean
matches_nearest_neighbour(
      kase *case_pointer,
      kase **nearest_known_neighbour_pointer,
      kase **nearest_unknown_neighbour_pointer)

/* Returns TRUE (and adjusts the appropriate pointer so as to point to the
   next equidistant case), if case_pointer points to a nearest neighbour
   (known or unknown). */

{
   if (case_pointer == *nearest_known_neighbour_pointer) {
      *nearest_known_neighbour_pointer =
      (*nearest_known_neighbour_pointer)->equidistant_known_next;
      return TRUE;
   } else if (case_pointer == *nearest_unknown_neighbour_pointer) {
      *nearest_unknown_neighbour_pointer =
            (*nearest_unknown_neighbour_pointer)->equidistant_unknown_next;
      return TRUE;
   } else
      return FALSE;
}

static void
log_case_number_and_name(
      file log_stream,
      cardinal case_number,
      string case_name)
{
   fprintf(log_stream, "C%u%s %s", case_number, case_number < 10 ? " " : "", case_name);
}

static void
log_case(
      file log_stream,
      kase *case_pointer,
      result *result_pointer,
      boolean additional,
      cardinal level,
      string *subheading)

/* Writes, to the log file, details of the case pointed to by case_pointer.
   Writes a character before the case name: "*", if additional is TRUE and
   result_pointer is not NULL (i.e. the case is suggested by an extra metric,
   but is not a neighbour, and has a different result to the nearest result);
   "+", if additional is TRUE (i.e. the case is suggested by an extra metric,
   but is not a neighbour); "-", otherwise (i.e. the case is not suggested by
   the metric, but is a nearest neighbour).  Writes the result in parentheses
   after the case name, if result_pointer is not NULL. */

{
   if (*subheading != NULL) {
      Indent(log_stream, level + 1);
      fprintf(log_stream, "%s:\n", *subheading);
      *subheading = NULL;
   }
   Indent(log_stream, level + 1);
   fprintf(log_stream, "  %s ", additional ? result_pointer != NULL ? "*" : "+" : "-");
   log_case_number_and_name(log_stream, case_pointer->number, case_pointer->short_name);
   if (result_pointer != NULL)
      fprintf(log_stream, " (%s)", result_pointer->identifier);
   fprintf(log_stream, "\n");
}

static void
log_case_if_necessary(
      file log_stream,
      result *result_pointer,
      result *nearest_result_pointer,
      kase *case_pointer,
      kase **nearest_known_case_pointer,
      kase **nearest_unknown_case_pointer,
      cardinal level,
      boolean neighbour,
      string *heading,
      string *subheading,
      boolean *different_result)

/* Writes, to the log file, details of those cases about which the
   known/unknown distance and the extra similarity measures disagree.  Sets
   different_result to TRUE, if an alternative metric suggests, as a nearest
   neighbour, a case which is not a nearest neighbour and which has a result
   different from the nearest result. */

{
   if (neighbour) {

      /* the alternative metric suggests this as a nearest neighbour */

      if (!matches_nearest_neighbour(case_pointer,
                  nearest_known_case_pointer, nearest_unknown_case_pointer)) {

         /* it isn't a nearest neighbour, so log it as an additional case */

         if (*heading != NULL) {
            Indent(log_stream, level);
            fprintf(log_stream, "%s:\n\n", *heading);
            *heading = NULL;
         }
         if (result_pointer != nearest_result_pointer) {

            /* the result of this case is not the nearest result, so include
               the result in the log */

            log_case(log_stream, case_pointer, result_pointer, TRUE, level, subheading);
            if (different_result != NULL)
               *different_result = TRUE;

         } else

            /* the result of this case is the nearest result, so don't
               include it in the log */

            log_case(log_stream, case_pointer, NULL, TRUE, level, subheading);
      }

   } else if (matches_nearest_neighbour(case_pointer,
               nearest_known_case_pointer, nearest_unknown_case_pointer)) {

      /* the alternative metric does not suggest this case as a nearest
         neighbour, but it is a nearest neighbour, so log it as a missing
         case */

      if (*heading != NULL) {
         Indent(log_stream, level);
         fprintf(log_stream, "%s:\n\n", *heading);
         *heading = NULL;
      }

      /* the result of this case is the nearest result, so don't include it
         in the log */

      log_case(log_stream, case_pointer, NULL, FALSE, level, subheading);
   }
}

static void
implement_safeguards(
      file log_stream,
      area *area_pointer,
      string instant_case_type,
      cardinal level)

/* Implements the safeguards based on the extra similarity measures, and
   issues a warning in each of the following circumstances: the weighted
   association coefficients suggest that a case, with a different result than
   that of the nearest neighbour, ought to be the nearest neighbour; the
   weighted correlation coefficients suggest that a case, with a different
   result than that of the nearest neighbour, ought to be the nearest
   neighbour; an ideal point suggesting a different result is at least as
   near to the instant case as is the nearest neighbour; a centroid
   suggesting a different result is at least as near to the instant case as
   is the nearest neighbour; or the specified directions suggest a different
   result or results. */

{
   result *result_pointer,
     *equidistant_pointer;
   kase *case_pointer,
     *nearest_known_case_pointer,
     *nearest_unknown_case_pointer;
   cardinal minimum_known_differences;
   floating_point minimum_association_coefficient,
      minimum_weighted_association_coefficient,
      max_correlation_coefficient,
      max_weighted_correlation_coefficient;
   string heading = "Safeguards",
      subheading = NULL;
   char message[Max_Error_Message_Length];
   boolean weighted_different_result = FALSE,
      ideal_point_different_result = FALSE,
      centroid_different_result = FALSE,
      specified_direction_different_result = FALSE;

   initialize_nearest_metrics(&minimum_known_differences,
         &minimum_association_coefficient, &minimum_weighted_association_coefficient,
         &max_correlation_coefficient, &max_weighted_correlation_coefficient,
         area_pointer->number_of_attributes);

   for (result_pointer = area_pointer->result_head; result_pointer != NULL;
         result_pointer = result_pointer->next)
      for (case_pointer = result_pointer->case_head; case_pointer != NULL;
            case_pointer = case_pointer->next)
         find_nearest_metrics(case_pointer->metrics, &minimum_known_differences,
               &minimum_association_coefficient,
               &minimum_weighted_association_coefficient, &max_correlation_coefficient,
               &max_weighted_correlation_coefficient, !area_pointer->infinite_weight);

   subheading = "Distance measures";

   nearest_known_case_pointer =
         area_pointer->nearest_result->nearest_known_case;
   nearest_unknown_case_pointer =
         area_pointer->nearest_result->nearest_unknown_case;

   for (result_pointer = area_pointer->result_head; result_pointer != NULL;
         result_pointer = result_pointer->next)
      for (case_pointer = result_pointer->case_head; case_pointer != NULL;
            case_pointer = case_pointer->next)
         log_case_if_necessary(log_stream, result_pointer, area_pointer->nearest_result,
               case_pointer, &nearest_known_case_pointer,
               &nearest_unknown_case_pointer, level,
               case_pointer->metrics.number_of_known_differences ==
               minimum_known_differences,
               &heading, &subheading, NULL);
   if (subheading == NULL)
      fprintf(log_stream, "\n");

   subheading = "Association coefficients";

   nearest_known_case_pointer =
         area_pointer->nearest_result->nearest_known_case;
   nearest_unknown_case_pointer =
         area_pointer->nearest_result->nearest_unknown_case;

   for (result_pointer = area_pointer->result_head; result_pointer != NULL;
         result_pointer = result_pointer->next)
      for (case_pointer = result_pointer->case_head; case_pointer != NULL;
            case_pointer = case_pointer->next)
         log_case_if_necessary(log_stream, result_pointer, area_pointer->nearest_result,
               case_pointer, &nearest_known_case_pointer,
               &nearest_unknown_case_pointer, level,
               Is_Equal((floating_point) case_pointer->
                     metrics.number_of_known_differences /
                     case_pointer->metrics.number_of_known_pairs,
                     minimum_association_coefficient, Precision),
               &heading, &subheading, NULL);
   if (subheading == NULL)
      fprintf(log_stream, "\n");

   if (!area_pointer->infinite_weight) {

      /* none of the weights is infinite, so the values obtained for the
         weighted association coefficients are meaningful */

      subheading = "Weighted association coefficients";

      nearest_known_case_pointer =
            area_pointer->nearest_result->nearest_known_case;
      nearest_unknown_case_pointer =
            area_pointer->nearest_result->nearest_unknown_case;

      for (result_pointer = area_pointer->result_head; result_pointer != NULL;
            result_pointer = result_pointer->next)
         for (case_pointer = result_pointer->case_head; case_pointer != NULL;
               case_pointer = case_pointer->next)
            log_case_if_necessary(log_stream, result_pointer, area_pointer->nearest_result,
                  case_pointer, &nearest_known_case_pointer,
                  &nearest_unknown_case_pointer, level,
                  Is_Equal(case_pointer->metrics.weighted_association_coefficient,
                        minimum_weighted_association_coefficient, Precision),
                  &heading, &subheading, &weighted_different_result);
      if (subheading == NULL)
         fprintf(log_stream, "\n");
   }

   subheading = "Correlation coefficients";

   nearest_known_case_pointer =
         area_pointer->nearest_result->nearest_known_case;
   nearest_unknown_case_pointer =
         area_pointer->nearest_result->nearest_unknown_case;

   for (result_pointer = area_pointer->result_head; result_pointer != NULL;
         result_pointer = result_pointer->next)
      for (case_pointer = result_pointer->case_head; case_pointer != NULL;
            case_pointer = case_pointer->next)
         if (!case_pointer->metrics.correlation_coefficient.meaningless)
            log_case_if_necessary(log_stream, result_pointer, area_pointer->nearest_result,
                  case_pointer, &nearest_known_case_pointer,
                  &nearest_unknown_case_pointer, level,
                  Is_Equal(max_correlation_coefficient,
                        case_pointer->metrics.correlation_coefficient.unweighted,
                        Precision),
                  &heading, &subheading, NULL);
   if (subheading == NULL)
      fprintf(log_stream, "\n");

   subheading = "Weighted correlation coefficients";

   nearest_known_case_pointer =
         area_pointer->nearest_result->nearest_known_case;
   nearest_unknown_case_pointer =
         area_pointer->nearest_result->nearest_unknown_case;

   for (result_pointer = area_pointer->result_head; result_pointer != NULL;
         result_pointer = result_pointer->next)
      for (case_pointer = result_pointer->case_head; case_pointer != NULL;
            case_pointer = case_pointer->next)
         if (!case_pointer->metrics.correlation_coefficient.meaningless)
            log_case_if_necessary(log_stream, result_pointer, area_pointer->nearest_result,
                  case_pointer, &nearest_known_case_pointer,
                  &nearest_unknown_case_pointer, level,
                  Is_Equal(max_weighted_correlation_coefficient,
                        case_pointer->metrics.correlation_coefficient.weighted, Precision),
                  &heading, &subheading, &weighted_different_result);
   if (subheading == NULL)
      fprintf(log_stream, "\n");

   subheading = "Ideal points";

   for (equidistant_pointer = area_pointer->nearest_ideal_point; equidistant_pointer != NULL;
         equidistant_pointer = equidistant_pointer->equidistant_ideal_point_next)
      if (equidistant_pointer != area_pointer->nearest_result) {
         if (heading != NULL) {
            Indent(log_stream, level);
            fprintf(log_stream, "%s:\n\n", heading);
            heading = NULL;
         }
         if (subheading != NULL) {
            Indent(log_stream, level + 1);
            fprintf(log_stream, "%s:\n", subheading);
            subheading = NULL;
         }
         Indent(log_stream, level + 2);
         fprintf(log_stream, "%s\n", equidistant_pointer->identifier);
         if (area_pointer->nearest_result->nearest_known_case != NULL)
            if (Relative_Distance(equidistant_pointer->ideal_point_metrics.distance,
                        area_pointer->nearest_result->nearest_known_case->
                        metrics.distance) != FURTHER)
               ideal_point_different_result = TRUE;
         if (area_pointer->nearest_result->nearest_unknown_case != NULL)
            if (Relative_Distance(equidistant_pointer->ideal_point_metrics.distance,
                        area_pointer->nearest_result->nearest_unknown_case->
                        metrics.distance) != FURTHER)
               ideal_point_different_result = TRUE;
      }
   if (subheading == NULL)
      fprintf(log_stream, "\n");

   subheading = "Centroids";

   for (equidistant_pointer = area_pointer->nearest_centroid; equidistant_pointer != NULL;
         equidistant_pointer = equidistant_pointer->equidistant_centroid_next)
      if (equidistant_pointer != area_pointer->nearest_result) {
         if (heading != NULL) {
            Indent(log_stream, level);
            fprintf(log_stream, "%s:\n\n", heading);
            heading = NULL;
         }
         if (subheading != NULL) {
            Indent(log_stream, level + 1);
            fprintf(log_stream, "%s:\n", subheading);
            subheading = NULL;
         }
         Indent(log_stream, level + 2);
         fprintf(log_stream, "%s\n", equidistant_pointer->identifier);
         if (area_pointer->nearest_result->nearest_known_case != NULL)
            if (Relative_Distance(equidistant_pointer->centroid_metrics.distance,
                        area_pointer->nearest_result->nearest_known_case->
                        metrics.distance) != FURTHER)
               centroid_different_result = TRUE;
         if (area_pointer->nearest_result->nearest_unknown_case != NULL)
            if (Relative_Distance(equidistant_pointer->centroid_metrics.distance,
                        area_pointer->nearest_result->nearest_unknown_case->
                        metrics.distance) != FURTHER)
               centroid_different_result = TRUE;
      }
   if (subheading == NULL)
      fprintf(log_stream, "\n");

   subheading = "Specified directions";

   for (equidistant_pointer = area_pointer->strongest_specified_direction;
         equidistant_pointer != NULL;
         equidistant_pointer = equidistant_pointer->equidistant_specified_direction_next)
      if (equidistant_pointer != area_pointer->nearest_result) {
         if (heading != NULL) {
            Indent(log_stream, level);
            fprintf(log_stream, "%s:\n\n", heading);
            heading = NULL;
         }
         if (subheading != NULL) {
            Indent(log_stream, level + 1);
            fprintf(log_stream, "%s:\n", subheading);
            subheading = NULL;
         }
         Indent(log_stream, level + 2);
         fprintf(log_stream, "%s\n", equidistant_pointer->identifier);
         specified_direction_different_result = TRUE;
      }
   if (subheading == NULL)
      fprintf(log_stream, "\n");

   subheading = "Ideal point directions";

   for (equidistant_pointer = area_pointer->strongest_ideal_point_direction;
         equidistant_pointer != NULL;
         equidistant_pointer = equidistant_pointer->equidistant_ideal_point_direction_next)
      if (equidistant_pointer != area_pointer->nearest_result) {
         if (heading != NULL) {
            Indent(log_stream, level);
            fprintf(log_stream, "%s:\n\n", heading);
            heading = NULL;
         }
         if (subheading != NULL) {
            Indent(log_stream, level + 1);
            fprintf(log_stream, "%s:\n", subheading);
            subheading = NULL;
         }
         Indent(log_stream, level + 2);
         fprintf(log_stream, "%s\n", equidistant_pointer->identifier);
      }
   if (subheading == NULL)
      fprintf(log_stream, "\n");

   subheading = "Centroid directions";

   for (equidistant_pointer = area_pointer->strongest_centroid_direction;
         equidistant_pointer != NULL;
         equidistant_pointer = equidistant_pointer->equidistant_centroid_direction_next)
      if (equidistant_pointer != area_pointer->nearest_result) {
         if (heading != NULL) {
            Indent(log_stream, level);
            fprintf(log_stream, "%s:\n\n", heading);
            heading = NULL;
         }
         if (subheading != NULL) {
            Indent(log_stream, level + 1);
            fprintf(log_stream, "%s:\n", subheading);
            subheading = NULL;
         }
         Indent(log_stream, level + 2);
         fprintf(log_stream, "%s\n", equidistant_pointer->identifier);
      }
   if (subheading == NULL)
      fprintf(log_stream, "\n");

   /* issue warnings if necessary */

   if (weighted_different_result)
      warning(log_stream,
            "one or both of the weighted safeguard metrics suggest "
            "that a case (or cases) with a different result should "
            "be the nearest neighbour (or neighbours)", level);

   if (ideal_point_different_result) {
      sprintf(message,
            "one or more ideal points with a different result are at "
            "least as near to the %s case as is the nearest neighbour",
            instant_case_type);
      warning(log_stream, message, level);
   }
   if (centroid_different_result) {
      sprintf(message,
            "one or more centroids with a different result are at "
            "least as near to the %s case as is the nearest neighbour",
            instant_case_type);
      warning(log_stream, message, level);
   }
   if (specified_direction_different_result)
      warning(log_stream,
            "the specified directions suggest a different result or results",
            level);
}

extern void
Write_Report(
      file report_stream,
      file log_stream,
      area *area_pointer,
      vector_element *facts_head,
      vector_element *original_facts,
      boolean verbose,
      boolean hypothetical,
      boolean same_result,
      cardinal number,
      cardinal level)

/* Writes SHYSTER's legal opinion about the facts pointed to by facts_head to
   report_stream (if it is not NULL). Cases are summarized in full, and
   opening and closing strings are written in full, if verbose is TRUE.

   If number is not zero then the instant case is actually a hypothetical (if
   hypothetical is TRUE) or an instantiation, and number is its number.  If
   the instant case is an instantiation or a hypothetical, original_facts
   points to the facts of the uninstantiated and unhypothesized instant case.
   If the instant case is a hypothetical and same_result is TRUE, the
   hypothetical has the same result as the unhypothesized instant case. */

{
   result *result_pointer;
   kase *case_pointer,
     *nearest_neighbour;
   string instant_case_type;

   if (number == 0) {

      /* the instant case is the uninstantiated and unhypothesized instant
         case */

      instant_case_type = "instant";

      if (report_stream != NULL) {

         fprintf(report_stream, "%s{%s area}\n\n"
               "%s{Instant case}\n\n",
               Heading, area_pointer->identifier, Subheading);

         /* write the opening string */

         if (area_pointer->opening != NULL) {
            if (verbose)
               Write(report_stream, area_pointer->opening, "\n", Top_Level, Hang);
            else
               Write(report_stream, "[Opening.]", "\n", Top_Level, Hang);
            fprintf(report_stream, "%s ", Skip);
         }

         /* write the facts of the instant case */

         fprintf(report_stream, "In the instant case,\n");
         list_facts(report_stream, facts_head, area_pointer->attribute_head,
               area_pointer->number_of_attributes);

         fprintf(report_stream, "\n%s In my opinion", Skip);
      }
   } else if (!hypothetical) {

      /* the instant case is instantiation number */

      instant_case_type = "instantiated";

      if (report_stream != NULL) {

         fprintf(report_stream, "%s{Instantiation %u}\n\n", Subheading, number);
         fprintf(report_stream,
               "It may be that the following is true of the instant case:\n");
         list_new_differences(report_stream, facts_head, original_facts,
               area_pointer->attribute_head,
               number_of_differences(facts_head, original_facts));

         fprintf(report_stream, "\n%s If that is so then in my opinion", Skip);
      }
   } else {

      /* the instant case is hypothetical number */

      instant_case_type = "hypothetical";

      if (report_stream != NULL) {

         fprintf(report_stream, "%s{Hypothetical %u}\n\n", Subheading, number);
         fprintf(report_stream, "Consider the instant case changed "
               "so that the following is true:\n");
         list_new_differences(report_stream, facts_head, original_facts,
               area_pointer->attribute_head,
               number_of_differences(facts_head, original_facts));

         if (same_result)

            /* this hypothetical has the same result as does the instant case */

            fprintf(report_stream,
                  "\n%s If that were so then I would be "
                  "more strongly of the\n"
                  "opinion that", Skip);

         else

            /* this hypothetical has a different result to that of the
               instant case */

            fprintf(report_stream,
                  "\n%s If that were so then my opinion would be\n"
                  "that", Skip);
      }
   }
   Indent(log_stream, level);
   fprintf(log_stream, "Nearest neighbours:\n\n");
   Indent(log_stream, level + 1);
   fprintf(log_stream, "%s:\n", area_pointer->nearest_result->identifier);

   if (area_pointer->nearest_result->nearest_known_compared_with_unknown != FURTHER) {

      /* the nearest known neighbour is the nearest neighbour (although there
         may be an equidistant case with an unknown distance) */

      nearest_neighbour = area_pointer->nearest_result->nearest_known_case;

      /* for every nearest known case with this result ... */

      for (case_pointer = area_pointer->nearest_result->nearest_known_case;
            case_pointer != NULL;
            case_pointer = case_pointer->equidistant_known_next) {

         /* log the case (a nearest known neighbour) */

         Indent(log_stream, level + 2);
         log_case_number_and_name(log_stream, case_pointer->number,
               case_pointer->short_name);
         if (Is_Zero_Subdistance(case_pointer->metrics.distance.known))
            fprintf(log_stream, " (identical)");
         fprintf(log_stream, "\n");
      }

      if (has_less_known_distance(area_pointer->nearest_result->nearest_unknown_case,
                  area_pointer->nearest_result->nearest_known_case))

         /* if not for its unknown distance, the nearest unknown neighbour
            would be the nearest neighbour, so for every nearest unknown case
            with this result ... */

         for (case_pointer = area_pointer->nearest_result->nearest_unknown_case;
               case_pointer != NULL;
               case_pointer = case_pointer->equidistant_unknown_next) {

            /* log the case (a nearest unknown neighbour) */

            Indent(log_stream, level + 2);
            log_case_number_and_name(log_stream, case_pointer->number,
                  case_pointer->short_name);
            fprintf(log_stream, "\n");

      } else

         /* the nearest unknown neighbours should be ignored */

         area_pointer->nearest_result->nearest_unknown_case = NULL;

      fprintf(log_stream, "\n");

      if (report_stream != NULL)
         handle_nearest_known(report_stream, area_pointer->nearest_result,
               area_pointer->nearest_result, nearest_neighbour,
               area_pointer, facts_head, instant_case_type, verbose);

   } else {

      /* the nearest unknown neighbour is the nearest neighbour */

      nearest_neighbour = area_pointer->nearest_result->nearest_unknown_case;

      /* for every nearest unknown case with this result ... */

      for (case_pointer = area_pointer->nearest_result->nearest_unknown_case;
            case_pointer != NULL;
            case_pointer = case_pointer->equidistant_unknown_next) {

         /* log the case (a nearest unknown neighbour) */

         Indent(log_stream, level + 2);
         log_case_number_and_name(log_stream, case_pointer->number,
               case_pointer->short_name);
         fprintf(log_stream, "\n");
      }

      /* for every nearest known case with this result ... */

      for (case_pointer = area_pointer->nearest_result->nearest_known_case;
            case_pointer != NULL;
            case_pointer = case_pointer->equidistant_known_next) {

         /* log the case (a nearest known neighbour) */

         Indent(log_stream, level + 2);
         log_case_number_and_name(log_stream, case_pointer->number,
               case_pointer->short_name);
         fprintf(log_stream, "\n");
      }
      fprintf(log_stream, "\n");

      if (report_stream != NULL)
         handle_nearest_unknown(report_stream, area_pointer->nearest_result,
               area_pointer, facts_head, instant_case_type, verbose);
   }

   Indent(log_stream, level);
   fprintf(log_stream, "Nearest others:\n\n");

   for (result_pointer = area_pointer->result_head; result_pointer != NULL;
         result_pointer = result_pointer->next)

      /* for every result ... */

      if (result_pointer != area_pointer->nearest_result) {

         /* this result is not the nearest result */

         Indent(log_stream, level + 1);
         fprintf(log_stream, "%s:\n", result_pointer->identifier);

         if ((result_pointer->nearest_known_case != NULL) ||
               (result_pointer->nearest_unknown_case != NULL)) {

            /* this result has a nearest case (i.e. it has at least one case) */

            if ((result_pointer->nearest_known_compared_with_unknown == FURTHER) ||
                  has_less_known_distance(result_pointer->nearest_unknown_case,
                        nearest_neighbour))

               /* the nearest unknown other is the nearest other or, if not
                  for its unknown distance, the nearest unknown other would
                  be the nearest neighbour, so for every nearest unknown case
                  with this result ... */

               for (case_pointer = result_pointer->nearest_unknown_case;
                     case_pointer != NULL;
                     case_pointer = case_pointer->equidistant_unknown_next) {

                  /* log the case (a nearest unknown other) */

                  Indent(log_stream, level + 2);
                  log_case_number_and_name(log_stream, case_pointer->number,
                        case_pointer->short_name);
                  fprintf(log_stream, "\n");
               }

            /* for every nearest known case with this result ... */

            for (case_pointer = result_pointer->nearest_known_case;
                  case_pointer != NULL;
                  case_pointer = case_pointer->equidistant_known_next) {

               /* log the case (a nearest known other) */

               Indent(log_stream, level + 2);
               log_case_number_and_name(log_stream, case_pointer->number,
                     case_pointer->short_name);
               fprintf(log_stream, "\n");
            }
            if (report_stream != NULL)
               handle_nearest_others(report_stream, result_pointer,
                     area_pointer->nearest_result, nearest_neighbour,
                     area_pointer->nearest_result->
                     nearest_known_compared_with_unknown != FURTHER,
                     area_pointer, facts_head, instant_case_type, verbose);
         }
         fprintf(log_stream, "\n");
      }
   implement_safeguards(log_stream, area_pointer, instant_case_type, level);

   /* log the nearest result */

   Indent(log_stream, level - 1);
   fprintf(log_stream, "Nearest result for ");

   if (number == 0)

      /* this is the unhypothesized, uninstantiated instant case */

      fprintf(log_stream, "the instant case");

   else if (!hypothetical)

      /* this is an instantiation */

      fprintf(log_stream, "instantiation %u", number);

   else

      /* this is a hypothetical */

      fprintf(log_stream, "hypothetical %u", number);

   fprintf(log_stream, " is %s.\n\n", area_pointer->nearest_result->identifier);

   if ((report_stream != NULL) && (number == 0) && (area_pointer->closing != NULL)) {

      /* write the closing string */

      fprintf(report_stream, "%s\n", Skip);
      if (verbose)
         Write(report_stream, area_pointer->closing, "\n", Top_Level, Hang);
      else
         Write(report_stream, "[Closing.]", "\n", Top_Level, Hang);
   }
}

Other SHYSTER modules: Shyster, Statutes, Cases, Tokenizer, Parser, Dumper, Checker, Scales, Adjuster, Consultant and Odometer.