/* #module    IdxRange    "3-001"
 ***********************************************************************
 *                                                                     *
 * The software was developed at the Monsanto Company and is provided  *
 * "as-is".  Monsanto Company and the auther disclaim all warranties   *
 * on the software, including without limitation, all implied warran-  *
 * ties of merchantabilitiy and fitness.                               *
 *                                                                     *
 * This software does not contain any technical data or information    *
 * that is proprietary in nature.  It may be copied, modified, and     *
 * distributed on a non-profit basis and with the inclusion of this    *
 * notice.                                                             *
 *                                                                     *
 ***********************************************************************
 */
/*
 * Module Name:	IdxRange
 *
 * Author:	R L Aurbach	CR&DS MIS Group    07-Apr-1987
 *
 * Function:
 *	Examine the list of page references to build range references.
 *
 * Modification History:
 *
 * Version     Initials	   Date		Description
 * ------------------------------------------------------------------------
 * 1-001	RLA	07-Apr-1987	Original Code
 * 1-002	RLA	14-Apr-1987	Fix algorithm for processing ranges
 *					  which contain highlights.
 * 3-001	F.H.	17-May-1991	converted to portable C
 */
/*
 * Module IdxRange - Module-Wide Data Description Section
 *
 * Include Files:
 */
#ifdef MSDOS
#include <stdlib.h>
#include <stdio.h>
#include <io.h>
#define F_OK		0	/* access(): File exists */
#else
#include <sys/file.h>
#endif
#include <string.h>
/* #include <malloc.h> */
#include "IdxDef.h"
/*
 * Module Definitions:
 */
#define	IDX_K_START	1	/* Start of range	*/
#define	IDX_K_MIDDLE	2	/* Middle of range	*/
#define	IDX_K_END	3	/* End of range		*/
#define	IDX_K_SIMPLE	0	/* Simple page-ref	*/
#define	IDX_K_COMPLEX	1	/* Complex page-ref	*/
#define	TRUE	    1
#define	FALSE	    0
typedef struct {
  char   *vol;	        /* Volume string ptr	*/
  char   *page_dsc;	/* Page-ref string	*/
  char   highlight;	/* Highlight flag	*/
  char   adjacent;	/* Adjacency flag	*/
  char   flag;	        /* Range type flag	*/
  char   type;	        /* Page-ref type flag	*/
  int	 start;	        /* Start of range	*/
} TABLE, *TABLE_PTR;
typedef struct {
  char   *vol;		/* Volume string ptr	*/
  char   *chapter;	/* Chapter string	*/
  int    page;		/* Page number		*/
  char   type;		/* Page-ref type flag	*/
} CMP, *CMP_PTR;
/*
 * Global Declarations:
 */
/*
 * Static Declarations:
 */
#ifdef MSDOS
void idx_build_range(TREE_PTR node);
void idx_page_parse(TABLE_PTR elem, CMP_PTR cmp);
int idx_is_adjacent(CMP_PTR curr, CMP_PTR prev);
void idx_deallocate_pglist(TREE_PTR node);
void idx_build_new_pglist(TREE_PTR node, TABLE_PTR table, int size);
void idx_allocate_new_pgnode(PGNODE_PTR *ptr, TREE_PTR node);
#else
void idx_build_range();
void idx_page_parse();
int idx_is_adjacent();
void idx_deallocate_pglist();
void idx_build_new_pglist();
void idx_allocate_new_pgnode();
#endif
static char *dash =	"-";
static char *simple = "--";
static char	*complex = " to ";
/*
 * External References:
 */
#ifdef MSDOS
extern int strposition(char *str, char *e_array, int st);
#else
int strposition();
#endif
/*
 * Functions Called:
 */
