/* $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);
}