/************************************************************************
 *									*
 *			Copyright (c) 1982, Fred Fish			*
 *			    All Rights Reserved				*
 *									*
 *	This software and/or documentation is released for public	*
 *	distribution for personal, non-commercial use only.		*
 *	Limited rights to use, modify, and redistribute are hereby	*
 *	granted for non-commercial purposes, provided that all		*
 *	copyright notices remain intact and all changes are clearly	*
 *	documented.  The author makes no warranty of any kind with	*
 *	respect to this product and explicitly disclaims any implied	*
 *	warranties of merchantability or fitness for any particular	*
 *	purpose.							*
 *									*
 ************************************************************************
 */


/*
 *  FILE
 *
 *	dex1.c   dynamic reconfiguration functions
 *
 *  KEY WORDS
 *
 *	dex files
 *	dex
 *
 *  DESCRIPTION
 *
 *	This file contains functions for doing dynamic
 *	reconfiguration.  DEX automatically searches
 *	a directory for a reconfiguration file the first
 *	time any file in that directory is processed.
 *	The default reconfiguration file is ".dexrc" but
 *	can be changed to any desired file via a command
 *	line switch.
 *
 *  FUNCTIONS
 *
 *	make_entry   make an entry in the name table
 *	reconfig     do dynamic reconfiguration
 *	rc_name      build reconfiguration name
 *	do_reconfig  process open reconfiguration file
 *	do_flags     process reconfiguration ".flags" line
 *	file_out     process reconfiguration ".output" line
 *
 *  AUTHOR
 *
 *	Fred Fish
 *
 */

#include <stdio.h>
#include "hashtbl.h"
#include "dex.h"

extern int debug;			/* Global debug flag */
extern int vflag;			/* Global verbose flag */
extern char *get_memory ();

static struct flgwrd {			/* Reconfiguration flags */
    char *word;				/* String form of flag */
    int flag;				/* Token form of flag */
};

static struct flgwrd flgwrds[] = {	/* Flags recognized */
    "PROCESS", PROCESS,
    "EMITTEXT", EMITTEXT,
    "EMITBOX", EMITBOX,
    "EMITFILL", EMITFILL,
    "EMITUL", EMITUL,
    "EMITBP", EMITBP,
    "REGION", REGION,
    NULL, NULL				/* Marks end of flags list */
};

/*
 *  FUNCTION
 *
 *	make_entry   make an entry in table with specified name
 *
 *  KEY WORDS
 *
 *	hash table insertion
 *
 *  SYNOPSIS
 *
 *	struct tbl_data *make_entry(name)
 *	char *name;
 *
 *  DESCRIPTION
 *
 *	Make_entry allocates memory for a table entry, initializes
 *	it's name field, and then adds it to the hash table.
 *	Each entry name is actually the string which is recognized
 *	as starting a new documentation section, such as
 *	"FUNCTION" or "DESCRIPTION".
 *
 *	Note that make_entry allocates fresh memory for the name
 *	string, and copies the name there.  This insures that the
 *	characters comprising the entry's name do not vanish
 *	when somebody's stack is popped.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin make_entry
 *	    Initialize entry pointer to NULL.
 *	    If name pointer is not invalid then
 *	        Get memory for the entry name.
 *	        If memory was allocated then
 *		    Copy the name to the fresh memory.
 *		    Get memory for the table data structure.
 *		    If memory was allocated then
 *		        Remember the name of the entry.
 *		        Add entry to table, getting pointer.
 *		    End if
 *	        End if
 *	    End if
 *	    Return table entry pointer.
 *	End make_entry
 *
 */

static struct tbl_data *make_entry(name)
char *name;
{
    struct tbl_data *temp, *tbl_add();
    char *name_save;

    temp = NULL;
    if (name != NULL) {
        name_save = (char *) get_memory(strlen(name)+1);
        if (name_save != NULL) {
	    strcpy(name_save,name);
            temp = (struct tbl_data *) get_memory(sizeof(struct tbl_data));
            if (temp != NULL) {
	        temp->name = name_save;
	        temp = tbl_add(temp);
		if (debug) {printf("make_entry: added \"%s\"\n",temp->name);}
	    }
        }
    }
    return(temp);
}

/*
 *  FUNCTION
 *
 *	reconfig   do dynamic reconfiguration if necessary
 *
 *  KEY WORDS
 *
 *	dynamic reconfiguration
 *	reconfiguration routines
 *
 *  SYNOPSIS
 *
 *	reconfig(pfn,rcfn)
 *	char *pfn;		(processed file name)
 *	char *rcfn;		(rc file base name)
 *
 *  DESCRIPTION
 *
 *	Tests to see if a new reconfiguration file needs to
 *	be processed and if so, opens it and calls routine
 *	to actually do the reconfiguration.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin reconfig
 *	    Build reconfiguration file name.
 *	    If name is not same as last used name then
 *		If the reconfiguration file can be opened
 *		    Do the reconfiguration.
 *		    Remember name of reconfig file.
 *		    Close the reconfiguration file.
 *		End if
 *	    End if
 *	End reconfig
 *
 */

