/***********************************************************************/
/* 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>
#include <string.h>
#include <math.h>

static Error Check(Object o);
static Error GroupCheck(Group g);
static Error FieldCheck(Field f);

static Error PositionsCheck(Array a);
static char *ConnectionsCheck(Array a);
static Error AreTetrasOK(Array pos, Array conn);
static Error AreTrisOK(Array pos, Array conn);
static Error AreLinesOK(Array pos, Array conn);

static int TetraVol(Point *p, Point *q, Point *r, Point *s);
static int TriArea(Point *p, Point *q, Point *r);

extern Error _dxfValidate(Field f);
Error _dxfIsCircular(Object o);

#define MAXRANK 32


 
/* look for malformed objects and print messages about them.
 */
int
m_Verify(Object *in, Object *out)
{
    out[0] = NULL;

    if (!in[0]) {
	DXSetError(ERROR_BAD_PARAMETER, "#10000", "input");
	return ERROR;
    }

    if (_dxfIsCircular(in[0]) == ERROR)
	return ERROR;

    if (Check(in[0]) == ERROR)
	return ERROR;
    
    out[0] = in[0];
    return OK;
}
 


static Error Check(Object o)
{
    Object subo, newo, subo2;
    Matrix m;
    Array a;
    int fixed, z;
    float pos;
    char *name;
    int i;

    switch (DXGetObjectClass(o)) {

      case CLASS_GROUP:
	/* for each member */
	for (i=0; subo = DXGetEnumeratedMember((Group)o, i, NULL); i++)
	    if (!Check(subo))
		return ERROR;

	if (!GroupCheck((Group)o))
	    return ERROR;

	break;
	    
      case CLASS_FIELD:

	if (!_dxfValidate((Field)o))
	    return ERROR;

	if (!FieldCheck((Field)o))
	    return ERROR;

	break;
	
      case CLASS_ARRAY:
	break;

      case CLASS_SCREEN:
	if (!DXGetScreenInfo((Screen)o, &subo, &fixed, &z))
	    return NULL;

	if (!Check(subo))
	    return ERROR;
	
	break;

      case CLASS_XFORM:
	if (!DXGetXformInfo((Xform)o, &subo, &m))
	    return NULL;
	
	if (!Check(subo))
	    return ERROR;
	
	break;
	
      case CLASS_CLIPPED:
	if (!DXGetClippedInfo((Clipped)o, &subo, &subo2))
	    return NULL;
	
	if (!Check(subo))
	    return ERROR;
	
	if (!Check(subo2))
	    return ERROR;
	
	break;

      default:

	break;
    }

    return OK;
}

struct mustmatch {
    Field f;
    int member;
    Array p;
    int posshape;
    Array c;
    char *contype;
    int isreg;
    int meshoff[MAXRANK];
};

static Error stuffstruct(struct mustmatch *m, Object o, int memnumber)
{
    int r, s[MAXRANK];

    /* is this too draconian? */
    if (DXGetObjectClass(o) != CLASS_FIELD) {
	DXSetError(ERROR_INVALID_DATA, 
		   "composite field members must be fields");
	return ERROR;
    }
    m->f = (Field)o;
    m->member = memnumber;

    m->p = (Array)DXGetComponentValue(m->f, "positions");
    if (m->p) {
	if (!DXGetArrayInfo(m->p, NULL, NULL, NULL, &r, s))
	    return ERROR;
	m->posshape = s[0];
    } else
	m->posshape = 0;

    m->c = (Array)DXGetComponentValue(m->f, "connections");
    if (m->c) {
	if (!DXGetStringAttribute((Object)m->c, "element type", &m->contype)) {
	    DXSetError(ERROR_INVALID_DATA, 
   "'connections' component is required to have an 'element type' attribute");
	    return ERROR;
	}
	if (DXGetArrayClass(m->c) == CLASS_MESHARRAY) {
	    m->isreg++;
	    if (!DXGetMeshOffsets((MeshArray)m->c, m->meshoff))
		return ERROR;
	} else
	    m->isreg = 0;
    } else
	m->contype = NULL;

    return OK;
}

