/*===========================================================================
  Copyright (C) 1990-2009 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 Massachusetts Ave, Cambridge, 
  MA 02139, USA.
 
  Correspondence 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
===========================================================================*/

/*++++++++++++++
.IDENTIFICATION tydoc.c
.LANGUAGE       C
.AUTHOR         ESO-IPG, Garching
.ENVIRONMENT    TermWindows
.KEYWORDS       TeX
.VERSION  1.0   28-Nov-1990
.COMMENTS       This module includes the various functions to 
		deal with Documents, i.e. a set of Windows.


 090901		last modif
---------------*/

#define	DEBUG		0	/* For debugging only		*/

#include <midas_def.h>	/* TermWindow Definitions	*/	

#define	PM_LEVEL 	LEVEL_TX

#include <twset.h>	/* TermWindow Definitions	*/	
#include <str.h>	/* String Utilities		*/

	/* Returned DOC numbers are in range 
		[DOC_OFFSET, DOC_OFFSET + DOC+MAX [
	*/

extern int tv_bell();

extern int tw_fill(), tw_attr(), tw_uattr(), tw_r(), tw_goto(), tw_rule();
extern int tw_st(), tw_uflag(), tw_chars(), tw_copw(), tw_flags();
extern int tw_uw(), tw_write(), tw_il(), tw_clear(), tw_dc();
extern int tw_nl(), tw_upw(), tw_uc(), tw_mvc(), tw_copy();
extern int tw_mrule(), tw_cus(), tw_gc2(), tw_close(), tw_cur();
extern int tw_home(), tw_tra(), tw_wa();

extern int pm_enter(), pm_iexit(), pm_tr2(), pm_ed_i();

extern int eh_ed_i(), eh_ed_as(), eh_put1(), eh_put();

extern int tex_exec(), tex_unit(), tex_getparm(), tex_mexec();
extern int tex_input(), tex_load(), tex_list();

extern int tx_jr(), tx_jc(), tx_justify();
extern int tx_display(), tx_option();
extern int tx_fdisplay(), tx_file(), tx_mdisplay();



#define DOC_OFFSET	900	
#define DOC_MAX		20	

typedef struct {
	char	*name;	/* Not used right now 	*/
	WINDOW 	*win;	/* Associated Window	*/
	int 	flags;
#define DOC_READY	1
	int	alp,	/* Allocated Pages	*/
		edp;	/* Edited Pages		*/
	int	nap;	/* Number of Bytes/page	*/
	ACHAR	**pages;/* Array of Pages	*/
	int	pageno;	/* Current Page Number	*/
	short	lines,	/* How many lines/page	*/
		lineno;	/* Which line is on Window Top	*/
   }	DOC;

static DOC  *document[DOC_MAX] = { (DOC *)0 };
static int  TheDoc = 0;			/* Current Document */

/*========================================================================
 *		Internal Housekeeping Routines
 *========================================================================*/

static DOC *Object(id)
/*++++++++++++++++
.PURPOSE  Convert number into Object Pointer
.RETURNS  Address of Corresponding object / NULL
-----------------*/
	int	id;	/* IN: Object number returned by ty_open */
{
	DOC	*pd;

  if ( (id < DOC_OFFSET) || (id >= DOC_OFFSET + DOC_MAX)) {
	ERR_ED_I("Bad Document #", id);
	return ((DOC *)0);
  }
  pd = document[id - DOC_OFFSET];
  if (!pd)
	ERR_ED_I("Document does not exist, #", id);
  return(pd);
}
 
static int new(name, w)
/*++++++++++++++++
.PURPOSE  Create a new DOC folder
.RETURNS  Number of Created Object / 0 if Fails
.REMARKS  
-----------------*/
	char	*name;	/* IN: Associated name	*/
	WINDOW	*w;	/* IN: Associated window*/
{
    	int	i;
	DOC	*pd;

	/* Find an emtpy slot */
  for (i=0; document[i] && (i < DOC_MAX); i++)	;
  if (i >= DOC_MAX) {
	ERR_ED_I("Too many opened documents: ", i);
	return(0);
  }
	/* Allocate & Initialize */
  document[i] = pd = MEM_GET(DOC, 1);
  pd->name  = strsave(name);
  pd->win   = w;
  pd->flags = 0;
  pd->alp   = 0;
  pd->edp   = 0;
  pd->nap   = w->dim[0] * w->dim[1];
  pd->pages = (ACHAR **)0;
  pd->pageno= -1;
  pd->lines = w->dim[0];
  pd->lineno= 0;

  return(i + DOC_OFFSET);
}

