/*+++++++++++   
.TYPE           Program
.LANGUAGE       C
.IDENTIFICATION	texroff.c
.VERSION 1.0	19-Jul-1992: Creation
.VERSION 1.1	24-Oct-1994: Changed the access to the .def file
.VERSION 1.2	13-Dec-2005: Standardized
.AUTHOR         Francois Ochsenbein [ESO-IPG]
.KEYWORDS       TeX, roff, man pages
.ENVIRONMENT    
.COMMENTS       

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

#define VERSION "1.2"
#define MAX_DEPTH	16	/* Depth of {} and Environments */

/* The "def" macro contains the definitions of Macros */
#include   <stdio.h>
#include   <stdlib.h>
#include   <buffer.h>
#include   <stesodef.h>
#include   <trace.h>		/* LOG Macros */
#include   <tex.h>
#include   <str.h>
#include   <error.h>

/*char *strdup(); (in string.h) */
typedef char *PCHAR;
typedef PCHAR (*ACTION)();


static TeX *tex;
static char verbop;	/* Verbose option */

typedef struct {	/* Depth */
	short 	linesize;	
	short	margin;
	short	counter;	/* e.g. enumerate */
	short	fno;		/* Output File	  */
	char	font;
	char	tex_mode;
#define MATHmode	01
	char	*env;		/* Environment M for Man Page */
  } DEPTH;
  
static DEPTH depth[MAX_DEPTH] = {
	{ 80, 0, 0, 1, 'R', 0, ""}	/* 'R' for Roman  */
 };
static DEPTH *pd   = depth;
#define       pd0    depth
static DEPTH *pd1  = &(depth[MAX_DEPTH]);

static short jpos  = 0;		/* Column Position */
static short RS    = 0;		/* RS count	   */
static char  last_char = ' ';

static char pgm[] = "texroff";
static char *usage[] = {
	"Usage: texroff [file]...",
	};
static char *options[] = {	/* List of Options with text */
	"-special", "\\input{$TEXINPUTS/texroff.tex} % DEFAULT File Inclusion\n",
   };
#define TEXT(s)		s,sizeof(s)-1

static int write_macro() ;

/*==================================================================
		Internal Routines
 *==================================================================*/
static int error(t1, t2)
/*++++++++++++++++
.PURPOSE  Error exit
.RETURNS  -
-----------------*/
	char *t1;	/* IN: Text before error */
	char *t2;	/* IN: Text before error */
{
	int i;
  
  fprintf(stderr, "*** %s: %s%s\n", pgm, t1?t1:"", t2?t2:"");
  for (i=0; i < ITEMS(usage); i++)
  	fprintf(stderr, "%s\n", usage[i]);
  exit(1);
}

static char *typeout(text)
/*++++++++++++++++
.PURPOSE  Display a text on the terminal, once parsed
.RETURNS  ""
-----------------*/
	char *text;	/* IN: Text to DISPLAY */
{
  LOG(text);
  return("");
}

static int help()
/*++++++++++++++++
.PURPOSE  Display help, then exit
.RETURNS  To OS
-----------------*/
{
	static char *text[] = {
		"     -n: don't include the standard texroff.def file",
		"  files: as if the input text were \\input{file}",
		"      -  stands for the standard input", 
	};
	int i;
  
  for (i=0; i < ITEMS(usage); i++)
  	fprintf(stderr, "%s\n", usage[i]);
  for (i=0; i < ITEMS(text); i++)
  	fprintf(stderr, "%s\n", text[i]);

  exit(1);
}

static char *option(s)
/*++++++++++++++++
.PURPOSE  Find out an option in the table of Options
.RETURNS  The found option 
-----------------*/
	char *s;	/* IN: Text to fin in options */
{
	char *p; int i;
  
  for (i=0; i<ITEMS(options); i += 2) {
	if (strdiff(s, options[i]))	continue;
	return (options[i+1]);
  }
  return("");
}

/*==========================================================================
 *		Output Routine
 *==========================================================================*/
