#ifndef lint
static char sccsid[] = "@(#)special.c	3.1\t11/16/89";
#endif lint

#include <stdio.h>
#include "struct.h"
#include "defs.h"
#include "commands.h"

extern FILE *outfp;
extern void SetPosn(), Warning(), CopyFile(), GetBytes();
extern char *strcpy();

#define SKIPPING	0
#define CHARFINDING	1
#define TYPESETTING	2
extern int state;
extern int G_pagecount;
extern int h, v, hh, vv, hhh, vvv;
extern int waiting;
extern bool G_FullPage;
extern char G_FullName[];

void  DoSpecial();
void  IncludeSpecial();
void  TextSpecial();
char *GetKeyStr();
bool  GetKeyVal();

typedef enum {
    None, String, Integer, Number, Dimension
}     ValTyp;

typedef struct {
    char *Key;		/* the keyword string */
    char *Val;		/* the value string */
    ValTyp vt;		/* the value type */
    union {		/* the decoded value */
	int   i;
	float n;
    }     v;
}     KeyWord;

typedef struct {
    char *Entry;
    ValTyp Type;
}     KeyDesc;

#define NTAGS  5
#define PSFILE 3
#define PSTEXT 4

KeyDesc KeyTab[] = {
    {"global", String},
    {"page", String},
    {"box", String},
    {"psfile", String},
    {"pstext", String},		/* this is the last of the recognized tags */
    {"hsize", Dimension},	/* all others are modifiers of tags */
    {"vsize", Dimension},
    {"hoffset", Dimension},
    {"voffset", Dimension},
    {"hscale", Number},
    {"vscale", Number},
    {"rotate", Number}
};

#define NKEYS (sizeof(KeyTab)/sizeof(KeyTab[0]))

/*********************************************************************/
/*****************************  DoSpecial  ***************************/
/*********************************************************************/
/* interpret a \special command, made up of keyword=value pairs */
void
DoSpecial(dvifp, command)
FILE *dvifp;
int   command;
{
    KeyWord k;
    char  special[STRSIZE];
    char *str;
    int   i;

    i = NoSignExtend(dvifp, command - XXX1 + 1);
    GetBytes(dvifp, special, i);
    if (state == SKIPPING)
	return;

    special[i] = '\0';
    str = GetKeyStr(special, &k);
/* \specials must have tags which we can recognize */
      if (k.vt == None || !GetKeyVal(&k, KeyTab, NKEYS, &i)) {
	Warning("Invalid keyword or value in \\special - \"%s\" ignored", k.Key);
	return;
    }
    switch (i) {
    case 0:		/* global */
	if (G_pagecount != 1) {
	    Warning("Global \\specials must appear on the first page.\n");
	    return;
	}
	if (state != CHARFINDING)
	    return;
	Warning("Global specials are not yet implemented.\n");
	break;
    case 1:		/* page */
	if (state != CHARFINDING)
	    return;
	Warning("Page specials are not yet implemented.\n");
	break;
    case 2:		/* box */
	Warning("Box specials are not yet implemented.\n");
	break;
    case PSFILE:
	if (state != TYPESETTING)
	    return;
	IncludeSpecial(&k, str);
	break;
    case PSTEXT:
	if (state != TYPESETTING)
	    return;
	TextSpecial(dvifp, &k);
	break;
    default:
	Warning("Invalid keyword or value in \\special - \"%s\" ignored", k.Key);
	break;
    }
}

void
IncludeSpecial(kp, str)
KeyWord *kp;
char    *str;
{
    char  spbuf[STRSIZE];
    char *sf = NULL;
    int   i;

    if (kp->vt == String && access(kp->Val, 0) == 0) {
	(void) strcpy(spbuf, kp->Val);
	sf = spbuf;
    }
    SetPosn(hh, vv);
    EMITS("@beginspecial\n");
    while ((str = GetKeyStr(str, kp)) != NULL) {
	if (kp->vt == None && access(kp->Key, 0) == 0) {
	    if (sf)
	      Warning(" More than one \\special file name given. %s ignored", sf);
	    (void) strcpy(spbuf, kp->Key);
	    sf = spbuf;
	} else if (GetKeyVal(kp, KeyTab, NKEYS, &i)) {
	    if (i <= PSTEXT) {
		Warning("Invalid keyword or value in \\special - \"%s\" ignored",
		  kp->Key);
	    } else if (i >= NTAGS)
		EMIT(outfp, "%f @%s\n", kp->v.n, KeyTab[i].Entry);
	} else
	    Warning(" Invalid keyword or value in \\special - \"%s\" ignored",
	      kp->Key);
    }
    EMITS("@setspecial\n");
    if (sf)
	CopyFile(sf);
    else
	Warning(" No special file name provided.");
    EMITS("@endspecial\n");
}