static int tykill(id)
/*++++++++++++++++
.PURPOSE  Kill (close) a document
.RETURNS  OK / NOK
-----------------*/
	int	id;	/* IN: Object to kill */
{
	DOC *pd;
	int i;
 
  if_not (pd = Object(id))	return(NOK);
	
	/* Remove allocated memory */
  for (i = pd->edp; --i >= 0; )	MEM_FREE(pd->pages[i]);
  MEM_FREE(pd->name);
  MEM_FREE(pd->pages);
  MEM_FREE(pd);
  document[id-DOC_OFFSET] = (DOC *)0;
  TheDoc = 0;

  return(OK);
}

static int save_page(pd)
/*++++++++++++++++
.PURPOSE  Insert the edited window as a new page
.RETURNS  OK / NOK
-----------------*/
	DOC *pd;	/* MOD: Document Concerned */
{
	int cursor;
	ACHAR *page;

  cursor = (pd->win)->pos;	/* Save */
  CursorHome(pd->win);
  if (pd->edp >= pd->alp) {	/* Must expand array of Pages */
	pd->alp += 16;
	pd->pages = MEM_EXP(ACHAR *,(char *) pd->pages, pd->alp);
  }
  page = MEM_GET(ACHAR, pd->nap);	
  SaveWindowText(pd->win, page, pd->nap);
  pd->pages[(pd->edp)++] = page;

  (pd->win)->pos = cursor;	/* Restore */
  return(OK);
}

static int init(win, str)
/*++++++++++++++++
.PURPOSE  Initialisation of a display function
.RETURNS  Document number (0 if fails). TheDoc is also initialized.
.METHOD   Check for non-clased documents. Initialize a new one if necessary.
-----------------*/
	WINDOW	*win;	/* IN: Window to echo the processed text */
	char	*str;	/* IN: NULL for Continuation */
{
	DOC *pd;
	int id;

  if (str) {			/* Create a New Document */
	if_not(id = new("<<DOC>>", win))
		return(0);
  	if (TheDoc) {	/* Check Current Document properly closed */
		pd = Object(TheDoc);
		if_not (pd->flags & DOC_READY) {
			WARNING("Non-closed document exists!");
			pd->flags |= DOC_READY;
		}
	}
	TheDoc = id;
  }
				/* Check there is something to edit */
  pd = Object(TheDoc);
  if (pd->flags & DOC_READY)  {	/* Nothing more...	*/
	WARNING("Document already complete...");
	return(0);
  }
  return(TheDoc);
}

static int next_page(pd)
/*++++++++++++++++
.PURPOSE  Display on Window the next page
.RETURNS  OK / NOK (Document already at bottom)
-----------------*/
	DOC *pd;	/* MOD: Document Concerned */
{
  if (pd->pageno < pd->edp) {
  	pd->lineno = 0;		/* Page adjusted on Window */
	pd->pageno += 1;	/* New page to show */
	CursorHome(pd->win);
	WriteAchars(pd->win, pd->pages[pd->pageno], pd->nap);
	return(OK);
  }
  else	return(NOK);
}

/*========================================================================
 *			Public Routines
 *========================================================================*/

