/* 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"
#include "externs.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;
}