/***********************************************************************/
/* 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 <ctype.h>
#include <dx/dx.h>

#define Return_Internal_Error \
    DXSetError(ERROR_INTERNAL, "at file %s, line %d",  __FILE__, __LINE__); \
    return ERROR


/*******

the general idea:

1) what is this object?
     if a group, list some of the member names ( < 10? )
     if a series, list some of the series pos (")
     if a list, list some of the values (")

2) if this is a potentially renderable thing (field, group, etc)
     what is the bbox?
     what is the data max/min?
     how many data values, what kind?
     what kind of connections are there?  Ndim?
     *** what kind of colors (float/byte, full list/delayed) ***

3) is it renderable at this point?
     if not, why not?
     if so, what is going to be rendered?


*******/
   



/* reasons why an object might not be renderable.
 */
struct renderinfo {
    int nfields;        /* need to be more than one to be renderable */
    int nlights;        /* if > 1, default lighting disabled */
    int badobjs;        /* if > 1, will get error from render */
    Class *badclass;    /* list of bad obj types */
    int badpos;         /* pos <= 0 or > 3d, error */
    int nocolors;       /* if missing colors, error from render */
};
struct nopicture {
    int nodep;          /* no errors, but no picture either */
    int novalid;
    int nopos;          /* no positions component (empty field) */
};
struct nD {
    int c0;             /* types of connections in the input */
    int c1;
    int c2;
    int c3;             /* points, lines, surfaces, volumes */
    int cplus;          /* more than 3d connections */
    int cbad;           /* an unrecognized type or malformed component */
    int cmissing;       /* missing elem type attribute */
};
struct nColors {
    int cubyte;         /* types of color representations in the input */
    int cfloat; 
    int cbytemap;
    int cunknown;
};
			   

struct info {
    struct renderinfo ri;
    struct nopicture np;
    struct nD nd;
    struct nColors nc;
};


static Error saytype(Object o, char *opt);
static Error saydata(Object o, char *opt);
static Error sayrender(Object o, char *opt);

static Error sayclass(Object o, char *intro, int descend);
static Error saymembers(Group g);
static Error traverse(Object o, struct info *ip, int nosee);
static Error validfield(Field f, struct info *ip, int nosee);
#if 0
static int queryNDconnections(Field f, int n);
#endif
static Error validcount(Field f, char *component, int *icount);
#if 0
static Error atleast1field(Object o);
static Error atleast1image(Object o);
static Error atleast1contype(Object o, int n);
#endif
static int qimage(Object o);

#define MEMBERCOUNT  10    /* print info about this many members */

/* figure a way to add a constant string to the front of this */
#define DescribeMsg DXMessage
    


/* 0 = input
 * 1 = string flags: "structure", "details", "render", "all"
 *     (default "all")
 *
 * which mean:    tell me about the input structure
 *                tell me about the geometry (positions, connections, bbox)
 *                tell me about the renderability
 */
int
m_Describe(Object *in, Object *out)
{
    char *opt = "all";


    if (!in[0]) {
	DescribeMsg("Object Description:");
	DescribeMsg("No input object was specified, or the input object is NULL.");
	return OK;
    }
    
    if (in[1] && !DXExtractString(in[1], &opt)) {
	DXErrorReturn(ERROR_BAD_PARAMETER, "Options string must be one of 'structure', `details', `render', or `all'");
    }
    
    /* ???  i should lcase opt, but have to make a copy first. */
    if (strcmp(opt, "all") && strcmp(opt, "structure") && 
	strcmp(opt, "details") && strcmp(opt, "render")) {
	
	DXErrorReturn(ERROR_BAD_PARAMETER, "Options string must be one of 'structure', `details', `render', or `all'");
    }
    
    DXBeginLongMessage();

    if (!strcmp(opt, "all"))
	DescribeMsg("Object Description:\n");

    if (!strcmp(opt, "all") || !strcmp(opt, "structure")) {
	if (!strcmp(opt, "structure"))
	    DescribeMsg("Object Structure:\n");
	if (!saytype(in[0], opt))
	    goto done;
    }

    if (!strcmp(opt, "all") || !strcmp(opt, "details")) {
	if (!strcmp(opt, "details"))
	    DescribeMsg("Object Details:\n");
	if (!saydata(in[0], opt))
	    goto done;
    }

    if (!strcmp(opt, "all") || !strcmp(opt, "render")) {
	if (!strcmp(opt, "render"))
	    DescribeMsg("Object Renderability:\n");
	if (!sayrender(in[0], opt))
	    goto done;
    }

    DescribeMsg("\n");
    DXEndLongMessage();

  done:
    return (DXGetError()==ERROR_NONE ? OK : ERROR);
}


#define IS    "is a"
#define IS_V  "is an"


static Error saytype(Object o, char *opt)
{
    return sayclass(o, "Input object", 1);
}

