/* @(#)thcreate.c	19.1 (ES0-DMD) 02/25/03 14:12:47 */
/*===========================================================================
  Copyright (C) 1995 European Southern Observatory (ESO)
 
  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 of 
  the License, 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., 675 Massachusetss Ave, Cambridge, 
  MA 02139, USA.
 
  Corresponding concerning ESO-MIDAS should be addressed as follows:
	Internet e-mail: midas@eso.org
	Postal address: European Southern Observatory
			Data Management Division 
			Karl-Schwarzschild-Strasse 2
			D 85748 Garching bei Muenchen 
			GERMANY
===========================================================================*/

/*++++++++++++++++++++++++++
.TYPE		Program
.IDENTIFICATION thcreate.c
.AUTHOR    	Francois Ochsenbein [ESO-IPG]
.LANGUAGE  	C
.KEYWORDS	Hierarchical Help using TermWindows
.ENVIRONMENT	TermWindows

.VERSION 1.0	07-Nov-1988: Creation
.VERSION 1.1	05-Dec-1988: Added `header'; remove insertion of comments.

.VERSION 1.2	13-Jan-1989: Remov bug in the sorting process:
		new internal `compare' routine to ensure that the }
		(closing brace) comes before any other character
.VERSION 1.3	22-Jun-1989: Make sure anything is aligned...
.VERSION 1.4	16-Feb-1990: Be sure the program doesn't crash...
.COMMENTS	The module creates an index to a help file.
	The generated file is made of the following parts:
\begin{TeX}
\begin{enumerate}
\item 	The various definitions taken from the included files,
	preceded by the {\tt \b fromHelpFiles} macro
\item	The {\tt \b HelpIndex } definitions
\item	The remaining part of the text.
\end{enumerate}


	The \b HelpIndex has 7 parameters:
\begin{enumerate}
\item The level, a number starting from 0 up to 9
\item	The topic, or filename for HelpLoad
\item	A short explanation, for survey purposes.
\item	The context specification, as a set of words separated by commas.
\item 	The name of the file where the help is included
\item 	The starting position just following the \b Help
\item 	The number of bytes to load.
\end{enumerate}

The default extension for the input file is {\tt .tex};
the default extension for the index file is {\tt .ind}.

\end{TeX}

----------------------------*/

#define	DEBUG		0	/* For debugging only		*/

#define	PM_LEVEL 	2

#define PASCAL_DEF	0
#include <stesodef.h>
#include <pgm.h>
#include <atype.h>
#include <str.h>
#include <tex.h>
#include <tra.h>
#include <buffer.h>
#include <trtime.h>	/* Time operations		*/

#define GetProgramName()	osfparse(GetProgramFile(),_FILE_)

#define NREC	20

static 	TeX	htex;
	TeX	*tx_tex();

typedef struct {
	long	pos;	/* Current position	*/
	int	fid;	/* File number		*/
	int	level;	/* File level		*/
	int	phase;	/* 0: ignore; 1: copy 	*/
	char	filename[1];	/* File name	*/
   }	PIECE;		/* Piece of text to use with DisplayArray */

typedef struct {
	int	previous, next;	/* Continuation	*/
	char	text[12];
   }	INDEX;	

static BUFFER	*bpiece = NULL_PTR(BUFFER);
static BUFFER	*bindex = NULL_PTR(BUFFER);
static BUFFER	*bstart = NULL_PTR(BUFFER);

static INDEX	start_index = {-1, -1, {'\\', 'H', 'e', 'l', 'p',
			'I','n','d','e','x','{'}};
static PIECE	*pp = NULL_PTR(PIECE);
static PIECE	piece = {0,0,0,0,""};

static long 	pos_in_file[NREC];
static char	buf[NREC*133];
static int	fout = 0;		/* Name of Output File	*/

static char	error_flag = 0;
static char	osort = 0;
static char	olist = 0;

#define FINISH		goto FIN

#define FROM_MACRO	"\\fromHelpFiles"
#define HELP_MACRO	"\\Help"
#define HELP_INDEX	"\\HelpIndex"
#define time_pic 	"DD-MMM-YYYY"

