/***********************************************************************/
/* Open Visualization Data Explorer                                    */
/* (C) Copyright IBM Corp. 1989,1999                                   */
/* ALL RIGHTS RESERVED                                                 */
/* This code licensed under the                                        */
/*    "IBM PUBLIC LICENSE - Open Visualization Data Explorer"          */
/***********************************************************************/

#include <dxconfig.h>



#include <dx/dx.h>

#define EmptyField2(o)  (DXGetObjectClass((Object)o) == CLASS_FIELD) && \
                         DXEmptyField((Field)o)

#define MAXRANK 16

/* types of objects which can be selected from: */
#define UNKNOWN    0
#define IS_SERIES  1
#define IS_GROUP   2
#define IS_STRING  3
#define IS_LIST    4

static Object doobject(Object o, Object whichone, int fexcept);
static Object doselect(Object o, Object whichone, int fexcept, int whattype); 

static Object selectnamedgroup(Group g, Object names);
static Object selectnumberedgroup(Group g, Object numbers);
static Object selectnumberedseries(Series s, Object numbers);
static Array  selectnumberedlist(Array a, Object numbers);
static Object selectnumberedstring(Object o, Object numbers);
static Object omitnamedgroup(Group g, Object names);
static Object omitnumberedgroup(Group g, Object numbers);
static Object omitnumberedseries(Series s, Object numbers);
static Array  omitnumberedlist(Array a, Object numbers);
static Object omitnumberedstring(Object o, Object numbers);

static int int_compare(void *a, void *b)
{
    return (*(int *)a - *(int *)b);
}
static int str_compare(void *a, void *b)
{
    return strcmp((char *)a, (char *)b);
}


/* if this is 0, then fields are not accepted as input.  if this is 1,
 * then if the input is a field the data component is extracted and then
 * handled as a list.  the upside of allowing fields as input is the 
 * user doesn't have to extract the data component first; the downside
 * is that most of the time when someone passes a field into select they
 * are doing it by mistake and we can help them out by giving them an error.
 */
#define DO_FIELDS 0


int
m_Select(Object *in, Object *out)
{
    int fexcept;

    out[0] = NULL;

    /* no input object 
     */
    if (!in[0]) {
	DXSetError(ERROR_BAD_PARAMETER, "#10000", "group or list");
	return ERROR;
    }

    /* empty input object
     */
    if (EmptyField2(in[0])) {
	out[0] = in[0];
	return OK;
    }

    /* flag which says copy everything except the members listed
     */
    if (in[2]) {
	if (!DXExtractInteger(in[2], &fexcept)) {
	    DXSetError(ERROR_BAD_PARAMETER, "#10070", "except"); 
	    return ERROR;
	}

	if (fexcept != 0 && fexcept != 1) {
	    DXSetError(ERROR_BAD_PARAMETER, "#10070", "except");
	    return ERROR;
	}
    } else
	fexcept = 0;


    out[0] = doobject(in[0], in[1], fexcept);

    
    return ((out[0] == NULL) ? ERROR : OK);
}


/* recurse thru container objects like xforms until we get to something
 * we can select from.  should this include screen objects?  clips?
 */
static Object
doobject(Object o, Object whichone, int fexcept)
{
    Object newo = NULL;
    Object subo = NULL;
    char *cp;
    int i;

    switch (DXGetObjectClass(o)) {
      case CLASS_STRING:
	return doselect(o, whichone, IS_STRING, fexcept);
	
      case CLASS_GROUP:
	switch (DXGetGroupClass((Group)o)) {
	  case CLASS_SERIES:
	    return doselect(o, whichone, IS_SERIES, fexcept);

	  default:
	    return doselect(o, whichone, IS_GROUP, fexcept);
	    
	}
	break;
	
#if DO_FIELDS
      case CLASS_FIELD:
        subo = DXGetComponentValue((Field)o, "data");
        if (!subo) {
            DXSetError(ERROR_INVALID_DATA, 
  	       "no data component found in field for selecting list items");
            return NULL;
        }

	return doselect(subo, whichone, IS_LIST, fexcept);
#endif

      case CLASS_ARRAY:
	return doselect(o, whichone, IS_LIST, fexcept);
	
      case CLASS_XFORM:
	/* get xform object, call select and put object back into
	 * a copy of xform.
	 */
	if (!DXGetXformInfo((Xform)o, &subo, NULL))
	    return NULL;
	
	if (!(subo = doobject(subo, whichone, fexcept)))
	    return NULL;
	
	if (!(newo = DXCopy(o, COPY_ATTRIBUTES))) {
	    DXDelete(subo);
	    return NULL;
	}
	
	if (!DXSetXformObject((Xform)newo, subo)) {
	    DXDelete(newo);
	    DXDelete(subo);
	    return NULL;
	}
	    
	return newo;
    }

    DXSetError(ERROR_BAD_PARAMETER, "input must be group or list");
    return NULL;
}

