
/* xref.c */

#define TITLE0000 "The Ramification's Principal Sections"

/* This somewhat abstract file provides the function xref_pass() to
 * build cross-reference and subramification structures during one pass
 * over the library data file.  The xref_pass() produces a complete tree
 * of ramifications with all the needed internal data links and marks
 * the ramifications the user has selected (explicitly or implicitly) on
 * the command line.
 *
 * The xref_pass() is central to the larger program's operation: the
 * program must xref_pass() before it attempts a main pass for output.
 * Unfortunately, as with most reference-builders, no easily-written
 * brief English-language description can shed sufficient light upon the
 * function's specific process and effects.  The best way to learn
 * specifically what xref_pass() does is to read the code.  Realizing
 * this, the programmer has attempted to cast the code in a clear and
 * straightforward manner.  Nevertheless, the xref_pass()'s action is
 * somewhat sophisticated and (unless you have experience with similar
 * code) you should expect no casual reading fully to illuminate it: you
 * must study it to understand it.
 *
 * This file additionally provides the access functions
 * first_selected(0) and next_selected(), for use after the xref_pass().
 * As their names suggest, first_selected(0) returns the node pointer
 * describing the first ram the user has selected, while next_selected()
 * iteratively returns the node pointer describing the next such ram.
 *
 * See also `xref.h'.
 *
 */

#include "xref.h"
#include "strx.h"
#include "argp.h"
#include "datum.h"
#include "alloc.h"
#include "gen.h"

struct n_deb { /* the number of .debs in a ram */
  struct n_deb *next        ;
  char          ram[N_DIG+1];
  int           n           ;
};

struct xram xram0000 = {0};

static void select_recursively( struct xram *const xram ) {
  struct xref *sref;
  xram->selected = 1;
  for ( sref = xram->sref; sref; sref = sref->next )
    select_recursively( sref->xram );
}

static void select_recursively_up(
  struct xram *const xram,
  int                step_one_down
) {
  xram->selected = 1;
  if ( step_one_down && opt.plan ) {
    struct xref *sref;
    for ( sref = xram->sref; sref; sref = sref->next )
      sref->xram->selected = 1;
  }
  if ( xram->up ) select_recursively_up( xram->up, 1 );
}

struct xram *xref_pass( void ) {

  /* ram ancestry */
  struct xram  *up[N_DIG+1] = { &xram0000     , 0 };
  struct xref **v [N_DIG+1] = { &xram0000.sref, 0 };

  struct n_deb *n_deb_first = 0;

  rewind_data();

  /* Begin with a ram-0000 node.  */
  strncpy( xram0000.ram, zero_ram, N_DIG+1 );
  xram0000.n_indent  = -SHIFTWIDTH;
  xram0000.title     =  TITLE0000 ;

  /* Read the xref data and associate subrams with their parents.  Also
   * read the deb-counts in each ram.  */
  {

    struct xram  *xram       = &xram0000;
    struct n_deb *n_deb_last = 0        ;

    while ( doc_part < NONE2 ) {

      get_datum(0);

      switch ( doc_part ) {

        case TITLE_O: {
          struct n_deb *n_deb = calloc2( 1, sizeof(struct n_deb) );
          if ( n_deb_first ) n_deb_last->next = n_deb;
          else               n_deb_first      = n_deb;
          n_deb_last                          = n_deb;
          strxcpy( n_deb->ram, datum.ram, datum.ramX );
          n_deb->n = atoi( datum.count );
        } break;

        case XREF_O: {
          xram = xram->next = calloc2( 1, sizeof(struct xram) );
          xram->n_indent =          datum.indentX - datum.indent     ;
          xram->title    = malloc2( datum.titleX  - datum.title + 1 );
          strxcpy( xram->title,     datum.title,    datum.titleX    );
          strxcpy( xram->ram  ,     datum.ram  ,    datum.ramX      );
          xram->ram_level =
            ( xram->n_indent + SHIFTWIDTH ) / SHIFTWIDTH;
          if (
            ( xram->n_indent + SHIFTWIDTH ) % SHIFTWIDTH
            || xram->ram_level < 0 || xram->ram_level > N_DIG
          ) error(
            EIO, 0,
            "irregular indentation at ram %s in the xref table\n",
            xram->ram
          );
          {
            /* cross-references */
            struct xref *xref_last = 0;
            int i;
            for ( i = 0; i < NMAX_XREF && datum.xref[i]; ++i ) {
              struct xref *xref = calloc2( 1, sizeof(struct xref) );
              if ( xref_last ) xref_last->next = xref;
              else             xram     ->xref = xref;
              xref_last                        = xref;
              strncpy( xref->ram, datum.xref[i], N_DIG );
            }
          }
          {
            /* subramifications */
            int i;
            for ( i = N_DIG; i > xram->ram_level; --i ) {
              up[i] = 0;
              v [i] = 0;
            }
            up[i] =  xram      ;
            v [i] = &xram->sref;
            if ( i ) {
              struct xref *sref = calloc2( 1, sizeof(struct xref) );
              *v[i-1]      =  sref      ;
              v [i-1]      = &sref->next;
              sref->xram   =  xram      ;
              strncpy( sref->ram, xram->ram, N_DIG+1 );
              xram->up = up[i-1];
            }
          }
        } break;

        default: break;

      }

    }

  }