static  char 		time_buf[sizeof(time_pic)];

/*==========================================================================*/
static int synopsis()
/*+++
.PURPOSE Just write the message if no argument.
.RETURNS 0
.REMARKS 
---*/
{
	static char text[] = "[-v] [-s] [-help] input_file [output_file]";

    printf("Usage: %s %s\n", GetProgramName(), text);
    return(0);

}

static void help()
/*+++
.PURPOSE Write a detailed Help
.RETURNS 0
.REMARKS 
---*/
{
	int	i;
  	static 	char *text[] = {
  "\nCreate an index for a set of TW Help Files",
  "  input_file  has an extension .tex by default",
  "  output_file is, by default, the input_file with an extension .ind",
  "       -s     sort the topics alphabetically in each topic level",
  "       -v     verbose mode"
  	};
  
  synopsis();
  for (i = 0; i < ITEMS(text); i++)	puts(text[i]);
}

/*==========================================================================*/
static INDEX *Previous(pc, level)
/*+++
.PURPOSE Find previous index for a specified level
.RETURNS Found index
.REMARKS 
---*/
	INDEX	*pc;	/* IN: Starting point	*/
	char	level;	/* IN: Level to check	*/
{
	register INDEX *pi;
	
  for (pi = pc; pi->text[11] > level; )
  	pi = (INDEX *)(bindex->buf + pi->previous);
  return(pi);
}
/*==========================================================================*/
static INDEX *Last(pc, level)
/*+++
.PURPOSE Find Last index for a specified level
.RETURNS Found index 
.REMARKS 
---*/
	INDEX	*pc;	/* IN: Starting point	*/
	char	level;	/* IN: Level to check	*/
{
	register INDEX *pi, *p;
	
  for (pi = pc; pi->next != (-1); pi = p)
  {	p = (INDEX *)(bindex->buf + pi->next);
  	if (p->text[11] <= level)	break;
  }
  return(pi);
}
/*==========================================================================*/
static INDEX *Next(pc, level)
/*+++
.PURPOSE Find Next index for a specified level
.RETURNS Found index / NULL if none
.REMARKS 
---*/
	INDEX	*pc;	/* IN: Starting point	*/
	char	level;	/* IN: Level to check	*/
{
	register INDEX *pi, *p;
	
  pi = Last(pc, level);
  p  = NULL_PTR(INDEX);

  if (pi->next != (-1))	
  {	pi = (INDEX *)(bindex->buf + pi->next);
  	if (pi->text[11] == level)	p = pi;
  }
  return(p);
}
  
/*==========================================================================
 *			help_load
 *==========================================================================*/
int help_load(filename, level)
/*+++
.PURPOSE Execute \HelpLoad
.RETURNS OK
.REMARKS 
---*/
	char	*filename; 	/* IN: New file to load	*/
	int	level;		/* IN: Current level	*/
{
	PIECE	*p;
	int	l, size;

  ENTER("help_load");
  TRACE(filename);
  
  l = strlen(filename);
  size = (l + sizeof(piece) + 4)&~3;	/* Adjust for Alignments */
  
  p = (PIECE *)	mm_bst(bpiece, (char *)(&piece), size);
  oscopy(p->filename, filename, l+1);
  p->level = level;
  if (pp)	p->level += pp->level;
  p->fid = fi_open(p->filename, READ|RECORD_MODE);
  if (p->fid <= 0)
  {	error_flag = 1;
  	BUF_Unstack(bpiece);
  	FINISH;
  }
  p->phase = 1;		/* Copy until next \Help	*/
  
  pp = p; 
  ReadNext();
  pp->phase = 0;
  
  FIN:
  EXIT(p->fid);
}  

/*==========================================================================
 *			act0
 *==========================================================================*/
static int act0(str, len)
/*+++
.PURPOSE Action Routine to execute definitions in a help file
.RETURNS len (does nothing)
.REMARKS 
---*/
	char	*str;	/* IN: Action string, should start with H */
	int	len;	/* IN: Length of action string */
{
  return(len);
}  