static int tex_output(str, len)
/*++++++++++++++++
.PURPOSE  This is the Output Routine
.RETURNS  Number of Bytes Processed. Multiple - are not output.
-----------------*/
	char *str;	/* IN: String to Output */
	int  len;	/* IN: Length of string */
{
	char *p, *p0, *p1;
	int i;

  if (len <= 0)	return(0);
  
	/* In VERBATIM mode, we've to take care of \  */

  if (tex->mode == _TeX_VERBATIM_) {
  	for (p = p0 = str, p1 = p + len; p < p1; p++) {
	    switch(*p) {
	      case '\\':
	        write(pd->fno, p0, (p-p0));
	        write(pd->fno, "\\\\", 2);
		break;
	      case '\n':
		write(pd->fno, p0, (p-p0));
		write(pd->fno, "\n.br\n", 5);
		break;
	      default: continue;
	    }
	    p0 = p+1;
	}
  } else {
  	/* Check whether the word can fit on the line */
  	if ((jpos > 0) && ((jpos + len) >= pd->linesize) && (*str != '.')) 
  	    write(pd->fno, "\n", 1), jpos = 0;

  	/* Take care of consecutive dashes, keep just one */
      	for (p = p0 = str, p1 = p + len; p < p1; p++) {
  	    if (p[0] != '-') continue;
  	    if (p[1] != '-') continue;
  	    write(pd->fno, p0, ++p - p0); jpos += p-p0;
  	    while ((p < p1) && (*p == '-')) p++;
  	    p0 = p; p--;
	}
  }
  write(pd->fno, p0, p-p0); jpos += p-p0;
  last_char = p[-1];
  if (isspace(last_char))	last_char = ' ';
  return (p - str);
}

static int tex_out(s) char *s ;{ return((*tex->output)(s, strlen(s))); }

/*==========================================================================*/
static int set_font(f)
/*++++++++++++++++
.PURPOSE  Set font to specfied value
.RETURNS  OK
-----------------*/
	char *f;	/* IN: Font to set */
{ 
	static char font[] = "\\fI";

  pd->font = font[2] = *f;
  (tex_out)(font);
  
  return(OK);
}
  	
static int font_em()	/* Emphasize	*/
{ return(set_font(pd->font == 'I' ? "R" : "I")); }

/*==========================================================================
 *		Internal Routines for tex_action Usage
 *==========================================================================*/
static int open_depth()
/*++++++++++++++++
.PURPOSE  Open a New Depth
.RETURNS  Current Depth
-----------------*/
{
  if (++pd >= pd1)	{
  	ERROR ("Too many non-closed {}");
  	return(-1);
  }
  *pd = pd[-1];		/* Copy the Current Depth */
  return (pd - depth);
}

static int close_depth()
/*++++++++++++++++
.PURPOSE  Close a Depth
.RETURNS  Current Depth
-----------------*/
{
  DEPTH *pdc;

    if (pd <= depth)	{
  	WARNING ("Too many right-}, ignored");
  	return(0);
    }
    pdc = pd; --pd;
  	/* Must reset the Fonts */
    if (pdc->font != pd->font)	 set_font(&pd->font);
  
	/* Must reset the Left Margin */
    if (pdc->margin != pd->margin) {
	if (pd->margin) write_macro(".RE\n.LP"); 
	write_macro("");
    }

  	/* Must close file ... */
    if (pdc->fno != pd->fno) {
	if (jpos) write(pdc->fno, TEXT("\n")); 
  	if (pdc->fno != 1) {
		close(pdc->fno);
		write(1, TEXT(" (done)\n"));
	}
    }
    return (pd - depth);
}

/*==========================================================================*/
static int open_env(name)
/*++++++++++++++++
.PURPOSE  Open a New Environment
.RETURNS  Current Depth
-----------------*/
  char *name;	/* IN: Environment Name */
{
  int ret;
    ret = open_depth();
    if (ret < 0) return(ret);
    pd->env = strdup(name);
    pd->counter = 0;
    return (ret);
}

static int close_env(name)
/*++++++++++++++++
.PURPOSE  Close a Environment
.RETURNS  Current Depth
-----------------*/
  char *name;	/* IN: Environment Name */
{
  DEPTH *pdc;
  char warning[90], *p;

    if (strdiff(pd->env, name)) {
	sprintf(warning, " Environment {%s} closed by {%s}", pd->env, name);
	WARNING(warning);
    }
    return (close_depth());
}

/*==========================================================================*/
static int write_macro(text)
/*++++++++++++++++
.PURPOSE  Write a Macro (line starting with a .)
.RETURNS  0
-----------------*/
	char *text;	/* IN: Macro to write	*/
{
	int len;

  if (jpos) write(pd->fno, TEXT("\n")), jpos = 0;
  len = strlen(text);
  write(pd->fno, text, strlen(text));
  jpos = text[len-1] == '\n' ? 0 : len;
  return(0);
}

/*==========================================================================
 *		Special Actions
 *==========================================================================*/
static int cR() 
/*++++++++++++++++
.PURPOSE  Move to next line
.RETURNS  0
-----------------*/
{
  if (jpos) write(pd->fno, TEXT("\n"));
  jpos = 0;
  return(0);
}

