/* $Id: latex-embed.cc,v 1.12 1997/04/17 20:03:19 dps Exp $ */
/* Embed handling for *TeX output */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include <stdio.h>
#ifdef HAVE_STRING_H
#include <string.h>
#endif /* HAVE_STRING_H */
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif /* HAVE_STRINGS_H */
#ifdef HAVE_CTYPE_H
#include <ctype.h>
#endif /* HAVE_CTYPE_H */
#include "interface.h"
#include "tblock.h"
#include "fmt-latex.h"


/* For detial of this see The TeXbook p. 141) */
typedef enum {Disp=0, DispP, Text, TextP,
	      Script, ScriptP,
	      SScript, SScriptP } style;

enum TypeIndex { Op_Sup=0, Op_Sub, Op_FTop, Op_FBot, Op_Cramp };
/* Style navigation map */
static const style style_map[][5]=
{
    { Script, ScriptP, Text, TextP, DispP },		  // style D
    { ScriptP, ScriptP, TextP, TextP, DispP },		  // style D'
    { Script, ScriptP, Script, ScriptP, TextP },	  // Style T
    { ScriptP, ScriptP, ScriptP, ScriptP, TextP },	  // Style T'
    { SScript, SScriptP, SScript, SScriptP, ScriptP },	  // Style S
    { SScriptP, SScriptP, SScriptP, SScriptP, ScriptP },  // Style S'
    { SScript, SScriptP, SScript, SScriptP, SScriptP },	  // Style SS
    { SScriptP, SScriptP, SScriptP, SScriptP, SScriptP }  // Style SS'
};

static tblock *cvt_eqn(const char *, const char *, const int, const style);


/* Skip a term */
const char *skip_to_next(const char *ptr)
{
    int blevel;
    int ign_nxt;

    ign_nxt=0;
    blevel=0;
    while(1)
    {
	switch(*ptr)
	{
	case '\\':
	    ign_nxt=1;
	    break;

	case '(':
	    if (!ign_nxt)
		blevel++;
	    break;

	case ')':
	    if (!ign_nxt)
		blevel--;
	    if (blevel<0)
		return ptr;
	    break;

	case ',':
	    if (!ign_nxt && blevel==0)
		return ptr;
	    break;

	case '\0':
	    return ptr;

	default:
	    break;
	}
	ptr++;
    }
}


/* Equation sub-bits */

/* \b bracket */
static const char *eqn_backet(tblock *res, const char *inp,
			      const int mline, const style sty)
{
    const char *end;
    tblock *r1;

    end=skip_to_next(inp);
    if (*inp!=')')
	return NULL;
    {
	r1=cvt_eqn(inp, end, mline, sty);
	res->add("\\left(");
	res->add(*r1);
	res->add("\\right)");
	delete(r1);
    }
    return end;
}

/* \l group */
static const char *eqn_list(tblock *res, const char *inp,
			    const int mline, const style sty)
{
    tblock *r1;
    const char *end;

    end=skip_to_next(inp);
    if (*inp!=')')
	return NULL;
    {
	r1=cvt_eqn(inp, end, mline, sty);
	res->add("{");
	res->add(*r1);
	res->add("}");
	delete(r1);
    }
    return end;
}

/* \o overlay */
static const char *eqn_overlay(tblock *res, const char *inp,
			       const int mline, const style sty)
{
    const char *end;
    tblock *r1;

    end=skip_to_next(inp);
    if (*inp!=')')
	return NULL;
    {
	r1=cvt_eqn(inp, end, mline, sty);
	res->add("\\smash{");
	res->add(*r1);
	res->add("}");
	delete(r1);
    }
    return end;
}

/* \r root */
static const char *eqn_root(tblock *res, const char *inp,
			const int mline, const style sty)

{
    tblock *r1, *r2;
    const char *mid, *end;

    mid=skip_to_next(inp);
    switch (*mid)
    {
    case ',':
	end=skip_to_next(mid+1);
	if (*end!=')')
	    return NULL;

	r1=cvt_eqn(inp, mid, mline, style_map[sty][Op_Cramp]);
	r2=cvt_eqn(mid+1, end, mline, style_map[sty][Op_Cramp]);
	res->add("\\sqrt["); res->add(*r1);
	res->add("]{"); // TeX syntax
	res->add(*r2);
	res->add('}');
	delete(r1);
	delete(r2);
	break;

    case ')':
	r1=cvt_eqn(inp, mid, mline, style_map[sty][Op_Cramp]);
	res->add("\\sqrt{");
	res->add(*r1);
	res->add(*r1);
	res->add('}');
	delete(r1);
	break;

    default:
	break;
    }
    return NULL;
}