static Error cmpstruct(struct mustmatch *m1, struct mustmatch *m2)
{
    int i;

    /* if they both have positions, make sure the dimensionality matches.
     */
    if (m1->p && m2->p) {
	if (m1->posshape != m2->posshape) {
	    DXSetError(ERROR_INVALID_DATA, 
		       "all members of a composite field must have the same positions dimensionality.  member %d is %dD, member %d is %dD", 
		       m1->member, m1->posshape, m2->member, m2->posshape);
	    return ERROR;
	}
    }
    
    /* if neither has connections (both are scattered points fields)
     *  that's fine and we are done.
     */
    if (!m1->c && !m2->c)
	return OK;
    
    /* do they both have a connections component?
     */
    if (!m1->c || !m2->c) {
	DXSetError(ERROR_INVALID_DATA,
		   "all members of a composite field must have the same type of connections.  member %d has %s connection component, member %d has %s connection component",
		   m1->member, m1->c ? "a" : "no", 
		   m2->member, m2->c ? "a" : "no");
	return ERROR;
    }
    /* does the element type match?
     */
    if (!m1->contype || !m2->contype || strcmp(m1->contype, m2->contype)) {
	DXSetError(ERROR_INVALID_DATA,
		   "all members of a composite field must have the same type of connections.  member %d has %s 'element type' attribute, member %d has %s 'element type' attribute",
		   m1->member, m1->contype ? m1->contype : "no", 
		   m2->member, m2->contype ? m2->contype : "no");
	return ERROR;
    }
    /* do the meshoffsets make sense if they are part of a reg grid? 
     */
    /* this isn't enough.  we need to compare all the meshoffsets with
     * all the other components and make sure they don't overlap.  the
     * cheap test here will catch if any are identical to the first non-
     * empty group member.  since none should be the same (and 0,0,0 is
     * the most likely value), this might help some.
     */
    if (m1->isreg && m2->isreg) {
	for (i=0; i<m1->posshape; i++) {
	    if (m1->meshoff[i] != m2->meshoff[i])
		break;
	}
	if (i == m1->posshape) {
	    DXSetError(ERROR_INVALID_DATA, 
		       "composite field members %d and %d should not have identical connection mesh offset values",
		       m1->member, m2->member);
	    return ERROR;
	}
    }

    return OK;
}

static Error GroupCheck(Group g)
{
    Object subo;
    struct mustmatch first, next;
    int needone = 1;
    int i;

    switch (DXGetGroupClass(g)) {
      case CLASS_COMPOSITEFIELD:

	for (i=0; subo = DXGetEnumeratedMember(g, i, NULL); i++) {
	    /* get info from first nonempty member */
	    if (needone) {
		if (!stuffstruct(&first, subo, i))
		    return ERROR;
		if (first.p != NULL) {
		    needone = 0;
		    continue;
		}
	    }
	
	    /* get info from subo member */
	    if (!stuffstruct(&next, subo, i))
		return ERROR;

	    if (next.p == NULL)
		continue;

	    /* make sure the positions are all the same shape, and that
	     *  all the connections are the same type, and that if the
	     *  positions are regular, that the mesh offsets make sense.
	     */
	    if (!cmpstruct(&first, &next))
		return ERROR;
	}

	break;

      case CLASS_MULTIGRID:
	/* ? */
	break;

      case CLASS_SERIES:
	/* warn if series positions are not monotonic? */
	break;

      default:  /* generic group, or new group type */
	/* no restrictions */
	break;
    }

    return OK;
}


static Error 
FieldCheck(Field f)
{
    Array subo, pos;
    char *name;
    char *elemtype;
    int haspositions = 0;
    int i;

    pos = (Array)DXGetComponentValue(f, "positions");
    if (pos)
	haspositions++;
    
    for (i=0; subo=(Array)DXGetEnumeratedComponentValue(f, i, &name); i++) {
	
	if (DXGetObjectClass((Object)subo) != CLASS_ARRAY) {
	    DXSetError(ERROR_INVALID_DATA, 
		     "field component `%s' not an array object", name);
	    return ERROR;
	}
	
	if (!strcmp(name, "positions")) {
	    if (!PositionsCheck(subo))
		return ERROR;
	    continue;
	}
	
	if (!strcmp(name, "connections")) {
	    if (!haspositions) {
		DXSetError(ERROR_INVALID_DATA, 
			   "field has 'connections' but no 'positions'");
		return ERROR;
	    }

	    if (!(elemtype = ConnectionsCheck(subo)))
		return ERROR;
	    
	    if (!strcmp(elemtype, "lines")) {
		if (!AreLinesOK(pos, subo))
		    return ERROR;
		continue;
	    }
	    if (!strcmp(elemtype, "quads")) {
		continue;
	    }
	    if (!strcmp(elemtype, "cubes")) {
		continue;
	    }
	    if (!strcmp(elemtype, "triangles")) {
		if (!AreTrisOK(pos, subo))
		    return ERROR;
		continue;
	    }
	    if (!strcmp(elemtype, "tetrahedra")) {
		if (!AreTetrasOK(pos, subo))
		    return ERROR;
		continue;
	    }
	}
	
	
	if (!strcmp(name, "faces")) {
	    continue;
	}
	if (!strcmp(name, "loops")) {
	    continue;
	}
	if (!strcmp(name, "edges")) {
	    continue;
	}
    }
    
    return OK;
}