static Error sayclass(Object o, char *intro, int descend)
{
    int count;
    int i;

    switch (DXGetObjectClass(o)) {
	
      case CLASS_GROUP:
	if (!DXGetMemberCount((Group)o, &count))
	    return ERROR;
	
	switch(DXGetGroupClass((Group)o)) {
	    
	  case CLASS_GROUP:
	    DescribeMsg("%s %s Group which contains %d member%s.\n", 
		      intro, IS, count, (count==1 ? "" : "s"));
	    break;
	    
	  case CLASS_COMPOSITEFIELD:
	    DescribeMsg("%s %s Partitioned Field which contains %d partition%s.\n", 
		      intro, IS, count, (count==1 ? "" : "s"));
	    break;
	    
	  case CLASS_MULTIGRID:
	    DescribeMsg("%s %s Multigrid Group which contains %d member%s.\n", 
		      intro, IS, count, (count==1 ? "" : "s"));
	    break;
	    
	  case CLASS_SERIES:
	    DescribeMsg("%s %s Series Group which contains %d series member%s.\n", 
		      intro, IS, count, (count==1 ? "" : "s"));
	    DescribeMsg("To work with one member at a time use the `Select' module\n");

	    break;
	
	  default:
	    DescribeMsg("%s %s Group Object but of unrecognized Group Type.\n",
		      intro, IS);
	    break;
	}
	
	if (descend)
	    saymembers((Group)o);
	break;
	
      case CLASS_FIELD:
        DescribeMsg("%s %s Field, the basic data carrying structure in DX.\n", 
		  intro, IS);
        break;

      case CLASS_ARRAY:
	DescribeMsg("%s %s Array Object, containing a list of data values.\n",
		  intro, IS_V);

	switch (DXGetArrayClass((Array)o)) {
	    
	  case CLASS_ARRAY:
	    break;

	  case CLASS_PRODUCTARRAY:
	  case CLASS_MESHARRAY:
	  case CLASS_PATHARRAY:
	  case CLASS_CONSTANTARRAY:
	  case CLASS_REGULARARRAY:
	    DescribeMsg("These values are stored in a compact format to save space.\n");
	    break;
	
	  default:
	    DescribeMsg("The Array is of an unrecognized type.\n");
	    break;
	}

	/* ??? add code here ??? - print the first N values? */
	break;
	
      case CLASS_STRING:
	DescribeMsg("%s %s String, a list of character values\n",
		  intro, IS);
	DescribeMsg("It contains the value `%s'\n", DXGetString((String)o));
	break;
	
      case CLASS_CAMERA:
	DescribeMsg("%s %s Camera, used to set the viewpoint for Rendering.\n",
		  intro, IS);
	break;

      case CLASS_XFORM:
	DescribeMsg("%s %s Transformed object, meaning that a transformation will be applied to the final object before rendering.\n",
		  intro, IS);
	/* sayclass of xformed obj? */
	break;

      case CLASS_OBJECT:
	DescribeMsg("%s %s Generic Object, of unrecognized type.\n", intro, IS);
	break;

      case CLASS_LIGHT:
	DescribeMsg("%s %s Light, used to set the Lighting parameters for Rendering.\n",
		  intro, IS);
	break;
	
      case CLASS_CLIPPED:
	DescribeMsg("%s %s Clipped Object, which means that at Rendering time part of the object will be invisible.\n",
		    intro, IS);
	/* sayclass of clipped obj? */
	break;
	
      case CLASS_INTERPOLATOR:
	DescribeMsg("%s %s Interpolator. Internal use only.\n", intro, IS);
	break;
	
      case CLASS_SCREEN:
	DescribeMsg("%s %s Screen Object, meaning this object stays aligned with the screen.\n", 
		  intro, IS);
	/* say something about the screen obj? */
	break;
	
      case CLASS_MAX:
	DescribeMsg("Unrecognized Object.  Object type above Class Max.\n");
	break;
	
      case CLASS_MIN:
	DescribeMsg("Unrecognized Object.  Object type below Class Min.\n");
	break;

      case CLASS_PRIVATE:
	DescribeMsg("%s %s Private object, defined by a user.\n", intro, IS);
	break;

      case CLASS_DELETED:
	DescribeMsg("%s %s Deleted Object. Should not happen.\n", intro, IS);
	break;

      default:
	DescribeMsg("%s %s Unrecognized object.  Bad return from GetObjectType().\n",
		  intro, IS);
	break;
    }

    return OK;
}