/* \f fraction */
static const char *eqn_fract(tblock *res, const char *inp,
			const int mline, const style sty)
{
    tblock *r1, *r2;
    const char *mid, *end;

    mid=skip_to_next(inp);
    if (*mid!=',')
	return NULL;

    end=skip_to_next(mid+1);
    if (*end!=')')
	return NULL;

    r1=cvt_eqn(inp, mid, mline, style_map[sty][Op_FTop]);
    r2=cvt_eqn(mid+1, end, mline, style_map[sty][Op_FBot]);
    res->add('{');
    res->add(*r1);
    res->add(" \\over "); // TeX syntax
    res->add(*r2);
    res->add('}');
    delete(r1);
    delete(r2);

    return end;
}


/* Anything I do not understand */
const char *eqn_default(tblock *res, const char *inp, const char *end,
			const int mline, const style sty)
{
    tblock *r1;

    res->add("{\\text{\tt \\backslash ");
    res->add(inp, end-inp+1);
    inp=end+1;
    do
    {
	end=skip_to_next(inp);
	r1=cvt_eqn(inp, end, mline, sty);
	res->add(*r1);
	if (*end!='\0')
	    res->add(*end);
	else
	    res->add("{\\it EOF}");
	delete(r1);
	inp=end+1;
    } while (*end!='\0' && *end!=')');
    res->add("}} ");

    return end;
}

/* The actual work is in this recursive procedure */
static tblock *cvt_eqn(const char *inp, const char *stop,
		       const int mline, const style sty)
{
    tblock *res;
    const char *mid, *end;
    int l, i;

    typedef const char *(*eqn_proc)(tblock *, const char *, const int,
				    const style);
    static const struct { const char *name; eqn_proc func; } eqp[]=
    {
	{ "b", eqn_backet },
	{ "l", eqn_list },
	{ "o", eqn_overlay },
	{ "r", eqn_root },
	{ "f", eqn_fract },
	{ NULL, NULL },
    };
    
    res=new(tblock);
    while (inp<stop)
    {
	if (isspace(*inp) && *inp!='\n')
	{
	    inp++;
	    continue;
	}

	switch (*inp)
	{
	case '\0':
	    return res;
	
	case '\n':
	    if (mline)
		res->add(" \\\\\n");
	    break;

	case '<':
	case '>':
	case '=':
	    if (mline)
	    {
		res->add(" & ");
		res->add(*inp);
		res->add(" & ");
	    }
	    else
		res->add(*inp);
	    break;

	case '\\':
	    inp++;
	    switch(*inp)
	    {
	    case '\0':
		cerr<<"cvt_eqn saw \\\\0\n";
		return res;	// Safety.

	    case '{':
		res->add(" \\{"); // More guesswork
		break;

	    case '}':
		res->add(" \\}"); // More guesswork
		break;

	    default:
		mid=inp;
		for (mid=inp; !isspace(*mid) && *mid!='('; mid++) ;
	        for (end=mid; *end!='('; end++)
		{
		    if (*end=='\0')
			break;
		}
		for (i=0; (unsigned) i<N(eqp); i++)
		{
		    if (eqp[i].name==NULL)
		    {
			inp=eqn_default(res, inp, end, mline, sty);
			break;
		    }
		    l=strlen(eqp[i].name);
		    if (l!=mid-inp)
			continue;
		    if (strncasecmp(eqp[i].name, inp, l)==0)
		    {
			if ((inp=(*(eqp[i].func))(res, end+1,
						  mline, sty))!=NULL)
			    break;
		    }
		}
		break;
	    }
	    break;
	case '+':
	case '-':
	    res->add(*inp);
	    break;
	case '*':
	    res->add("\\times ");
	    break;

	case ' ':
	    res->add("\\ ");
	    break;

	case '_':
	    res->add("\\hbox{\\_}");
	    break;

	default:
	    int flg=0;
	    const char *scn;

	    /*
	     * This section is meant to catch 72 dpi and render it as
	     * \hbox{72 dpi} but not catch 12 * 18 (which should become
	     * 12\times 18).
	     */
	    if (isdigit(*inp) || *inp=='-' || *inp=='+')
	    {

		/* Scan forward to see what comes text */
		scn=inp;		
		if (*scn=='-' || *scn=='+')
		    scn++;	// Skip sign	
		while (scn<stop && isdigit(*scn))
		    scn++;	// Skip number
		if (*scn=='.')
		{
		    scn++;
		    while (scn<stop && isdigit(*scn))
			scn++;	//  Hanlde decimals number
		}

		/* Now start looking at what comes next */
		while (scn<stop)
		{
		    if (isspace(*scn))
		    {
			scn++;
			continue;
		    }
		    if (isupper(*scn) || islower(*scn))
			flg=1;
		    else
			flg=0;
		    break;
		}
	    }

	    /*
	     * This section is meant to catch strings and render them nicely
	     * in a mbox.
	     */
	    if (islower(*inp) || isupper(*inp) || flg)
	    {
		res->add("\\text{"); 		
		if (flg)	// If flag set then add everything up to scn
		{
		    while (inp<scn)
		    {
			res->add(*inp);
			inp++;
		    }
		}

		flg=0;		// Re-use flg
		while (inp<stop && (islower(*inp) || isupper(*inp)
				    || isspace(*inp)
				    || *inp=='_'
				    || *inp=='^'))
		{
		    if (isspace(*inp))
		    {
			flg=1;
			inp++;
			continue;	  // If space, just set the flag
		    }
		    if (flg)
			res->add(' ');	  // If skiped a space, add one
		    flg=0;		  // Clear flag
		    if (*inp=='_' || *inp=='^')
			res->add('\\');
		    res->add(*inp);
		    inp++;
		}
		res->add("} ");
		inp--;
		break;
	    }
	    res->add(*inp);
	    break;
	}
	inp++;
    }
    return res;
}

