/* Copyright 1989, 1990 by James Aspnes, David Applegate, and Bennet Yee */
/* See the file COPYING for distribution information */
#include <ctype.h>

#include "set.h"
#include "db.h"

extern alist get_aliases(datum);

int member(set s, datum d)
{
    return(!set_empty(s) && assoc(s->list, d, 0));
}

static set new_set(void)
{
    set s;

    s = (set) malloc(sizeof(struct _set));
    s->list = empty_alist();
    s->names = empty_alist();

    return s;
}

set add_member(set s, datum d)
{
    datum name;
    datum dummy;
    alist aliases;

    if(d == NOTHING) return s;

    if(set_empty(s)) s = new_set();

    if(!member(s, d)) {
	if(!isempty(s->names) && !isempty(aliases = get_aliases(d))) {
	    FOREACH(aliases, name, dummy) {
		s->names = add_assoc(s->names, name, d);
	    } END_FOREACH;
	}
	s->list = add_assoc(s->list, d, NOTHING);
    }

    return s;
}

set del_member(set s, datum d)
{
    datum name;
    datum dummy;
    alist aliases;

    if(member(s, d)) {
	if(alist_count(s->list) == 1) {
	    /* last one */
	    free_set(s);
	    return empty_set();
	} else {
	    if(!isempty(s->names) && !isempty(aliases = get_aliases(d))) {
		FOREACH(aliases, name, dummy) {
		    s->names = del_assoc(s->names, name, d);
		} END_FOREACH;
	    }
	    s->list = del_assoc(s->list, d, NOTHING);
	}
    }

    return s;
}
    
set copy_set(set s)
{
    set s1;

    if(s) {
	s1 = new_set();
	s1->list = copy_alist(s->list);
	s1->names = copy_alist(s->names);

	return s1;
    } else {
	return empty_set();
    }    
}

void free_set(set s)
{
    if(s) {
	free_alist(s->list);
	free_alist(s->names);
	free((void *) s);
    }
}

alist get_aliases(datum obj)
{
    datum a;
    char buf[MAX_STRLEN+1];
    char *p;
    char *end;
    char *next;
    alist l;

    if((a = lookup(obj, ALIASES_NAME)) == NOTHING) {
	/* no aliases */
	return empty_alist();
    } else if(isempty(l = get_expansion(a))) {
	/* not expanded yet */
	/* get a copy we can work on */
	strip_whitespace(string(a), buf);
	    
	l = empty_alist();

	p = buf;
	for(;;) {
	    /* zip over empty names etc. */
	    while(*p && (isspace(*p) || *p == ALIAS_SEPARATOR)) p++;

	    if(!*p) break;	/* nothing left */
	    
	    /* find the next separator */
	    for(next = p; *next && *next != ALIAS_SEPARATOR; next++);

	    /* back over spaces */
	    /* terminates because p is sitting on a non-space */
	    for(end = next - 1; isspace(*end); end--);

	    /* cut it off */
	    if(*next) next++;	/* get next out of the way */
	    *++end = '\0';

	    /* add it to the list */
	    l = set_assoc(l, intern(p), NOTHING);

	    /* go on to the next one */
	    p = next;
	}

	/* put it in */
	set_expansion(a, l);
    }

    return l;
}

void set_build_name_list(set s)
{
    datum obj;
    datum name;
    datum dummy;
    alist a;

    if(s != 0) {
	if(isempty(s->names) && !isempty(s->list)) {
	    FOREACH(s->list, obj, dummy) {
		if(!isempty(a = get_aliases(obj))) {
		    FOREACH(a, name, dummy) {
			s->names = add_assoc(s->names, name, obj);
		    } END_FOREACH;
		}
	    } END_FOREACH;
	}
    }
}

void set_clear_name_list(set s)
{
    if(s && !isempty(s->names)) {
	free_alist(s->names);
	s->names = empty_alist();
    }
}

datum set_next(set s, set_iterator *i)
{
    if(s) {
	return alist_next(s->list, i, 0);
    } else {
	return NOTHING;
    }
}

datum set_first(set s, set_iterator *i)
{
    if(s) {
	return alist_first(s->list, i, 0);
    } else {
	return NOTHING;
    }
}

datum set_next_match(set s, set_iterator *i)
{
    datum value;

    /* assume names has been rebuilt */
    if(s && alist_next_match(s->names, i, &value)) {
	return value;
    } else {
	return NOTHING;
    }
}

datum set_first_match(set s, set_iterator *i, datum key)
{
    datum value;

    set_build_name_list(s);
    if(s && alist_first_match(s->names, i, &value, key)) {
	return value;
    } else {
	return NOTHING;
    }
}
    
set add_members(set victim, set operand)
{
    datum next;

    if(!set_empty(operand)) {
	set_clear_name_list(victim); /* make it fast */
	SET_FOREACH(operand, next) {
	    victim = add_member(victim, next);
	} END_SET_FOREACH;
    }

    return victim;
}

set del_members(set victim, set operand)
{
    datum next;

    if(!set_empty(operand)) {
	set_clear_name_list(victim); /* make it fast */
	SET_FOREACH(operand, next) {
	    victim = del_member(victim, next);
	} END_SET_FOREACH;
    }

    return victim;
}