/* multi.c -- multitable stuff for makeinfo.
   $Id: multi.c,v 1.9 1997/07/24 22:01:00 karl Exp $

   Copyright (C) 1996, 97 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

#include "system.h"
#include "makeinfo.h"

#define MAXCOLS 100             /* remove this limit later @@ */


/*
 * Output environments.  This is a hack grafted onto existing
 * structure.  The "output environment" used to consist of the
 * global variables `output_paragraph', `fill_column', etc.
 * Routines like add_char would manipulate these variables.
 *
 * Now, when formatting a multitable, we maintain separate environments
 * for each column.  That way we can build up the columns separately
 * and write them all out at once.  The "current" output environment"
 * is still kept in those global variables, so that the old output
 * routines don't have to change.  But we provide routines to save
 * and restore these variables in an "environment table".  The
 * `select_output_environment' function switches from one output
 * environment to another.
 *
 * Environment #0 (i.e., element #0 of the table) is the regular
 * environment that is used when we're not formatting a multitable.
 *
 * Environment #N (where N = 1,2,3,...) is the env. for column #N of
 * the table, when a multitable is active.
 */

/* contents of an output environment */
/* some more vars may end up being needed here later @@ */
struct env
{
  unsigned char *output_paragraph;
  int output_paragraph_offset;
  int output_column;
  int paragraph_is_open;
  int current_indent;
  int fill_column;
} envs[MAXCOLS];                /* the environment table */

/* index in environment table of currently selected environment */
static int current_env_no;

/* column number of last column in current multitable */
static int last_column;

/* flags indicating whether horizontal and vertical separators need
   to be drawn, separating rows and columns in the current multitable. */
static int hsep, vsep;

/* Output a row.  Have to keep `output_position' up-to-date for each
   character we output, or the tags table will be off, leading to
   chopped-off output files and undefined nodes (because they're in the
   wrong file, etc.).  Perhaps it would be better to accumulate this
   value somewhere and add it once at the end of the table, or return it
   as the value, but this seems simplest.  */
static void
out_char (ch)
    int ch;
{
  extern int output_position;
  putc (ch, output_stream);
  output_position++;
}


void
draw_horizontal_separator ()
{
  int i, j, s;

  for (s = 0; s < envs[0].current_indent; s++)
    out_char (' ');
  if (vsep)
    out_char ('+');
  for (i = 1; i <= last_column; i++) {
    for (j = 0; j <= envs[i].fill_column; j++)
      out_char ('-');
    if (vsep)
      out_char ('+');
  }
  out_char ('\n');
}

void
do_multitable ()
{
  int ncolumns;

  /*
   *  multitable strategy:
   *  for each item {
   *     for each column in an item {
   *      initialize a new paragraph
   *      do ordinary formatting into the new paragraph
   *      save the paragraph away
   *      repeat if there are more paragraphs in the column
   *    }
   *    dump out the saved paragraphs and free the storage
   *  }
   */

  if (multitable_active)
    {
      line_error ("Multitables cannot be nested");
      return;
    }

  /* scan the current item function to get the field widths
     and number of columns, and set up the output environment list
     accordingly. */
  ncolumns = setup_multitable_parameters ();
  if (hsep)
    draw_horizontal_separator ();

  /* The next @item command will direct stdout into the first column
     and start processing.  @tab will then switch to the next column,
     and @item will flush out the saved output and return to the first
     column.  Environment #1 is the first column.  (Environment #0 is
     the normal output) */

  ++multitable_active;
}

/* Read the parameters for a multitable from the current command
   line, save the parameters away, and return the
   number of columns. */
int
setup_multitable_parameters ()
{
  char *params = insertion_stack->item_function;
  int nchars;
  float columnfrac;
  char command[200]; /* naughty, should be no fixed limits */
  int i = 1;

  /* We implement @hsep and @vsep even though TeX doesn't.
     We don't get mixing of @columnfractions and templates right,
     but TeX doesn't either.  */
  hsep = vsep = 0;

  while (*params) {
    while (whitespace (*params))
      params++;

    if (*params == '@') {
      sscanf (params, "%200s", command);
      nchars = strlen (command);
      params += nchars;
      if (strcmp (command, "@hsep") == 0)
        hsep++;
      else if (strcmp (command, "@vsep") == 0)
        vsep++;
      else if (strcmp (command, "@columnfractions") == 0) {
        /* Clobber old environments and create new ones, starting at #1.
           Environment #0 is the normal output, so don't mess with it. */
        for ( ; i <= MAXCOLS; i++) {
          if (sscanf (params, "%f", &columnfrac) < 1)
            goto done;
          /* Unfortunately, can't use %n since some m68k-hp-bsd libc
             doesn't support it.  So skip whitespace (preceding the
             number) and then non-whitespace (the number).  */
          while (*params && (*params == ' ' || *params == '\t'))
            params++;
          /* Hmm, but what what @columnfractions 3foo.  Well, I suppose
             it's invalid input anyway.  */
          while (*params && *params != ' ' && *params != '\t'
                 && *params != '\n' && *params != '@')
            params++;
          setup_output_environment (i,
                     (int) (columnfrac * (fill_column - current_indent) + .5));
        }
      }

    } else if (*params == '{') {
      char *start = params;
      while ((*params != '}' || params[-1] == '@') && *params) {
        params++;
      }
      /* This gives us two spaces between columns.  Seems reasonable.
         Really should expand the text, though, so a template of
         `@code{foo}' has a width of five, not ten.  Also have to match
         braces, then.  How to take into account current_indent here?  */
      setup_output_environment (i++, params++ - start);
      
    } else {
      warning (_("ignoring stray text `%s' after @multitable"), params);
      break;
    }
  }

done:
  flush_output ();
  inhibit_output_flushing ();

  last_column = i - 1;
  return last_column;
}