/*==========================================================================
 *			acti
 *==========================================================================*/
static int acti(str, len)
/*+++
.PURPOSE Action Routine to deal with Help
.RETURNS 0 (must stop)
.REMARKS 
---*/
	char	*str;	/* IN: Action string, should start with H */
	int	len;	/* IN: Length of action string */
{
	int	o, l, level;
	char	alevel, *p;
	long	fpos;
	static 	char 	zeroes[4] = "";
	
  if (*str != 'H')	return(len);
  
  switch(str[1])
  { case '+':		/* \fromHelpFiles: Ignore	*/
  	pp->phase = 0;
  	help_tell();
  	break;

    case ':':		/* \HelpIndex: Ignore		*/
  	pp->phase = 0;	/* No copy	*/
  	help_tell();
  	break;

    case '.':		/* \Help: Create \HelpIndex	*/
    	o = bindex->used;
    	BUF_AppendItem(bindex, INDEX, &start_index);
	bindex->used -= 1;	/* !?!?!? Suppress the ending EOS */
    	tex_getparm(1);		/* Level	*/
    	level = atoi(htex.ap);
    	alevel = level + pp->level + '0';
    	BUF_AppendItem(bindex, char, &alevel);
    	BUF_AppendString(bindex, "}{");
    	tex_getparm(2);		/* Topic	*/
    	BUF_AppendString(bindex, htex.ap), BUF_AppendString(bindex, "}{");
    	l = tex_getvparm(3);	/* Explanation	*/
    	BUF_AppendItems(bindex, char, htex.ap, l);
    	BUF_AppendString(bindex, "}{");
    	l = tex_getvparm(4);	/* Context	*/
    	BUF_AppendItems(bindex, char, htex.ap, l);
    	BUF_AppendString(bindex, "}{");
    	BUF_AppendString(bindex, pp->filename);
    	BUF_AppendString(bindex, "}{");
    	help_tell();
    	fpos = pp->pos; p = ltoa(fpos); 
    	BUF_AppendString(bindex, p); BUF_AppendString(bindex, "}{");
    	ReadNext();
    	p = ltoa(pp->pos - fpos);
    	BUF_AppendString(bindex, p); 
    	BUF_AppendItems(bindex, char, "}", 1);
    	BUF_AppendItems(bindex, char, zeroes, 4-(bindex->used&3));
	LinkIndex(o);
    	break;

    case '@':		/* \HelpLoad	*/
    	tex_getparm(1);		/* Level	*/
    	level = atoi(htex.ap);
    	tex_getparm(2);		/* FileName	*/
    	help_tell();
    	help_load(htex.ap, level);
    	break;
  }

  return(0);
}  

static int compare(str1, str2, len)
/*++++++++++++++++++++++++++++++
.PURPOSE Compare two strings of equal length, case insensitive.
	Note that the {} are equated ti zero.
.RETURNS Sign of (str1-str2), i.e. 0 for identical strings, negative number
	if str1<str2, positive number if str1>str2.
.REMARKS The `}' is smaller than any other character.
----------------------------------------------*/
     unsigned char *str1;	/* IN: string to compare	*/
     unsigned char *str2;	/* IN: second string		*/
     int len;			/* IN: length of strings	*/
{
 	register unsigned char *p, *pe;
	register int index;

  index = 0;
  for (p = str1, pe = str1+len; p < pe; p++, str2++)
  	if (index = (toupper(*p) - toupper(*str2)))		break;

  if (index)
  {	if (*p == '}')	/* Ending brace */	index = -1;
 	if (*str2 == '}')			index = 1;
  }

  return(index);
}

PGM(THCreate)
/*==========================================================================
 *			Main Program
 *==========================================================================*/