/*
 * Function Idx_Build_Range - Documentation Section
 *
 * Discussion:
 *	Examine a list of page references.  If the list contains any pages
 *	which are in consecutive order, build a new list of page references
 *	which contain one or more range specifications.
 *
 * Calling Synopsis:
 *	Call Idx_Build_Range (node)
 *
 * Inputs:
 *	node	    ->	pointer to the current node of the item tree.
 *
 * Outputs:
 *	none	    ->	pointer to the current node of the item tree.
 *
 * Return Value:
 *	none
 *
 * Global Data:
 *	none
 *
 * Files Used:
 *	none
 *
 * Assumed Entry State:
 *	none
 *
 * Normal Exit State:
 *	none
 *
 * Error Conditions:
 *	none
 *
 * Algorithm:
 *	A. Count the number of page references in the current list.
 *	    1. If less than 2, there is nothing to do.
 *	B. Allocate a TABLE structure big enough to hold all the references.
 *	C. For each reference,
 *	    1. Fill in the TABLE entry.
 *	    2. Call Idx_Page_Parse to parse the page reference and place the
 *	       results of the parse operation in the "new" CMP structure.
 *	    3. Call Idx_Is_Adjacent to compare the information in the "old"
 *	       and "new" CMP structures to determine if the page number is
 *	       adjacent to the previous one.
 *	    4. If it is,
 *		a. Set the adjacent flag in the TABLE entry.
 *		b. Set the "ranges_found" flag.
 *	D. If no ranges were found,
 *	    1. Deallocate the TABLE.
 *	    2. Return.
 *	E. Else,
 *	    1. Process the TABLE to set up the "flag" and "start" fields.
 *	    2. Call Idx_Deallocate_PgList to deallocate the old page-refs.
 *	    3. Call Idx_Build_New_PgList to allocate a new page-ref list.
 *	    4. Deallocate the TABLE.
 *
 * Special Notes:
 *	none
 */
/*
 * Function Idx_Build_Range - Code Section
 */
void idx_build_range(node)
     TREE_PTR	node;		/* Current tree node		*/
{
/*
 * Local Declarations
 */
  PGNODE_PTR	pgnode;		/* Current page node		*/
  int		ref_ct = 0;	/* Number of page nodes		*/
  TABLE_PTR	table;		/* Local structure pointer	*/
  int		ranges_found = FALSE;	/* Ranges found flag	*/
  CMP		old;		/* Parse struct for prev ref	*/
  CMP		new;		/* Parse struct for curr ref	*/
  int		i, j;		/* Table index			*/
/*
 * Module Body
 */
/*
 * Count the number of page references for the current tree node.
 * If there are less than 2, there is nothing to do -- no ranges are possible.
 */
  pgnode = node->pghead;
  while (pgnode != 0) {
    ref_ct++;
    pgnode = pgnode->link;
  }
  if (ref_ct < 2) return;
/*
 * Allocate an internal data table sufficiently large to contain all of the
 * existing page reference entries.
 */
  table = (TABLE_PTR) malloc ((unsigned int)ref_ct * sizeof(TABLE));
  if (table == 0) return;
/*
 * Initialize the previous page reference parse structure to null.
 */
  old.vol = 0;
  old.page = 0;
  old.type = IDX_K_SIMPLE;
  pgnode = node->pghead;
/*
 * For each page reference in the page reference list, we must build the
 * appropriate entry in the internal data table.  To do this, we first
 * initialize the entry using the available data.
 */
  for (i = 0; i < ref_ct; i++) {
    table[i].vol       = pgnode->vol;
    table[i].page_dsc  = pgnode->page_dsc;
    table[i].highlight = pgnode->highlight;
    table[i].adjacent  = FALSE;
    table[i].flag      = IDX_K_NONE;
    table[i].type      = IDX_K_SIMPLE;
    table[i].start     = 0;
    pgnode	       = pgnode->link;
/*
 * Now we parse the page descriptor and fill out the data structure used
 * for comparison and compare it against the previous page reference.  If
 * the pages are consecutive, we flag the table entry and set the ranges_found
 * flag.  Finally, we copy the new structure to the old structure to prepare
 * for the next iteration.
 */
    idx_page_parse (&table[i], &new);
    if (idx_is_adjacent (&new, &old)) {
      table[i].adjacent = TRUE;
      ranges_found = TRUE;
    }
    free((char *)&old.chapter);
    old = new;
  }
/*
 * Since we are now done with the "old" and "new" structures, deallocate
 * the dynamic strings they reference.  (So that we don't forget).
 */
  free((char *)&old.chapter);    /* Just once, please! */
/*
 * If we haven't found any ranges, then all of this analysis was for nought.
 * We just deallocate the table and leave, with the initial list intact.
 */
  if (!ranges_found) {
    free((char *)&table);
    return;
  }
/*
 * At this point, we know we have found at least one range, so we have work
 * to do.  First, we need to process the table so that the "flag" and "start"
 * fields are set up correctly.
 */
  for (i = 1; i < ref_ct; i++) {
/*
 * If the current entry is part of a range and has the FOLLOW highlight, then
 * make sure that every element of the range has the same highlight.
 */
    if (table[i].adjacent && table[i].highlight == IDX_K_FOLLOW) {
      if (table[i-1].flag == IDX_K_NONE)
	table[i-1].highlight = IDX_K_FOLLOW;
      else
	for (j = table[i-1].start; j < i; j++)
	  table[j].highlight = IDX_K_FOLLOW;
    }
/*
 * If the current entry is part of a range which has the FOLLOW highlight, then
 * it should have that highlight also.
 */
    if (table[i].adjacent && table[i-1].highlight == IDX_K_FOLLOW)
      table[i].highlight = IDX_K_FOLLOW;
/*
 * If the current entry is part of a range, then update the flag and start
 * values in the table.
 */
    if (table[i].adjacent && (table[i].highlight
			      == table[i-1].highlight)) {
      if (table[i-1].flag == IDX_K_NONE) {
	table[i-1].flag  = IDX_K_START;
	table[i-1].start = i-1;
      }
      table[i].flag    = IDX_K_MIDDLE;
      table[i].start   = table[i-1].start;
    }
/*
 * If the entry is not part of a range, then if the preceeding entry was part
 * of a range, it was the end of that range.  Mark it so.
 */
    else {
      if (table[i-1].flag == IDX_K_MIDDLE)
	table[i-1].flag = IDX_K_END;
    }
  }
/*
 * If the last entry in the table was part of a range, it hasn't been correctly
 * marked that way yet.  Do so now.
 */
  if (table[ref_ct-1].flag == IDX_K_MIDDLE)
    table[ref_ct-1].flag = IDX_K_END;
/*
 * At this point, we know we are going to create a new page-ref list, so we
 * can deallocate the old list.  Note that we don't need to free any strings,
 * because they are all contained in the data table...
 */
  idx_deallocate_pglist (node);
/*
 * Now we use the information in the table to build the new page-ref list.
 */
  idx_build_new_pglist (node, table, ref_ct);
/*
 * We are done.  To clean up correctly, we must deallocate the table.  To
 * do this, we first deallocate any strings which are defined in the table,
 * then deallocate the table.  This should clean up virtual memory usage
 * nicely.
 */
  for (i = 0; i < ref_ct; i++) free((char *)table[i].page_dsc);
  free ((char *)&table);
}