static Error
PositionsCheck(Array a)
{
    int items;
    Type type;
    Category cat;
    int rank, shape[MAXRANK];

    if (!DXGetArrayInfo(a, &items, &type, &cat, &rank, shape))
	return ERROR;

    if (type != TYPE_FLOAT) {
	DXSetError(ERROR_INVALID_DATA, "positions must be type float");
	return ERROR;
    }

    if (cat != CATEGORY_REAL) {
	DXSetError(ERROR_INVALID_DATA, "positions must be category real");
	return ERROR;
    }
    
    if (rank != 1) {
	DXSetError(ERROR_INVALID_DATA, "positions must be a vector");
	return ERROR;
    }

    return OK;
	
}


static char *
ConnectionsCheck(Array a)
{
    int items;
    Type type;
    Category cat;
    int rank, shape[MAXRANK];
    char *elemtype;

    if (!DXGetArrayInfo(a, &items, &type, &cat, &rank, shape))
	return ERROR;

    if (type != TYPE_INT) {
	DXSetError(ERROR_INVALID_DATA, "connections must be type integer");
	return ERROR;
    }

    if (cat != CATEGORY_REAL) {
	DXSetError(ERROR_INVALID_DATA, "connections must be category real");
	return ERROR;
    }
    
    if (rank != 1) {
	DXSetError(ERROR_INVALID_DATA, "connections must be a vector");
	return ERROR;
    }

    if (!DXGetStringAttribute((Object)a, "element type", &elemtype)) {
	DXSetError(ERROR_INVALID_DATA, 
		 "`connections' component has no `element type' attribute");
	return ERROR;
    }

    return elemtype;
}

static Error
AreTetrasOK(Array pos, Array conn)
{
    int pitems, citems;
    int prank, pshape[MAXRANK];
    int crank, cshape[MAXRANK];
    int i, j, *ip;
    int handed = 0, this;
    Point *pp;

    if (!DXGetArrayInfo(pos, &pitems, NULL, NULL, &prank, pshape))
	return ERROR;

    if (prank != 1 || pshape[0] != 3) {
	DXSetError(ERROR_INVALID_DATA, 
		   "tetrahedra positions must be 3-vectors");
	return ERROR;
    }

    if (pitems == 0) {
	DXWarning("positions array contains no items");
	return OK;
    }

    pp = (Point *)DXGetArrayData(pos);
    if (!pp)
	return ERROR;

    if (!DXGetArrayInfo(conn, &citems, NULL, NULL, &crank, cshape))
	return ERROR;

    if (crank != 1 || cshape[0] != 4) {
	DXSetError(ERROR_INVALID_DATA, 
		   "tetrahedra connections must be 4-vectors");
	return ERROR;
    }
	
    if (citems == 0) {
	DXWarning("connections array contains no items");
	return OK;
    }

    ip = (int *)DXGetArrayData(conn);
    if (!ip)
	return ERROR;

    for (i=0; i<citems; i++, ip+=4) {
	for (j=0; j<4; j++) {
	    if (ip[j] < 0 || ip[j] >= pitems) {
		DXSetError(ERROR_INVALID_DATA, 
		    "connection index %d out of range (%d); must be 0 to %d",
			   i, ip[j], pitems-1);
		return ERROR;
	    }
	}

	/* find the first tetra which is has either positive or negative
	 *  volume, and make sure all others agree.
	 */
	this = TetraVol(pp+ip[0], pp+ip[1], pp+ip[2], pp+ip[3]);
	if (this && !handed) {
	    handed = this;
	    continue;
	}

	if (this && this != handed) {
	    DXSetError(ERROR_INVALID_DATA, 
		"the positions in tetrahedra %d are ordered inconsistently compared to other tetrahedra in this field",
		       i);
	    return ERROR;
	}
    }

    return OK;
}