/* the input to this routine should be something you can select from:
 *  a group or list.  whichone should either be a list of indicies or
 *  a list of names (for a group with named members) and whattype should
 *  have been set by the calling routine saying whether the input object
 *  is a group, series, number or string list or simple string.
 */
static Object
doselect(Object o, Object whichone, int whattype, int fexcept)
{
    char *cp;
    int i;


    /* whichone default: return first group member or first member of list
     */
    if (!whichone) {
	switch (whattype) {
	  case IS_SERIES:
	    if (fexcept)
		return omitnumberedseries((Series)o, NULL);
	    else
		return selectnumberedseries((Series)o, NULL);
	    
	  case IS_GROUP:
	    if (fexcept)
		return omitnumberedgroup((Group)o, NULL);
	    else
		return selectnumberedgroup((Group)o, NULL);
	    
	  case IS_STRING:
	    if (fexcept)
		return (Object)omitnumberedstring(o, NULL);
	    else
		return (Object)selectnumberedstring(o, NULL);
	    
	  case IS_LIST:
	  default:
	    if (fexcept)
		return (Object)omitnumberedlist((Array)o, NULL);
	    else 
		return (Object)selectnumberedlist((Array)o, NULL);

        }
    }


    /* if user specified a name, select by name.  if user specified an int,
     *  select by enumerated member number (this is not series position, which
     *  is a float.)
     */
    if (DXExtractString(whichone, &cp) || DXExtractNthString(whichone, 0, &cp)) {
	
	switch (whattype) {
	  case IS_SERIES:
	  case IS_GROUP:
	    if (fexcept)
		return omitnamedgroup((Group)o, whichone);
	    else
		return selectnamedgroup((Group)o, whichone);

	  case IS_STRING:
            DXSetError(ERROR_BAD_PARAMETER, 
                     "cannot extract items from a string by name");
            return NULL;

	  case IS_LIST:
	  default:
            DXSetError(ERROR_BAD_PARAMETER, 
		       "cannot extract items from a list by name");
            return NULL;
        }
        
    } else if (DXQueryParameter(whichone, TYPE_INT, 0, &i)) {
        
	switch (whattype) {
	  case IS_SERIES:
	    if (fexcept)
		return omitnumberedseries((Series)o, whichone);
	    else
		return selectnumberedseries((Series)o, whichone);
            
	  case IS_GROUP:
	    if (fexcept)
		return omitnumberedgroup((Group)o, whichone);
	    else
		return selectnumberedgroup((Group)o, whichone);
            
	  case IS_STRING:
	    if (fexcept)
		return (Object)omitnumberedstring(o, whichone);
	    else
		return (Object)selectnumberedstring(o, whichone);

	  case IS_LIST:
	  default:
	    if (fexcept)
		return (Object)omitnumberedlist((Array)o, whichone);
	    else
		return (Object)selectnumberedlist((Array)o, whichone);
        }
        
    } 
	
    DXSetError(ERROR_BAD_PARAMETER, "#10051", "which member");
    return NULL;
}

/* select members from a series by index number.  the index is different from
 * the series position; the series position is a float and can be any value.
 * series groups are handled differently from other types of groups because 
 * the series position has to be preserved if the output is also a group
 * (which happens when more than one member is selected).
 */
