/*
 * sets - performs set operations on two sets of arguments and
 *        prints the result on the standard output stream
 * 
 * usage: sets	[-p[aths]]  e1 e2 ... en \-u[nion] e1 e2 ... en
 *					OR
 * 		            e1 e2 ... en \-d[ifference] e1 e2 ... en
 *					OR
 * 		            e1 e2 ... en \-i[ntersection] e1 e2 ... en
 *
 * This code may be freely distributed provided this comment
 * is not removed or substantially altered.  Please mail me any
 * fixes, changes, or enhancements.
 *
 * Christopher Tweed, EdCAAD, University of Edinburgh, Scotland.
 * chris@caad.ed.ac.uk
 * ..mcvax!ukc!edcaad!chris
 *
 * 3 December 1987.
 * 
 */

#include <stdio.h>
#include <string.h>

#define MAXSET	256	/* maximum size of a set */

#define STREQ(s1, s2)	(strcmp((s1), (s2)) == 0)
#define NOT(p)		((p) == FALSE)
#define NAME(s)		((ignorep == TRUE) ? nopath(s) : s)

typedef enum { FALSE=0, TRUE } BOOLEAN;
typedef enum { NULL_OP=0, UNION, DIFF, INTERSECT } OPERATOR;

extern	int 	strcmp();
static	void	too_many();
static	void	usage();
static	char	*nopath();
static	BOOLEAN	member();
static	BOOLEAN	ignorep = TRUE;

main(argc, argv)
int argc;
char *argv[];
{
	int i, j;			/* general purpose */
	BOOLEAN second = FALSE;		/* flag set after operator */
	char *set1[MAXSET];		/* the first set */
	int n1 = 0;			/* number of elements in first set */
	char *set2[MAXSET];		/* the second set */
	int n2 = 0;			/* number of elements in second set */
	int n;				/* number in each set */
	register OPERATOR op = NULL_OP;	/* set operation to perform */

	if (argc < 2) {
	    fprintf(stderr, "not enough arguments\n");
	    usage(argv[0]);		/* EXITS */
	}

	n2 = n1 = 0;
	/* collect sets */
	while(--argc) {
	    if (argv[1][0] == '-') {
		second = TRUE;			/* found an operator */
		switch (argv[1][1]) {
		    case 'u':			/* set union */
			op = UNION;
			break;
		    case 'd':			/* set difference */
			op = DIFF;
			break;
		    case 'i':			/* set intersection */
			op = INTERSECT;
			break;
		    case 'p':			/* don't ignore paths */
			ignorep = FALSE;
			break;
		    default:
			fprintf(stderr, "illegal set operator %c\n",
					 argv[1][1]);
			usage(argv[0]);	/* EXITS */
		}
	    } else {
		if (second == TRUE) {
		    if (n2 == MAXSET)
			too_many();	/* EXITS */
		    set2[n2++] = argv[1];
		} else {
		    if (n1 == MAXSET)
			too_many();	/* EXITS */
		    set1[n1++] = argv[1];
		}
	    }
	    argv++;
	}

	if (op == NULL_OP) {
	    fprintf(stderr, "missing operator\n");
	    usage(argv[0]);
	}

	/* remove duplicates */
	n1 = nodups(set1, n1);
	n2 = nodups(set2, n2);
	/*
	 * do set operation and print result
	 *
	 */
	n = (op == UNION) ? (n1 + n2) : n1;
	for (i = 0; i < n; i++) {
	    switch(op) {
		case UNION:
		    j = i - n1;
		    if (i < n1)
			printf("%s ", set1[i]);
		    else if (NOT(member(set2[j], set1, n1)))
			printf("%s ", set2[j]);
		    break;
		case DIFF:
		    if (member(set1[i], set2, n2) == FALSE) {
			printf("%s ", set1[i]);
		    }
		    break;
		case INTERSECT:
		    if (member(set1[i], set2, n2) == TRUE) {
			printf("%s ", set1[i]);
		    }
		    break;
	    }
	}

	printf("\n");
	exit(0);
}

/*
 * nodups(set, n)
 *
 * removes duplicates from set of n elements and returns number
 * of remaining elements in the set
 *
 */

int
nodups(set, n)
char *set[];
int n;
{
	register int i;
	register int j;
	register int k;
	register int nn = n;

	/*
	 * start at the top of the list
	 *
	 */
	for(i=n-1; i>0; i--)
	    for(j=0; j<i; j++) {
		if (set[i][0] == set[j][0] && STREQ(set[i], set[j])) {
		    set[i] = NULL;	/* cancel the duplicate */
		    /*
		     * move everything above
		     * the duplicate down one
		     *
		     */
		    for(k=i+1; k<nn; k++) {
			set[k-1] = set[k];
			set[k] = NULL;
		    }
		    nn--;
		    break;
		}
	    }
	return nn;
}

/*
 * member(s, set, n)
 *
 * returns TRUE if string s is a member of set which has n members
 * otherwise return FALSE
 *
 */

static BOOLEAN
member(s, set, n)
register char *s, *set[];
register int n;
{
	register int i;

	for (i = 0; i < n; i++)
	    if (STREQ(NAME(s), NAME(set[i])))
		return TRUE;

	return FALSE;
}

/*
 * nopath(s)
 *
 * Strips leading path from s if necessary; otherwise
 * returns s.
 *
 */

static char *
nopath(s)
char *s;
{
	char *p;

#ifdef AMIGA
	if ((p = strrchr(s, '/')) || (p = strrchr(s, ':')))
#else
	if (p=strrchr(s, '/'))
#endif
	    return ++p;
	else
	    return s;
}

static void
too_many()
{
	fprintf(stderr, "too many members\n");
	exit(1);
}

static void
usage(prog)
char *prog;
{
	char *set = "e1 e2 ... en";

	fprintf(stderr, "%s\t%s -u[nion] %s\n", prog, set, set);
	fprintf(stderr, "\t%s -d[ifference] %s\n", set, set);
	fprintf(stderr, "\t%s -i[ntersection] %s\n", set, set);
	fprintf(stderr, "\t-p[aths]\t/* don't ignore leading paths */\n");
	exit(1);
}