/* print something about each member, up to MEMBERCOUNT items */
static Error saymembers(Group g)
{
    int i, count;
    char *name;
    Object subo, firstsubo;
    int same;
    Class grouptype;
    float position;
    
    
    if (!DXGetMemberCount(g, &count))
	return ERROR;
    
    /* no members, nothing to do here */
    if (count <= 0)
	return OK;

    /* save group type; we'll use it later on */
    grouptype = DXGetGroupClass(g);
    switch (grouptype) {
	
      case CLASS_GROUP:
	if (count <= MEMBERCOUNT) {
	    for (i=0; i<count; i++) {
		if (!(subo = DXGetEnumeratedMember(g, i, &name)))
		    return ERROR;
		if (name)
		    DescribeMsg(" member %d is named %s\n", i, name);
	    }
	} else {
	    for (i=0; i<MEMBERCOUNT/2; i++) {
		if (!(subo = DXGetEnumeratedMember(g, i, &name)))
		    return ERROR;
		if (name)
		    DescribeMsg(" member %d is named %s\n", i, name);
	    }
	    DescribeMsg(" ...\n");
	    for (i=count-MEMBERCOUNT/2; i<count; i++) {
		if (!(subo = DXGetEnumeratedMember(g, i, &name)))
		    return ERROR;
		if (name)
		    DescribeMsg(" member %d is named %s\n", i, name);
	    }
	}
	break;
	
      case CLASS_COMPOSITEFIELD:
	break;
	
      case CLASS_MULTIGRID:
	/* bbox? */
	break;
	
      case CLASS_SERIES:
	if (count <= MEMBERCOUNT) {
	    for (i=0; i<count; i++) {
		if (!(subo = DXGetSeriesMember((Series)g, i, &position)))
		    return ERROR;
		DescribeMsg(" member %d is at position %g\n", i, position);
	    }
	} else {
	    for (i=0; i<MEMBERCOUNT/2; i++) {
		if (!(subo = DXGetSeriesMember((Series)g, i, &position)))
		    return ERROR;
		DescribeMsg(" member %d is at position %g\n", i, position);
	    }
	    DescribeMsg(" ...\n");
	    for (i=count-MEMBERCOUNT/2; i<count; i++) {
		if (!(subo = DXGetSeriesMember((Series)g, i, &position)))
		    return ERROR;
		DescribeMsg(" member %d is at position %g\n", i, position);
	    }
	    
	}
	break;
	
      default:
	break;
    }

    /* if only one member, say what it is and return */
    if (count <= 1) {
	subo = DXGetEnumeratedMember(g, 0, NULL);
	return sayclass(subo, "The member", 0);
    }
	
    /* if there is more than one member of this group, check out
     * whether the group members are all the same type of object.
     * this has to be true for a composite field and should be true
     * for a multigrid, and usually for a series.
     */
    same = 1;
    firstsubo = DXGetEnumeratedMember(g, 0, NULL);
    for (i=1; i<count; i++) {
	if (!(subo = DXGetEnumeratedMember(g, i, NULL)))
	    return ERROR;
	if (DXGetObjectClass(firstsubo) != DXGetObjectClass(subo)) {
	    same = 0;
	    break;
	}
    }
    
    switch (grouptype) {
      case CLASS_GROUP:
	if (same)
	    sayclass(firstsubo, "Each group member", 0);
	else 
	    DescribeMsg("The group contains members of different object types\n");
	break;
	
      case CLASS_COMPOSITEFIELD:
	if (!same || (DXGetObjectClass(firstsubo) != CLASS_FIELD)) {
	    DescribeMsg("This is not a well-formed Partitioned Field.\n");
	    DescribeMsg("Members should all be Field objects and they are not.\n");
	    if (!same) 
		DescribeMsg("It contains members of more than one object type.\n");
	    if ((DXGetObjectClass(firstsubo) != CLASS_FIELD))
		DescribeMsg("It contains members which are not Field objects.\n");
	}
	break;
	
      case CLASS_MULTIGRID:
	/* what about partitioned members? i think they are ok */
	break;
#if 0
	if (!same || (DXGetObjectClass(firstsubo) != CLASS_FIELD)) {
	    DescribeMsg("This is not a well-formed MultiGrid Group.\n");
	    DescribeMsg("Members should all be Field objects and they are not.\n");
	    if (!same) 
		DescribeMsg("It contains members of more than one object type.\n");
	    if ((DXGetObjectClass(firstsubo) != CLASS_FIELD))
		DescribeMsg("It contains members which are not Field objects.\n");
	}
	break;
#endif
	
      case CLASS_SERIES:
	if (same)
	    sayclass(firstsubo, "Each series member", 0);
	else 
	    DescribeMsg("The series contains members of different object types\n");
	break;
	
      default:
	break;
    }
    
    return OK;
}


static void pinfo(int isitems, int items, int vitems, 
		  Type type, Category category, int rank, int *shape)
{
    int i;
    char tbuf[32];  /* temp buf */
    char mbuf[256]; /* message buf */

    mbuf[0] = '\0';

    if (isitems) {
	if (items != vitems)
	    sprintf(mbuf, "There %s %d data item%s (%d valid), each is of type ", 
		    (items==1 ? "is" : "are"), items, (items==1 ? "" : "s"), 
		    vitems);
	else
	    sprintf(mbuf, "There %s %d data item%s, each is of type ", 
		    (items==1 ? "is" : "are"), items, (items==1 ? "" : "s") );
    } else
	strcpy(mbuf, "Each item data type is ");

    switch (rank) {
      case 0:
	/* strcat(mbuf,  "scalar, "); */
	break;
      case 1:
	sprintf(tbuf, "%d-vector, ", shape[0]);
	strcat(mbuf, tbuf);
	break;
      case 2:
	sprintf(tbuf,  "%d by %d matrix, ", shape[0], shape[1]);
	strcat(mbuf, tbuf);
	break;
      default:
	sprintf(tbuf,  "%d ", shape[0]);
	strcat(mbuf, tbuf);
	for(i=1; i < rank; i++) {
	    sprintf(tbuf,  "by %d ", shape[i]);
	    strcat(mbuf, tbuf);
	}
	strcat(mbuf,  "tensor, ");
	break;
    }

    switch (category) {
      case CATEGORY_REAL:
	break;
      case CATEGORY_COMPLEX:
	strcat(mbuf,  "complex, "); break;
      case CATEGORY_QUATERNION:
	strcat(mbuf,  "quaternion, "); break;
      default:
	strcat(mbuf,  "unrecognized, "); break;
    }
    
    switch (type) {
      case TYPE_STRING:
	strcat(mbuf,  "string (a list of characters).\n"); break;
      case TYPE_SHORT:
	strcat(mbuf,  "short integer (2-byte or int*2).\n"); break;
      case TYPE_INT:
	strcat(mbuf,  "integer (4-byte or int*4).\n"); break;
      case TYPE_HYPER:
	strcat(mbuf,  "hyperlong (8-byte or int*8).\n"); break;
      case TYPE_FLOAT:
	strcat(mbuf,  "float (4-byte or real*4).\n"); break;
      case TYPE_DOUBLE:
	strcat(mbuf,  "double (8-byte or real*8).\n"); break;
      case TYPE_UINT:
	strcat(mbuf,  "unsigned integer (4-byte or int*4).\n"); break;
      case TYPE_USHORT:
	strcat(mbuf,  "unsigned short (2-byte or int*2).\n"); break;
      case TYPE_UBYTE:
	strcat(mbuf,  "unsigned byte (1-byte or int*1).\n"); break;
      case TYPE_BYTE:
	strcat(mbuf,  "byte (1-byte or int*1).\n"); break;
      default:
	strcat(mbuf,  "unrecognized.\n"); break;
    }

    DescribeMsg(mbuf);
    return;
}