int ty_display(win, str, len, option)
/*+++
.PURPOSE Display a text on a Document
.RETURNS --- Document number (positive number)
	--- NOK Error occured.
.REMARKS 
---*/
	WINDOW	*win;	/* IN: Window to echo the processed text */
	char	*str;	/* IN: text to display, or NULL (continuation )*/
	int	len;	/* IN: Length of text	*/
	int	option;	/* IN: Option 1 to edit the complete document	*/
{
	int	echo_state, status;
	DOC	*pd;

  ENTER("+ty_display");

  if_not(init(win, str))	EXIT(NOK);

  pd = Object(TheDoc);

		/* Deactivate Window to display the Current Page */
  echo_state = DeactiveWindow(pd->win);
  status = tx_display(pd->win, str, len, 1);	/* Display with clear_page */
  save_page(pd);

  if (option)	while (status == NOK) {		/* Edit ALL pages */
	status = tx_display(pd->win, (char *)0, 0, 1);
	save_page(pd);
  }
  if (status != NOK)	pd->flags |= DOC_READY;

				/* Edit Next Page if any */
  next_page(pd);
  if (echo_state)	ActiveWindow(pd->win);

  EXIT(TheDoc);
}

/*========================================================================*/
int ty_mdisplay(win, str, nstr, option)
/*+++
.PURPOSE Display a text on the window made of several pieces of text.
        The pieces are provided by the array of strings str made of
        (2*nstr) pointers, specifying nstr times (start, end).
        (a pointer to the end of a string is the byte just following
        the last byte of the string, i.e. the address of the null character
        for a standard string)
.RETURNS --- Document number (positive number)
	--- NOK Error occured.
.REMARKS
---*/
	WINDOW	*win;	/* IN: Window to echo the processed text */
        char    **str;  /* IN: Array of strings to display, \
                                as n* (start, end) */
        int     nstr; 	/* IN: Number of strings      */
        int     option; /* IN: Option 1 to edit the complete document   */
{
	int	echo_state, status;
	DOC	*pd;

  ENTER("+ty_mdisplay");

  if_not(init(win, (char *)str))	EXIT(NOK);

  pd = Object(TheDoc);
  
		/* Deactivate Window to display the Current Page */
  echo_state = DeactiveWindow(pd->win);
  status = tx_mdisplay(pd->win, str, nstr, 1);	/* Display with clear_page */
  save_page(pd);

  if (option && (status == NOK))	/* Edit the rest of the Doc	*/
	ty_display(win, (char *)0, 0, 1);

  if (status != NOK)	pd->flags |= DOC_READY;

		/* Edit Next Page if any */
  next_page(pd);
  if (echo_state)	ActiveWindow(pd->win);

  EXIT(TheDoc);
}

/*========================================================================*/
int ty_fdisplay(win, fid, len, option)
/*+++
.PURPOSE Display a file extract on the window.
.RETURNS --- Document number (positive number)
	--- NOK Error occured.
.REMARKS 
---*/
	WINDOW	*win;	/* IN: Window to echo the processed text */
	int	fid;	/* IN: File number (opened by fi_open)	*/
	int	len;	/* IN: Maximal number of bytes to read	*/
        int     option; /* IN: Option 1 to edit the complete document   */
{
	int	echo_state, status;
	DOC	*pd;

  ENTER("+ty_fdisplay");

  init(win, "");	/* Non-null char pointer */
  pd = Object(TheDoc);
  
		/* Deactivate Window to display the Current Page */
  echo_state = DeactiveWindow(pd->win);
  status = tx_fdisplay(pd->win, fid, len, 1);	/* Display with clear_page */
  save_page(pd);

  if (option && (status == NOK))	/* Edit the rest of the Doc	*/
	ty_display(win, (char *)0, 0, 1);

  if (status != NOK)	pd->flags |= DOC_READY;

		/* Edit Next Page if any */
  next_page(pd);
  if (echo_state)	ActiveWindow(pd->win);

  EXIT(TheDoc);
}

/*========================================================================*/
int ty_file(win, fname, option)
/*+++
.PURPOSE Display a complete file extract on the window.
.RETURNS --- Document number (positive number)
	--- NOK Error occured.
.REMARKS 
---*/
	WINDOW	*win;	/* IN: Window to echo the processed text */
	char	*fname;	/* IN: File name 	*/
        int     option; /* IN: Option 1 to edit the complete document   */
{
	int	echo_state, status;
	DOC	*pd;

  ENTER("ty_file");

  init(win, fname);	/* Initialize the Document */
  pd = Object(TheDoc);
  
		/* Deactivate Window to display the Current Page */
  echo_state = DeactiveWindow(pd->win);
  status = tx_file(pd->win, fname, 1);	/* Display with clear_page */
  save_page(pd);

  if (option && (status == NOK))	/* Edit the rest of the Doc	*/
	ty_display(win, (char *)0, 0, 1);

  if (status != NOK)	pd->flags |= DOC_READY;

		/* Edit Next Page if any */
  next_page(pd);
  if (echo_state)	ActiveWindow(pd->win);

  EXIT(TheDoc);
}