static char last_rcfile[MAXNAMESIZE];

reconfig(pfn,rcfn)
char *pfn;
char *rcfn;
{
    char buffer[MAXNAMESIZE];
    FILE *rcfp, *fopen();

    rc_name(buffer,pfn,rcfn);
    if (debug) {printf("reconfig: %s & %s => %s\n",pfn,rcfn,buffer);}
    if (strcmp(buffer,last_rcfile) != 0) {
	if ((rcfp = fopen(buffer,"r")) != NULL) {
	    if (vflag) {printf("dex: reconfiguring using \"%s\"\n",buffer);}
	    do_reconfig(rcfp);
	    strcpy(last_rcfile,buffer);
	    fclose(rcfp);
	}
    }
}

/*
 *  FUNCTION
 *
 *	rc_name   build the reconfiguration file name
 *
 *  KEY WORDS
 *
 *	dynamic reconfiguration
 *	reconfiguration file
 *
 *  SYNOPSIS
 *
 *	static rc_name(out,pfn,rcfn)
 *	char *out;			(output goes here)
 *	char *pfn;			(processed file name)
 *	char *rcfn;			(rc base file name)
 *
 *  DESCRIPTION
 *
 *	Builds the name used when an attempt is made to open
 *	the reconfiguration file.  Assumes that if the specified
 *	base reconfiguration file name does not contain a slash character
 *	"/" then any prefix in the processed file name is to be
 *	extracted and prepended to the specified base reconfiguration
 *	file name.
 *
 *	For example, if the processed file name is "/usr/me/myfile"
 *	and the base reconfiguration file name is
 *	".dexrc" then the name used to open the reconfiguration
 *	file will be "/usr/me/.dexrc".
 *	If however the base reconfiguration file name is
 *	"/usr/me/newrc" then this name will simply be
 *	returned unchanged
 *
 *	This seems to be the best way of handling reconfiguration
 *	file names since if the full pathname is given then
 *	only that file will be used regardless of where the
 *	processed file is.  However a generic name may be
 *	given (such as ".dexrc") in which case a search will
 *	be made in the processed file's directory for a file
 *	with that generic name.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin rc_name
 *	    If the specified base rc file name contains "/"
 *		Copy that name to output.
 *	    Else
 *		Copy processed file name to work buffer.
 *		If processed file name has no last "/" in it then
 *		    Simply copy base rc file name to output.
 *		Else
 *		    Append base rc file name after last "/"
 *		    Copy concatenated names to output.
 *		End if
 *	    End if
 *	End rc_name
 *
 */

static rc_name(out,pfn,rcfn)
char *out;			/* Pointer to output place */
char *pfn;			/* Pointer to processed file name */
char *rcfn;			/* Pointer to reconfig base file name */
{
    char buffer[MAXNAMESIZE];	/* Concatenation work buffer */
    char *cp;			/* Work pointer for name build */
    char *rindex();		/* Pointer to last occurrence of char */

    if (index(rcfn,'/') != NULL) {
	strcpy(out,rcfn);
    } else {
	strcpy(buffer,pfn);
	cp = rindex(buffer,'/');
	if (cp == NULL) {
	    strcpy(out,rcfn);
	} else {
	    strcpy(++cp,rcfn);
	    strcpy(out,buffer);
	}
    }
}

/*
 *  FUNCTION
 *
 *	do_reconfig   process open reconfiguration file
 *
 *  KEY WORDS
 *
 *	dynamic reconfiguration
 *	reconfiguration routines
 *
 *  SYNOPSIS
 *
 *	do_reconfig(rcfp)
 *	FILE *rcfp;
 *
 *  DESCRIPTION
 *
 *	Reads each record from the currently open reconfiguration
 *	file, creating table entries if necessary, and adding
 *	the reconfiguration information to the specified table
 *	entries.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin do_reconfig
 *	    While a record can be read from reconfiguration file
 *		Zap the newline at the end.
 *		Skip over any leading whitespace.
 *		If the line has something useful then
 *		    Extract the option field.
 *		    Skip to start of next field.
 *		    If the option is to process flags then
 *			Process all flags on the line.
 *		    Else if option is change output file
 *			Change name of output file.
 *		    Else
 *			Print warning message.
 *		    End if
 *		End if
 *	    End while
 *	End do_reconfig
 *
 */