#define MAXRANK 100

static Error saydata(Object o, char *opt)
{
    Array a;
    int nitems;
    Type type;
    Category category;
    int rank;
    int shape[MAXRANK];
    int valids;
    Point boxlist[8];
    Point vboxlist[8];
    float min, max, avg;

    if (DXGetObjectClass(o) == CLASS_ARRAY) {
	DXGetArrayInfo((Array)o, &nitems, &type, &category, &rank, shape);
	pinfo(1, nitems, nitems, type, category, rank, shape);
    } else if (DXGetObjectClass(o) == CLASS_FIELD) {
	a = (Array)DXGetComponentValue((Field)o, "positions");
	if (!a) {
	    DescribeMsg("The Field does not contain any positions.  It will not cause an error, but will be\n");
	    DescribeMsg("silently ignored by most modules since positions are required.\n");
	    DescribeMsg("Use the `Mark' or `Replace' module to create a `positions' component.\n");
	}
	a = (Array)DXGetComponentValue((Field)o, "data");
	if (!a) {
	    DescribeMsg("The Field contains nothing marked as data items.\n");
            DescribeMsg("If the Field does contain data under another name, use the `Mark' module\n");
            DescribeMsg("to select what should be used as data.\n");
	} else {
	    DXGetArrayInfo(a, &nitems, &type, &category, &rank, shape);
	    if (!validcount((Field)o, "data", &valids))
		return ERROR;
	    pinfo(1, nitems, valids, type, category, rank, shape);
	}
    } else if (!DXGetType((Object)o, &type, &category, &rank, shape)) {
	/* DescribeMsg("no data type information\n"); */
	DXResetError();
    } else
	pinfo(0, 0, 0, type, category, rank, shape);

    /* get bbox and print it in a non-threatening format */
    if (!DXBoundingBox(o, boxlist)) {
	/* DescribeMsg("Cannot get information about the positions of the data\n"); */
	DXResetError();
    } else {
	DescribeMsg("The positions are enclosed within the box defined by the corner points:\n");
	DescribeMsg("[ %g %g %g ] and [ %g %g %g ]\n", 
		  boxlist[0].x, boxlist[0].y, boxlist[0].z,
		  boxlist[7].x, boxlist[7].y, boxlist[7].z);
    }
    if (!DXValidPositionsBoundingBox(o, vboxlist)) {
	/* DescribeMsg("Cannot get information about the valid positions of the data\n"); */
	DXResetError();
    } else {
	if ((boxlist[0].x != vboxlist[0].x) || (boxlist[7].x != vboxlist[7].x)
	 || (boxlist[0].y != vboxlist[0].y) || (boxlist[7].y != vboxlist[7].y)
	 || (boxlist[0].z != vboxlist[0].z) || (boxlist[7].z != vboxlist[7].z)) {
	    DescribeMsg("The VALID positions are enclosed within the box defined by the corner points:\n");
	    DescribeMsg("[ %g %g %g ] and [ %g %g %g ]\n", 
		      vboxlist[0].x, vboxlist[0].y, vboxlist[0].z,
		      vboxlist[7].x, vboxlist[7].y, vboxlist[7].z);
	}
    }

    /* something about data stats */
    if (!DXStatistics(o, "data", &min, &max, &avg, NULL)) {
	/* DescribeMsg("Cannot get information about the data values\n"); */
	DXResetError();
    } else {
	DescribeMsg("Data range is:\n");
	DescribeMsg("minimum = %g, maximum = %g, average = %g\n", 
		    min, max, avg);
	if (rank > 0) {
	    /* ??? call vectorstats here if rank >= 1 
	     * look at the code in histogram; does rank=1, nothing
	     * about larger.
	     */
	    DescribeMsg("(These are the scalar statistics which will be used by modules\n");
	    DescribeMsg("which need scalar values.  The length is computed for vectors and\n");
	    DescribeMsg("the determinant for matricies.)\n");
	}
    }
    return OK;
	
}


#define NOTREADY  "Input is not ready to be rendered because"
#define READY     "Input is ready to be rendered"