static Object 
selectnumberedseries(Series s, Object numbers)
{
    Object selected;
    Series news = NULL;
    float position;
    int i, count, members;
    int *memberlist = NULL;

    /* if empty group, can't select.
     */
    if (!DXGetMemberCount((Group)s, &members) || members == 0) {
	DXSetError(ERROR_INVALID_DATA, "#11310"); 
	/* group has no members */
	return NULL;
    }

    /* if no numbers, return the first member */
    if (!numbers) {
        selected = DXGetEnumeratedMember((Group)s, 0, NULL);
        if(!selected)
            return NULL;
        
        return selected;
    }
    
    /* if one number, return it */
    if (DXExtractInteger(numbers, &i)) {
        selected = DXGetEnumeratedMember((Group)s, i, NULL);
	if(!selected) {
	    if (members == 1) {
		DXSetError(ERROR_BAD_PARAMETER, 
			"'%s' must be 0, since the series has only one member",
			"which member");
	    } else {
		DXSetError(ERROR_BAD_PARAMETER, 
		   "'%s' must be an integer or integer list between %d and %d",
                     "which member", 0, members-1);
	    }
	    return NULL;
	}
        
        return selected;
    }

    /* if a list of numbers, build up a new series */
    news = (Series)DXCopy((Object)s, COPY_ATTRIBUTES);
    if (!news)
        return NULL;
    
    if (!DXQueryParameter(numbers, TYPE_INT, 0, &count)) {
        DXSetError(ERROR_BAD_PARAMETER, 
                 "'%s' must be an integer or integer list",
                 "which member");
        goto error;
    }

    memberlist = (int *)DXAllocate(sizeof(int) * count);
    if (!memberlist)
        goto error;
    
    if (!DXExtractParameter(numbers, TYPE_INT, 0, count, (Pointer)memberlist))
        goto error;


    for (i=0; i<count; i++) {
        selected = DXGetSeriesMember(s, memberlist[i], &position);
	if(!selected) {
	    if (members == 1) {
		DXSetError(ERROR_BAD_PARAMETER, 
			"'%s' must be 0, since the series has only one member",
			"which member");
	    } else {
		DXSetError(ERROR_BAD_PARAMETER, 
                  "'%s' must be an integer or integer list between %d and %d",
                  "which member", 0, members-1);
	    }
	    goto error;
	}
        
        if (!DXSetSeriesMember(news, i, (double)position, selected))
            goto error;
        
    }
     
    DXFree((Pointer)memberlist);
    return (Object)news;

  error:
    DXFree((Pointer)memberlist);
    DXDelete((Object)news);
    return NULL;
}

/* select members from a group by name.  normally used on generic groups,
 * composite fields and multigrid groups, but technically not illegal for
 * series groups (it is hard to create a series group where the members have
 * names, and if there is more than one member in the output (so the output is
 * a series and not just the individual member), the series positions will
 * be lost.)
 */
static Object 
selectnamedgroup(Group g, Object names)
{
    Object selected;
    Group newg = NULL;
    char *cp;
    int i;

    /* if empty group, can't select.
     */
    if (!DXGetMemberCount(g, &i) || i == 0) {
	DXSetError(ERROR_INVALID_DATA, "#11310");  /* group has no members */
	return NULL;
    }
    
    /* if no names, return the first member */
    if (!names) {
        selected = DXGetEnumeratedMember(g, 0, NULL);
        if(!selected)
            return NULL;
	
        return selected;
    }
    
    /* if just one name, return it */
    if (DXExtractString(names, &cp)) {
        selected = DXGetMember(g, cp);
	if(!selected) {
	    DXSetError(ERROR_BAD_PARAMETER, "#11320", cp);
	    return NULL;
	}
       
        return selected;
    }

    /* if a list of names, build up a new group */
    newg = (Group)DXCopy((Object)g, COPY_ATTRIBUTES);
    if (!newg)
        return NULL;

    for (i=0; DXExtractNthString(names, i, &cp); i++) {
        selected = DXGetMember(g, cp);
        if(!selected) {
	    DXSetError(ERROR_BAD_PARAMETER, "#11320", cp);
	    goto error;
	}
        
        if (!DXSetMember(newg, cp, selected))
            goto error;
    }
     
    return (Object)newg;

  error:
    DXDelete((Object)newg);
    return NULL;
}

/* select group members by index number.  preserve names if present.
 * see the comment on selectnumberedseries for why series groups are
 * treated separately.
 */