static Error
AreTrisOK(Array pos, Array conn)
{
    int pitems, citems;
    int prank, pshape[MAXRANK];
    int crank, cshape[MAXRANK];
    int i, j, *ip;
    Point *pp;

    if (!DXGetArrayInfo(pos, &pitems, NULL, NULL, &prank, pshape))
	return ERROR;

    if (prank != 1 || (pshape[0] != 2 && pshape[0] != 3)) {
	DXSetError(ERROR_INVALID_DATA, 
		   "triangle positions must be 2- or 3-vectors");
	return ERROR;
    }

    if (pitems == 0) {
	DXWarning("positions array contains no items");
	return OK;
    }

    pp = (Point *)DXGetArrayData(pos);
    if (!pp)
	return ERROR;

    if (!DXGetArrayInfo(conn, &citems, NULL, NULL, &crank, cshape))
	return ERROR;

    if (crank != 1 || cshape[0] != 3) {
	DXSetError(ERROR_INVALID_DATA, 
		   "triangle connections must be 3-vectors");
	return ERROR;
    }
	
    if (citems == 0) {
	DXWarning("connections array contains no items");
	return OK;
    }

    ip = (int *)DXGetArrayData(conn);
    if (!ip)
	return ERROR;

    for (i=0; i<citems; i++, ip+=3) {
	for (j=0; j<3; j++) {
	    if (ip[j] < 0 || ip[j] >= pitems) {
		DXSetError(ERROR_INVALID_DATA, 
		    "connection index %d out of range (%d); must be 0 to %d",
			   i, ip[j], pitems-1);
		return ERROR;
	    }
	}

#if 0
	/* this doesn't mean anything */
	if (!TriArea(pp+ip[0], pp+ip[1], pp+ip[2])) {
	    DXSetError(ERROR_INVALID_DATA, 
		 "the positions in triangle %d are ordered inconsistently",
		       i);
	    return ERROR;
	}
#endif

    }

    return OK;
}


static Error
AreLinesOK(Array pos, Array conn)
{
    int pitems, citems;
    int prank, pshape[MAXRANK];
    int crank, cshape[MAXRANK];
    int i, j, *ip;
    float *fp;

    if (!DXGetArrayInfo(pos, &pitems, NULL, NULL, &prank, pshape))
	return ERROR;

    if (prank != 1 || pshape[0] < 1 || pshape[0] > 3) {
	DXSetError(ERROR_INVALID_DATA, 
		   "line positions must be 1-, 2- or 3-vectors");
	return ERROR;
    }

    if (pitems == 0) {
	DXWarning("positions array contains no items");
	return OK;
    }

    fp = (float *)DXGetArrayData(pos);
    if (!fp)
	return ERROR;

    if (!DXGetArrayInfo(conn, &citems, NULL, NULL, &crank, cshape))
	return ERROR;

    if (crank != 1 || cshape[0] != 2) {
	DXSetError(ERROR_INVALID_DATA, 
		   "line connections must be 2-vectors");
	return ERROR;
    }
	
    if (citems == 0) {
	DXWarning("connections array contains no items");
	return OK;
    }

    ip = (int *)DXGetArrayData(conn);
    if (!ip)
	return ERROR;

    for (i=0; i<citems; i++, ip+=2) {
	for (j=0; j<2; j++) {
	    if (ip[j] < 0 || ip[j] >= pitems) {
		DXSetError(ERROR_INVALID_DATA, 
		    "connection index %d out of range (%d); must be 0 to %d",
			   i, ip[j], pitems-1);
		return ERROR;
	    }
	}

    }

    return OK;
}

/* compute the volume of the tetra and see if it is a positive
 *  or negative number.  this returns -1, 0 or 1 for negative, too small
 *  to tell, and positive, respectively.  
 *  if the volume is negative, switching the order of any two of
 *  the point indicies will fix the problem.
 */
static int TetraVol(Point *p, Point *q, Point *r, Point *s)
{
    double xqp, yqp, zqp;
    double xrp, yrp, zrp;
    double xsp, ysp, zsp;
    double  vx,  vy,  vz;
    double vol;

    xqp = q->x - p->x; yqp = q->y - p->y; zqp = q->z - p->z;
    xrp = r->x - p->x; yrp = r->y - p->y; zrp = r->z - p->z;
    xsp = s->x - p->x; ysp = s->y - p->y; zsp = s->z - p->z;

    vx = (yrp * zqp) - (yqp * zrp);
    vy = (zrp * xqp) - (zqp * xrp);
    vz = (xrp * yqp) - (xqp * yrp);

    vol = vx*xsp + vy*ysp + vz*zsp;

    if (fabs(vol) < DXD_FLOAT_EPSILON)
	return 0;

    return (vol < 0.0) ? -1 : 1;
}