/*
 * Function Idx_Page_Parse - Documentation Section
 *
 * Discussion:
 *	Parse a page number into its component parts.
 *
 * Calling Synopsis:
 *	call Idx_Page_Parse (elem, cmp)
 *
 * Inputs:
 *	elem	    ->	is an element of the table array, describing the
 *			current page reference.  It is passed by reference.
 *
 * Outputs:
 *	elem	    ->	is an element of the table array, describing the
 *			current page reference.  It is passed by reference.
 *			The "type" flag is updated.
 *
 *	cmp	    ->	is the comparison data structure into which the data
 *			for this page reference is parsed.  It is passed by
 *			reference.
 *
 * Return Value:
 *	none
 *
 * Global Data:
 *	none
 *
 * Files Used:
 *	none
 *
 * Assumed Entry State:
 *	none
 *
 * Normal Exit State:
 *	none
 *
 * Error Conditions:
 *	none
 *
 * Algorithm:
 *	A. Initialize the output data structure.
 *	B. If the input string contains a '-',
 *	    1. Copy the characters prior to the '-' to the chapter string.
 *	    2. Set the flags to mark this page number as complex.
 *	C. Skip over any '-'s.
 *	D. Convert the rest of the string to an integer.
 *
 * Special Notes:
 *	none
 */

/*
 * Function Idx_Page_Parse - Code Section
 */
void idx_page_parse (elem, cmp)
     TABLE_PTR	elem;		/* Pointer to a table entry	*/
     CMP_PTR	cmp;		/* Pointer to a CMP structure	*/
{
/*
 * Local Declarations
 */
  int	    i;		/* String Index	*/
  char *temp;
/*
 * Module Body
 */
/* Initialize the cmp structure. */
  cmp->vol     = elem->vol;
  cmp->page    = 0;
  cmp->type    = IDX_K_SIMPLE;
/*
 * This routine assumes that the chapter string is delimited by one or more
 * "-" characters.  Determine if the page reference string contains such a
 * delimiter.  If it does, then copy the chapter portion of the string to
 * the output string and set the "type" field to IDX_K_COMPLEX.
 */
  i = strposition(elem->page_dsc, dash, 0);
  if (i != -1) {
    i--;
    (void)strncpy(cmp->chapter, elem->page_dsc, i);
    cmp->type  = IDX_K_COMPLEX;
    elem->type = IDX_K_COMPLEX;
  }
/* Skip all dash characters. */
  while (elem->page_dsc[i] == '-') i++;
  if (i<0) i=0;
/* Convert the page number to an integer */
  temp = strdup(&elem->page_dsc[i]);
  (void)sscanf(temp, "%d", &cmp->page);
}