static Object 
selectnumberedgroup(Group g, Object numbers)
{
    Object selected;
    Group newg = NULL;
    char *cp;
    int i, count, members;
    int *memberlist = NULL;

    /* if empty group, can't select.
     */
    if (!DXGetMemberCount(g, &members) || members == 0) {
	DXSetError(ERROR_INVALID_DATA, "#11310"); 
	/* group has no members */
	return NULL;
    }

    /* if no numbers, return the first member */
    if (!numbers) {
        selected = DXGetEnumeratedMember(g, 0, NULL);
        if(!selected)
            return NULL;
        
        return selected;
    }
    
    /* if one number, return it */
    if (DXExtractInteger(numbers, &i)) {
        selected = DXGetEnumeratedMember(g, i, NULL);
	if(!selected) {
	    if (members == 1) {
		DXSetError(ERROR_BAD_PARAMETER, 
			 "'%s' must be 0, since the group has only one member",
			 "which member");
	    } else {
		DXSetError(ERROR_BAD_PARAMETER, 
		   "'%s' must be an integer or integer list between %d and %d",
                     "which member", 0, members-1);
	    }
	    return NULL;
	}
        
        return selected;
    }

    /* if a list of numbers, build up a new group */
    newg = (Group)DXCopy((Object)g, COPY_ATTRIBUTES);
    if (!newg)
        return NULL;
    
    if (!DXQueryParameter(numbers, TYPE_INT, 0, &count)) {
        DXSetError(ERROR_BAD_PARAMETER, 
                 "'%s' must be an integer or integer list",
                 "which member");
        goto error;
    }

    memberlist = (int *)DXAllocate(sizeof(int) * count);
    if (!memberlist)
        goto error;
    
    if (!DXExtractParameter(numbers, TYPE_INT, 0, count, (Pointer)memberlist))
        goto error;


    for (i=0; i<count; i++) {
        selected = DXGetEnumeratedMember(g, memberlist[i], &cp);
	if(!selected) {
	    if (members == 1) {
		DXSetError(ERROR_BAD_PARAMETER, 
			 "'%s' must be 0, since the group has only one member",
			 "which member");
	    } else {
		DXSetError(ERROR_BAD_PARAMETER, 
                  "'%s' must be an integer or integer list between %d and %d",
                     "which member", 0, members-1);
	    }
	    goto error;
	}
        
        if (!DXSetMember(newg, cp, selected))
            goto error;
        
    }
     
    DXFree((Pointer)memberlist);
    return (Object)newg;

  error:
    DXFree((Pointer)memberlist);
    DXDelete((Object)newg);
    return NULL;
}

/* select an item from a list by index number.  this works for number lists
 * as well as string lists.
 */
static Array
selectnumberedlist(Array a, Object numbers)
{
    Array new = NULL;
    Type type;
    Category cat;
    int rank, shape[MAXRANK];
    int items;
    int size;
    int i, count;
    int *memberlist = NULL;
    char *src, *src2;


    /* get info about the old array, and make an empty new array.
     */
    if (!DXGetArrayInfo(a, &items, &type, &cat, &rank, shape))
        return NULL;
    
    if (items == 0) {
	DXSetError(ERROR_INVALID_DATA, "list contains no items");
	return NULL;
    }

    new = DXNewArrayV(type, cat, rank, shape);
    if (!new)
        goto error;
    

    /* if numbers is NULL, return first item in array.
     */
    if (!numbers) {
        
	/* copy first data from old to new */
	src = (char *)DXGetArrayData(a);
        if (!src)
            goto error;
        
        if (!DXAddArrayData(new, 0, 1, (Pointer)src))
            goto error;
        
        return new;
	
    }


    /* if numbers is one item, return it
     */

    if (DXExtractInteger(numbers, &i)) {

        /* range check */
        if (i < 0 || i >= items) {
	    if (items == 1) {
		DXSetError(ERROR_BAD_PARAMETER, 
			 "'%s' must be 0, since the list has only one item",
			 "which item");
	    } else {
		DXSetError(ERROR_BAD_PARAMETER, 
		   "'%s' must be an integer or integer list between %d and %d",
			 "which item", 0, items-1);
	    }
	    goto error;
        }
	
        size = DXGetItemSize(a);
	if (size <= 0)
	    goto error;

        /* copy Nth data item data from old to new */
        src = (char *)DXGetArrayData(a);
        if (!src)
            goto error;

        src += size * i;
        
        if (!DXAddArrayData(new, 0, 1, (Pointer)src))
            goto error;
        
        return new;

    }

    /* if a list of numbers, build up a new array
     */
    if (!DXQueryParameter(numbers, TYPE_INT, 0, &count)) {
        DXSetError(ERROR_BAD_PARAMETER, 
                 "'%s' must be an integer or integer list",
                 "which item");
        goto error;
    }
    
    memberlist = (int *)DXAllocate(sizeof(int) * count);
    if (!memberlist)
        goto error;
    
    if (!DXExtractParameter(numbers, TYPE_INT, 0, count, (Pointer)memberlist))
        goto error;
    
    
    src = (char *)DXGetArrayData(a);
    if (!src)
	goto error;

    size = DXGetItemSize(a);
    if (size <= 0)
	goto error;
    
    
    for (i=0; i<count; i++) {

        /* range check */
        if (memberlist[i] < 0 || memberlist[i] >= items) {
	    if (items == 1) {
		DXSetError(ERROR_BAD_PARAMETER, 
			 "'%s' must be 0, since the list has only one item",
			 "which item");
	    } else {
		DXSetError(ERROR_BAD_PARAMETER, 
		   "'%s' must be an integer or integer list between %d and %d",
			 "which item", 0, items-1);
	    }
	    goto error;
        }
	
        src2 = src + (size * memberlist[i]);

	if (!DXAddArrayData(new, i, 1, (Pointer)src2))
	    goto error;
	
    }

    DXFree((Pointer)memberlist);
    return new;

  error:
    DXFree((Pointer)memberlist);
    DXDelete((Object)new);
    return ERROR;
}