static Error sayrender(Object o, char *opt)
{
    struct info info;
    int i;

    /* zero out the struct */
    memset((char *)&info, '\0', sizeof(struct info));

    /* and fill it with info */
    if (!traverse(o, &info, 0))
	goto error;


    /* sort out what we've found.  first report on things which
     * will prevent rendering.
     */
    if (info.ri.badpos > 0) {
	DescribeMsg("%s %d Field(s) in the Input object have bad positions (less than 1D or greater than 3D).\n",
		    NOTREADY, info.ri.badpos);
	DescribeMsg("If > 3D, use the `Slice' module or `Mark' and `Compute' to reduce them to 3D or less.\n");
	goto done;
    }

    if (info.ri.nocolors > 0) {
	if (info.ri.nocolors == 1)
	    DescribeMsg("%s the Field does not have colors yet.\n", 
			NOTREADY);
	else
	    DescribeMsg("%s %d Fields in the Input object do not have colors yet.\n", 
			NOTREADY, info.ri.nocolors);
	
	DescribeMsg("Use the `AutoColor', `AutoGreyScale', or `Color' modules to add colors.\n");
	goto done;
    }

    if (info.np.nodep > 0) {
	DescribeMsg("%s the `colors' component does not have a `dep' attribute\n",
		    NOTREADY);
	DescribeMsg("to indicate whether they are per vertex or per cell\n");
	DescribeMsg("Use the modules in the Structuring category to add the attribute.\n");
	goto done;
    }

    if (info.nc.cunknown > 0) {
	DescribeMsg("%s the Input contained Fields with an unsupported `color' component format.\n", NOTREADY);
	DescribeMsg("Use the `Color', `AutoColor', or `AutoGreyScale' to create a valid `color' component.\n");
	goto done;
    }

    /* warn about empty fields and fields were all items are invalid */
    if (info.np.novalid > 0) {
	if (info.ri.nfields == 0) { 
	    DescribeMsg("%s no valid items were found in any of the Field(s) in the input.\n", NOTREADY);
	    goto done;
	} else {
	    DescribeMsg("%d Fields(s) contain no valid items and will be ignored.\n",
			info.np.novalid);
	}
    }
    if (info.np.nopos > 0) {
	if (info.ri.nfields == 0) {	
	    DescribeMsg("%s none of the Field(s) in the Input object have a `positions' component.\n", NOTREADY);
	    DescribeMsg("Fields without positions are intentionally ignored without error by almost all modules.\n");
	    DescribeMsg("Use the `Mark' or `Replace' modules to create positions.\n");
	    goto done;
	} else {
	    DescribeMsg("%d Field(s) contain no positions and will be ignored.\n",
			info.np.nopos);
	}
    }


    if (info.ri.badobjs > 0) {
	for (i=0; i<info.ri.badobjs; i++) {
	    switch (info.ri.badclass[i]) {
	      case CLASS_STRING:
		DescribeMsg("A string cannot be rendered directly.  It can be converted\n");
		DescribeMsg("into text with the `Caption' or `Text' modules.\n");
		break;

	      case CLASS_ARRAY:
		DescribeMsg("A list of values cannot be rendered directly.  It can be converted\n");
		DescribeMsg("into geometry with the `AutoGlyph' module or into text with\n");
		DescribeMsg("the `Format' and then `Caption' or `Text' modules.\n");
		break;

	      case CLASS_CAMERA:
		DescribeMsg("A Camera cannot be part of the rendered object.\n");
		DescribeMsg("There is a separate Camera input on the `Render' and `Display' modules.\n");
		DescribeMsg("It is not required for the `Image' module but can be specified to control the initial or reset viewpoint.\n");
		break;

	      case CLASS_INTERPOLATOR:
	      case CLASS_MAX:
	      case CLASS_MIN:
	      case CLASS_PRIVATE:
	      case CLASS_DELETED:
	      default:
		DescribeMsg("An unexpected (and unrenderable) object was found in the input.\n");
		break;
	    }
	}
	goto done;  /* ??? or fall thru? */
    }
    
    
    /* check here for bad connection types? */
    if (info.nd.cbad > 0) {
	if (info.nd.cbad == 1)
	    DescribeMsg("%s the Field has a bad `connections' component.\n",
			NOTREADY);
	else
	    DescribeMsg("%s %d Fields in the Input object have a bad `connections' component.\n",
			NOTREADY, info.nd.cbad);
	goto done;
    }
    
    if (info.nd.cmissing > 0) {
	if (info.nd.cmissing == 1)
	    DescribeMsg("%s the Field has no `element type' attribute on the `connections' component.\n", NOTREADY);
	else
	    DescribeMsg("%s %d Fields have no `element type' attribute on the `connections' component.\n",
			NOTREADY, info.nd.cmissing);
	DescribeMsg("The renderer must know what type of geometry is in each Field (e.g. `triangles', `lines').\n");
	DescribeMsg("This attribute can be added using the modules in the Structuring category.\n");
	goto done;
    }
    
    if (info.nd.cplus > 0) {
	if (info.nd.cplus == 1)
	    DescribeMsg("%s the Field has a `connections' component of more than 3D.\n", NOTREADY);
	else
	    DescribeMsg("%s %d Fields have a `connections' component of more than 3D.\n", NOTREADY, info.nd.cplus);
	DescribeMsg("The `Slice' module can be used to reduce the input to 3D or lower.\n");
	goto done;
    }

    /* ok, we've checked for bad things.  how about nothing? */
    if (info.ri.nfields <= 0) {
	DescribeMsg("There is nothing to render in the input because it does not contain any Field objects.\n");
	DescribeMsg("Field objects are usually created by the `Import' module\n");
	DescribeMsg("or by the modules in the `Realization' category.\n");
	goto done;
    }

    
    
    /* ok, praps it can be rendered after all */
    
    /* warn about disabling the default lighting. */
    if (info.ri.nlights > 0)
	DescribeMsg("%d Light%s found in the Input; the default lighting will not be used.\n",
		    info.ri.nlights, info.ri.nlights > 1 ? "s were" : " was");
    
    
    
    /* if already image, suggest Display (w/no camera???) */
    if (qimage(o)) {
	DescribeMsg("Input is already an Image.  It can be displayed fastest using the `Display' module with no `Camera' input.\n");
	DescribeMsg("Use the `Image' module, which will be slower, if you want to resize the image, rotate it, zoom in or out,\n");
	DescribeMsg("or combine the image with other renderable items.\n");
    }
    
    /* connections?  points, lines, surfaces, volumes */
    if (info.nd.c0 > 0) {
	DescribeMsg("Input contains a set of scattered points.  They will be rendered\n");
	DescribeMsg("as individual pixels.  Use the `AutoGlyph' or `Glyph'\n");
	DescribeMsg("modules to make larger objects, or use the `Connect' module\n");
	DescribeMsg("for 2D data or the `AutoRegrid' or `Regrid' modules for 3D data.\n");
    }

    /* lines? */
    if (info.nd.c1 > 0) {
	DescribeMsg("Input contains a set of lines.  They will be rendered as a single\n");
	DescribeMsg("pixel-wide line.  Use the `Tube' or `Ribbon' modules\n");
	DescribeMsg("to make larger objects.\n");
    }
    
    /* surfaces */
    if (info.nd.c2 > 0) {
	DescribeMsg("Input contains a surface.  Planar (flat) surfaces exactly aligned\n");
	DescribeMsg("along the viewpoint will NOT be visible.  To see the\n");
	DescribeMsg("surface move the viewpoint slightly off-axis.\n");
	/* something about front/back colors and hardware rendering */
    }
	
    /* volumes */
    if (info.nd.c3) {
	DescribeMsg("Input contains a volume and will be rendered using volume rendering techniques.\n");
	DescribeMsg("These are the slowest types of objects to render, and they produce\n");
	DescribeMsg("images with `fuzzy cloud' effects.  To get a sharp image, convert\n");
	DescribeMsg("part of the volume into a surface using the `ShowBoundary',\n");
	DescribeMsg("`MapToPlane', or `Isosurface' modules.   The software renderer usually\n");
	DescribeMsg("gives better results for volumes since most hardware cards do not support volume rendering.\n");
    }

    /* and last, tell them about colors */
    if (info.nc.cbytemap > 0) {
	DescribeMsg("Input contains Fields with Delayed colors.  Delayed colors use a 256-entry lookup table of colors,\n");
	DescribeMsg("with the data values as the index into the table.\n"); 
	DescribeMsg("Data must be of type `ubyte' to use Delayed colors.\n");
	DescribeMsg("Each entry in the lookup table itself is a 3-float triplet specifying the color.\n");
	DescribeMsg("Valid color values are between 0 and 1 for each of Red, Green and Blue.\n");
    }
    if (info.nc.cubyte > 0) {
	DescribeMsg("Input contains Fields with Byte colors.  Byte colors use 3-byte triplets per data value.\n");
	DescribeMsg("Valid color values are between 0 and 255 for each of Red, Green and Blue.\n");
    }
    if (info.nc.cfloat > 0) {
	DescribeMsg("Input contains Fields with Float colors.  Float colors use 3-float triplets per data value.\n");
	DescribeMsg("Valid color values are between 0 and 1 for each of Red, Green and Blue.\n");
    }

  done:
    DXFree((Pointer)info.ri.badclass);
    return OK;

  error:
    DXFree((Pointer)info.ri.badclass);
    return ERROR;

}