/*
 * Function Idx_Is_Adjacent - Documentation Section
 *
 * Discussion:
 *	Compare two page references and determine if the first is adjacent
 *	to the second.
 *
 * Calling Synopsis:
 *	boolean = Idx_Is_Adjacent (curr, prev)
 *
 * Inputs:
 *	curr	    ->	is the CMP data structure for the current page ref,
 *			passed by reference.
 *
 *	prev	    ->	is the CMP data structure for the previous page ref,
 *			passed by reference.
 *
 * Outputs:
 *	none
 *
 * Return Value:
 *	boolean	    ->	is a boolean result, passed by value.
 *
 * Global Data:
 *	none
 *
 * Files Used:
 *	none
 *
 * Assumed Entry State:
 *	none
 *
 * Normal Exit State:
 *	boolean = TRUE	    Pages are consecutive.
 *	boolean = FALSE	    Pages are not consecutive.
 *
 * Error Conditions:
 *	none
 *
 * Algorithm:
 *	A. If the page refs don't have the same type, return FALSE.
 *	B. If the page refs don't have the same vol-string ptr, return FALSE.
 *	C. If the page ref type is COMPLEX,
 *	    1. If the two chapter strings are not the same, return FALSE.
 *	D. If the page numbers are not consecutive, return FALSE.
 *	E. Return TRUE.
 *
 * Special Notes:
 *	none
 */
/*
 * Function Idx_Is_Adjacent - Code Section
 */
int idx_is_adjacent (curr, prev)
     CMP_PTR	curr;		/* Current page-ref structure	*/
     CMP_PTR	prev;		/* Previous page-ref structure	*/
{
/*
 * Local Declarations
 */
/*
 * Module Body
 */
  if (curr->type != prev->type) return(FALSE);
  if (prev->vol == 0) return(FALSE);
  if ((strcmp(curr->vol,prev->vol)) != 0) return(FALSE); 
  if (curr->type == IDX_K_COMPLEX)
    if (strcmp(curr->chapter, prev->chapter) != 0) return(FALSE);
  if ((curr->page == prev->page) 
      || (curr->page == (prev->page + 1))) return(TRUE);
  return(FALSE);
}

/*
 * Function Idx_Deallocate_PgList - Documentation Section
 *
 * Discussion:
 *	Deallocate the old page list for the current tree node.
 *
 * Calling Synopsis:
 *	call Idx_Deallocate_PgList (node)
 *
 * Inputs:
 *	node	    ->	is the current tree node, passed by ref.
 *
 * Outputs:
 *	node	    ->	is the current tree node, passed by ref.
 *
 * Return Value:
 *	none
 *
 * Global Data:
 *	none
 *
 * Files Used:
 *	none
 *
 * Assumed Entry State:
 *	none
 *
 * Normal Exit State:
 *	none
 *
 * Error Conditions:
 *	none
 *
 * Algorithm:
 *	A. While the page listhead is not empty,
 *	    1. Unlink the current top of the list.
 *	    2. Deallocate it.
 *
 * Special Notes:
 *	none
 */
/*
 * Function Idx_Deallocate_PgList - Code Section
 */ 
void idx_deallocate_pglist (node)
     TREE_PTR	node;
{
/*
 * Local Declarations
 */
  PGNODE_PTR	curr;		/* Current page node */
/*
 * Module Body
 */
  while (node->pghead != 0) {
    curr = node->pghead;
    node->pghead = curr->link;
    free((char *)curr);
  }
}