/* 
 */
static int TriArea(Point *p, Point *q, Point *r)
{
    double xpq, ypq;
    double xrq, yrq;
    double area;

    xpq = p->x - q->x; ypq = p->y - q->y;
    xrq = r->x - q->x; yrq = r->y - q->y;

    area = (xpq * yrq) - (xrq * ypq);
    
    if (fabs(area) < DXD_FLOAT_EPSILON)
	return 0;

    return (area < 0.0) ? -1 : 1;
}



/*
 * try to detect circular references.  attrs can be references to things
 *  further up the hierarchy, but direct descendents (like components or
 *  group members) can't refer back to something in their parent chain.
 */
struct plist {
    Object this;
    struct plist *next;
};

static Error cpath(struct plist *p, Object o);
static Error pushchild(struct plist *p, Object newo);
static void popchild(struct plist *p);

Error _dxfIsCircular(Object o)
{
    struct plist pl;
    
    pl.this = o;
    pl.next = NULL;

    return cpath(&pl, o);
}

static Error cpath(struct plist *p, Object o)
{
    Object child = NULL;
    char *name = NULL;
    int i;

    switch (DXGetObjectClass(o)) {

      case CLASS_GROUP:
	for (i=0; child = DXGetEnumeratedMember((Group)o, i, &name); i++) {
	    if (pushchild(p, child) == ERROR) {
		DXAddMessage("member %d", i);
		return ERROR;
	    }
	    popchild(p);
	}
	return OK;

      case CLASS_FIELD:
	for (i=0; child = DXGetEnumeratedComponentValue((Field)o, i, &name); i++) {
	    if (pushchild(p, child) == ERROR) {
		DXAddMessage("member %d", i);
		return ERROR;
	    }
	    popchild(p);
	}
	return OK;
	
      case CLASS_SCREEN:
	if (!DXGetScreenInfo((Screen)o, &child, NULL, NULL))
	    return NULL;
	
	if (pushchild(p, child) == ERROR) {
	    DXAddMessage("screen object");
	    return ERROR;
	}
	popchild(p);

	return OK;

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

	if (pushchild(p, child) == ERROR) {
	    DXAddMessage("clipped object");
	    return ERROR;
	}
	popchild(p);

	if (!DXGetClippedInfo((Clipped)o, NULL, &child))
	    return NULL;

	if (pushchild(p, child) == ERROR) {
	    DXAddMessage("clipping object");
	    return ERROR;
	}
	popchild(p);

	return OK;

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

	if (pushchild(p, child) == ERROR) {
	    DXAddMessage("transform object");
	    return ERROR;
	}
	popchild(p);

	return OK;

      /* all other objects can't contain others */
      default:
	return OK;

    }

    /* not reached */
}

/* private for debugging 
 */
static void printplist(struct plist *p)
{
    struct plist *pp;

    pp = p;
    do {
	DXMessage("p = 0x%08x: object = 0x%08x, next = 0x%08x", 
		  (uint)pp, (uint)pp->this, (uint)pp->next);
	pp = pp->next;
    } while (pp);
}

/* check list on the way down.  if you find a duplicate, return error.
 *  otherwise alloc space for a new entry and add this object to the
 *  end of the list.
 */
static Error pushchild(struct plist *p, Object newo)
{
    struct plist *pl, *pp;

    pp = p;
    do {
	if (newo == pp->this) {
	    DXSetError(ERROR_INVALID_DATA, "circular reference in object");
	    return ERROR;
	}
	if (pp->next)
	    pp = pp->next;
    } while (pp->next);

    pl = (struct plist *)DXAllocateLocal(sizeof(struct plist));
    if (!pl) 
	return ERROR;

    pl->this = newo;
    pl->next = NULL;

    pp->next = pl;

    return cpath(p, newo);
}

/* remove the last entry from the list.
 */
static void popchild(struct plist *p)
{
    struct plist *pp;

    pp = p;
    while (pp->next) {
	if (!pp->next->next) {
	    DXFree((Pointer)pp->next);
	    pp->next = NULL;
	    return;
	}
	pp = pp->next;
    }
    /* not reached */
}