/* Initialize environment number ENV_NO, of width WIDTH.
   The idea is that we're going to use one environment for each column of
   a multitable, so we can build them up separately and print them
   all out at the end. */
int
setup_output_environment (env_no, width)
    int env_no;
    int width;
{
  int old_env = select_output_environment (env_no);

  /* clobber old environment and set width of new one */
  init_paragraph ();

  /* make our change */
  fill_column = width;

  /* Save new environment and restore previous one. */
  select_output_environment (old_env);

  return env_no;
}

/* Direct current output to environment number N.  Used when
   switching work from one column of a multitable to the next.
   Returns previous environment number. */
int 
select_output_environment (n)
    int n;
{
  struct env *e = &envs[current_env_no];
  int old_env_no = current_env_no;

  /* stash current env info from global vars into the old environment */
  e->output_paragraph = output_paragraph;
  e->output_paragraph_offset = output_paragraph_offset;
  e->output_column = output_column;
  e->paragraph_is_open = paragraph_is_open;
  e->current_indent = current_indent;
  e->fill_column = fill_column;

  /* now copy new environment into global vars */
  current_env_no = n;
  e = &envs[current_env_no];
  output_paragraph = e->output_paragraph;
  output_paragraph_offset = e->output_paragraph_offset;
  output_column = e->output_column;
  paragraph_is_open = e->paragraph_is_open;
  current_indent = e->current_indent;
  fill_column = e->fill_column;
  return old_env_no;
}

/* advance to the next environment number */
void
nselect_next_environment ()
{
  if (current_env_no >= last_column) {
    line_error (_("Too many columns in multitable item (max %d)"), last_column);
    return;
  }
  select_output_environment (current_env_no + 1);
}


static void output_multitable_row ();

/* do anything needed at the beginning of processing a
   multitable column. */
void
init_column ()
{
  /* don't indent 1st paragraph in the item */
  cm_noindent ();

  /* throw away possible whitespace after @item or @tab command */
  skip_whitespace ();
}

/* start a new item (row) of a multitable */
int
multitable_item ()
{
  if (!multitable_active) {
    /* impossible, I think. */
    error (_("multitable item not in active multitable"));
    exit (1);
  }
  if (current_env_no > 0) {
    output_multitable_row ();
  }
  /* start at column 1 */
  select_output_environment (1);
  if (!output_paragraph) {
    line_error (_("Cannot select column #%d in multitable"), current_env_no);
    exit (FATAL);
  }

  init_column ();

  return 0;
}

static void
output_multitable_row ()
{
  int i, j, s, remaining;

  /* offset in the output paragraph of the next char needing
     to be output for that column. */
  int offset[MAXCOLS];

  for (i = 0; i <= last_column; i++)
    offset[i] = 0;

  /* select the current environment, to make sure the env variables
     get updated */
  select_output_environment (current_env_no);

#define CHAR_ADDR(n) (offset[i] + (n))
#define CHAR_AT(n) (envs[i].output_paragraph[CHAR_ADDR(n)])

  /* remove trailing whitespace from each column */
  for (i = 1; i <= last_column; i++) {
    while (cr_or_whitespace (CHAR_AT (envs[i].output_paragraph_offset - 1))) {
      envs[i].output_paragraph_offset--;
    }
  }

  /* read the current line from each column, outputting them all
     pasted together.  Do this til all lines are output from all
     columns.  */
  for (;;) {
    remaining = 0;
    /* first, see if there is any work to do */
    for (i = 1; i <= last_column; i++) {
      if (CHAR_ADDR (0) < envs[i].output_paragraph_offset) {
        remaining = 1;
        break;
      }
    }
    if (!remaining)
      break;
    
    for (s = 0; s < envs[0].current_indent; s++)
      out_char (' ');
    
    if (vsep)
      out_char ('|');

    for (i = 1; i <= last_column; i++) {
      for (s = 0; i < envs[i].current_indent; s++)
        out_char (' ');
      for (j = 0; CHAR_ADDR (j) < envs[i].output_paragraph_offset; j++) {
        if (CHAR_AT (j) == '\n')
          break;
        out_char (CHAR_AT (j));
      }
      offset[i] += j + 1;       /* skip last text plus skip the newline */
      for (; j <= envs[i].fill_column; j++)
        out_char (' ');
      if (vsep)
        out_char ('|'); /* draw column separator */
    }
    out_char ('\n');    /* end of line */
  }

  if (hsep)
    draw_horizontal_separator ();

  /* Now dispose of the buffered output. */
  for (i = 1; i <= last_column; i++) {
    select_output_environment (i);
    init_paragraph ();
  }
}

#undef CHAR_AT
#undef CHAR_ADDR

/* select a new column in current row of multitable */
void
cm_tab ()
{
  if (!multitable_active)
    error (_("ignoring @tab outside of multitable"));
  
  nselect_next_environment ();
  init_column ();
}

/* close a multitable, flushing its output and resetting
   whatever needs resetting */
void
end_multitable ()
{
  output_multitable_row ();

  /* Multitables cannot be nested.  Otherwise, we'd have to save the
     previous output environment number on a stack somewhere, and then
     restore to that environment.  */
  select_output_environment (0);
  close_paragraph ();
  insert ('\n'); /* we swallow newlines, so insert one of our own */
  
  multitable_active = 0;
  uninhibit_output_flushing ();

#if 0
  printf (_("** Multicolumn output from last row:\n"));
  for (i = 1; i <= last_column; i++) {
    select_output_environment (i);
    printf (_("* column #%d: output = %s\n"), i, output_paragraph);
  }
#endif
}
