/* Copyright (c) Xerox Corporation 1993. All rights reserved. */
/* tioga.c -- Read a tioga file.
   David Nichols
   December, 1990 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "tioga.h"
#include "tread.h"

extern char *malloc();

#define TRUE	1
#define FALSE	0
#define MAXLEVELS	50	/* max nesting levels */

tioga←FromFile(r, f, procs, data)
    struct tread←Reader *r;
    FILE *f;
    struct tread←Procs *procs;
    char *data;
{
    struct stat s;
    unsigned char *p;
    int result;

    if (fstat(fileno(f), &s) < 0)
	return -1;
    p = (unsigned char *) malloc(s.st←size);
    if (p == NULL)
	return -1;
    if (fread(p, 1, s.st←size, f) != s.st←size) {
	free(p); 
	return -1;
    }
    result = tioga←FromBuffer(r, p, s.st←size, procs, data);
    if (r == NULL)
	free(p);
    return result;
}

tioga←FromBuffer(r, buf, len, procs, data)
    struct tread←Reader *r;
    unsigned char *buf;
    long len;
    struct tread←Procs *procs;
    char *data;
{
    struct tread←Reader myR;

    if (r == NULL)
	r = &myR;
    if (tread←Init(r, buf, len, procs, data) < 0)
	return -1;
    /* Ok, we're ready to go. */
    return DoWork(buf, r);
}

/* Actually do the work.  For now, we'll just print out what we see. */
static int DoWork(buf, r)
    unsigned char *buf;
    struct tread←Reader *r;
{
    enum tioga←ControlOp op;
    int iFormat, iProp;
    int terminalNode = FALSE;
    int lastWasTerminal = FALSE;
    int level = 0;
    int i;
    int nRuns;
    long length;
    long runLen = 0;
    long len;

    /* main loop */
    op = tread←GetOp(r);
    for (;;) {
	if (terminalTextNodeFirst <= op && op <= terminalTextNodeLast) {
	    /* Get the format name, then drop down */
	    iFormat = (int) op - (int) terminalTextNodeFirst;
	    if (iFormat >= r->nFormats) {
		tioga←Error("Illegal format index %d\n", iFormat);
		iFormat = 0;
	    }
	    terminalNode = TRUE;
	}
	else if (startNodeFirst <= op && op <= startNodeLast) {
	    /* Get the format name, then drop down */
	    iFormat = (int) op - (int) startNodeFirst;
	    if (iFormat >= r->nFormats) {
		tioga←Error("Illegal format index %d\n", iFormat);
		iFormat = 0;
	    }
	    terminalNode = FALSE;
	}
	else {
	    switch (op) {
		case endNode:
		    /* todo */
		    if (level >= 0)
			--level;
		    else
			tioga←Error("Too many endNodes.\n");
		    (*r->procs->endNode)(r);
		    op = tread←GetOp(r);
		    continue;
		case startNode:
		    tread←GetStr(r);
		    iFormat = tread←AddFormat(r, r->str);
		    terminalNode = FALSE;
		    break;
		case terminalTextNode:
		    tread←GetStr(r);
		    iFormat = tread←AddFormat(r, r->str);
		    terminalNode = TRUE;
		    break;
		case rope:
		case comment:
		    length = tread←GetInt(r);
		    /* Get newline, just don't pass it to client. */
		    tread←SGetRope(r, op == rope ? &r->text : &r->com,
				   length + 1);
		    /* convert newlines if should */
		    FixNewlines(r->str, length + 1);
		    (*r->procs->insertText)(r, r->str, length, op == comment);
		    if (runLen != 0 && runLen != length)
			tioga←Error("Rope length(%d) doesn't match run length(%d)\n",
				    length, runLen);
		    runLen = 0;
		    /* bump by one */
		    op = tread←GetOp(r);
		    continue;
		case runs:
		    nRuns = tread←GetInt(r);

		    runLen = 0;
		    for (i = 0; i < nRuns; ++i) {
			int rl;
			int iLook;
			op = tread←GetOp(r);
			if (looksFirst <= op && op <= looksLast)
			    /* Look it up. */
			    iLook = (int) op - (int) looksFirst;
			else if (look1 <= op && op <= look3)
			    iLook = GetLookChars(r,
					     (int) op - (int) look1 + 1);
			else if (op == looks) {
			    /* 4 bytes of bit vector */
			    long l = tread←GetByte(r);
			    l = (l << 8) | tread←GetByte(r);
			    l = (l << 8) | tread←GetByte(r);
			    l = (l << 8) | tread←GetByte(r);
			    iLook = tread←AddLooks(r, l);
			}
			if (iLook >= r->nLooks) {
			    tioga←Error("Look index(%d) too large.\n",
					iLook);
			    iLook = 0;
			}
			rl = tread←GetInt(r);
			(*r->procs->addLooks)(r, r->looks[iLook], runLen, rl);
			runLen += rl;
		    }
		    op = tread←GetOp(r);
		    continue;
		case prop:
		    tread←GetStr(r);
		    iProp = tread←AddProp(r, r->str);
		    len = tread←GetInt(r);
		    tread←SGetRope(r, &r->control, len);
		    (*r->procs->handleProp)(r, r->props[iProp], r->str, len);
		    op = tread←GetOp(r);
		    continue;
		case propShort:
		    iProp = tread←GetByte(r);
		    if (iProp >= r->nProps) {
			tioga←Error("Property index(%d) out of range.\n",
				    iProp);
			iProp = 0;
		    }
		    len = tread←GetInt(r);
		    tread←SGetRope(r, &r->control, len);
		    (*r->procs->handleProp)(r, r->props[iProp], r->str, len);
		    op = tread←GetOp(r);
		    continue;
		case endOfFile:
		    if (level == 0)
			break;	/* top level */
		    /* Supply missing endNode ops. */
		    op = endNode;
		    continue;
		case otherNode:
		case otherNodeShort:
		case otherNodeSpecs:
		case otherNodeSpecsShort:
		default:
		    tioga←Error("Illegal op code: %d\n", (int) op);
		    op = tread←GetOp(r);
		    continue;
	    }
	}
	if (level == 0 && op == endOfFile)
	    break;
	/* If we make it here, then we want to start a new text node. */
	if (lastWasTerminal)
	    (*r->procs->endNode)(r);
	lastWasTerminal = terminalNode;
	(*r->procs->startNode)(r, r->formats[iFormat]);
	if (!terminalNode)
	    ++level;
	/* else stayed at same level */
	op = tread←GetOp(r);
    }
    return 0;
}

/* Print, converting newlines. */
static FixNewlines(s, len)
    register char *s;
    register long len;
{
    while (len > 0) {
	if (*s == '\r')
	    *s = '\n';
	++s;
	--len;
    }
}

static long GetLookChars(r, n)
    struct tread←Reader *r;
    int n;
{
    long l = 0;
    int c;

    /* todo: find out about bit ordering in Mesa */
    while (n > 0) {
	c = tread←GetByte(r);
	if ('a' <= c && c <= 'a' + 31)
	    l |= 1 << (31 - (c - 'a'));
	else
	    tioga←Error("Illegal look char: %d\n", c);
	--n;
    }
    return tread←AddLooks(r, l);
}

tioga←Error(msg, a1, a2, a3)
    char *msg;
{
    fprintf(stderr, "Error: ");
    fprintf(stderr, msg, a1, a2, a3);
}