/* Equations --- need more examples here */
static void equation(const char *txt, const docfmt *fmt, FILE *out,
		     void *d)
{

    static const cmap comment_map[]={ { '\n', "\n% (contd) % " } };
    struct latex_data *dp;
    tblock *cvt, eqn, *op;
    const char *s;
    int mline;
    
    dp=(struct latex_data *) d;
    cvt=map_string(txt, comment_map);
    fprintf(out, "%%\n%% EMBED %s\n", (const char *) (*cvt));
    delete(cvt);

    for (mline=0, s=txt; *s!='\0'; s++)
    {
	if (*s=='\n')
	{
	    mline=1;
	    break;
	}
    }
    if (!mline)
	eqn.add((dp->par_flg) ? "$" : "$$");
    else
	eqn.add("\\begin{eqnarray*}\n");
    cvt=cvt_eqn(txt+3, txt+strlen(txt), mline, (dp->par_flg) ? Disp : Text);
    eqn.add(*cvt);
    delete(cvt);

    if (!mline)
	eqn.add((dp->par_flg) ? "$" : "$$%");
    else
	eqn.add("\n\\end{eqnarray*}\n");

    op=word_wrap(eqn, "\n", "\n", fmt->maxline);
    fputs((const char *) (*op), out);
    fputc('\n', out);
    delete(op);
}
    


/* Table of contents entries, used as a cue for stuff like sections */
/* This code jus stashes it away for the paragraph code */
static void add_contents(const char *txt, const docfmt *fmt, FILE *out,
			 void *d)
{
    const char *open, *close;
    tblock entry;
    struct latex_data *dp;

    fmt=fmt;
    out=out;
    dp=(struct latex_data *) d;
    for (open=txt; *open!='"'; open++)
    {
	if (*open=='\0')
	{
	    cerr<<"Found tc entry but no openning quote\n";
	    return;
	}
    }
	
    for (close=open+1; *close!='"'; close++)
    {
	if (*close=='\0')
	{
	    cerr<<"Found tc entry but no closing quote\n";
	    return;
	}
    }

    if (close-open==1)
    {
	cerr<<"Ignoring empty table of contents entry\n";
	return;
    }

    while (++open<close)
	entry.add(*open);
    if (dp->last_tc!=NULL)
	free((void *) dp->last_tc);
    dp->last_tc=strdup(entry);

}

static struct embed emb[]=
{
    { "tc ", 3, add_contents },	// Table of contents line
    { "eq ", 3, equation },	// Equations
};

void ltx_embed(const tok_seq::tok *t, const docfmt *fmt, FILE *out,
	       void *d)
{ 
    int i;

    for (i=0; (unsigned) i<N(emb); i++)
    {
	if (strncmp(t->data.d, emb[i].key, emb[i].key_len)==0)
	{
	    (emb[i].handle)(t->data.d, fmt, out, d);
	    return;
	}
    }
    fprintf(out, "%%\n%% %s\n", t->data.d);
}
