/*
 * tech.c --
 *
 * Read in a technology file.
 *
 *     ********************************************************************* 
 *     * Copyright (C) 1985, 1990 Regents of the University of California. * 
 *     * Permission to use, copy, modify, and distribute this              * 
 *     * software and its documentation for any purpose and without        * 
 *     * fee is hereby granted, provided that the above copyright          * 
 *     * notice appear in all copies.  The University of California        * 
 *     * makes no representations about the suitability of this            * 
 *     * software for any purpose.  It is provided "as is" without         * 
 *     * express or implied warranty.  Export of this software outside     * 
 *     * of the United States of America may require an export license.    * 
 *     *********************************************************************
 */

#ifndef lint
static char rcsid[] = "$Header: /ufs/repository/magic/tech/tech.c,v 1.4 2006/02/08 03:48:30 fang Exp $";
#endif  /* not lint */

#include <stdio.h>
#include <ctype.h>
#include "misc/magic.h"
#include "utils/geometry.h"
#include "utils/utils.h"
#include "tech/tech.h"
#include "textio/textio.h"
#include "graphics/graphics.h"
#include "utils/malloc.h"

#ifndef NO_VARARGS
#include <stdarg.h>
#endif  /* NO_VARARGS */

global char *TechDefault = NULL;

int techLineNumber;
char *techFileName = NULL;

static int
techGetTokens(char *line, int size, register FILE *file, char *argv[]);

#define	iseol(c)	((c) == EOF || (c) == '\n')

/*
 * Each client of the technology module must make itself known by
 * a call to TechAddClient().  These calls provide both the names
 * of the sections of the technology file, as well as the procedures
 * to be invoked with lines in these sections.
 *
 * The following table is used to record clients of the technology
 * module.
 */

    typedef struct tC
    {
	bool		(*tc_proc)(char*, int, char*[]);
					/* Procedure to be called for each
					 * line in section.
					 */
	void		(*tc_init)();	/* Procedure to be called before any
					 * lines in a section are processed.
					 */
	void		(*tc_final)();	/* Procedure to be called after all
					 * lines in section have been processed.
					 */
	struct tC	*tc_next;	/* Next client in section */
    } techClient;

    typedef struct
    {
	char		*ts_name;	/* Name of section */
	techClient	*ts_clients;	/* Pointer to list of clients */
	bool		 ts_read;	/* Flag: TRUE if section was read */
	bool		 ts_optional;	/* Flag: TRUE if section is optional */
	SectionID	 ts_thisSect;	/* SectionID of this section */
	SectionID	 ts_prevSects;	/* Mask of sections that must be
					 * read in before this one.  The
					 * mask is constructed from the
					 * section identifiers set by
					 * TechAddClient().
					 */
    } techSection;

#define	MAXSECTIONS	(8 * sizeof (int))	/* Not easily changeable */
#define	MAXARGS		30
#define MAXLINESIZE	1024

#define	SectionToMaskBit(s)		(1 << (s))
#define SectionMaskHasSection(m, s)	(m & SectionToMaskBit(s))

int techSectionNum;			/* ID of next new section */
SectionID techSectionMask;		/* Mask of sections already read */

techSection techSectionTable[MAXSECTIONS];
techSection *techSectionFree;		/* Pointer to next free section */
techSection *techCurrentSection;	/* Pointer to current section */

techSection *techFindSection();

/*
 * ----------------------------------------------------------------------------
 *
 * TechInit --
 *
 * Initialize the technology module.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Initializes the technology read-in module.
 *	This function must be called before any other functions in
 *	this module are called.  It is called exactly once at the start
 *	of a magic session.
 *
 * ----------------------------------------------------------------------------
 */