/*
 * Function Idx_Build_New_PgList - Documentation Section
 *
 * Discussion:
 *	Use the data table to construct a new pagelist.
 *
 * Calling Synopsis:
 *	call Idx_Build_New_PgList (node, table, size)
 *
 * Inputs:
 *	node	    ->	is the current tree node, passed by reference.
 *
 *	table	    ->	is the data table, passed by reference.
 *
 *	size	    ->	is the number of elements in the table, passed by value.
 *
 * Outputs:
 *	node	    ->	is the current tree node, passed by reference.  The
 *			new page list is linked to the pghead.
 *
 * Return Value:
 *	none
 *
 * Global Data:
 *	none
 *
 * Files Used:
 *	none
 *
 * Assumed Entry State:
 *	The tree node has a null page list.
 *
 * Normal Exit State:*
 *	none
 *
 * Error Conditions:
 *	none
 *
 * Algorithm:
 *	A. For each entry in the table,
 *	    1. If the entry is not part of a range,
 *		a. Allocate a new element for the page list.
 *		b. Fill it out.
 *	    2. If the entry is the beginning of a range,
 *		a. Allocate a new element for the page list.
 *		b. Begin filling out the page list element. 
 *	    3. If the entry is the middle of a range,
 *		a. Ignore it. 
 *	    4. If the entry is the end of a range, 
 *		a. Complete filling out the page list element.
 *
 * Special Notes:o
 *	nonet
 */ 
/*
 * Function Idx_Build_New_PgList - Code Sectionm
 */
void idx_build_new_pglist (node, table, size)
     TREE_PTR	node;
     TABLE_PTR	table;
     int	size;
{ 
/*
 * Local Declarations 
 */ 
  PGNODE_PTR	ptr = 0;	/* New PageRef Pointer */ 
  int		i;		/* Current table entry */
/*
 * Module Body
 */
  for (i = 0; i < size; i++) {
    switch (table[i].flag) {
    case IDX_K_NONE:
      idx_allocate_new_pgnode(&ptr, node);
      ptr->vol = table[i].vol; 
      (void)strcpy(ptr->page_dsc, table[i].page_dsc);
      ptr->highlight = table[i].highlight;
      break;
    case IDX_K_START:
      idx_allocate_new_pgnode(&ptr, node);
      ptr->vol = table[i].vol; 
      (void)strcpy(ptr->page_dsc, table[i].page_dsc);
      if ((ptr->highlight = table[i].highlight) == IDX_K_FOLLOW)
	break;
      if (table[i].type == IDX_K_SIMPLE)
	(void)strcat(ptr->page_dsc, simple);
      else (void)strcat(ptr->page_dsc, complex);
      break;
    case IDX_K_MIDDLE:
      break;
    case IDX_K_END:
      if (ptr->highlight != IDX_K_FOLLOW)
	(void)strcat(ptr->page_dsc, table[i].page_dsc);
      break;
    }
  }
}

/*
 * Function Idx_Allocate_New_PgNode - Documentation Section 
 *
 * Discussion:
 *	Allocate and link a new page-ref node into the current list.
 *
 * Calling Synopsis:
 *	call Idx_Allocate_New_PgNode(ptr, node)
 *
 * Inputs:
 *	ptr	    ->	is the current page-ref node pointer, passed by ref.
 *
 *	node	    ->	is the current tree-node pointer.
 *
 * Outputs:
 *	ptr	    ->	is the new page-ref pointer, passed by reference.r
 *
 * Return Value:
 *	none 
 *
 * Global Data:
 *	none
 *
 * Files Used:
 *	nones
 *
 * Assumed Entry State:
 *	none
 *
 * Normal Exit State:
 *	none;
 *
 * Error Conditions:
 *	none
 *
 * Algorithm:
 *	A. Allocate a new pgnode.
 *	B. Link it into the chain after the current pointer.
 *
 * Special Notes:
 *	none
 */
/*
 * Function Idx_Allocate_New_PgNode - Code Section
 */
void idx_allocate_new_pgnode (ptr, node)
     PGNODE_PTR	*ptr;		/* Page-Ref Node Pointer */
     TREE_PTR	node;		/* Node pointer		 */
{
/*
 * Local Declarations
 */
  PGNODE_PTR	new;		/* New Page-Ref Node Ptr */
/*
 * Module Body
 */
  new = (PGNODE_PTR) malloc(sizeof(PGNODE));
  new->link      = 0;
  new->vol       = 0;
  new->highlight = IDX_K_NONE;
  if ((*ptr) == 0) node->pghead = new;
  else (*ptr)->link = new;
  new->page_dsc = malloc(133);
  *ptr = new;
}