void
TextSpecial(dvifp, kp)
FILE *dvifp;
KeyWord *kp;
{
    bool  isfile = FALSE;
    char  SpecialStr[STRSIZE];
    char  spbuf[STRSIZE];
    char *sf = NULL;
    char *str;
    int   command;
    int   i;

    SetPosn(hh, vv);
    EMITS("@beginspecial\n");
    EMITS("@setspecial\n");
    EMITS(kp->Val);
    EMITC('\n');

/* Now look for more \special's */
    while ((command = NoSignExtend(dvifp, 1)) >= XXX1 && command <= XXX4) {
	i = NoSignExtend(dvifp, command - XXX1 + 1);
	GetBytes(dvifp, SpecialStr, i);
	SpecialStr[i] = '\0';
	str = SpecialStr;
	spbuf[0] = '\0';

	while ((str = GetKeyStr(str, kp)) != NULL) {
	    if (kp->vt == None && access(kp->Key, 0) == 0) {
		if (sf)
		  Warning(" More than one \\special file name given. %s ignored",
		      sf);
		(void) strcpy(spbuf, kp->Key);
		sf = spbuf;
	    }
	    else if (GetKeyVal(kp, KeyTab, NKEYS, &i) && i != -1) {
		if (i == PSFILE) {
		    isfile = TRUE;
		    if (sf)
			Warning(" More than one \\special file name. %s ignored", 
			    sf);
		    (void) strcpy(spbuf, kp->Val);
		    sf = spbuf;
		}else if (i == PSTEXT) {
		    EMITS(kp->Val);
		    EMITC('\n');
		} else	/* the keywords are simply output as PS procedure calls */
		    EMIT(outfp, "%f @%s\n", kp->v.n, KeyTab[i].Entry);
	    } else
		Warning("Invalid keyword or value in \\special - \"%s\" ignored",
		  kp->Key);
	}

	if (sf) {
	    CopyFile(sf);
	    isfile = FALSE;
	    sf = NULL;
	}
	else if (isfile)
	    Warning("  No special file name provided.");
    }
    EMITS("@endspecial\n");
    (void) fseek(dvifp, (long)-1, 1);
}


/**********************************************************************/
/*****************************  GetKeyStr  ****************************/
/**********************************************************************/
/* extract first keyword-value pair from string (value part may be null) return
 * pointer to remainder of string return NULL if none found       */

char  KeyStr[STRSIZE];
char  ValStr[STRSIZE];

char *
GetKeyStr(str, kw)
char *str;
KeyWord *kw;
{
    char *s, *k, *r, t;

    if (!str)
	return (NULL);

    for (s = str; *s == ' '; s++);	/* skip over blanks */
    if (*s == '\0')
	return (NULL);

    for (k = KeyStr;	/* extract keyword portion */
      *s != ' ' && *s != '\0' && *s != '=';
      *k++ = *s++);
    *k = '\0';
    kw->Key = KeyStr;
    kw->Val = r = NULL;
    kw->vt = None;

    for (; *s == ' '; s++);	/* skip over blanks */
    if (*s != '=')	/* look for "=" */
	return (s);

    for (s++; *s == ' '; s++);	/* skip over blanks */
    if (*s == '\'' || *s == '\"')	/* get string delimiter */
	t = *s++;
    else
	t = ' ';
    for (r = ValStr;	/* copy value portion up to delim */
      *s != t && *s != '\0';
      *r++ = *s++);
    if (t != ' ' && *s == t)
	s++;
    *r = '\0';
    kw->Val = ValStr;
    kw->vt = String;

    return (s);
}


/**********************************************************************/
/*****************************  GetKeyVal  ****************************/
/**********************************************************************/
/* get next keyword-value pair; decode value according to table entry */
bool
GetKeyVal(kw, tab, nt, tno)
KeyWord *kw;
KeyDesc tab[];
int   nt;
int  *tno;
{
    int   i;
    char  c = '\0';

    *tno = -1;

    for (i = 0; i < nt; i++)
	if (IsSame(kw->Key, tab[i].Entry)) {
	    *tno = i;
	    switch (tab[i].Type) {
	    case None:
		if (kw->vt != None)
		    return (FALSE);
		break;
	    case String:
		if (kw->vt != String)
		    return (FALSE);
		break;
	    case Integer:
		if (kw->vt != String)
		    return (FALSE);
		if (sscanf(kw->Val, "%d%c", &(kw->v.i), &c) != 1 || c != '\0')
		    return (FALSE);
		break;
	    case Number:
	    case Dimension:
		if (kw->vt != String)
		    return (FALSE);
		if (sscanf(kw->Val, "%f%c", &(kw->v.n), &c) != 1 || c != '\0')
		    return (FALSE);
		break;
	    }
	    kw->vt = tab[i].Type;
	    return (TRUE);
	}

    return (TRUE);
}