static int PushMargin() 
/*++++++++++++++++
.PURPOSE  Take care of indenting paragraphs
.RETURNS  The margin indentation
-----------------*/
{
  write_macro(".LP"); 
  if (pd->margin) write_macro(".RS");
  pd->margin += 1;
  write_macro("");
  return(pd->margin);
}

static int item() 
/*++++++++++++++++
.PURPOSE  Write an \item
.RETURNS  OK
-----------------*/
{
	static char the[20] = ".B   ";
	int i;

  write_macro(".TP 4"); write_macro("");
  pd->counter += 1;

  switch(*(pd->env)) {
	case 'e': sprintf(the+3, "%d.", pd->counter);		break;
	case 'a': sprintf(the+3, "%c>", pd->counter + 'a'-1);	break;
	case 'A': sprintf(the+3, "%c>", pd->counter + 'A'-1);	break;
	default:  the[3] = '='; the[4] = '>'; the[5] = 0;	break;
  }
  i = strlen(the); the[i++] = '\n'; 
  write(pd->fno, the, i);
  jpos = 0;
  return(0);
}

static int manstart(sec, name, title, version, keywords)
/*++++++++++++++++
.PURPOSE  Starting \begin{manpage}
.RETURNS  0 (OK) / -1
-----------------*/
  char *sec;	/* IN: Section Number 	*/
  char *name;	/* IN: Program Name	*/
  char *title;	/* IN: Short title	*/
  char *version;	/* IN: Version of Prog.	*/
  char *keywords;	/* IN: KeyWords of Pgm	*/
{
  char *filename, *p;
  int i;
	
	/* 'M' is the Symbol of this Environment */
    if(open_env("manpage") < 0)	return(-1);	/* ERROR */
  
  	/* Open the file name.sec */
    p = filename = malloc(2+strlen(name)+strlen(sec));
    p += strcopy(p, name); *(p++) = '.'; p += strcopy(p, sec);
    strdel(filename, ' ');	/* Remove Blanks */
    pd->fno = open(filename, 1);	
    if (pd->fno <= 0)	pd->fno = 1;	/* stdout */
    write(1, TEXT("....Manual page: ")); write(1, filename, strlen(filename));
    free(filename);
    jpos = 0; last_char = ' ';

  	/* Issue now the Header of Man Page */
    write_macro(".TH "); tex_out(name); tex_out(" "); tex_out(sec);
    write_macro(".SH NAME"); write_macro(""); tex_out(name); 
    tex_out(" \\- "); tex_out(title);
    if (*version) {
	jpos = 0;
  	tex_out("\n                    (Rev. "); tex_out(version); 
  	tex_out(")");
    }

    p = keywords;
    p += strspan(p, _SPACE_);
    if (*p) {
  	write_macro(".NXB"); 
  	while (*p) {
  	    i = strscan(p, _SPACE_);
  	    (*tex->output)(" \"", 2); 
	    (*tex->output)(p, i); 
	    (*tex->output)("\"", 1);
  	    p += i; p += strspan(p, _SPACE_);
	}
	write_macro("");
    }
    return(0);
}

/*==========================================================================*/
static int mansec(str)
/*++++++++++++++++
.PURPOSE  MAN Section. Add quotes if there are several words.
.RETURNS  OK
-----------------*/
	char *str;	/* IN: Action to Execute */
{
	static char sec[] = "\"MAXIMAL SIZE OF DESCRIPTION ALLOWED HERE\"";
	int  len;
	char *p, blank;

  len = strlen(str);
  len = MIN(len, sizeof(sec)-3);
  write_macro(".SH ");
  blank = str[strloc(str, ' ')];
  p = sec; if (blank) *(p++) = '"';
  p += oscopy(p, str, len); 
  if (blank) *(p++) = '"'; *p = 0; 
  strupper(sec);
  (*tex->output)(sec, p-sec); write_macro("");

  return(OK);
}

/*==========================================================================
 *		Action Routine
 *==========================================================================*/