  /* Cross-link the tree's cross-references.  */
  {
    struct xram *xram;
    for ( xram = &xram0000; xram; xram = xram->next ) {
      struct xref *xref;
      for ( xref = xram->xref; xref; xref = xref->next ) {
        xref->xram = &xram0000;
        while (
          xref->xram
          && strncmp( xref->xram->ram, xref->ram, N_DIG )
        ) xref->xram = xref->xram->next;
        if ( !xref->xram ) error(
          EIO, 0,
          "cannot find ram %s in the xref table",
          xref->ram
        );
      }
    }
  }

  /* Total the deb counts.  */
  {
    struct xram *xram = &xram0000;
    struct n_deb *n_deb;
    for ( n_deb = n_deb_first; n_deb; n_deb = n_deb->next ) {
      while ( xram && strncmp( n_deb->ram, xram->ram, N_DIG ) )
        xram = xram->next;
      if ( !xram ) error(
        EIO, 0,
        "cannot find ram %s in sequence in the data file's main body",
        n_deb->ram
      );
      xram->n_deb = n_deb->n;
    }
  }
  {
    int i;
    for ( i = N_DIG-1 ; i >= 0; --i ) {
      struct xram *xram;
      for ( xram = &xram0000; xram; xram = xram->next )
        if ( xram->ram_level == i ) {
          struct xref *sref;
          for ( sref = xram->sref; sref; sref = sref->next )
            xram->n_deb += sref->xram->n_deb;
        }
    }
  }

  /* Mark the rams selected (explicitly or implicitly) on the command
   * line.  */
  {
    struct ram_cl *ram_cl;
    for ( ram_cl = opt.ram_cl_first; ram_cl; ram_cl = ram_cl->next ) {
      struct xram *xram;
      for ( xram = &xram0000; xram; xram = xram->next )
        if ( !strncmp( xram->ram, ram_cl->ram, N_DIG ) ) break;
      if ( !xram ) error(
        EINVAL, 0,
        "unknown ramification %s",
        ram_cl->ram
      );
      if ( opt.recursive    ) select_recursively   ( xram );
      if ( opt.recursive_up ) select_recursively_up(
        xram,
        STEP_ONE_DOWN
      );
      else xram->selected = 1;
    }
  }

  rewind_data();
  return &xram0000;

}

/* As one would expect, the following two functions return null when no
 * further node is available.  Therefore, to iterate through all the ram
 * nodes, call first_selected(0) once then (unless first_selected(0) has
 * returned null) call next_selected() repeatedly until next_selected()
 * returns null.  Each time, feed next_selected() the address returned
 * by the previous call.
 *
 * Here is an example of the correct usage.
 *
 *   struct xram *xram;
 *   for (
 *     xram = first_selected(0   );
 *     xram                       ;
 *     xram = next_selected (xram)
 *   ) {
 *     ...
 *   }
 *
 */

/* This function should normally be called with a null argument.  */
struct xram *first_selected( struct xram *xram ) {
  if ( !xram ) xram = &xram0000;
  while ( xram && !xram->selected ) xram = xram->next;
  return xram;
}

struct xram *next_selected( struct xram *xram ) {
  xram = xram->next;
  while ( xram && !xram->selected ) xram = xram->next;
  return xram;
}

