/* rdflags.c -- Copyright 1994 Liam R. E. Quin.
 * All Rights Reserved.
 * This code is NOT in the public domain.
 * See the file COPYRIGHT for full details.
 *
 * $Id: rdflags.c,v 1.12 2001/05/31 03:50:13 liam Exp $
 *
 * Turn a flag string sequence into a flag value.
 */

#include "globals.h" /* defines and declarations for database filenames */
#include "error.h"

#include <sys/types.h> /* for fileinfo.h */
#ifndef FILE
# include <stdio.h>
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#else
# include <strings.h>
#endif

#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#else
# include <malloc.h>
#endif


#include "fileinfo.h"
#include "wordinfo.h"
#include "wordrules.h"
#include "emalloc.h"
#include "lqutil.h"
#include "liblqtext.h"
#include "lqtrace.h"

/** Unix system calls that need declaring: **/
/** Unix/C Library Functions that need declaring: **/
/** lqtext library functions that need declaring: **/
/** Functions within this file that are used before being defined: **/
/** **/

/* <Function>
 *   <Name>LQT_StringToFlags
 *   <Class>Tracing
 *   <Purpose>
 *	<P>Tries to reverse the operation of LQT_FlagsToString.
 *	In other words, LQT_StringToFlags takes a string which it assumes
 *	to be a sequence of names of flags found in the given FlagNames
 *	array, separated by the given constant string, and returns the
 *	bitwise `or' of the Value members corresponding to the Names that
 *	are found.</P>
 *	<P>In addition, a leading + or - is used to indicate that the
 *	following flags are to be added (with bitwise or) or removed
 *	(usinbg bitwise and on their negation) from the result.
 *   <Returns>
 *      a pointer to the first unconverted character in String, and the
 *	actual value in Flagp
 *   <SeeAlso>
 *	LQT_StringToWordFlags
 *	LQT_WordFlagsToString
 * </Function>
 */ 
API char *
LQT_StringToFlags(String, Flagp, WordFlagNamePairArray, Separator)
    char *String;
    unsigned long *Flagp;
    t_FlagNamePair *WordFlagNamePairArray;
    char *Separator;
{
    register char *p;
    int SeparatorLength = strlen(Separator);
    char *StartOfNextChunk;
    enum {
	AddValue, SubtractValue, SetValue
    } Action = SetValue;
    unsigned long Result = 0;
    unsigned long Pending = 0;

    if (!String) {
	*Flagp = 0;
	return String;
    }

    if (!*String) {
	*Flagp = (unsigned long) 0;
	return String;
    }

    for (p = StartOfNextChunk = String; *p; /*NULL*/) {
	char *EndOfChunk;
	t_FlagNamePair *wp;
	char *Candidate;
	int SawTrailingSeparator = 0;

	EndOfChunk = 0;
	if (*p == '+' || *p == '-' || *p == '=') {
	    if (Pending) {
		switch (Action) {
		    case AddValue: Result |= Pending; break;
		    case SubtractValue: Result &= ~Pending; break;
		    case SetValue: Result = Pending; break;
		}
		Pending = 0;
	    }
	    switch (*p) {
	    case '+':
		Action = AddValue; break;
	    case '-':
		Action = SubtractValue; break;
	    case '=':
		Action = SetValue; break;
	    default:
		Error(E_FATAL|E_BUG|E_INTERNAL,
		    "%s: %d: unhandled case 0%o=%c in switch", *p, *p
		);
	    }
	    /* skip over the character */
	    StartOfNextChunk = ++p;
	}
	/** Look for a possible flag string **/
	Candidate = StartOfNextChunk;
	for (p = &StartOfNextChunk[1]; *p; p++) {
	    if (STRNCMP(p, Separator, SeparatorLength) == 0) {
		EndOfChunk = &p[-1];
		/* String = StartOfNextChunk; */
		p = StartOfNextChunk = &p[SeparatorLength];
		if (!*StartOfNextChunk) {
		    SawTrailingSeparator = 1; /* oops */
		}
		break;
	    } else if (*p == '+' || *p == '-' || *p == '=') {
		EndOfChunk = &p[-1];
		/* String = StartOfNextChunk; */
		StartOfNextChunk = &p[1];
		if (!*StartOfNextChunk) {
		    SawTrailingSeparator = 1; /* oops */
		}
		break;
	    }
	}

	if (!*p) {
	    EndOfChunk = &p[-1];
	    /* String = StartOfNextChunk; */
	    StartOfNextChunk = p;
	}

	/** if we didn't get one, we're done **/
	if (!EndOfChunk) {
	    if (Pending) {
		switch (Action) {
		    case AddValue: Result |= Pending; break;
		    case SubtractValue: Result &= ~Pending; break;
		    case SetValue: Result = Pending; break;
		}
	    }
	    *Flagp = Result;
	    return p;
	}

	/** for each known flag value: **/
	for (wp = WordFlagNamePairArray; wp->Name; wp++) {
	    /** if it matches, use the value and stop looking: **/
	    if (STRNCMP(wp->Name, Candidate, (EndOfChunk - Candidate) + 1)==0){
		Pending |= wp->Value;
		break;
	    }
	}

	if (!wp->Name) {
	    /* error: unrecognised string */
	    return Candidate;
	}

	if (SawTrailingSeparator) {
	    /* error: trailing garbage */
	    return &StartOfNextChunk[-1];
	}

    } /* for */

    if (Pending) {
	switch (Action) {
	    case AddValue: Result |= Pending; break;
	    case SubtractValue: Result &= ~Pending; break;
	    case SetValue: Result = Pending; break;
	}
    }

    *Flagp = Result;
#ifdef ASCIITRACE
    LQT_Trace(LQTRACE_DEBUG,
	"string to flags: [%s] -> %d / 0%o / 0x%x -> %s",
	String, Result, Result, Result,
	LQT_FlagsToString(Result, WordFlagNamePairArray, Separator)
    );
#endif
    return p;
}

/* <Function>
 *   <Name>LQT_StringToWordFlags
 *   <Class>Tracing
 *   <Purpose>
 *	Tries to reverse the operation of LQT_WordFlagsToString.
 *	In other words, LQT_StringToWordFlags takes a string which it
 *	assumes to be a sequence of names of flags as defined in the
 *	header file <h>wordrules.h</h> separated by LQTpWordFlagSep (a comma),
 *	and returns the
 *	bitwise `or' of the Word Flags corresponding to the Names that
 *	are found.
 *   <Returns>
 *      a pointer to the first unconverted character in String, and the
 *	actual value in Flagp
 *   <SeeAlso>
 *	LQT_StringToWordFlags
 *	LQT_WordFlagsToString
 * </Function>
 */ 
API char *
LQT_StringToWordFlags(db, String, Flagp)
    t_LQTEXT_Database *db;
    char *String;
    unsigned long *Flagp;
{
    extern t_FlagNamePair LQTp_WordFlagArray[];
    /* later, this will be included in the database structure */

    return LQT_StringToFlags(
	String,
	Flagp,
	LQTp_WordFlagArray,
	LQTpWordFlagSep
    );
}