/*========================================================================*/
int ty_close(id)
/*+++
.PURPOSE Close a Document
.RETURNS OK / NOK
.REMARKS 
---*/
	int	id;	/* IN: Document to Close */
{
	int status;

  ENTER("ty_close");
  if (id)	tykill(id);
  else		status = NOK;
  EXIT(status);
}

/*========================================================================*/
int ty_pseek(id, times, mode)
/*+++
.PURPOSE Move in the document by PAGE units
.RETURNS Current position / -1 for Errors
.REMARKS Just SET the position; use ty_show to display the current contents.
	The position specified is adjusted if it's outside the document limits.
---*/
	int	id;	/* IN: Document concerned */
	int	times;	/* IN: Number of Pages to move	*/
	int	mode;	/* IN: Mode as 0(Absolute) / 1 (Relative) / 2 (EOF) */
{
	DOC *pd;
	int echo_state, pageno;;

  ENTER("+ty_pseek");
  if_not (pd = Object(id))	EXIT(-1);
  echo_state = tw_st(pd->win, Echo, 0);		/* Don't write on Window  */
			
			/* Compute ABSOLUTE page number according to mode */
  switch(mode) {
    case 1:
	pageno = times + pd->pageno;	break;
    case 2: 
	if_not (pd->flags & DOC_READY)	ty_display(pd->win, (char *)0, 0, 1);
	pageno = times + pd->edp;	break;
    default:
	pageno = times;			break;
  }
			
  if (pageno < 0)	pageno = 0;
	
			/* If new edition has to be done, do it. */
  while (pageno >= pd->edp) {
	if (pd->flags & DOC_READY)	break;
	ty_display(pd->win, (char *)0, 0, 0);	/* Display Next Page */
  }
  if (pageno >= pd->edp) pageno = pd->edp - 1;
  pd->lineno = 0, pd->pageno = pageno;

  tw_st(pd->win, Echo, echo_state);
  EXIT(pageno);
}

/*========================================================================*/
int ty_lseek(id, times, mode)
/*+++
.PURPOSE Move in the document by LINE units
.RETURNS Current position / -1 for Errors
.REMARKS Just SET the position; use ty_show to display the current contents.
---*/
	int	id;	/* IN: Document concerned */
	int	times;	/* IN: Number of Lines to Move	*/
	int	mode;	/* IN: Mode as 0(Absolute) / 1 (Relative) / 2 (EOF) */
{
	DOC *pd;
	int echo_state, i, lineno, pageno;;

  ENTER("+ty_lseek");
  if_not (pd = Object(id))	EXIT(-1);
  echo_state = tw_st(pd->win, Echo, 0);		/* Don't write on Window  */
  lineno = pd->lineno;
  pageno = pd->pageno;

			/* Transform to Absolute seek */
  switch(mode) {
    case 2:		/* From EOF */
	ty_pseek(id, 0, 2);
	lineno = pd->edp * pd->lines + times; 		break;
    case 1:		/* Relative */
	if (times == 0)		goto DONE;		/* Just inquire */
  	lineno = pageno * pd->lines + lineno + times; 	break;
    default:
	lineno = times; 				break;
  }
  if (lineno < 0)	lineno = 0;
  i = lineno/pd->lines;

			/* Move First by Page	*/
  pageno = ty_pseek(id, i, 0);	/* Load Page */
  if (pageno == i) {		/* Another page required ? */
	lineno %= pd->lines;
	if (lineno) {
		++i;
		if (ty_pseek(id, i, 0) != i)	lineno = 0;
	}
  }
  else	lineno = 0;

  DONE:
  pd->pageno = pageno;	pd->lineno = lineno;
  tw_st(pd->win, Echo, echo_state);
  i = pageno * pd->lines + lineno;

  EXIT(i);
}

