/* Copyright 1997 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/***************************************************/
/* File   : Trace.c                                */
/*                                                 */
/* Purpose: Trace build only debugging functions   */
/*          - not the sort of thing that hacks     */
/*          up some text display or whatever; that */
/*          is the domain of TML. These functions  */
/*          are for principally file-based output  */
/*          of things like token list dumps.       */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 02-Jul-97: Created.                    */
/***************************************************/

#ifdef TRACE

  #include <stdlib.h>
  #include <stdio.h>
  #include <string.h>

  #include "swis.h"

  #include "HTMLLib.h" /* HTML library API, Which will include html2_ext.h, tags.h and struct.h */

  #include "wimp.h"
  #include "wimplib.h"
  #include "event.h"

  #include "svcprint.h"
  #include "Global.h"
  #include "MiscDefs.h"
  #include "Utils.h"

  #include "Trace.h"

  /* Local definitions */

  #define Trace_DumpTokens_Filename_L "<Browse$Dir>.Tokens-L"
  #define Trace_DumpTokens_Filename_S "<Browse$Dir>.Tokens-S"

  #define TrOut(f, d, p) {                                                       \
                           int temp_debug;                                       \
                                                                                 \
                           for (temp_debug = 0; temp_debug < (d); temp_debug ++) \
                           {                                                     \
                             if (fprintf((f), "\t") < 0) return 1;               \
                           }                                                     \
                                                                                 \
                           if (fprintf p < 0) return 1;                          \
                         }                                                       \

  /* Static function prototypes */

  static int          trace_dump_tokens_by_line_r   (browser_data * b, reformat_cell * cell, FILE * file, int depth);
  static int          trace_dump_tokens_by_stream_r (browser_data * b, HStream * streambase, FILE * file, int depth);
  static int          trace_dump_hstream            (FILE * file, HStream * token, int depth);
  static int          trace_dump_tstream            (FILE * file, table_stream * table, int depth);
  static int          trace_dump_row                (FILE * file, table_row * row, int depth);
  static int          trace_dump_head               (FILE * file, table_headdata * head, int depth);
  static const char * trace_tag_name                (tag_no tagno);

  /*************************************************/
  /* trace_dump_tokens_by_line()                   */
  /*                                               */
  /* Outputs a diagnostic description of the       */
  /* token list of a browser. The browser is       */
  /* obtained by getting the client handle of the  */
  /* ancestor of whatever object raised the        */
  /* ETraceTokenDumpByLine event (see TBEvents.h). */
  /*                                               */
  /* This function follows line arrays to get the  */
  /* tokens (so not every token output by the      */
  /* library will be included, but those that the  */
  /* browser is actually using will be).           */
  /*                                               */
  /* Parameters are as standard for a Toolbox      */
  /* event handler.                                */
  /*************************************************/

  int trace_dump_tokens_by_line(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
  {
    FILE            * tfile;
    browser_data    * b;
    _kernel_oserror * e;

    /* Try to open out the file */

    tfile = fopen(Trace_DumpTokens_Filename_L, "wb");

    if (!tfile)
    {
      erb.errnum = Utils_Error_Custom_Message;
      StrNCpy0(erb.errmess,
               "trace_dump_tokens: Error trying to open output file");

      show_error_ret(&erb);

      return 0;
    }

    /* Try to get the browser_data structure */

    e = toolbox_get_client_handle(0, idb->ancestor_id, (void *) &b);
    if (e)
    {
      show_error_ret(e);
      return 0;
    }

    if (!is_known_browser(b))
    {
      erb.errnum = Utils_Error_Custom_Message;
      sprintf(erb.errmess,
              "trace_dump_tokens: Can't determine what token list to use ('%08x' is not a known browser)",
              (int) b);

      show_error_ret(&erb);
      return 0;
    }

    /* OK, can proceed */

    if (fprintf(tfile, "Token dump list for browser %08x\n\n\n\n", (int) b) < 0) goto tdtbl_fo_err;

    if (trace_dump_tokens_by_line_r(b, b->cell, tfile, 0)) goto tdtbl_fo_err;

    /* Finished */

    fclose(tfile);

    _swix(OS_CLI,
          _IN(0),

          "SetType "
          Trace_DumpTokens_Filename_L
          " fff");

    return 1;

    /* Now some special case code */

tdtbl_fo_err: /* If there's an error whilst writing to the file */

    fclose(tfile);

    erb.errnum = Utils_Error_Custom_Message;
    StrNCpy0(erb.errmess,
             "trace_dump_tokens: Error whilst writing token dump file");

    show_error_ret(e);

    return 0;
  }

  /*************************************************/
  /* trace_dump_tokens_by_line_r()                 */
  /*                                               */
  /* Recursive back-end to                         */
  /* trace_dump_tokens_by_line.                    */
  /*                                               */
  /* Parameters: Pointer to a browser_data struct  */
  /*             relevant to the overall stream;   */
  /*                                               */
  /*             Pointer to a reformat_cell struct */
  /*             holding the lines to examine;     */
  /*                                               */
  /*             Pointer to a FILE structure for   */
  /*             outputting the information;       */
  /*                                               */
  /*             Nesting depth.                    */
  /*                                               */
  /* Returns:    1 if there was an error, else 0.  */
  /*************************************************/

  static int trace_dump_tokens_by_line_r(browser_data * b, reformat_cell * cell, FILE * file, int depth)
  {
    int               line, chunk, chunkmax;
    unsigned int      valid;
    HStream         * token = NULL;
    HStream         * last  = NULL;
    _kernel_oserror * e;

    TrOut(file, depth, (file, "Browser %08x, cell %08x:\n\n\n\n", (int) b, (int) cell));

    /* If no lines, is there apparently line or chunk data allocated? */

    if (!cell->nlines)
    {
      TrOut(file, depth, (file, "This cell has no lines.\n\n"));

      if (cell->ldata || cell->cdata)
      {
        TrOut(file, depth, (file,
                    "The cell appears corrupt; nlines = 0, but ldata = %08x and cdata = %08x.\n\n",
                    (int) cell->ldata,
                    (int) cell->cdata));
      }

      return 0;
    }

    /* If there is line data, but nlines is non-zero, the cell is broken */

    if (!cell->ldata)
    {
      TrOut(file, depth, (file,
                  "This cell appears corrupted - nlines = %d, but ldata = NULL.\n\n",
                  cell->nlines));

      return 0;
    }

    /* If we get here, there are lines and line data. If there's no chunk data, */
    /* the cell isn't necessarily broken but we should flag a warning - various */
    /* areas of the browser won't be able to handle this.                       */

    if (!cell->cdata)
    {
      TrOut(file, depth, (file,
                  "Warning: nlines = %d, ldata = %08x, but cdata = NULL.\n\n",
                  cell->nlines,
                  (int) cell->ldata));
    }

    for (line = 0; line < cell->nlines; line ++)
    {
      chunk    = cell->ldata[line].chunks;
      chunkmax = cell->ldata[line].n + chunk;

      for (/* 'chunk' is already initialised */ ; chunk < chunkmax; chunk ++)
      {
        token = cell->cdata[chunk].t;

        if (token != last)
        {
          last = token;

          /* Output info on the token as an HStream */

          TrOut(file, depth, (file,
                      "Line %d chunk %d, new token %08x:\n\n",
                      line,
                      chunk,
                      (int) token));

          /* Deal with tables */

          if (token->tag == TABLE && ISBODY(token))
          {
            TrOut(file, depth, (file, "This token represents a table. As a table_stream struct:\n\n"));

            if (trace_dump_tstream(file, (table_stream *) token, depth)) return 1;

            {
              table_stream   * table     = (table_stream *) token;
              table_row      * row       = table->List;
              table_headdata * head      = NULL;
              reformat_cell  * c         = NULL;
              reformat_cell  * cellarray = table->cells;
              int              cellmax   = table->ColSpan * table->RowSpan;
              int              cellindex;

              if (cellarray)
              {
                {
                  int i;

                  TrOut(file, depth, (file, "Cell array contents:\n\n"));

                  for (i = 0; i < cellmax; i ++)
                  {
                    TrOut(file, depth, (file, "[%d]\t%08x\n", (int) i, (int) &cellarray[i]));
                  }

                  TrOut(file, depth, (file, "\n"));
                }

                while (row)
                {
                  head = row->List;

                  while (
                          head                           &&
                          head->RowOffs < table->RowSpan &&
                          head->ColOffs < table->ColSpan
                        )
                  {
                    switch (head->Tag)
                    {
                      case TagTableData:
                      case TagTableHead:
                      {
                        /* Find the reformat_cell structure for this table cell */

                        cellindex = head->RowOffs * table->ColSpan + head->ColOffs;

                        if (cellindex < cellmax)
                        {
                          c = &cellarray[cellindex];

                          TrOut(file, 0, (file, "\n\n"));

                          if (c)
                          {
                            if (trace_dump_tokens_by_line_r(b, c, file, depth + 1)) return 1;
                          }
                          else
                          {
                            TrOut(file, depth, (file, "Warning: Entry %d in cellarray is NULL\n", cellindex));
                          }
                        }
                      }
                    }

                    if (head->Next)
                    {
                      /* Check the 'next' pointer is valid */

                      e = _swix(OS_ValidateAddress,
                                _INR(0,1) | _OUT(_FLAGS),

                                head->Next,
                                head->Next + 1,

                                &valid);

                      if (e || (valid & _C))
                      {
                        fprintf(file, "Warning: This table_headdata has an invalid 'Next' pointer - aborting list here\n");

                        return 1;
                      }
                    }

                    /* Get next item */

                    head = head->Next;
                  }

                  if (row->Next)
                  {
                    /* Check the 'next' pointer is valid */

                    e = _swix(OS_ValidateAddress,
                              _INR(0,1) | _OUT(_FLAGS),

                              row->Next,
                              row->Next + 1,

                              &valid);

                    if (e || (valid & _C))
                    {
                      fprintf(file, "Warning: This table_row has an invalid 'Next' pointer - aborting list here\n");

                      return 1;
                    }
                  }

                  /* Get next item */

                  row = row->Next;
                }
              }
              else
              {
                TrOut(file, depth, (file, "Warning: No cellarray - can't scan the lines inside the table.\n"));
              }
            }
          }
          else
          {
            /* Normal tokens */

            if (trace_dump_hstream(file, token, depth)) return 1;
          }
        }
        else
        {
          TrOut(file, depth, (file,
                      "Line %d chunk %d, same token (%08x) as last chunk.\n\n",
                      line,
                      chunk,
                      (int) token));
        }
      }
    }

    return 0;
  }

  /*************************************************/
  /* trace_dump_tokens_by_stream()                 */
  /*                                               */
  /* Outputs a diagnostic description of the       */
  /* token list of a browser. The browser is       */
  /* obtained by getting the client handle of the  */
  /* ancestor of whatever object raised the        */
  /* ETraceTokenDumpByStream event (see            */
  /* TBEvents.h).                                  */
  /*                                               */
  /* This function follows the raw stream to get   */
  /* each token, so everything output by the       */
  /* library should be included, even if the       */
  /* browser isn't actually using it.              */
  /*                                               */
  /* Parameters are as standard for a Toolbox      */
  /* event handler.                                */
  /*************************************************/

  int trace_dump_tokens_by_stream(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
  {
    FILE            * tfile;
    browser_data    * b;
    _kernel_oserror * e;

    /* Try to open out the file */

    tfile = fopen(Trace_DumpTokens_Filename_S, "wb");

    if (!tfile)
    {
      erb.errnum = Utils_Error_Custom_Message;
      StrNCpy0(erb.errmess,
               "trace_dump_tokens: Error trying to open output file");

      show_error_ret(&erb);

      return 0;
    }

    /* Try to get the browser_data structure */

    e = toolbox_get_client_handle(0, idb->ancestor_id, (void *) &b);
    if (e)
    {
      show_error_ret(e);
      return 0;
    }

    if (!is_known_browser(b))
    {
      erb.errnum = Utils_Error_Custom_Message;
      sprintf(erb.errmess,
              "trace_dump_tokens: Can't determine what token list to use ('%08x' is not a known browser)",
              (int) b);

      show_error_ret(&erb);
      return 0;
    }

    /* OK, can proceed */

    if (fprintf(tfile, "Token dump list for browser %08x\n\n\n\n", (int) b) < 0) goto tdtbs_fo_err;

    if (trace_dump_tokens_by_stream_r(b, b->stream, tfile, 0)) goto tdtbs_fo_err;

    /* Finished */

    fclose(tfile);

    _swix(OS_CLI,
          _IN(0),

          "SetType "
          Trace_DumpTokens_Filename_S
          " fff");

    return 1;

    /* Now some special case code */

tdtbs_fo_err: /* If there's an error whilst writing to the file */

    fclose(tfile);

    erb.errnum = Utils_Error_Custom_Message;
    StrNCpy0(erb.errmess,
             "trace_dump_tokens: Error whilst writing token dump file");

    show_error_ret(e);

    return 0;
  }

  /*************************************************/
  /* trace_dump_tokens_by_stream_r()               */
  /*                                               */
  /* Recursive back-end to                         */
  /* trace_dump_tokens_by_stream.                  */
  /*                                               */
  /* Parameters: Pointer to a browser_data struct  */
  /*             relevant to the overall stream;   */
  /*                                               */
  /*             Pointer to the first HStream      */
  /*             struct to examine;                */
  /*                                               */
  /*             Pointer to a FILE structure for   */
  /*             outputting the information.       */
  /*                                               */
  /*             Nesting depth.                    */
  /*                                               */
  /* Returns:    1 if there was an error, else 0.  */
  /*************************************************/

  static int trace_dump_tokens_by_stream_r(browser_data * b, HStream * streambase, FILE * file, int depth)
  {
    HStream         * token = streambase;
    unsigned int      valid;
    _kernel_oserror * e;

    TrOut(file, depth, (file, "Browser %08x, stream base %08x:\n\n\n\n", (int) b, (int) streambase));

    while (token)
    {
      TrOut(file, depth, (file, "Token %08x:\n\n", (int) token));

      /* Deal with tables */

      if (token->tag == TABLE && ISBODY(token))
      {
        TrOut(file, depth, (file, "This token represents a table. As a table_stream struct:\n\n"));

        if (trace_dump_tstream(file, (table_stream *) token, depth)) return 1;

        /* Recursive table scan */

        {
          table_stream   * table = (table_stream *) token;
          table_row      * row   = table->List;
          table_headdata * head  = NULL;

          while (row)
          {
            TrOut(file, depth, (file, "Table %08x: Row %08x\n\n", (int) table, (int) row));

            if (trace_dump_row(file, row, depth + 1)) return 1;

            head = row->List;

            while (head)
            {
              HStream * base = (HStream *) head->List;

              TrOut(file, depth, (file, "Table %08x, row %08x: Head %08x\n\n", (int) table, (int) row, (int) head));

              if (trace_dump_head(file, head, depth + 1)) return 1;

              TrOut(file, 0, (file, "\n\n"));

              if (trace_dump_tokens_by_stream_r(b, base, file, depth + 1)) return 1;

              if (head->Next)
              {
                /* Check the 'next' pointer is valid */

                e = _swix(OS_ValidateAddress,
                          _INR(0,1) | _OUT(_FLAGS),

                          head->Next,
                          head->Next + 1,

                          &valid);

                if (e || (valid & _C))
                {
                  fprintf(file, "Warning: This table_headdata has an invalid 'Next' pointer - aborting list here\n");

                  return 1;
                }
              }

              /* Get next item */

              head = head->Next;
            }

            if (row->Next)
            {
              /* Check the 'next' pointer is valid */

              e = _swix(OS_ValidateAddress,
                        _INR(0,1) | _OUT(_FLAGS),

                        row->Next,
                        row->Next + 1,

                        &valid);

              if (e || (valid & _C))
              {
                fprintf(file, "Warning: This table_row has an invalid 'Next' pointer - aborting list here\n");

                return 1;
              }
            }

            /* Get next item */

            row = row->Next;
          }
        }
      }
      else
      {
        /* Normal tokens */

        if (trace_dump_hstream(file, token, depth)) return 1;
      }

      if (token->next)
      {
        /* Check the 'next' pointer is valid */

        e = _swix(OS_ValidateAddress,
                  _INR(0,1) | _OUT(_FLAGS),

                  token->next,
                  token->next + 1,

                  &valid);

        if (e || (valid & _C))
        {
          fprintf(file, "Warning: This token has an invalid 'next' pointer - aborting list here\n");

          return 1;
        }
      }

      /* Follow the list */

      token = token->next;
    }

    return 0;
  }

  /*************************************************/
  /* trace_dump_hstream()                          */
  /*                                               */
  /* Output the contents of an HStream in a        */
  /* readable form to the given file.              */
  /*                                               */
  /* Parameters: Pointer to a FILE structure for   */
  /*             the file to write to;             */
  /*                                               */
  /*             Pointer to the HStream struct;    */
  /*                                               */
  /*             Nesting depth.                    */
  /*                                               */
  /* Returns:    0 for success, 1 for failure (an  */
  /*             error, usually when writing to    */
  /*             the file).                        */
  /*************************************************/

  static int trace_dump_hstream(FILE * file, HStream * token, int depth)
  {
    unsigned int mask;

    /* Display the known values in the flags word */

    TrOut(file, depth, (file, "flags\t\t0x%x\n",        (int) token->flags));

    if (token->flags & HFlags_DealtWithToken)
    {
      TrOut(file, depth, (file, "\t\tHFlags_DealtWithToken\n"));
    }

    if (token->flags & HFlags_LinkVisited)
    {
      TrOut(file, depth, (file, "\t\tHFlags_LinkVisited\n"));
    }

    mask = HFlags_DealtWithToken |
           HFlags_LinkVisited;

    if (token->flags & ~mask)
    {
      TrOut(file, depth, (file, "\t\tWarning: undefined flags bits set\n"));
    }

    TrOut(file, depth, (file, "parent\t\t0x%x\n",       (int) token->parent));

    /* Deal with known 'type' field values */

    TrOut(file, depth, (file, "type\t\t%08x\n",         (int) token->type));

    if (ISHEAD(token))
    {
      TrOut(file, depth, (file, "\t\tISHEAD\n"));
    }

    if (ISBODY(token))
    {
      TrOut(file, depth, (file, "\t\tISBODY\n"));
    }

    if (ISNULL(token))
    {
      TrOut(file, depth, (file, "\t\tISNULL\n"));
    }

    if (ISFRAMESET(token))
    {
      TrOut(file, depth, (file, "\t\tISFRAMESET\n"));
    }

    /* General info */

    TrOut(file, depth, (file, "style\t\t%08x\n",        (int) token->style));
  /*TrOut(file, depth, (file, "style2\t\t%08x\n",       (int) token->style2));*/
    TrOut(file, depth, (file, "indent\t\t0x%x\n",       (int) token->indent));

    TrOut(file, depth, (file, "anchor\t\t%08x: '%s'\n", (int) token->anchor, token->anchor   ? token->anchor  : "(NULL)"));
    TrOut(file, depth, (file, "text\t\t%08x: '%s'\n",   (int) token->text,   token->text     ? token->text    : "(NULL)"));

    TrOut(file, depth, (file, "tag\t\t%08x\n",          (int) token->tag));
    TrOut(file, depth, (file, "tagno\t\t%d\t: <%s>\n",        token->tagno,  trace_tag_name(token->tagno)));

    TrOut(file, depth, (file, "src\t\t%08x: '%s'\n",    (int) token->src,    token->src      ? token->src     : "(NULL)"));

    TrOut(file, depth, (file, "next\t\t%08x\n",         (int) token->next));
    TrOut(file, depth, (file, "prev\t\t%08x\n",         (int) token->prev));

    TrOut(file, depth, (file, "enctype\t\t%08x: '%s'\n",(int) token->enctype, token->enctype ? token->enctype : "(NULL)"));
    TrOut(file, depth, (file, "name\t\t%08x: '%s'\n",   (int) token->name,    token->name    ? token->name    : "(NULL)"));
    TrOut(file, depth, (file, "value\t\t%08x: '%s'\n",  (int) token->value,   token->value   ? token->value   : "(NULL)"));
    TrOut(file, depth, (file, "target\t\t%08x: '%s'\n", (int) token->target,  token->target  ? token->target  : "(NULL)"));

    TrOut(file, depth, (file, "size\t\t0x%x\n",         (int) token->size));
    TrOut(file, depth, (file, "maxlen\t\t0x%x\n",       (int) token->maxlen));
    TrOut(file, depth, (file, "rows\t\t0x%x\n",         (int) token->rows));
    TrOut(file, depth, (file, "colour\t\t0x%06xxx\n",   (int) token->colour));
    TrOut(file, depth, (file, "fontsize\t%d\n\n",       (int) token->fontsize));

    return 0;
  }

  /*************************************************/
  /* trace_dump_tstream()                          */
  /*                                               */
  /* Output the contents of a table_stream in a    */
  /* readable form to the given file.              */
  /*                                               */
  /* Parameters: Pointer to a FILE structure for   */
  /*             the file to write to;             */
  /*                                               */
  /*             Pointer to the table_stream       */
  /*             structure;                        */
  /*                                               */
  /*             Nesting depth.                    */
  /*                                               */
  /* Returns:    0 for success, 1 for failure (an  */
  /*             error, usually when writing to    */
  /*             the file).                        */
  /*************************************************/

  static int trace_dump_tstream(FILE * file, table_stream * table, int depth)
  {
    unsigned int mask;

    /* Display the flags */

    TrOut(file, depth, (file, "flags\t\t0x%x\n", (int) table->flags));

    if (table->flags & HFlags_DealtWithToken)
    {
      TrOut(file, depth, (file, "\t\tHFlags_DealtWithToken\n"));
    }

    if (table->flags & HFlags_LinkVisited)
    {
      TrOut(file, depth, (file, "\t\tHFlags_LinkVisited\n"));
    }

    mask = HFlags_DealtWithToken |
           HFlags_LinkVisited;

    if (table->flags & ~mask)
    {
      TrOut(file, depth, (file, "\t\tWarning: undefined flags bits set\n"));
    }

    TrOut(file, depth, (file, "parent\t\t0x%x\n",  (int) table->parent));

    /* Deal with known cases of the 'type' field */

    TrOut(file, depth, (file, "type\t\t%08x\n",    (int) table->type));

    if (ISHEAD(table))
    {
      TrOut(file, depth, (file, "\t\tISHEAD\n"));
    }

    if (ISBODY(table))
    {
      TrOut(file, depth, (file, "\t\tISBODY\n"));
    }

    if (ISNULL(table))
    {
      TrOut(file, depth, (file, "\t\tISNULL\n"));
    }

    if (ISFRAMESET(table))
    {
      TrOut(file, depth, (file, "\t\tISFRAMESET\n"));
    }

    TrOut(file, depth, (file, "style\t\t%08x\n",   (int) table->style));
  /*TrOut(file, depth, (file, "style2\t\t%08x\n",  (int) table->style2));*/
    TrOut(file, depth, (file, "Tag\t\t%08x\n",     (int) table->Tag));

    TrOut(file, depth, (file, "ColSpan\t\t%d\n",   (int) table->ColSpan));
    TrOut(file, depth, (file, "RowSpan\t\t%d\n",   (int) table->RowSpan));

    /* Display the arrays of row and column offsets, if possible */

    TrOut(file, depth, (file, "ColOffs\t\t%08x\n", (int) table->ColOffs));

    if (table->ColOffs)
    {
      int col, limit;

      /* Try to use the table_stream's record of number of rows/cols - only */
      /* filled in if the browser has parsed the table. If this is zero,    */
      /* use 16 (more or less arbitrary); otherwise, output as many as      */
      /* indicated plus 4 to check that the array wasn't overshot (should   */
      /* commonly see silly values in those extra 4). Don't do more than 64 */
      /* though, or we'll be here all day... (Potentially, a corrupted or   */
      /* uninitialised ColSpan/RowSpan could indicate a *very* large number */
      /* of rows or columns!).                                              */

      limit = table->ColSpan;

      if (limit == 0) limit = 16;
      else            limit += 4;

      if (limit > 64) limit = 64;

      for (col = 0; col < limit; col ++)
      {
        TrOut(file, depth, (file, "\t\t[%d]\t%08x (%08x)\n", col, (int) table->ColOffs[col], (int) table->ColOffs[col]/400));

        if (table->ColSpan && col == table->ColSpan - 1)
        {
          TrOut(file, depth, (file, "\t\t(This is probably the last entry)\n"));
        }
      }
    }

    TrOut(file, depth, (file, "RowOffs\t\t%08x\n", (int) table->RowOffs));

    /* Same as above, for rows */

    if (table->RowOffs)
    {
      int row, limit;

      limit = table->RowSpan;

      if (limit == 0) limit = 16;
      else            limit += 4;

      if (limit > 64) limit = 64;

      for (row = 0; row < limit; row ++)
      {
        TrOut(file, depth, (file, "\t\t[%d]\t%08x (%08x)\n", row, (int) table->RowOffs[row], (int) table->RowOffs[row]/400));

        if (table->RowSpan && row == table->RowSpan - 1)
        {
          TrOut(file, depth, (file, "\t\t(This is probably the last entry)\n"));
        }
      }
    }

    /* General info */

    TrOut(file, depth, (file, "Next\t\t%08x\n",      (int) table->Next));
    TrOut(file, depth, (file, "Prev\t\t%08x\n",      (int) table->Prev));
    TrOut(file, depth, (file, "List\t\t%08x\n",      (int) table->List));

    TrOut(file, depth, (file, "bgcol\t\t%08x\n",     (int) table->bgcol));
    TrOut(file, depth, (file, "cols\t\t%d\n",        (int) table->cols));
    TrOut(file, depth, (file, "width\t\t%d\n",       (int) table->width));
    TrOut(file, depth, (file, "border\t\t%d\n",      (int) table->border));
    TrOut(file, depth, (file, "cellspacing\t%d\n",   (int) table->cellspacing));
    TrOut(file, depth, (file, "cellpadding\t%d\n",   (int) table->cellpadding));

    TrOut(file, depth, (file, "stackedstyle\t%08x\n",(int) table->stackedstyle));
    TrOut(file, depth, (file, "cells\t\t%08x\n",     (int) table->cells));

    TrOut(file, depth, (file, "Align\t\t0x%x\n",     (int) table->Align));

    /* Deal with specific bitfield flags */

    if (table->awaiting_tr)
    {
      TrOut(file, depth, (file, "awaiting_tr\tyes\n"));
    }
    else
    {
      TrOut(file, depth, (file, "awaiting_tr\tno\n"));
    }

    if (table->finished)
    {
      TrOut(file, depth, (file, "finished\tyes\n\n"));
    }
    else
    {
      TrOut(file, depth, (file, "finished\tno\n\n"));
    }

    return 0;
  }

  /*************************************************/
  /* trace_dump_row()                              */
  /*                                               */
  /* Output the contents of a table_row in a       */
  /* readable form to the given file.              */
  /*                                               */
  /* Parameters: Pointer to a FILE structure for   */
  /*             the file to write to;             */
  /*                                               */
  /*             Pointer to the table_row struct;  */
  /*                                               */
  /*             Nesting depth.                    */
  /*                                               */
  /* Returns:    0 for success, 1 for failure (an  */
  /*             error, usually when writing to    */
  /*             the file).                        */
  /*************************************************/

  static int trace_dump_row(FILE * file, table_row * row, int depth)
  {
    /* General info */

    TrOut(file, depth, (file, "Next\t\t%08x\n",   (int) row->Next));
    TrOut(file, depth, (file, "Prev\t\t%08x\n",   (int) row->Prev));
    TrOut(file, depth, (file, "parent\t\t%08x\n", (int) row->parent));
    TrOut(file, depth, (file, "List\t\t%08x\n",   (int) row->List));

    /* 8 bit values */

    TrOut(file, depth, (file, "align\t\t%d\n",    (int) row->align));
    TrOut(file, depth, (file, "valign\t\t%d\n",   (int) row->valign));

    /* Background colour (inherited by cells on the row) */

    TrOut(file, depth, (file, "bgcol\t\t%08x\n\n",(int) row->bgcol));

    return 0;
  }

  /*************************************************/
  /* trace_dump_head()                             */
  /*                                               */
  /* Output the contents of a table_headdata in a  */
  /* readable form to the given file.              */
  /*                                               */
  /* Parameters: Pointer to a FILE structure for   */
  /*             the file to write to;             */
  /*                                               */
  /*             Pointer to the table_headdata     */
  /*             structure;                        */
  /*                                               */
  /*             Nesting depth.                    */
  /*                                               */
  /* Returns:    0 for success, 1 for failure (an  */
  /*             error, usually when writing to    */
  /*             the file).                        */
  /*************************************************/

  static int trace_dump_head(FILE * file, table_headdata * head, int depth)
  {
    TrOut(file, depth, (file, "ColSpan\t\t%d\n",   (int) head->ColSpan));
    TrOut(file, depth, (file, "RowSpan\t\t%d\n",   (int) head->RowSpan));
    TrOut(file, depth, (file, "ColOffs\t\t%d\n",   (int) head->ColOffs));
    TrOut(file, depth, (file, "RowOffs\t\t%d\n",   (int) head->RowOffs));

    TrOut(file, depth, (file, "Tag\t\t0x%x\n",     (int) head->Tag));

    switch (head->Tag)
    {
      case TagTableHead:
      {
        TrOut(file, depth, (file, "\t\tTagTableHead\n"));
      }
      break;

      case TagTableData:
      {
        TrOut(file, depth, (file, "\t\tTagTableData\n"));
      }
      break;

      default:
      {
        TrOut(file, depth, (file, "\t\tWarning: Unrecognised Tag type\n"));
      }
      break;
    }

    /* General info */

    TrOut(file, depth, (file, "parent\t\t%08x\n",   (int) head->parent));
    TrOut(file, depth, (file, "Next\t\t%08x\n",     (int) head->Next));
    TrOut(file, depth, (file, "Prev\t\t%08x\n",     (int) head->Prev));
    TrOut(file, depth, (file, "List\t\t%08x\n",     (int) head->List));

    /* 8 bit values */

    TrOut(file, depth, (file, "Align\t\t%d\n",      (int) head->Align));
    TrOut(file, depth, (file, "VAlign\t\t%d\n",     (int) head->VAlign));

    /* Background image */

    TrOut(file, depth, (file, "background\t%08x: '%s'\n", (int) head->background, head->background ? head->background : "(NULL)"));

    /* More general info */

    TrOut(file, depth, (file, "width\t\t%d (%d)\n", (int) head->width,  (int) head->width  / 400));
    TrOut(file, depth, (file, "height\t\t%d (%d)\n",(int) head->height, (int) head->height / 400));

    /* Background colour */

    TrOut(file, depth, (file, "bgcol\t\t%08x\n\n",  (int) head->bgcol));

    return 0;
  }

  /*************************************************/
  /* trace_dump_buffer()                           */
  /*                                               */
  /* Outputs a given buffer's contents over TML,   */
  /* with unprintable chars shown in green (well,  */
  /* this depends on the buffer type - see below)  */
  /* as '[xx]', where xx is the character's ASCII  */
  /* code in hex.                                  */
  /*                                               */
  /* For clarity at the output terminal, it is     */
  /* possible to specify the type of buffer - a    */
  /* buffer for data transmission, reception, or   */
  /* a miscellaneous buffer. Control code colours  */
  /* will change for this; green for transmission, */
  /* red for reception, cyan for others.           */
  /*                                               */
  /* Parameters: Pointer to the buffer;            */
  /*                                               */
  /*             Size of the buffer;               */
  /*                                               */
  /*             1 for a transmit buffer, 2 for a  */
  /*             receive buffer, else 'other'.     */
  /*************************************************/

  void trace_dump_buffer(void * buffer, int buffer_size, int type)
  {
    int  i;
    char c;

    /* Is the buffer empty (well, zero bytes long or less...)? */

    if (buffer_size < 1)
    {
      if      (type == 1) Printf("\nTransmission buffer %p empty\n\n", buffer);
      else if (type == 2) Printf("\nReception buffer %p empty\n\n", buffer);
      else                Printf("\nGeneral buffer %p empty\n\n", buffer);

      return;
    }

    /* If not, dump the contents - first, a header message */

    if      (type == 1) Printf("\nTransmission buffer %p contents:\n\n", buffer);
    else if (type == 2) Printf("\nReception buffer %p contents:\n\n", buffer);
    else                Printf("\nGeneral buffer %p contents:\n\n", buffer);

    /* Now, the body of the buffer */

    for (i = 0; i < buffer_size; i++)
    {
      c = *((char *) buffer + i);

      if (type == 1)
      {
        if (c < 32 || c == 127) Printf("\0212[%02x]\0217", (int) c);
        else Printf("%c", c);
      }
      else if (type == 2)
      {
        if (c < 32 || c == 127) Printf("\0211[%02x]\0217", (int) c);
        else Printf("%c", c);
      }
      else
      {
        if (c < 32 || c == 127) Printf("\0216[%02x]\0217", (int) c);
        else Printf("%c", c);
      }
    }

    /* Ensure the next output item is one blank line after the buffer */
    /* contents (aesthetics and clarity)                              */

    Printf("\n\n");
  }

  /*************************************************/
  /* trace_tag_name()                              */
  /*                                               */
  /* Converts a tag number (enum tag_no) to        */
  /* textual form (why isn't there a built in      */
  /* function to do this?).                        */
  /*                                               */
  /* Parameters: Tag number.                       */
  /*                                               */
  /* Returns:    Pointer to a statically allocated */
  /*             name.                             */
  /*************************************************/

  static const char * trace_tag_name(tag_no tagno)
  {
    switch (tagno)
    {
      case TAG_NONE: return "none";
      case TAG_A: return "A";
      case TAG_ADDRESS: return "ADDRESS";
      case TAG_BOLD: return "BOLD";
      case TAG_BASE: return "BASE";
      case TAG_BASEFONT: return "BASEFONT";
      case TAG_BLOCKQUOTE: return "BLOCKQUOTE";
      case TAG_BR: return "BR";
      case TAG_CENTER: return "CENTER";
      case TAG_CITE: return "CITE";
      case TAG_CODE: return "CODE";
      case TAG_DD: return "DD";
      case TAG_DIR: return "DIR";
      case TAG_DL: return "DL";
      case TAG_DT: return "DT";
      case TAG_EM: return "EM";
      case TAG_FONT: return "FONT";
      case TAG_FORM: return "FORM";
      case TAG_H1: return "H1";
      case TAG_H2: return "H2";
      case TAG_H3: return "H3";
      case TAG_H4: return "H4";
      case TAG_H5: return "H5";
      case TAG_H6: return "H6";
      case TAG_HR: return "HR";
      case TAG_ITALIC: return "ITALIC";
      case TAG_IMG: return "IMG";
      case TAG_INPUT: return "INPUT";
      case TAG_ISINDEX: return "ISINDEX";
      case TAG_KBD: return "KBD";
      case TAG_LI: return "LI";
      case TAG_LINK: return "LINK";
      case TAG_MENU: return "MENU";
      case TAG_META: return "META";
      case TAG_OL: return "OL";
      case TAG_OPTION: return "OPTION";
      case TAG_P: return "P";
      case TAG_PRE: return "PRE";
      case TAG_SAMP: return "SAMP";
      case TAG_SCRIPT: return "SCRIPT";
      case TAG_SELECT: return "SELECT";
      case TAG_STRONG: return "STRONG";
      case TAG_STYLE: return "STYLE";
      case TAG_TABLE: return "TABLE";
      case TAG_TD: return "TD";
      case TAG_TEXTAREA: return "TEXTAREA";
      case TAG_TITLE: return "TITLE";
      case TAG_TH: return "TH";
      case TAG_TR: return "TR";
      case TAG_TT: return "TT";
      case TAG_UL: return "UL";
      case TAG_VAR: return "VAR";
      case TAG_XMP: return "XMP";
      case TAG_FRAME: return "FRAME";
      case TAG_BODY: return "BODY";
      case TAG_FRAMESET: return "FRAMESET";
      case TAG_HEAD: return "HEAD";
      case TAG_NOFRAMES: return "NOFRAMES";
      case TAG_SUB: return "SUB";
      case TAG_SUP: return "SUP";
      case TAG_AREA: return "AREA";
      case TAG_MAP: return "MAP";
      case TAG_OBJECT: return "OBJECT";

      default: return "unknown";
    }
  }

#else

  /* This'll work, but it is more efficient not to link to  */
  /* Trace.o at all for non-debug builds, as if TRACE is    */
  /* undefined, absolutely nothing within Trace.o will be   */
  /* referenced (and nothing will #include Trace.h either). */

  void trace_keep_compiler_happy(void)
  {
    int a;
    a = 0;
  }

#endif