#if 0
/* return all fields which aren't empty.
 */
static Field getpartplus(Object o, int *i, int *count)
{
    int again = 1;
    Field f;

    while (again) {
	f = DXGetPart(o, *i);
	if (!f)
	    return NULL;
	
	(*i)++;

	if (DXEmptyField(f))
	    continue;

	(*count)++;
	return f;
    }

    /* not reached */
}


/* unlike Inquire, say yes if you find any */
static int queryNDconnections(Field f, int n)
{
    char *cp;
    Object con;
    
    
    if (!(con = DXGetComponentValue(f, "connections"))) {
	if (n == 0)
	    return 1;
	return 0;
    }
    
    if (DXGetObjectClass(con) != CLASS_ARRAY)
	return 0;
    
    if (!DXGetStringAttribute(con, "element type", &cp))
	return 0;
    
    if (!strcmp(cp, "lines") && (n == 1))
	return 1;
    
    if ((!strcmp(cp, "triangles") || !strcmp(cp, "quads")) && (n == 2))
	return 1;
    
    if ((!strcmp(cp, "tetrahedra") || !strcmp(cp, "cubes")) && (n == 3))
	return 1;

    if (!strncmp(cp, "cubes", 5) && (strlen(cp) > 5) && (n > 3))
	return 1;

    return 0;
}
#endif

/* return how many items in this field are valid */
static Error validcount(Field f, char *component, int *icount)
{
    Array a = NULL;
    char *dep = NULL;
    char *invalid = NULL;
    InvalidComponentHandle ih;

    /* assume none and be happily surprised when it finds some */
    *icount = 0;

    /* this must be called at the field level.  if you want to recurse
     * through a hierarchy, do the recursion above this subroutine.
     */
    if (DXGetObjectClass((Object)f) != CLASS_FIELD) {
	Return_Internal_Error;
    }
    
    a = (Array)DXGetComponentValue(f, component);
    if (!a) {
	DXSetError(ERROR_INVALID_DATA, "field has no `%s' component",
		   component);
	return ERROR;
    }

    /* follow a dependency. */
    if (!DXGetStringAttribute((Object)a, "dep", &dep)) {
	DXSetError(ERROR_INVALID_DATA, 
		   "The `%s' component does not have a `dep' attribute",
		   component);
	return ERROR;
    }
    
    if (!DXGetArrayInfo(a, icount, NULL, NULL, NULL, NULL))
	return ERROR;

#define INVLEN 10     /* strlen("invalid "); */    
    
    if (!(invalid = (char *)DXAllocate(strlen(dep) + INVLEN)))
	return ERROR;

    strcpy(invalid, "invalid ");
    strcat(invalid, dep);

    /* if no invalid component, all valid */
    if (!DXGetComponentValue(f, invalid)) {
	DXFree((Pointer)invalid);
	return OK;
    }
    DXFree((Pointer)invalid);

    ih = DXCreateInvalidComponentHandle((Object)f, NULL, dep);
    if (!ih) 
	return ERROR;

    *icount = DXGetValidCount(ih);
    DXFreeInvalidComponentHandle(ih);

    return OK;
}