/*========================================================================*/
int ty_end(id)
/*+++
.PURPOSE Check if Document is at its end
.RETURNS 0 (not at End) / 1 (at End)
---*/
	int	id;	/* IN: Document concerned */
{
	DOC *pd;
	int at_end;

  ENTER("+ty_end");
  at_end = 0;
  pd = Object(id);
  if(pd)  {
	if_not(pd->flags & DOC_READY)	;
	else  at_end = (pd->pageno >= (pd->edp - 1));
  }
  EXIT(at_end);
}

/*========================================================================*/
int ty_show(id)
/*+++
.PURPOSE Display on Window the Document at current Position
.RETURNS OK / NOK
---*/
	int	id;	/* IN: Document concerned */
{
	DOC *pd;
	int cursor, echo_state, len1, len;

  ENTER("ty_show");
  if_not (pd = Object(id))      EXIT(NOK);

  echo_state = tw_st(pd->win, Echo, 0);         /* Don't write on Window  */
  cursor = (pd->win)->pos;	/* Save */

  CursorHome(pd->win);
  len = pd->lineno * (pd->win)->dim[1];
  len1= pd->nap - len;
  WriteAchars(pd->win, pd->pages[pd->pageno] + len, len1);
  if (len) {
	(pd->win)->pos = len1;
	WriteAchars(pd->win, pd->pages[pd->pageno + 1], len);
  }
  (pd->win)->pos = cursor;	/* Restore */

  TouchWindow(pd->win);
  EXIT(OK);
}

/*========================================================================*/
int ty_more(id, key)
/*+++
.PURPOSE Execute the Unix "more-like" function.
.RETURNS OK / NOK (bad key) / EOF (Ask to Exit). Actions may be:
\begin{TeX}
	\begin{itemize}
	\item {\em Control--}{\tt B} : upwards full-screen
	\item {\em Control--}{\tt U} : upwards half-screen
	\item {\em Control--}{\tt D} : downwards half-screen
	\item {\em Control--}{\tt F} or {\em Space bar}: downwards full-screen
	\item {\em Return} : downwards one line
	\item {\tt Q}: Quit
	\item {\tt R}: Refresh
	\end{itemize}
\end{TeX}
.REMARKS Document shown only if Window is active...
---*/
	int	id;	/* IN: Document concerned */
	int	key;	/* IN: Key specifying the action */
{
	DOC *pd;
	int	before, after, status, hpage;

  ENTER("ty_more");

  if_not (pd = Object(id))	EXIT(-1);
  hpage  = pd->lines / 2;
  status = OK;
  
  before = ty_lseek(id, 0, 1);		/* Current Position	*/
  switch(key) { 
    case 'q': case 'Q':	case EOF:
    	status = EOF;				break;
    case '$': case 'G': 
    	after = ty_pseek(id, 0, 2);		break;	/* Bottom */
    case 'B': case '%': case 'g': case CNTRL('T'):
    	after = ty_pseek(id, 0, 0);		break;
    case 'b': case CNTRL('B'):
	after = ty_lseek(id, -pd->lines, 1);	break;
    case 'u': case CNTRL('U'):
	after = ty_lseek(id, -hpage, 1);	break;
    case 'y': case CNTRL('Y'): case '-':
	after = ty_lseek(id, -1, 1);		break;
    case '\r': case '\n': case 'e': case CNTRL('E'): case '+': case 0: case '.':
	after = ty_lseek(id, 1, 1);		break;
    case 'd': case CNTRL('D'): 
	after = ty_lseek(id, hpage, 1);		break;
    case ' ': case CNTRL('F'): case 'f': case 'F':	/* Scroll Down */
	after = ty_lseek(id, pd->lines, 1);	break;
    case 'R': case 'r': case CNTRL('L'): case CNTRL('R'):
	RefreshScreen();			EXIT(OK);
    case 'p': case 'P': 				/* Current Page */
	after = ty_pseek(id, 0, 1);		break;
    default:
	status = NOK;	Bell();			break;
  }
  if (status == OK) {
	if (before == after)	status = NOK;
	else 			ty_show(id);
  }
  
  EXIT(status);
}