/* this routine handles the case where string data comes in as a string object
 * instead of an array of type string.  a string object can contain only a
 * single item, so this can only succeed if the "which item" input is either
 * not set or is set and the value is 0.
 * this could have been handled by the selectnumberedlist code but it's so
 * different that it was cleaner to separate it out into a different routine.
 */
static Object 
selectnumberedstring(Object o, Object numbers)
{
    char *cp;
    int i;

    if (DXGetObjectClass(o) != CLASS_STRING) {
	DXSetError(ERROR_INTERNAL, 
		   "selectnumberedstring routine called on non-string input");
	return NULL;
    }

    cp = DXGetString((String)o);
    if (!cp) {
	DXSetError(ERROR_INVALID_DATA, 
		   "input is an empty string, must be group or list");
	return NULL;
    }

    /* default - return first (and only) member of list. */
    if (numbers == NULL)
        return o;

    /* if numbers contains one item and the value is 0, return it;
     * otherwise if there is more than one item in the list
     * or the value isn't 0, it's an error.
     */
    if (DXExtractInteger(numbers, &i) && (i == 0))
	return o;


    DXSetError(ERROR_BAD_PARAMETER, 
	       "'%s' must be 0, since the string has only one item",
	       "which item");
    return NULL;
}


/* omit members from a series by index number.  the index is different from
 * the series position; the series position is a float and can be any value.
 * series groups are handled differently from other types of groups because 
 * the series position has to be preserved if the output is also a group
 * (which happens unless all but one member is omitted).
 */
static Object 
omitnumberedseries(Series s, Object numbers)
{
    Object omitted;
    Series news = NULL;
    float position;
    int i, newi, j, selected;
    int count, members;
    int *memberlist = NULL;

    /* if empty group, return error
     */
    if (!DXGetMemberCount((Group)s, &members) || members == 0) {
	DXSetError(ERROR_INVALID_DATA, "#11310"); 
	/* group has no members */
	return NULL;
    }

    /* if no numbers, return all but the first member */
    if (!numbers) {

	news = (Series)DXCopy((Object)s, COPY_ATTRIBUTES);
	if (!news)
	    return NULL;
	
	for (i=1; i<members; i++) {
	    omitted = DXGetSeriesMember(s, i, &position);
	    if (!omitted)
		goto error;
        
	    if (!DXSetSeriesMember(news, i-1, (double)position, omitted))
		goto error;

	}

        return (Object)news;
    }
    
    /* if one number, return all but it */
    if (DXExtractInteger(numbers, &selected)) {

	if (selected >= members) {
	    if (members == 1) {
		DXSetError(ERROR_BAD_PARAMETER, 
			"'%s' must be 0, since the series has only one member",
			"which member");
	    } else {
		DXSetError(ERROR_BAD_PARAMETER, 
		   "'%s' must be an integer or integer list between %d and %d",
                     "which member", 0, members-1);
	    }
	    return NULL;
	}

	news = (Series)DXCopy((Object)s, COPY_ATTRIBUTES);
	if (!news)
	    return NULL;
	
	for (i=0, newi=0; i<members; i++) {
	    if (i == selected)
		continue;

	    omitted = DXGetSeriesMember(s, i, &position);
	    if (!omitted)
		goto error;
	    
	    if (!DXSetSeriesMember(news, newi++, (double)position, omitted))
		goto error;

	}

        return (Object)news;
    }

    /* if a list of numbers, build up a new series */
    news = (Series)DXCopy((Object)s, COPY_ATTRIBUTES);
    if (!news)
        return NULL;
    
    if (!DXQueryParameter(numbers, TYPE_INT, 0, &count)) {
        DXSetError(ERROR_BAD_PARAMETER, 
                 "'%s' must be an integer or integer list",
                 "which member");
        goto error;
    }

    memberlist = (int *)DXAllocate(sizeof(int) * count);
    if (!memberlist)
        goto error;
    
    if (!DXExtractParameter(numbers, TYPE_INT, 0, count, (Pointer)memberlist))
        goto error;

    /* sort list here into numeric order */
    qsort((Pointer)memberlist, count, sizeof(int), int_compare);

    /* look for illegal values */
    for (i=0; i<count; i++) {
	if (memberlist[i] >= members) {
	    if (members == 1) {
		DXSetError(ERROR_BAD_PARAMETER, 
			"'%s' must be 0 since the series has only one member",
			"which member");
	    } else {
		DXSetError(ERROR_BAD_PARAMETER, 
	"'%s' (item %d) must be an integer or integer list between %d and %d",
		   "which member", i, 0, members-1);
	    }
	    goto error;
	}
    }

    for (i=0, newi=0; i<members; i++) {

	/* anything on the list doesn't get copied */
	if (bsearch((Pointer)&i, (Pointer)memberlist, count, sizeof(int),
		    int_compare))
	    continue;
	
        omitted = DXGetSeriesMember(s, i, &position);
	if (!omitted)
	    goto error;
        
        if (!DXSetSeriesMember(news, newi++, (double)position, omitted))
            goto error;
        
    }
     
    DXFree((Pointer)memberlist);
    return (Object)news;

  error:
    DXFree((Pointer)memberlist);
    DXDelete((Object)news);
    return NULL;
}