/* make sure the field contains colors and that > 1 are valid.
 * i guess we can look to see that the positions are 1D <= p <= 3D
 * as well.  and we could look at connections here as well.
 */
static Error validfield(Field f, struct info *ip, int nosee)
{
    int icount = 0;
    Array colors, pos, conn;
    char *elem = NULL;
    char *dep = NULL;
    char *invalid = NULL;
    InvalidComponentHandle ih;

    
    /* check for fields - recurse above this */
    if (DXGetObjectClass((Object)f) != CLASS_FIELD) {
	Return_Internal_Error;
    }

    /* check pos first */
    pos = (Array)DXGetComponentValue(f, "positions");
    if (!pos) {
	ip->np.nopos++;
	return OK;
    }

    if (!DXGetArrayInfo(pos, &icount, NULL, NULL, NULL, NULL))
	return ERROR;

    if (icount <= 0) {
	ip->np.nopos++;
	return OK;
    }

    /* pos must be vector, rank 1,  1 <= shape <= 3 */
    if ((!DXTypeCheck(pos, TYPE_FLOAT, CATEGORY_REAL, 1, 1)) &&
	(!DXTypeCheck(pos, TYPE_FLOAT, CATEGORY_REAL, 1, 2)) &&
	(!DXTypeCheck(pos, TYPE_FLOAT, CATEGORY_REAL, 1, 3))) {
	ip->ri.badpos++;
	return OK;
    }

    /* check Nd of conn here.  if "nosee" is true, don't count the
     * Nd of the connections because normal modules won't "see" these
     * as normal fields.
     */
    
    if (!nosee) {
	conn = (Array)DXGetComponentValue(f, "connections");
	if (!conn)
	    ip->nd.c0++;
	else if (DXGetObjectClass((Object)conn) != CLASS_ARRAY)
	    ip->nd.cbad++;
	else if (!DXGetStringAttribute((Object)conn, "element type", &elem))
	    ip->nd.cmissing++;
	else if (!strcmp(elem, "lines"))
	    ip->nd.c1++;
	else if (!strcmp(elem, "triangles") || !strcmp(elem, "quads"))
	    ip->nd.c2++;
	else if (!strcmp(elem, "tetrahedra") || !strcmp(elem, "cubes"))
	    ip->nd.c3++;
	else if (!strncmp(elem, "cubes", 5) && (strlen(elem) > 5))
	    ip->nd.cplus++;
	else
	    ip->nd.cbad++;
    }

   
    /* expect colors and follow dependency */
    colors = (Array)DXGetComponentValue(f, "colors");
    if (!colors) {
	ip->ri.nocolors++;
	return OK;
    } else {
	/* check for colormap component */
	if (DXGetComponentValue(f, "color map") != NULL)
	    ip->nc.cbytemap++;
	else if (DXTypeCheck(colors, TYPE_UBYTE, CATEGORY_REAL, 1, 3))
	    ip->nc.cubyte++;	
	else if (DXTypeCheck(colors, TYPE_FLOAT, CATEGORY_REAL, 1, 3))
	    ip->nc.cfloat++;
	else
	    ip->nc.cunknown++; 
    }

    /* follow colors dependency.  it will be unusual to find no dep. */
    if (!DXGetStringAttribute((Object)colors, "dep", &dep)) {
	ip->np.nodep++;
	return OK;
    }
    
    if (!DXGetArrayInfo(colors, &icount, NULL, NULL, NULL, NULL))
	return ERROR;

    if (icount == 0) {
	ip->np.novalid++;
	return OK;
    }
	
#define INVLEN 10     /* strlen("invalid "); */    
    
    if (!(invalid = (char *)DXAllocate(strlen(dep) + INVLEN)))
	return ERROR;

    strcpy(invalid, "invalid ");
    strcat(invalid, dep);

    /* if no invalid component, all valid */
    if (!DXGetComponentValue(f, invalid)) {
	DXFree((Pointer)invalid);
	ip->ri.nfields++;
	return OK;
    }
    DXFree((Pointer)invalid);

    ih = DXCreateInvalidComponentHandle((Object)f, NULL, dep);
    if (!ih) 
	return ERROR;

    icount = DXGetValidCount(ih);
    DXFreeInvalidComponentHandle(ih);

    if (icount == 0) {
	ip->np.novalid++;
	return OK;
    }

    ip->ri.nfields++;
    return OK;
}


/* accumulate info about what's what
 */