/*+++
.PURPOSE Modify the File to add an Index
.RETURNS to OS.
.REMARKS Parameter is FileName. Option -s to sort, -v for Verbose 
	-l to list files.
---*/
{
	MID_STATIC char definitions[] = 
		"\\def\\Help#4{\\action{H.}}\\def\\HelpLoad#4{\\action{H@}}\
\\def\\HelpIndex#7{\\action{H:}}\\def\\fromHelpFiles#1{\\action{H+}}";
	char	*filename, *p;
	int	l;
	INDEX	*pi;
	MID_STATIC char *header[] = {"\
%+++++++++++++++\n%.IDENTIFICATION ", 	"\n\
%.AUTHOR\t ESO - IPG (for problems: Span ESOMC1::FRANCOIS)\n\
%.LANGUAGE \t TeX-like\n\
%.KEYWORDS\t Hierarchical Help using TermWindows\n\
%.ENVIRONMENT\t TermWindows\n\
%.VERSION 3.1\t ", ": Generated by THCREATE\n\
%.COMMENTS\t This index improves the access to the topics.\n\
%---------------\n%\n%\t Definitions from included files\n%\n","\
%\n%\t Complete Index\n%\n"};

  SaveParms();
  if (GetParmString("-help"))	{ help(); return; }

  htex.output = act0, htex.action = acti;
  TeX_Execute(&htex, definitions, sizeof(definitions)-1);

	/* Buffers	*/
	
  bpiece = BUF_Open(PIECE, 10, 10);
  bindex = BUF_Open(char, 1024, 1024);
  bstart = BUF_Open(char, 1024, 1024);

	/* Get File Name	*/
 
  if_not(filename = GetNextParm())
  	{ synopsis (); return; }

  osort = GetOption('s');
  olist = GetOption('v') | GetOption('l');

	/* Load Help file	*/

  if_not (help_load(NameFile(filename, ".tex"), 0))	FINISH;
  BUF_AppendItems(bstart, char, FROM_MACRO, sizeof(FROM_MACRO)-1);
  BUF_AppendString(bstart, "{  }\n");

  while(pp)
  {	if (ReadNext() == EOF)	
  	{	fi_close(pp->fid);
  		if (olist)
			LOG_ED_STRING("File processed: ", pp->filename);
  		pp = BUF_UnstackItem(bpiece, PIECE);
  		continue;
  	}
	l = ReadBuffer(pp->fid);
	TeX_Execute(&htex, buf, l);
  }

  if(error_flag)
  {	ERROR("Index not created");
  	return;
  }

	/* Check if something was found! */

  if (bindex->used == 0)	/* Nothing ! */
  {	ERROR("Input file has no \\Help or \\HelpLoad...");
  	ERROR("Index Not Created");
  	return;
  }

	/* Copy now the 3 buffers	*/
 
  p = GetNextParm();			/* Output file name	*/
  if_not(p)	p = ParseFile(filename, _PATH_|_FILE_);
  fout = fi_open(NameFile(p, ".ind"), WRITE);

	/* Add the Header	*/
 
  fi_put (fout, header[0]);
  fi_put (fout, p);		/* File Name */
  fi_put (fout, header[1]);
  ed_t   (time_buf, time_pic, oshtime());
  fi_put (fout, time_buf);		/* File Name */
  fi_put (fout, header[2]);

  fi_write(fout, bstart->buf, bstart->used);
  fi_put (fout, header[3]);

  pi = (INDEX *)(bindex->buf);
  while(1)
  {	fi_puts(fout, pi->text);
  	if (pi->next == (-1))	break;
  	pi = (INDEX *)(bindex->buf + pi->next);
  }

  fi_close(fout);

  FIN:
  return;
}

/*==========================================================================
 *			ReadBuffer
 *==========================================================================*/
int ReadBuffer(fid)
/*+++
.PURPOSE Read NREC lines from current input file.
.RETURNS Length of buffer
.REMARKS 
---*/
	int	fid;		/* IN: File identifier	*/
{
	char 	*p;
	int 	i, k;

  ENTER("+ReadBuffer");

  pos_in_file[0] = fi_tell(fid);
  for (i=0, p=buf; i<NREC-1; )
  { 	k = fi_gets(fid, p, sizeof(buf) - (p-buf));
  	if (k == NOK)
  		ERROR("Bad File"),
  		error_flag = 1;
	pos_in_file[++i] = fi_tell(fid);
  	if (k != OK)	break;
	p += strlen(p);
	*(p++) = '\n';
  }

  EXIT(p-buf);
}
  