/* omit members from a group by name.  normally used on generic groups,
 * composite fields and multigrid groups, but technically not illegal for
 * series groups (it is hard to create a series group where the members have
 * names, and if there is more than one member in the output (so the output is
 * a series and not just the individual member), the series positions will
 * be lost.)
 */
static Object 
omitnamedgroup(Group g, Object names)
{
    Object omitted;
    Group newg = NULL;
    char *membername, *cp, *namelist = NULL;
    int namlen, count, members;
    int i;

    /* if empty group, return error
     */
    if (!DXGetMemberCount(g, &members) || members == 0) {
	DXSetError(ERROR_INVALID_DATA, "#11310");  /* group has no members */
	return NULL;
    }
    
    /* if no names, return all but the first member */
    if (!names) {
	newg = (Group)DXCopy((Object)g, COPY_ATTRIBUTES);
	if (!newg)
	    return NULL;
	
	for (i=1; i<members; i++) {
	    omitted = DXGetEnumeratedMember(g, i, &membername);
	    if (!omitted)
		goto error;
        
	    if (!DXSetMember(newg, membername, omitted))
		goto error;

	}

        return (Object)newg;
    }
    
    /* if just one name, return all but it */
    if (DXExtractString(names, &membername)) {

	/* if you are asking for the output to not have this
	 * member, then it really isn't an error if the group
	 * member isn't there in the first place...
	 * but print a warning in case the name is misspelled.
	 */
        omitted = DXGetMember(g, membername);
	if (!omitted) {
	    DXWarning("#11320", membername);
	    return (Object)g;
	}
	
	newg = (Group)DXCopy((Object)g, COPY_ATTRIBUTES);
	if (!newg)
	    return NULL;
	
	for (i=0; i<members; i++) {
	    omitted = DXGetEnumeratedMember(g, i, &cp);
	    if (!omitted)
		goto error;
        
	    if (!strcmp(membername, cp))
		continue;
	    
	    if (!DXSetMember(newg, cp, omitted))
		goto error;

	}

        return (Object)newg;
    }


    /* if a list of names, build up a new group */
    newg = (Group)DXCopy((Object)g, COPY_ATTRIBUTES);
    if (!newg)
        return NULL;

    /* sort the names */
    namlen = DXGetItemSize((Array)names);
    if (namlen <= 0)
	goto error;

    if (!DXGetArrayInfo((Array)names, &count, NULL, NULL, NULL, NULL))
	goto error;
    
    namelist = (char *)DXAllocate(namlen * count);
    if (!namelist)
	goto error;

    cp = (char *)DXGetArrayData((Array)names);
    if (!cp)
	goto error;

    /* if you are asking for the output to not have these
     * members, then it really isn't an error if any of the group
     * members aren't there in the first place...
     * but print a warning in case the name is misspelled.
     */
    for (i=0; i<count; i++) {
	/* first see if they are all valid names */
        omitted = DXGetMember(g, cp + i*namlen);
	if (!omitted) 
	    DXWarning("#11320", cp + i*namlen);
    }

    /* sort the list so it's easier to look up names in it */
    memcpy(namelist, cp, namlen*count);

    qsort((Pointer)namelist, count, namlen, str_compare);

    for (i=0; i < members; i++) {
        omitted = DXGetEnumeratedMember(g, i, &cp);
        if (!omitted)
	    goto error;

	/* anything on the list doesn't get copied */
        if (cp && bsearch((Pointer)cp, (Pointer)namelist, count, namlen,
			  str_compare))
	    continue;

        if (!DXSetMember(newg, cp, omitted))
            goto error;
    }
     
    DXFree((Pointer)namelist);
    return (Object)newg;

  error:
    DXFree((Pointer)namelist);
    DXDelete((Object)newg);
    return NULL;
}