static Error traverse(Object o, struct info *ip, int nosee)
{
    Object subo, old;
    Class curclass;
    int i;

    switch (curclass = DXGetObjectClass(o)) {
      case CLASS_FIELD:
	return validfield((Field)o, ip, nosee);
	
      case CLASS_GROUP:
	/* traverse members */
	for (i=0; subo = DXGetEnumeratedMember((Group)o, i, NULL); i++) {
	    if (!traverse(subo, ip, nosee))
		return ERROR;
	}
	break;
	
      case CLASS_SCREEN:
	if (!DXGetScreenInfo((Screen)o, &subo, NULL, NULL))
	    return ERROR;

	return traverse(subo, ip, 1);

      case CLASS_XFORM:
	if (!DXGetXformInfo((Xform)o, &subo, NULL))
	    return ERROR;

	return traverse(subo, ip, nosee);

      case CLASS_CLIPPED:
	if (!DXGetClippedInfo((Clipped)o, &subo, NULL))
	    return ERROR;

	return traverse(subo, ip, nosee);  /* is this right? */

      case CLASS_LIGHT:
	ip->ri.nlights++;
	break;
	
      case CLASS_INTERPOLATOR:
      case CLASS_MAX:
      case CLASS_MIN:
      case CLASS_PRIVATE:
      case CLASS_DELETED:
      case CLASS_STRING:
      case CLASS_ARRAY:
      case CLASS_CAMERA:	
      default:
	for (i=0; i<ip->ri.badobjs; i++) {
	    if (ip->ri.badclass[i] == curclass)
		return OK;
	}
	ip->ri.badobjs++;
	ip->ri.badclass = (Class *)DXReAllocate((Pointer)ip->ri.badclass, 
						sizeof(Class) * ip->ri.badobjs);
	ip->ri.badclass[ip->ri.badobjs - 1] = curclass;

	break;
    }

    return OK;
}

static int qimage(Object o)
{
    int n;
    Array a;
    char *s;
    
    /* an image has got to be a field or a composite field */
    if (DXGetObjectClass(o) != CLASS_FIELD) {
	if (DXGetObjectClass(o) != CLASS_GROUP)
	    return 0;
	if (DXGetGroupClass((Group)o) != CLASS_COMPOSITEFIELD)
	    return 0;
	o = DXGetEnumeratedMember((Group)o, 0, NULL);
    }

    /* check to see if it's the right shape, etc */

    /* check positions */
    a = (Array) DXGetComponentValue((Field)o, "positions");
    if (!a || !DXQueryGridPositions(a, &n, NULL, NULL, NULL) || n!=2)
	return 0;
    
    /* check connections */
    a = (Array) DXGetComponentValue((Field)o, "connections");
    if (!a || !DXQueryGridConnections(a, &n, NULL) || n!=2)
	return 0;

    /* is there a colors component */
    a = (Array) DXGetComponentValue((Field)o, "colors");
    if (!a)
	return 0;

    /* check dep */
    s = DXGetString((String)DXGetAttribute((Object)a, "dep"));
    if (s && strcmp(s, "positions"))
	return 0;
    
    return 1;
}


#if 0
static Error atleast1field(Object o)
{
    Object subo, old;
    int i;

    switch (DXGetObjectClass(o)) {
      case CLASS_FIELD:
	return OK;

      case CLASS_GROUP:
	/* traverse members */
	for (i=0; subo = DXGetEnumeratedMember((Group)o, i, NULL); i++)
	    if (atleast1field(subo))
		return OK;
	break;
    
      case CLASS_SCREEN:
	if (!DXGetScreenInfo((Screen)o, &subo, NULL, NULL))
	    return ERROR;

	return atleast1field(subo);

      case CLASS_XFORM:
	if (!DXGetXformInfo((Xform)o, &subo, NULL))
	    return ERROR;

	return atleast1field(subo);

      case CLASS_CLIPPED:
	if (!DXGetClippedInfo((Clipped)o, &subo, NULL))
	    return ERROR;

	return atleast1field(subo);

      default:
	break;
    }

    return ERROR;
}


static Error atleast1image(Object o)
{
    Object subo, old;
    int i;

    switch (DXGetObjectClass(o)) {
      case CLASS_FIELD:
	if (qimage(o))
	    return OK;
	break;

      case CLASS_GROUP:
	/* traverse members */
	for (i=0; subo = DXGetEnumeratedMember((Group)o, i, NULL); i++)
	    if (atleast1image(subo))
		return OK;
	break;
    
      case CLASS_SCREEN:
	if (!DXGetScreenInfo((Screen)o, &subo, NULL, NULL))
	    return ERROR;

	return atleast1image(subo);

      case CLASS_XFORM:
	if (!DXGetXformInfo((Xform)o, &subo, NULL))
	    return ERROR;

	return atleast1image(subo);

      case CLASS_CLIPPED:
	if (!DXGetClippedInfo((Clipped)o, &subo, NULL))
	    return ERROR;

	return atleast1image(subo);

      default:
	break;
    }

    return ERROR;
}


static Error atleast1contype(Object o, int n)
{
    Object subo, old;
    int i;

    switch (DXGetObjectClass(o)) {
      case CLASS_FIELD:
	if (queryNDconnections((Field)o, n))
	    return OK;
	break;

      case CLASS_GROUP:
	/* traverse members */
	for (i=0; subo = DXGetEnumeratedMember((Group)o, i, NULL); i++)
	    if (atleast1contype(subo, n))
		return OK;
	break;
    
      case CLASS_SCREEN:
	/* these don't act like other objs;  don't count them for this */
	break;

      case CLASS_XFORM:
	if (!DXGetXformInfo((Xform)o, &subo, NULL))
	    return ERROR;

	return atleast1contype(subo, n);

      case CLASS_CLIPPED:
	if (!DXGetClippedInfo((Clipped)o, &subo, NULL))
	    return ERROR;

	return atleast1contype(subo, n);

      default:
	break;
    }

    return ERROR;
}
#endif