void
TechInit(void)
{
    techCurrentSection = (techSection *) NULL;
    techSectionFree = techSectionTable;
    techSectionNum = 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * TechAddClient --
 *
 * Add a client to the technology module.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Identifies "sectionName" as a valid name for a section of a .tech
 *	file, and specifies that init() is the procedure to be called when
 *	a new technology is loaded, proc() as the procedure to be called
 *	for each line in the given section, and final() as the procedure to
 *      be called after the last line in the given section.
 *
 *	The init() procedure takes no arguments.
 *	The proc() procedure should be of the following form:
 *		bool
 *		proc(sectionName, argc, argv)
 *			char *sectionName;
 *			int argc;
 *			char *argv[];
 *		{
 *		}
 *	The final() procedure takes no arguments.
 *
 *	The argument prevSections should be a mask of the SectionID's
 *	of all sections that must be read in before this one.
 *
 *	If the argument 'pSectionID' is non-NULL, it should point to
 *	an int that will be set to the sectionID of this section.
 *
 *	It is legal for several procedures to be associated with a given
 *	sectionName; this is accomplished through successive calls to
 *	TechAddClient with the same sectionName.  The procedures will
 *	be invoked in the order in which they were handed to TechAddClient().
 *
 *	If the procedure given is NULL for init(), proc(), or final(), no
 *	procedure is invoked.
 *
 * ----------------------------------------------------------------------------
 */

/* bool */
void
TechAddClient(
    char *sectionName,
#if 0
    Void (*init)(),
    bool (*proc)(),
    Void (*final)(),
#else
    void (*init)(void),
    bool (*proc)(char*, int, char*[]),
    void (*final)(void),
#endif
    SectionID prevSections,
    SectionID *pSectionID,
    bool opt) /* optional section */
{
    techSection *tsp;
    techClient *tcp, *tcl;

    tsp = techFindSection(sectionName);
    if (tsp == (techSection *) NULL)
    {
	tsp = techSectionFree++;
	ASSERT(tsp < &techSectionTable[MAXSECTIONS], "TechAddClient");
	tsp->ts_name = StrDup((char **) NULL, sectionName);
	tsp->ts_clients = (techClient *) NULL;
	tsp->ts_thisSect = SectionToMaskBit(techSectionNum);
	tsp->ts_prevSects = (SectionID) 0;
	tsp->ts_optional = opt;
	techSectionNum++;
    }

    tsp->ts_prevSects |= prevSections;
    if (pSectionID)
	*pSectionID = tsp->ts_thisSect;

    tcp = (techClient *) mallocMagic(sizeof (techClient));
    ASSERT(tcp != (techClient *) NULL, "TechAddClient");
    tcp->tc_init = init;
    tcp->tc_proc = proc;
    tcp->tc_final = final;
    tcp->tc_next = (techClient *) NULL;

    if (tsp->ts_clients == (techClient *) NULL)
	tsp->ts_clients = tcp;
    else
    {
	for (tcl = tsp->ts_clients; tcl->tc_next; tcl = tcl->tc_next)
	    /* Nothing */;
	tcl->tc_next = tcp;
    }
}

/*
 * ----------------------------------------------------------------------------
 * TechLoad --
 *
 * Initialize technology description information from a file.
 *
 * Results:
 *	TRUE if technology is successfully initialized (all required
 *	sections present and error free); FALSE otherwise.  Unrecognized
 *	sections cause an error message to be printed, but do not otherwise
 *	affect the result returned by TechLoad().
 *
 * Side effects:
 *	Calls technology initialization routines of other modules
 *	to initialize technology-specific information.
 *
 * ----------------------------------------------------------------------------
 */

bool
TechLoad(char* filename)
{
    register FILE *tf;
    register techSection *tsp;
    register techClient *tcp;
    char suffix[20], line[MAXLINESIZE], *realname;
    char *argv[MAXARGS];
    SectionID mask, badMask;
    int argc, s;
    bool retval, skip;

    techLineNumber = 0;
    badMask = (SectionID) 0;
    (void) sprintf(suffix, ".tech%d", VERSION);
    tf = PaOpen(filename, "r", suffix, ".", SysLibPath, &realname);
    if (tf == (FILE *) NULL) {
	TxError("Could not find file '%s%s' in any of these directories:\n         %s\n",
	    filename, suffix, SysLibPath);
	return (FALSE);
    }
    (void) StrDup(&techFileName, realname);

    /*
     * Mark all sections as being unread.
     */
    techSectionMask = 0;
    for (tsp = techSectionTable; tsp < techSectionFree; tsp++)
    {
	tsp->ts_read = FALSE;
    }

    /*
     * Sections in a technology file begin with a single line containing
     * the keyword identifying the section, and end with a single line
     * containing the keyword "end".
     */

    retval = TRUE;
    skip = FALSE;
    while ((argc = techGetTokens(line, sizeof line, tf, argv)) >= 0)
    {
	if (!skip && techCurrentSection == NULL)
	{
	    if (argc != 1)
	    {
		TechError("Bad section header line\n");
		goto skipsection;
	    }

	    tsp = techFindSection(argv[0]);
	    if (tsp == (techSection *) NULL)
	    {
		TechError("Unrecognized section name: %s\n", argv[0]);
		goto skipsection;
	    }
	    if ((mask = (tsp->ts_prevSects & ~techSectionMask)))
	    {
		register techSection *sp;

		TechError("Section %s appears too early.\n", argv[0]);
		TxError("\tMissing prerequisite sections:\n");
		for (sp = techSectionTable; sp < techSectionFree; sp++)
		    if (mask & sp->ts_thisSect)
			TxError("\t\t%s\n", sp->ts_name);
		goto skipsection;
	    }
	    techCurrentSection = tsp;

	    /* Invoke initialization routines for all clients that
	     * provided them.
	     */

	    for (tcp = techCurrentSection->ts_clients;
		    tcp != NULL;
		    tcp = tcp->tc_next)
	    {
		if (tcp->tc_init)
		    (void) (*tcp->tc_init)();
	    }
	    continue;
	}

	/* At the end of the section, invoke the finalization routine
	 * of the client's, if there is one.
	 */

	if (argc == 1 && strcmp(argv[0], "end") == 0)
	{
	    if (!skip)
	    {
		techSectionMask |= techCurrentSection->ts_thisSect;
		techCurrentSection->ts_read = TRUE;
		for (tcp = techCurrentSection->ts_clients;
			tcp != NULL;
			tcp = tcp->tc_next)
		{
		    if (tcp->tc_final)
			(*tcp->tc_final)();
		}
	    }
	    techCurrentSection = (techSection *) NULL;
	    skip = FALSE;
	    continue;
	}

	if (!skip)
	    for (tcp = techCurrentSection->ts_clients;
			tcp != NULL;
			tcp = tcp->tc_next)
		if (tcp->tc_proc)
		{
		    if (!(*tcp->tc_proc)(techCurrentSection->ts_name,argc,argv))
		    {
			retval = FALSE;
			badMask |= techCurrentSection->ts_thisSect;
		    }
		}
	continue;

skipsection:
	TxError("[Skipping to \"end\"]\n");
	skip = TRUE;
    }

    if (badMask)
    {
	TxError("The following sections of %s contained errors:\n", realname);
	for (s = 0; s < techSectionNum; s++)
	    if (SectionMaskHasSection(badMask, s))
		TxError("    %s\n", techSectionTable[s].ts_name);
    }

    for (tsp = techSectionTable; tsp < techSectionFree; tsp++)
	if (!tsp->ts_read && !tsp->ts_optional)
	{
	    TxError("Section \"%s\" was missing from %s.\n",
		tsp->ts_name, realname);
	    retval = FALSE;
	}

    fclose(tf);
    return (retval);
}

/*
 * ----------------------------------------------------------------------------
 *
 * TechError --
 *
 * Print an error message referring to a given line number in the
 * technology module.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Prints an error message.
 *
 * ----------------------------------------------------------------------------
 */

void
TechPrintLine(void)
{
    char *section;

    if (techCurrentSection)
	section = techCurrentSection->ts_name;
    else
	section = "(none)";

    TxError("%s: line %d: section %s:\n\t",
		techFileName, techLineNumber, section);
}

#ifndef	NO_VARARGS
 /*VARARGS0*/
void
TechError(char* fmt, ...)
{
    va_list args;
    TechPrintLine();
    va_start(args, fmt);
    (void) GrVfprintf(stderr, fmt, args);
    va_end(args);
}
#else	/* NO_VARARGS */

void
TechError(f, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
    char *format;
    FILE *f;
{
    TechPrintLine();
    (void) GrFprintf(stderr, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, 
		     a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20);
}
#endif	/* NO_VARARGS */

/* ================== Functions local to this module ================== */

/*
 * ----------------------------------------------------------------------------
 *
 * techFindSection --
 *
 * Return a pointer to the entry in techSectionTable for the section
 * of the given name.
 *
 * Results:
 *	A pointer to the new entry, or NULL if none could be found.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

techSection *
techFindSection(sectionName)
    char *sectionName;
{
    register techSection *tsp;

    for (tsp = techSectionTable; tsp < techSectionFree; tsp++)
	if (strcmp(tsp->ts_name, sectionName) == 0)
	    return (tsp);

    return ((techSection *) NULL);
}

/*
 * ----------------------------------------------------------------------------
 *
 * techGetTokens --
 *
 * Read a line from the technology file and split it up into tokens.
 * Blank lines are ignored.  Lines ending in backslash are joined
 * to their successor lines.
 * We assume that all macro definition and comment elimination has
 * been done by the C preprocessor.
 *
 * Results:
 *	Returns the number of tokens into which the line was split, or
 *	-1 on end of file.  Never returns 0.
 *
 * Side effects:
 *	Copies the line just read into 'line'.  The trailing newline
 *	is turned into a '\0'.  The line is broken into tokens which
 *	are then placed into argv.
 *
 * ----------------------------------------------------------------------------
 */

int
techGetTokens(line, size, file, argv)
    char *line;			/* Character array into which line is read */
    int size;			/* Size of character array */
    register FILE *file;	/* Open technology file */
    char *argv[];		/* Vector of tokens built by techGetTokens() */
{
    register char *get, *put, *getp;
    bool inquote;
    int argc = 0;
    int currspace;		/* chars remaining before end of line[size] */

    /* Read one line into the buffer, joining lines when they end
     * in backslashes.
     */

    /* Code revision (MDG, Stanford):  Prevent the 1024-character limit due */
    /* to unconditional decrement of size.  Long comment lists could cause  */
    /* infinite looping.  New code interprets first non-space character '#' */
    /* as a comment character, rather than requiring it to be in the first  */
    /* column.								    */

start:
     get = line;
     currspace = size;
     while (currspace > 0)
     {
	techLineNumber += 1;
	if (fgets(get, currspace, file) == NULL) return (-1);
	getp = get;
	while(isspace(*getp)) getp++;
	if (*getp == '#') continue;
	for (put = get; *put != '\n'; put++) currspace -= 1;
	if ((put != get) && (*(put-1) == '\\'))
	{
	    get = put-1;
	    continue;
	}
	*put= '\0';
	break;
    }
    if (currspace == 0) TechError("long line truncated\n");

    get = put = line;

    while (*get != '\0')
    {
	/* Skip leading blanks */

	while (isspace(*get)) get++;

	/* Beginning of the token is here. */

	argv[argc] = put = get;
	if (*get == '"')
	{
	    get++;
	    inquote = TRUE;
	} else inquote = FALSE;

	/*
	 * Grab up characters to the end of the token.  Any character
	 * preceded by a backslash is taken literally.
	 */
	
	while (*get != '\0')
	{
	    if (inquote)
	    {
		if (*get == '"') break;
	    }
	    else if (isspace(*get)) break;

	    if (*get == '\\')	/* Process quoted characters literally */
	    {
		get += 1;
		if (*get == '\0') break;
	    }

	    /* Copy into token receiving area */
	    *put++ = *get++;
	}

	/*
	 * If we got no characters in the token, we must have been at
	 * the end of the line.
	 */
	if (get == argv[argc])
	    break;
	
	/* Terminate the token and advance over the terminating character. */

	if (*get != '\0') get++;	/* Careful!  could be at end of line! */
	*put++ = '\0';
	argc++;
    }

    if (argc == 0)
	goto start;

    return (argc);
}