/* omit group members by index number.  preserve names if present.
 * see the comment on omitnumberedseries for why series groups are
 * treated separately.
 */
static Object 
omitnumberedgroup(Group g, Object numbers)
{
    Object omitted;
    Group newg = NULL;
    char *name;
    int i, j, selected, skipme;
    int count, members;
    int *memberlist = NULL;

    /* if empty group, return error
     */
    if (!DXGetMemberCount(g, &members) || members == 0) {
	DXSetError(ERROR_INVALID_DATA, "#11310"); 
	/* group has no members */
	return NULL;
    }

    /* if no numbers, return all but the first member */
    if (!numbers) {

	newg = (Group)DXCopy((Object)g, COPY_ATTRIBUTES);
	if (!newg)
	    return NULL;
	
	for (i=1; i<members; i++) {
	    omitted = DXGetEnumeratedMember(g, i, &name);
	    if (!omitted)
		goto error;
        
	    if (!DXSetMember(newg, name, omitted))
		goto error;

	}

        return (Object)newg;
    }
    
    /* if one number, return all but it */
    if (DXExtractInteger(numbers, &selected)) {

	if (selected >= members) {
	    if (members == 1) {
		DXSetError(ERROR_BAD_PARAMETER, 
			"'%s' must be 0, since the series has only one member",
			"which member");
	    } else {
		DXSetError(ERROR_BAD_PARAMETER, 
		   "'%s' must be an integer or integer list between %d and %d",
                     "which member", 0, members-1);
	    }
	    return NULL;
	}

	newg = (Group)DXCopy((Object)g, COPY_ATTRIBUTES);
	if (!newg)
	    return NULL;
	
	for (i=0; i<members; i++) {
	    if (i == selected)
		continue;

	    omitted = DXGetEnumeratedMember(g, i, &name);
	    if (!omitted)
		goto error;
	    
	    if (!DXSetMember(newg, name, omitted))
		goto error;

	}

        return (Object)newg;
    }

    /* if a list of numbers, build up a new group */
    newg = (Group)DXCopy((Object)g, COPY_ATTRIBUTES);
    if (!newg)
        return NULL;
    
    if (!DXQueryParameter(numbers, TYPE_INT, 0, &count)) {
        DXSetError(ERROR_BAD_PARAMETER, 
                 "'%s' must be an integer or integer list",
                 "which member");
        goto error;
    }

    memberlist = (int *)DXAllocate(sizeof(int) * count);
    if (!memberlist)
        goto error;
    
    if (!DXExtractParameter(numbers, TYPE_INT, 0, count, (Pointer)memberlist))
        goto error;

    /* look for illegal values */
    for (i=0; i<count; i++) {
	if (memberlist[i] >= members) {
	    if (members == 1) {
		DXSetError(ERROR_BAD_PARAMETER, 
			"'%s' must be 0 since the series has only one member",
			"which member");
	    } else {
		DXSetError(ERROR_BAD_PARAMETER, 
	"'%s' (item %d) must be an integer or integer list between %d and %d",
		   "which member", i, 0, members-1);
	    }
	    goto error;
	}
    }

    /* sort list here into numeric order */
    qsort((Pointer)memberlist, count, sizeof(int), int_compare);

    for (i=0; i<members; i++) {

	/* anything on the list doesn't get copied */
	if (bsearch((Pointer)&i, (Pointer)memberlist, count, sizeof(int),
		    int_compare))
	    continue;
	
        omitted = DXGetEnumeratedMember(g, i, &name);
	if (!omitted)
	    goto error;
        
        if (!DXSetMember(newg, name, omitted))
            goto error;
        
    }
     
    DXFree((Pointer)memberlist);
    return (Object)newg;

  error:
    DXFree((Pointer)memberlist);
    DXDelete((Object)newg);
    return NULL;
}


/* omit an item from a list by index number.  this works for number lists
 * as well as string lists.
 */