/*==========================================================================
 *			ReadNext
 *==========================================================================*/
int ReadNext()
/*+++
.PURPOSE Read next line, until special macro found.
.RETURNS OK / NOK / EOF
.REMARKS 
---*/
{
	int	l, st;
	
  ENTER("ReadNext");

  while(1)
  {   	pp->pos = fi_tell(pp->fid);
    	st = fi_gets(pp->fid, buf, sizeof(buf));
  	if (st == NOK)
  	{	ERR_ED_STRING("Bad record in ", pp->filename);
  		error_flag = 1;
		st = EOF;
  	}
	if (st == EOF )	break;
	if (buf[0] == '%')	continue;	/* Comment */
  	l = strlen(buf);
  	if (l == 0)		continue;
  	if (oscindex(buf, l, HELP_MACRO, sizeof(HELP_MACRO)-1) < l)
  		break;		/* \Help found	*/
  	if (oscindex(buf, l, FROM_MACRO, sizeof(FROM_MACRO)-1) < l)
  		break;		/* \Help found	*/
	buf[l++] = '\n';
  	if (pp->phase)		BUF_AppendItems(bstart, char, buf, l);
  }
  if (st == OK)	fi_seek(pp->fid, pp->pos, FILE_START);

  EXIT(st);
}
  
/*==========================================================================
 *			help_tell
 *==========================================================================*/
int help_tell()
/*+++
.PURPOSE Tell were we're in the current input file
.RETURNS OK / NOK
.REMARKS 
---*/
{
	int l, nn, i;

  ENTER("help_tell");

  l = tex_tell();
  for (i=0, nn=0; i<l; nn++, i++)	/* Count newlines	*/
	i += oscloc(buf+i, sizeof(buf) - i, '\n');
  	
  pp->pos = pos_in_file[nn];
  fi_seek(pp->fid, pp->pos, FILE_START);

  EXIT(OK);
}

/*==========================================================================
 *			LinkIndex
 *==========================================================================*/
int LinkIndex(o)
/*+++
.PURPOSE Add new index
.RETURNS OK 
.REMARKS Sorted only if osort is on.
---*/
	int	o;	/* IN: Position in bindex buffer	*/
{
	INDEX	*pi, *pip, *p;
	int	l, st, brace;
	char	level;
	
  ENTER("LinkIndex");

  pi = (INDEX *)(bindex->buf + o);
  pi->previous = bindex->offset;
  pi->next = -1;			/* End of chain		*/
  bindex->offset = o;
  if (o == 0)	FINISH;			/* First element 	*/

  pip = (INDEX *)(bindex->buf + pi->previous);		/* Last	*/
  
  if_not(osort)		/* Don't sort	*/
  {	pip->next = o;
  	FINISH;
  }
	/* Modify links to have result sorted	*/

  brace = oscloc(pi->text+14, 80, '}');		/* To get a correct sort */
  pi->text[14+brace] = '\0';
  level = pi->text[11];

	/* Find first previous record with same level	*/
	
  pip = Previous(pip, level);
  
	/* If new topic comes after previous one, move forwards	*/

  while ((pip->text[11] == level) && (compare(pi->text, pip->text, 94) > 0))
  {	p = Next(pip, level);
  	if (p)	pip = p;
  	else	break;
  }
  	
	/* If new topic comes before previous one, move backwards	*/

  while ((pip->text[11] == level) && (compare(pi->text, pip->text, 94) < 0))
  {	pip = (INDEX *)(bindex->buf + pip->previous);
  	pip = Previous(pip, level);
  }

  pip = Last(pip, level);	/* Insert new topic just after this one	*/
  
  pi->text[14+brace] = '}';
  pi->previous = ((char *)pip) - bindex->buf;
  pi->next = pip->next;
  pip->next = o;
  
  if (pi->next != (-1))
  {	p = (INDEX *)(bindex->buf + pi->next);
  	p->previous = o;
  }

  FIN:
  EXIT(OK);
}

