#ifndef lint
static char Sccsid[] = "@(#)findrcds.c	3.2    DeltaDate 12/9/90    ExtrDate 12/9/90";
#endif

/*	FINDRCDS.C	*/
/*	This module is used to find records in the data base
**	matching the selection criteria.
**	The user may select multiple values of one field
**	connected by '&' or '|' which will be or'd and/or
**	multiple fields which will be and'd.
**	In other words, if field 1 is specified
**	as value1|value2 and field3 is specified as value3.
**	Records with field1==(value1 || value2) && field3==value3
**	will be selected. An external routine 'display', passed
**	as a parameter will be called to process the selected records.
*/

#include <stdio.h>
#include <errno.h>
#include "ascii.h"
#include "cardfile.h"
#ifdef NEWS
extern int	errno;
#endif

extern	int	readonly;

struct Keys {
    char	K_flag;
    long	K_offset;
};
/*
 * Possible values of the K_flag field
 */
#define	GOOD	0	/* good record */
#define	MATCH	1	/* found on this search argument */
#define	END	32	/* physical end of key array */
#define	FREE	64	/* first free key block */


findrcds(fields, dbname, display, first)
struct  Fdata	*fields;
char    *dbname;
int	(*display)();
char    *first;
{
    struct	Fdata	*fp;
    struct	Sdata	find_screen[MAXFLDS+1], *sp;
    int 	found;
    FILE	*filep;
    char	filename[FNSIZE];
    diskptr	offset;
    char	*last =
		"Separate alternate values with a | for 'or' or a & for 'and'";
    int		knum, firstflag;
    long	getkey();
    char	*keyval, *cp;
    char	*rcd;
    char	operator, nextop;
    char	out_line[SWIDTH+1];
    struct	Keys	keys[MAXKEYS+1], *kp;
    int		i;
    int 	rc;
    char	*getslct(), *malloc();
    
    fp = fields;
    sp = find_screen;
    while (fp->F_title[0] != '\0') {
	if (fp->F_key == 'N') {
	    ++fp;
	    continue;
	}
	sp->S_title = fp->F_title;
	sp->S_length = 2 * fp->F_length;
	sp->S_page = -1;
	sp->S_Lrow = -1;
	sp->S_Lcol = -1;
	sp->S_Drow = -1;
	sp->S_Dcol = -1;
	sp->S_Dfmt = "";
	sp->S_result = malloc((unsigned)sp->S_length+1);
	sp->S_dfault = 0;
	++fp;
	++sp;
    }
    sp->S_title = 0;
    while (screen(first, find_screen, last, 0, FALSE) != 0) {
	firstflag = 1;
	knum = -1;
	keys[0].K_flag = FREE;
	for (i=1; i<MAXKEYS; ++i)
	    keys[i].K_flag = GOOD;
	keys[MAXKEYS].K_flag = END;
	sp = find_screen;
	while (sp->S_title) {		/* loop on key field */
	    ++knum;
	    if (*(sp->S_result) == '\0') {	/* was key entered */
		++sp;
		continue;
	    }
	    sprintf(filename, "%s%s.ak%d", datadir, dbname, knum);
	    if ((filep = fopen(filename, "r")) == NULL) {
		msg("Unable to open alternate key file");
		getout();
	    }
	    operator = '&';
	    if (firstflag) {
		firstflag = 0;
		operator = '|';
	    }
	    cp = sp->S_result;
	    /* loop for each key for this field */
	    while ((keyval = getslct(cp, "&|", &nextop)) != NULL) {
		cp = NULL;
		/* loop getting offsets for this value */
		while ((offset = getkey(filep, keyval)) >= 0L) {
		    keyval = 0;
		    if (operator == '|') {
			if (orkey(offset, keys) == -1)
			    break;
		    }
		    if (operator == '&') {
			andkey(offset, keys);
		    }
		}
		if (operator == '&') {
		    purgekey(keys);
		}
		operator = nextop;
	    }
	    fclose(filep);
	    ++sp;
	}
	sprintf(filename, "%s%s.db", datadir, dbname);
	if (readonly) {
	    if ((filep = fopen(filename, "r")) == NULL) {
		sprintf(out_line, "Unable to open %s, errno=%d\n", filename, errno);
		msg(out_line);
		return(1);
	    }
	} else {
	    if ((filep = fopen(filename, "r+")) == NULL) {
		if (errno == EACCES) {
		    strcpy(out_line, "You do not have permission to modify this database");
		} else {
		    sprintf(out_line, "Unable to open %s, errno=%d\n", filename, errno);
		}
		msg(out_line);
		return(1);
	    }
	}
	found = 0;
	rcd = malloc(DBSIZE);
	kp = keys;
	while (kp->K_flag == GOOD) {
	    if (kp->K_offset <0L) {
		++kp;
		continue;
	    }
	    fseek(filep, kp->K_offset, 0);
	    if (fgets(rcd, DBSIZE, filep) == NULL) {
		msg("Error reading DB file");
		getout();
	    }
	    if (*rcd == 'D') {
		kp->K_offset = -1L;
		++kp;
		continue;
	    }
	    ++found;
	    if ((rc = (*display)(fields, rcd, filep, dbname)) == 1)
		break;
	    if (rc == -1) {	/* reverse */
		while (kp > &keys[0]) {
		    --kp;
		    if (kp->K_offset >= 0L)
			break;
		}
	    } else {
		++kp;
	    }
	}
	fclose(filep);
	free(rcd);
	if (found == 0) {
	    msg("No records found");
	}
    }
    sp = find_screen;
    while (sp->S_title) {
	free(sp->S_result);
	++sp;
    }
    return(0);
}


char *
getslct(string, seps, sepval)   /* identical to getfld except separator is */
char    *string, *seps, *sepval;	/* returned thru sepval */
{
    register char	*p, *r;
    static   char	*savept;
    
    p = (string == NULL)? savept : string;
    if (p == 0)
	return(NULL);
    if (*p == '\0')
	return(NULL);
    if ((r = strpbrk(p, seps)) == NULL) {	/* move past token */
	*sepval = '\0';
	savept = 0;		/* indicate this is the last token */
    } else {
	*sepval = *r;
	*r = '\0';
	savept = ++r;
    }
    return(p);
}


orkey(offset, keys)
diskptr	offset;
struct	Keys	*keys;
{
    struct Keys	*save;

    save = NULL;
    while (keys->K_flag == GOOD) {
	if (offset == keys->K_offset) {
	    return(0);
	}
	if (keys->K_offset < 0L && save == NULL) {
	    save = keys;	/* found free point for insertion */
	}
	++keys;
    }
    if (save == NULL) {
	if (keys->K_flag == END) {
	    msg("Too many records selected");
	    return(-1);
	}
	save = keys;
	if ((++keys)->K_flag != END)
	    keys->K_flag = FREE;
    }
    save->K_flag = GOOD;
    save->K_offset = offset;
    return(0);
}

andkey(offset, keys)
diskptr	offset;
struct Keys	*keys;
{

    while(keys->K_flag < END) {
	if (offset == keys->K_offset) {
	    keys->K_flag = MATCH;
	    break;
	}
	++keys;
    }
}


purgekey(keys)
struct Keys	*keys;
{

    while(keys->K_flag < END) {
	if (keys->K_flag != MATCH) {
	    keys->K_offset = -1L;
	} else {
	    keys->K_flag = GOOD;
	}
	++keys;
    }
}