static int tex_action(a, len)
/*++++++++++++++++
.PURPOSE  This is the Output Routine
.RETURNS  Number of Bytes Processed. Multiple - are not output.
-----------------*/
  char *a;	/* IN: Action to Execute */
  int  len;	/* IN: Length of Action  */
{
    switch(*a) {
    case ' ':	/* Stretchable blank */
  	if (last_char == ' ')	break;
  	if (jpos == 0)			break;
  	if ((jpos+1) >= pd->linesize)	break;
  	last_char = ' ';
  	(*tex->output)(&last_char, 1), jpos++;
  	break;
    case _TeX_BEGIN_:
	open_env(a+1);
	break;
    case '{':	if(open_depth() < 0) return(-1);
  	break;
    case _TeX_END_:
	close_env(a+1);
	break;
    case '}':	if(close_depth() < 0) return(-1); 
  	break;
    case '0':	/* Name of Macro */
	if (*(tex->ap) == _TeX_BEGIN_) 	  open_env(tex->ap + 1); 
	else if (*(tex->ap) == _TeX_END_) close_env(tex->ap + 1); 
	break;
    case '\0': case '\\':	/* May be found after Macro Substitution */
  	break;
    case '$':	/* Math Mode */
  	pd->tex_mode ^= MATHmode; 
  	break;
    case '&': 	/* Tab ...	*/
    case '^':	/* Superscript 	*/
    case '_':	/* Subscript   	*/
  	(*tex->output)(a, 1);
  	break;
    case '\r': 
  	write_macro(".br\n");
  	break;
    case '\n':	/* New Paragraph */
  	write_macro(".PP\n");
  	break;
    default:
	ERR_ED_STR2("Unknown action: ", a, len);
  	return(-1);		/* STOP */
    }
    return(len);
}
 
/*==========================================================================*/
main(argc, argv) int argc; char **argv;
{ 
  static char definitions[] = "\
\\def\\b{\05\01\\\05\02}\
\\def\\NOput#1{}\
\\defenv\\manpage#1#2#3#4#5{\\manstart{#1}{#2}{#3}{#4}{#5}}{\\0}\
";
  BUFFER *buffer;
  char line[512];
  char *p, *o;
  int  nf, len;

    tex = TeX_Create(tex_output,tex_action,0);
    TeX_SetAction(tex, "\\typeout#1", (ACTION)typeout);
    TeX_SetAction(tex, "\\SetfonT#1", (ACTION)set_font);
    TeX_SetAction(tex, "\\cR", (ACTION)cR);
    TeX_SetAction(tex, "\\PushMargin", (ACTION)PushMargin);
    TeX_SetAction(tex, "\\item", (ACTION)item);
    TeX_SetAction(tex, "\\em", (ACTION)font_em);
    TeX_SetAction(tex, "\\mansec#1", (ACTION)mansec);
    TeX_SetAction(tex, "\\manstart#1#2#3#4#5", (ACTION)manstart);
    TeX_Execute(tex, definitions, sizeof(definitions)-1);
    buffer = BUF_Open(char, 8192, 8192);
  	/* Insert the $0.def  file */
    BUF_AppendString(buffer, "\\input{");
#ifdef def
    BUF_AppendString(buffer, def);
#else
    BUF_AppendString(buffer, *argv);
#endif
    BUF_AppendString(buffer, ".def}"); 
    nf = 0;

    while (--argc > 0) {
    	p = *++argv;
    	switch(*p) {
	case '-':	/* Options  */
	    if (p[1] == 'v') {
		if (strncmp(*argv, "-ver", 4) == 0) {
		    printf("texroff (CDS) Version %s\n", VERSION);
		    exit(0);
		}
		verbop = 1;
		if (buffer->used) fprintf(stderr, 
		    "++++Begin with %s\n", buffer->buf);
		continue;
	    }
	    if (strdiff(p, "-help") == 0) {
		help();
		exit(0);
	    }
			/* The -n option is executed with the following trick:
			   \input   just replaced by \NOput ...		  */
	    if (strdiff(p, "-n") == 0) {	
		buffer->buf[1] = 'N'; buffer->buf[2] = 'O';
		continue;
	    }
	    if (p[1] == '\0') {
		while (fgets(line, sizeof(line), stdin))   /* Read stdin */
		    BUF_AppendString(buffer, line);
		nf++;
		continue;
	    }
	    if (o = option(p)) { 	/* A special option */
		BUF_AppendString(buffer, o);
		continue;
	    }
	    error("Bad option: ", p);
	default:	/* Just input as a file */
	    BUF_AppendString(buffer, "\\input{");
	    BUF_AppendString(buffer, p);
	    BUF_AppendString(buffer, "} % Parameter \n");
	    nf++;
	    continue;
    	}
    }
	/* If no file was given, use default */

    if (nf == 0) {
	BUF_AppendString(buffer, option(""));
	while (fgets(line, sizeof(line), stdin))	/* Read stdin */
	    BUF_AppendString(buffer, line);
    }

	/* Execute result */

    TeX_Execute(tex, buffer->buf, buffer->used);

    exit(0);
}