do_reconfig(rcfp)
FILE *rcfp;
{
    char buffer[256];
    char string[128];
    char *cp, *skpbt(), *xfield();

    while (fgets(buffer,sizeof(buffer),rcfp) != NULL) {
	buffer[strlen(buffer)-1] = NULL;
	cp = skpbt(buffer);
	if (*cp != '#' && *cp != NULL && *cp != '\014') {
	    cp = xfield(string,cp);
	    cp = skpbt(cp);
	    if (strcmp(string,".flags") == 0) {
		do_flags(cp);
	    } else if (strcmp(string,".output") == 0) {
		file_out(cp);
	    } else {
		fprintf(stdout,"dex: \"%s\" reconfiguration action?\n",string);
	    }
	}
    }
}

/*
 *  FUNCTION
 *
 *	do_flags   process flags for specified section
 *
 *  KEY WORDS
 *
 *	dynamic reconfiguration
 *	flags
 *
 *  SYNOPSIS
 *
 *	do_flags(flags_line)
 *	char *flags_line;
 *
 *  DESCRIPTION
 *
 *	Processes all flags for the current section, setting
 *	or resetting those specified.  Flags not specified are
 *	unchanged from the default (reset).
 *
 *	Flags may begin with an explicit action character
 *	"-" or "+" for reset or set respectively.  The
 *	default is to set the named flag.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin do_flags
 *	    If the passed pointer is not invalid then
 *		If there is a line to process then
 *		    Extract the name of the section.
 *		    Skip to next field (first flag).
 *		    If the section is not in table
 *			Create a new table entry.
 *		    End if
 *		    While there is a flag field left
 *			Extract flag field.
 *			Skip to next field.
 *			Init flag field pointer.
 *			If explicit state character
 *			    Skip over it.
 *			End if
 *			For each entry in flags list
 *			    If entry matches flag string
 *				If reset requested
 *				    Reset flag.
 *				Else
 *				    Set flag.
 *				End if
 *				Break for loop
 *			    End if
 *			End for
 *		    End while
 *		End if
 *	    End if
 *	End do_flags
 *
 */

static do_flags(cp)
char *cp;
{
    char string[128];
    char *xfield(), *skpbt();
    struct tbl_data *rp, *make_entry(), *tbl_find();
    struct flgwrd *flagp;
    char *stp;

    if (cp != NULL) {
        if (*cp != NULL) {
	    cp = xfield(string,cp);
	    cp = skpbt(cp);
	    if (debug) {printf("do_flags: region \"%s\"\n",string);}
	    if ((rp = tbl_find(string)) == NULL) {
	        rp = make_entry(string);
	    }
	    while (*cp != NULL) {
	        cp = xfield(string,cp);
	        cp = skpbt(cp);
		if (debug) {printf("do_flags: got flag \"%s\"\n",string);}
		stp = string;
		if (*stp == '-' || *stp == '+') {
		    stp++;
		}
		for (flagp = flgwrds; flagp->word != NULL; flagp++) {
		    if (strcmp(flagp->word,stp) == 0) {
			if (string[0] == '-') {
			    rp->flags &= ~(flagp->flag);
			} else {
			    rp->flags |= flagp->flag;
			}
			break;
		    }
		}
	    }
	}
    }
}

/*
 *  FUNCTION
 *
 *	file_out   reconfigure for new output file
 *
 *  KEY WORDS
 *
 *	dynamic reconfiguration
 *	output file
 *
 *  SYNOPSIS
 *
 *	file_out(file_line)
 *	char *file_line;
 *
 *  DESCRIPTION
 *
 *	Saves file name for output collection when specified
 *	section is processed.  Note that the name must
 *	be stuffed safely away in static memory since
 *	it will go away when one of this routines ancestors
 *	exits.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin file_out
 *	    If passed pointer is not invalid then
 *		If there is a line to use then
 *		    Extract name of section.
 *		    Skip to file name field.
 *		    If section is not in table
 *			Create a new table entry.
 *		    End if
 *		    Extract name of file.
 *		    Allocate some static memory.
 *		    Save the name in static memory.
 *		    Remember where it is.
 *		End if
 *	    End if
 *	End file_out
 *
 */

file_out(cp)
char *cp;
{
    char string[128];
    char *xfield(), *skpbt();
    struct tbl_data *rp, *make_entry(), *tbl_find();
    char *file_save;

    if (cp != NULL) {
        if (*cp != NULL) {
	    cp = xfield(string,cp);
	    cp = skpbt(cp);
	    if ((rp = tbl_find(string)) == NULL) {
	        rp = make_entry(string);
	    }
            cp = xfield(string,cp);
	    file_save = (char *) get_memory(strlen(string)+1);
	    strcpy(file_save,string);
	    rp->out = file_save;
	    if (debug) {printf("file_out: out file \"%s\"\n",rp->out);}
	}
    }
}