static Array
omitnumberedlist(Array a, Object numbers)
{
    Array new = NULL;
    Type type;
    Category cat;
    int rank, shape[MAXRANK];
    int items;
    int size;
    int i, newi, count;
    int *memberlist = NULL;
    char *src, *src2;


    /* get info about the old array, and make an empty new array.
     */
    if (!DXGetArrayInfo(a, &items, &type, &cat, &rank, shape))
        return NULL;
    
    if (items == 0) {
	DXSetError(ERROR_INVALID_DATA, "list contains no items");
	return NULL;
    }

    new = DXNewArrayV(type, cat, rank, shape);
    if (!new)
        goto error;
    

    /* if numbers is NULL, copy all but first item in array.
     */
    if (!numbers) {
        
	/* copy data from old to new */
	src = (char *)DXGetArrayData(a);
        if (!src)
            goto error;
        
	size = DXGetItemSize(a);
        if (!DXAddArrayData(new, 0, items-1, (Pointer)(src+size)))
            goto error;
        
        return new;
	
    }


    /* if numbers is one item, copy all but it
     */

    if (DXExtractInteger(numbers, &i)) {

        /* range check */
        if (i < 0 || i >= items) {
	    if (items == 1) {
		DXSetError(ERROR_BAD_PARAMETER, 
			 "'%s' must be 0, since the list has only one item",
			 "which item");
	    } else {
		DXSetError(ERROR_BAD_PARAMETER, 
		   "'%s' must be an integer or integer list between %d and %d",
			 "which item", 0, items-1);
	    }
	    goto error;
        }
	
        size = DXGetItemSize(a);
	if (size <= 0)
	    goto error;

        /* copy 0 to Nth-1 and Nth to End data items from old to new */
        src = (char *)DXGetArrayData(a);
        if (!src)
            goto error;

	if (i > 0 && !DXAddArrayData(new, 0, i, (Pointer)src))
	    goto error;
        
        src += size * (i+1);
        
        if (i < items-1 && !DXAddArrayData(new, i, items-i-1, (Pointer)src))
            goto error;
        
        return new;

    }

    /* if a list of numbers, build up a new array
     */
    if (!DXQueryParameter(numbers, TYPE_INT, 0, &count)) {
        DXSetError(ERROR_BAD_PARAMETER, 
                 "'%s' must be an integer or integer list",
                 "which item");
        goto error;
    }
    
    memberlist = (int *)DXAllocate(sizeof(int) * count);
    if (!memberlist)
        goto error;
    
    if (!DXExtractParameter(numbers, TYPE_INT, 0, count, (Pointer)memberlist))
        goto error;
    
    
    /* look for illegal values */
    for (i=0; i<count; i++) {
	if (memberlist[i] >= items) {
	    if (items == 1) {
		DXSetError(ERROR_BAD_PARAMETER, 
			"'%s' must be 0 since the series has only one member",
			"which member");
	    } else {
		DXSetError(ERROR_BAD_PARAMETER, 
	"'%s' (item %d) must be an integer or integer list between %d and %d",
		   "which member", i, 0, items-1);
	    }
	    goto error;
	}
    }

    /* sort list here into numeric order */
    qsort((Pointer)memberlist, count, sizeof(int), int_compare);

    src = (char *)DXGetArrayData(a);
    if (!src)
	goto error;

    size = DXGetItemSize(a);
    if (size <= 0)
	goto error;
    
    
    for (i=0, newi=0; i<items; i++) {

	/* anything on the list doesn't get copied */
	if (bsearch((Pointer)&i, (Pointer)memberlist, count, sizeof(int),
		    int_compare))
	    continue;
	
        src2 = src + (size * i);

	if (!DXAddArrayData(new, newi++, 1, (Pointer)src2))
	    goto error;
	
    }

    DXFree((Pointer)memberlist);
    return new;

  error:
    DXFree((Pointer)memberlist);
    DXDelete((Object)new);
    return ERROR;
}

/* this routine handles the case where string data comes in as a string object
 * instead of an array of type string.  a string object can contain only a
 * single item, so this can only succeed if the "which item" input is either
 * not set or is set and the value is 0.
 * this could have been handled by the omitnumberedlist code but it's so
 * different that it was cleaner to separate it out into a different routine.
 */
static Object 
omitnumberedstring(Object o, Object numbers)
{
    char *cp;
    int i;

    if (DXGetObjectClass(o) != CLASS_STRING) {
	DXSetError(ERROR_INTERNAL, 
		   "omitnumberedstring routine called on non-string input");
	return NULL;
    }

    cp = DXGetString((String)o);
    if (!cp) {
	DXSetError(ERROR_INVALID_DATA, 
		   "input is an empty string, must be group or list");
	return NULL;
    }

    /* default - delete first (and only) member of list. */
    if (numbers == NULL)
        return (Object)DXNewArray(TYPE_STRING, CATEGORY_REAL, 1, 1);

    /* if numbers contains one item and the value is 0, return same as above.
     * otherwise if there is more than one item in the list
     * or the value isn't 0, it's an error.
     */
    if (DXExtractInteger(numbers, &i) && (i == 0))
        return (Object)DXNewArray(TYPE_STRING, CATEGORY_REAL, 1, 1);


    DXSetError(ERROR_BAD_PARAMETER, 
	       "'%s' must be 0, since the string has only one item",
	       "which item");
    return NULL;
}

