/***********************************************************************/
/* 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>


/*
 * $Header: /home/gda/dxcvs/dx/src/exec/dxmods/text.c,v 1.3 1999/05/10 15:45:32 gda Exp $
 */


#include <dx/dx.h>

/* XXX (and is char* ok in arg block) */

extern Array _dxfPostArray(Field, char *, char *);



typedef struct textarg
{
   Field fieldin;
   Group groupout;
   float height;
   char *font;
   Vector direction;
   Vector up;
   int direction_defaulted;
   int up_defaulted;
} Textarg;



static Error TextField(Pointer p)
{
  Textarg *textarg = (Textarg *)p;
  Field fieldin; 
  Group groupout;
  Object font=NULL, o, t;
  float height;
  char *f, *attr, *attr1, *string;
  Vector direction, up, z;
  float dotprod;
  Matrix m;
  Point position;
  int i, j=0, numitems, rank, shape[8], posdim, datadim, regcolors, posted;
  int trank, tshape[8]; 
  int brank, bshape[8]; 
  int nrank, nshape[8];
  int direction_defaulted, up_defaulted, delayed;
  int numtextpos, n, counts[8], groupcount=0;
  float posorigin[30], posdeltas[30];
  Type type, ttype, btype, ntype;
  Category category;
  float *pos_ptr=NULL;
  RGBColor *colors_ptr;
  RGBColor color;
  Array positions=NULL, data, invalid, colors, textcolors=NULL, textpositions;
  Array tangents=NULL, binormals=NULL, normals=NULL, colormap=NULL;
  InvalidComponentHandle invalidhandle=NULL;
  float *tangents_ptr, *binormals_ptr, *normals_ptr;
  ubyte *delayedcolors_ptr, delayedcolor;
  Point current_tangent, current_binormal, current_normal;
  RGBColor *colormap_ptr;
  fieldin = textarg->fieldin;
  groupout = textarg->groupout;
  height = textarg->height;
  f = textarg->font;
  direction = textarg->direction;
  up = textarg->up;
  direction_defaulted = textarg->direction_defaulted;
  up_defaulted = textarg->up_defaulted;

  /* uses cache */
  if (!f)
    f = "variable";
  font = DXGetFont(f, NULL, NULL);
  if (!font)
    goto error;



  data = (Array)DXGetComponentValue(fieldin,"data");
  if (!data) {
    DXSetError(ERROR_INVALID_DATA,"#10250", "field","data");
    goto error;
  }
  
  DXGetArrayInfo(data, &numitems, &type, &category, &rank, shape);
  if (type != TYPE_STRING) {
    DXSetError(ERROR_INVALID_DATA,"#10370", "data component", "type string");
    goto error;
  } 
  
  if (category != CATEGORY_REAL) {
    DXSetError(ERROR_INVALID_DATA,"data component must be category real");
    goto error;
  } 
  
  if (rank != 1) {
    DXSetError(ERROR_INVALID_DATA,"rank of data component must equal 1");
    goto error;
  }
  
  datadim = shape[0];
  
  attr = DXGetString((String)DXGetComponentAttribute((Field)fieldin, "data",
						     "dep"));
  if (!attr) {
    DXSetError(ERROR_MISSING_DATA,"#10241", "data");
    goto error;
  }
  if (strcmp(attr,"positions")&&(strcmp(attr,"connections"))) {
    DXSetError(ERROR_INVALID_DATA,
               "data component must be dep positions or dep connections");
    goto error;
  }
  
  
  /* see if there's an invalid positions array */
  if (!strcmp(attr,"positions")) {
    invalid = (Array)DXGetComponentValue(fieldin,"invalid positions");
    if (invalid) {
      invalidhandle = DXCreateInvalidComponentHandle((Object)fieldin, 
                                                      NULL, "positions");
      if (!invalidhandle)
        goto error;
    }
  }
  else {
    invalid = (Array)DXGetComponentValue(fieldin,"invalid connections");
    if (invalid) {
      invalidhandle = DXCreateInvalidComponentHandle((Object)fieldin, 
                                                      NULL,"connections");
      if (!invalidhandle)
        goto error;
    }
  } 
  
  /* see if there's a colors component */
  colors = (Array)DXGetComponentValue(fieldin,"colors");
  /* check for delayed */
  if (colors) {
    DXGetArrayInfo(colors, NULL, &type, NULL, &rank, shape);
    if ((type==TYPE_FLOAT)&&(rank==1)&&(shape[0]==3)) { 
       delayed = 0;
    }
    else {
      if (rank==0) {
         rank=1;
         shape[0]=1;
      }
      /* better be delayed */
      if ((type != TYPE_UBYTE)||(rank != 1) || (shape[0] != 1)) {
        DXSetError(ERROR_INVALID_DATA,"#11363", "byte colors", 1);
        goto error;
      }
      if (!(colormap = (Array)DXGetComponentValue(fieldin,"color map"))) {
        DXSetError(ERROR_MISSING_DATA,"#13050");
        goto error;
      }
      colormap_ptr = (RGBColor *)DXGetArrayData(colormap);
      delayed = 1;
    }
  }

  regcolors = 1;
  if (colors) {
    if (!delayed) {
      if (!(DXQueryConstantArray(colors, NULL, (Pointer)&color)))
         regcolors = 0;
      colors_ptr = (RGBColor *)DXGetArrayData(colors);
    }
    else
      if (!(DXQueryConstantArray(colors, NULL, (Pointer)&delayedcolor)))
         regcolors = 0;
      delayedcolors_ptr = (ubyte *)DXGetArrayData(colors);
  }
  
  
  
  posted = 0;
  if (!strcmp(attr,"connections")) {
    /* I need to check whether the positions are regular. If they
     * are, a simple grid shift is in order. Otherwise, use PostArray
     * to shift them */
    Array pos, con;
    pos = (Array)DXGetComponentValue((Field)fieldin,"positions");
    con = (Array)DXGetComponentValue((Field)fieldin,"connections");
    if ((DXQueryGridPositions(pos, &n, counts, posorigin, posdeltas)) &&
        DXQueryGridConnections(con, NULL, NULL)) {
      
      for (i=0; i<n; i++) {
        for (j=0; j<n; j++) {
          if (counts[j] > 1) {
            posorigin[i]+=0.5*posdeltas[i + j*n];
          }
        }
      }
      for (i=0; i<n; i++) {
        if (counts[i] >1) counts[i] = counts[i]-1;
      }
      
      positions = DXMakeGridPositionsV(n, counts, posorigin, posdeltas);
      
    }
    /* else the positions are irregular */
    else {
      positions = _dxfPostArray((Field)fieldin, "positions","connections");
    }
    if (!positions) {
      DXSetError(ERROR_BAD_PARAMETER,
                 "could not compute text positions for cell-centered data");
      goto error;
    }
    posted = 1;
  }
  else {
    /* get positions component */
    positions = (Array)DXGetComponentValue(fieldin,"positions");
    if (!positions) {
      DXSetError(ERROR_INVALID_DATA,"#10250", "field", "positions");
      goto error;
    }
  }
  DXGetArrayInfo(positions, &numitems, &type, &category, &rank, shape);
  if ((type != TYPE_FLOAT)||(category != CATEGORY_REAL)) {
    DXSetError(ERROR_INVALID_DATA,
	       "positions must be type float, category real");
    goto error;
  }
  
  if (rank != 1) {
    DXSetError(ERROR_INVALID_DATA,
	       "positions must be rank 1");
    goto error;
  }
  
  if ((shape[0]<1) || (shape[0] > 3)) {
    DXSetError(ERROR_INVALID_DATA, "#10370", "positions", 
               "1, 2, or 3-dimensional");
    goto error; 
  }
  posdim= shape[0];
  
  /* see if there's a tangent component (correponds to direction) */
  /* if the user specified direction in the config dialog box, we
     don't really care if there's a tangents component */
  tangents = (Array)DXGetComponentValue(fieldin,"tangents");
  if (tangents) {
    if (direction_defaulted) {
      tangents_ptr = (float *)DXGetArrayData(tangents);
      DXGetArrayInfo(tangents, &numitems, &ttype, NULL, &trank, tshape);
      if (ttype != TYPE_FLOAT) {
        DXSetError(ERROR_INVALID_DATA,"tangents component must be type float");
        goto error;
      }
      if (trank != 1) {
        DXSetError(ERROR_INVALID_DATA,"tangents component must be rank 1");
        goto error;
      }
      if (tshape[0] != 2 && tshape[0] != 3) {
        DXSetError(ERROR_INVALID_DATA,"tangents component must be 2D or 3D");
        goto error;
      }
      attr1 = DXGetString((String)DXGetComponentAttribute((Field)fieldin, 
                                                          "tangents", "dep"));
      if (!attr1) {
	DXSetError(ERROR_MISSING_DATA, "#10242", "tangents");
	goto error;
      }
      if (strcmp(attr,attr1)) {
	DXSetError(ERROR_INVALID_DATA, "#10360", "tangents", "data");
	goto error;
      }
    }
    else {
      DXWarning("tangents component is ignored because direction is set in the configuration dialog box");
      tangents=NULL;
    }
  }
  binormals = (Array)DXGetComponentValue(fieldin,"binormals");
  if (binormals) {
    if (up_defaulted) {
      binormals_ptr = (float *)DXGetArrayData(binormals);
      DXGetArrayInfo(binormals, &numitems, &btype, NULL, &brank, bshape);
      if (btype != TYPE_FLOAT) {
        DXSetError(ERROR_INVALID_DATA,
                   "binormals component must be type float");
        goto error;
      }
      if (brank != 1) {
        DXSetError(ERROR_INVALID_DATA,"binormals component must be rank 1");
        goto error;
      }
      if (bshape[0] != 2 && bshape[0] != 3) {
        DXSetError(ERROR_INVALID_DATA,"binormals component must be 2D or 3D");
        goto error;
      }

      attr1 = DXGetString((String)DXGetComponentAttribute((Field)fieldin, 
                                                          "binormals", "dep"));
      if (!attr1) {
	DXSetError(ERROR_MISSING_DATA, "#10241", "binormals");
	goto error;
      }
      if (strcmp(attr,attr1)) {
	DXSetError(ERROR_INVALID_DATA, "#10360", "binormals", "data");
	goto error;
      }
    }
    else {
      DXWarning("binormals component ignored because up is set in configuration dialog box");
      binormals=NULL;
    }
  }
  /* we only care about normals if tangents and binormals aren't both
     already specified */
  normals = (Array)DXGetComponentValue(fieldin,"normals");
  if (normals) {
    if ( (!binormals || up_defaulted ) || (!tangents || direction_defaulted)) {
      normals_ptr = (float *)DXGetArrayData(normals);
      DXGetArrayInfo(normals, &numitems, &ntype, NULL, &nrank, nshape);
      if (ntype != TYPE_FLOAT) {
        DXSetError(ERROR_INVALID_DATA,
                   "normals component must be type float");
        goto error;
      }
      if (nrank != 1) {
        DXSetError(ERROR_INVALID_DATA,"normals component must be rank 1");
        goto error;
      }
      if (nshape[0] != 2 && nshape[0] != 3) {
        DXSetError(ERROR_INVALID_DATA,"normals component must be 2D or 3D");
        goto error;
      }


      attr1 = DXGetString((String)DXGetComponentAttribute((Field)fieldin, 
                                                          "normals", "dep"));
      if (!attr1) {
	DXSetError(ERROR_MISSING_DATA, "#10241", "normals");
	goto error;
      }
      if (strcmp(attr,attr1)) {
	DXSetError(ERROR_INVALID_DATA, "#10360", "normals", "data");
	goto error;
      }
    }
    else {
      DXWarning("normals component is ignored because text direction is already sufficiently specified through direction (or tangents) and up (or binormals)");
      normals=NULL;
    }
  } 
  
  pos_ptr = (float *)DXGetArrayData(positions);
  /* the ones coming in from above */
  /* these should not be colinear, so I don't need to worry here */
  direction = DXNormalize(direction);
  up = DXNormalize(up);
  z = DXNormalize(DXCross(direction, up));
  
  for (i=0; i<numitems; i++) {
    if (!((invalid)&&(DXIsElementInvalid(invalidhandle, i)))) {
      switch (posdim) {
      case (1):
	position= DXPt(pos_ptr[i], 0.0, 0.0);
	break;
      case (2):
	position= DXPt(pos_ptr[2*i], pos_ptr[2*i+1], 0.0);
	break;
      case (3):
	position= DXPt(pos_ptr[3*i], pos_ptr[3*i+1], pos_ptr[3*i+2]);
	break;
      }
      
      /* override if components are present */
      if (tangents) {
        if (tshape[0]==3)
          current_tangent = DXPt(tangents_ptr[3*i], 
                                 tangents_ptr[3*i+1],
                                 tangents_ptr[3*i+2]);
        else
          current_tangent = DXPt(tangents_ptr[2*i], 
                                 tangents_ptr[2*i+1],
                                 0.0);
	direction=DXNormalize(current_tangent);
      }
      if (binormals) {
        if (bshape[0]==3)
          current_binormal = DXPt(binormals_ptr[3*i], 
                                  binormals_ptr[3*i+1],
                                  binormals_ptr[3*i+2]);
        else
          current_binormal = DXPt(binormals_ptr[2*i], 
                                  binormals_ptr[2*i+1],
                                  0.0);
	up = DXNormalize(current_binormal);
      }

      /* check for colinearity */
      dotprod = (direction.x*up.x + direction.y*up.y + direction.z*up.z);
      if (dotprod==1 || dotprod==-1) {
        DXWarning("direction and up are colinear, resetting up");
        if (direction.x==0)
          up = DXVec(0, -direction.z, direction.y);
        else
          up = DXVec(-direction.y, direction.x, 0);
      }

      /* reset z from it's default */
      if (tangents || binormals) {
	z = DXNormalize(DXCross(direction, up));
      }
      /* reset z if normals has been set */
      if (normals) {
        if (nshape[0]==3)
          current_normal = DXPt(normals_ptr[3*i], 
                                normals_ptr[3*i+1],
                                normals_ptr[3*i+2]);
        else
          current_normal = DXPt(normals_ptr[2*i], 
                                normals_ptr[2*i+1],
                                0.0);
	z = DXNormalize(current_normal);
      }
      
      /* compute the transform */
      
      m.A[0][0] = direction.x*height;
      m.A[0][1] = direction.y*height;
      m.A[0][2] = direction.z*height;
      m.A[1][0] = up.x*height;
      m.A[1][1] = up.y*height;
      m.A[1][2] = up.z*height;
      m.A[2][0] = z.x*height; 
      m.A[2][1] = z.y*height;
      m.A[2][2] = z.z*height;
      m.b[0] = position.x;
      m.b[1] = position.y;         
      m.b[2] = position.z;
      
      /* get the string */
      if (!DXExtractNthString((Object)data,i,&string)) {
	DXSetError(ERROR_INTERNAL,"error extracting %dth string from data",
		   i);
	goto error;
      }
      /* create the object */
      o = DXGeometricText(string, font, NULL);
      if (!o)
        goto error;
      /* if there was a colors component, use it */
      if (colors) {
	textpositions = (Array)DXGetComponentValue((Field)o,"positions");
	DXGetArrayInfo(textpositions, &numtextpos, NULL, NULL, NULL, NULL);
        if (!delayed) {
	if (!regcolors)
          textcolors = (Array)DXNewConstantArray(numtextpos, 
                                                 (Pointer)&colors_ptr[i],
                                                 TYPE_FLOAT, CATEGORY_REAL, 
                                                 1, 3);
	else
          textcolors = (Array)DXNewConstantArray(numtextpos, (Pointer)&color,
						 TYPE_FLOAT, 
						 CATEGORY_REAL, 1, 3);
        }
        /* else delayed */
        else {
	if (!regcolors)
          textcolors = (Array)DXNewConstantArray(numtextpos, 
                                (Pointer)&colormap_ptr[delayedcolors_ptr[i]],
                                TYPE_FLOAT, CATEGORY_REAL, 
                                1, 3);
	else {
          color = colormap_ptr[(int)delayedcolor];
          textcolors = (Array)DXNewConstantArray(numtextpos, 
                                                (Pointer)&color,
						 TYPE_FLOAT, 
						 CATEGORY_REAL, 1, 3);
         }
        }
	if (!DXSetComponentValue((Field)o, "colors", (Object)textcolors))
          goto error;
	textcolors = NULL;
      }
      t = (Object)DXNewXform(o, m);
      if (!t) {
        DXDelete(o);
        goto error;
      }
      if (!DXSetFloatAttribute(t, "fuzz", 1.0))
        goto error;
      
      /* Geometric text is a field with positions, connections, and colors */
      if (!DXSetEnumeratedMember(groupout, groupcount++, t))
	goto error; 
    }
  }
  
  if (posted) DXDelete((Object)positions);
  DXDelete((Object)textcolors);
  DXDelete((Object)font);
  DXFreeInvalidComponentHandle(invalidhandle);
  return OK;
 error:
  if (posted) DXDelete((Object)positions);
  DXDelete((Object)textcolors);
  DXDelete((Object)font);
  DXFreeInvalidComponentHandle(invalidhandle);
  return ERROR;
  
}


static Error TextObject(Object in, Group out, 
                        float h, char *f, Vector x, Vector y,
                        int direction_defaulted, int up_defaulted)
{
  struct textarg textarg;
  int i;
  Object childin;
  Group group=NULL;
  
  
  switch (DXGetObjectClass(in)) {
  case (CLASS_FIELD):
    group = DXNewGroup();
    DXSetMember((Group)out,NULL,(Object)group);
    textarg.fieldin = (Field)in;
    textarg.groupout = group;
    textarg.height = h;
    textarg.font = f;
    textarg.direction = x;
    textarg.up = y;
    textarg.direction_defaulted = direction_defaulted;
    textarg.up_defaulted = up_defaulted;
    if (!DXAddTask(TextField, (Pointer)&textarg, sizeof(textarg), 1.0))
      goto error;
    break;
  case (CLASS_GROUP):
    switch (DXGetGroupClass((Group)in)) {
      case (CLASS_GROUP):
         group = DXNewGroup();
         DXSetMember((Group)out,NULL,(Object)group);
         break;
      case (CLASS_SERIES):
         group = (Group)DXNewSeries();
         DXSetMember((Group)out,NULL,(Object)group);
         break;
      case (CLASS_COMPOSITEFIELD):
         group = (Group)DXNewCompositeField();
         DXSetMember((Group)out,NULL,(Object)group);
         break;
      case (CLASS_MULTIGRID):
         group = (Group)DXNewMultiGrid();
         DXSetMember((Group)out,NULL,(Object)group);
         break;
      default:
         DXSetError(ERROR_INVALID_DATA,"unknown group class");
         goto error;
    }
    for (i=0; childin = DXGetEnumeratedMember((Group)in, i, NULL); i++) {
      if (!TextObject(childin, group, h, f, x, y, direction_defaulted,
                      up_defaulted))
	goto error;
    }
    
    break;
  default:
    DXSetError(ERROR_INVALID_DATA,"#10112", "string");
    goto error;
  }

  return OK;
    
  error:
    DXDelete((Object)group);
    return ERROR;
  
}


static
  Object
  Text(char *s, Point p, float h, char *f, Vector x, Vector y)
{
  Matrix m;
  Vector z;
  Object o, t, font;
  
  /* check vectors */
  if (DXLength(x)==0) {
    DXSetError(ERROR_BAD_PARAMETER, "#11822", "direction");
    return ERROR;
  }
  if (DXLength(y)==0) {
    DXSetError(ERROR_BAD_PARAMETER, "#11822", "up");
    return ERROR;
  }
  
  /* uses cache */
  if (!f)
    f = "variable";
  font = DXGetFont(f, NULL, NULL);
  if (!font)
    goto error;
  
  /* compute the transform */
  x = DXNormalize(x);
  y = DXNormalize(y);
  z = DXNormalize(DXCross(x, y));
  
  m.A[0][0] = x.x*h;    m.A[0][1] = x.y*h;    m.A[0][2] = x.z*h;
  m.A[1][0] = y.x*h;    m.A[1][1] = y.y*h;    m.A[1][2] = y.z*h;
  m.A[2][0] = z.x*h;    m.A[2][1] = z.y*h;    m.A[2][2] = z.z*h;
  m.b[0] = p.x;         m.b[1] = p.y;         m.b[2] = p.z;
  
  /* create the object */
  o = DXGeometricText(s, font, NULL);
  if (!o)
    goto error;
  t = (Object)DXNewXform(o, m);
  if (!t) {
    DXDelete(o);
    goto error;
  }
  if (!DXSetFloatAttribute(t, "fuzz", 1.0))
    goto error;
  
  /* all ok */
  DXDelete(font);
  return t;
  
 error:
  DXDelete(font);
  return NULL;
}

Error
  m_Text(Object *in, Object *out)
{
  struct textarg textarg;
  Vector x, y;
  Point p;
  char *s, *f;
  float h;
  int textfield=0, direction_defaulted, up_defaulted;
  Group outobject=NULL;
  
  /* string */
  if (!in[0]) {
    DXSetError(ERROR_BAD_PARAMETER, "#10000", "string");
    return ERROR;
  } else
    if (!DXExtractString(in[0], &s)) textfield=1;
  
  
  /* origin */
  p = DXPt(0,0,0);
  if ((textfield)&&(in[1])) 
    DXWarning("position is ignored if string parameter is a field");
  
  if (in[1] && !DXExtractParameter(in[1], TYPE_FLOAT, 3, 1, (Pointer)&p)) {
    DXSetError(ERROR_BAD_PARAMETER, "#10230", "position", 3);
    return ERROR;
  }
 
  /* height */
  h = 1;
  if (in[2] && !DXExtractFloat(in[2], &h)) {
    DXSetError(ERROR_BAD_PARAMETER, "#10080", "height");
    return ERROR;
  }
  
  /* font */
  f = NULL;
  if (in[3] && !DXExtractString(in[3], &f)) {
    DXSetError(ERROR_BAD_PARAMETER, "#10200", "font");
    return ERROR;
  }
 
  direction_defaulted=0; 
  /* baseline */
  x = DXVec(1,0,0);
  if (!in[4]) direction_defaulted=1;
  if (in[4] && !DXExtractParameter(in[4], TYPE_FLOAT, 3, 1, (Pointer)&x)) {
    DXSetError(ERROR_BAD_PARAMETER, "#10230", "direction", 3);
    return ERROR;
  }
  
  /* up */
  if (x.x==0)
    y = DXVec(0, -x.z, x.y);
  else
    y = DXVec(-x.y, x.x, 0);

  up_defaulted=0;
  if (!in[5]) up_defaulted=1;
  if (in[5] && !DXExtractParameter(in[5], TYPE_FLOAT, 3, 1, (Pointer)&y)) {
    DXSetError(ERROR_BAD_PARAMETER, "#10230", "up", 3);
    return ERROR;
  }
  
  /* do it */
  if (!textfield)
    out[0] = Text(s, p, h, f, x, y);
  else {
    DXCreateTaskGroup();
    /* make a new holder of the same type as the old */
    switch (DXGetObjectClass(in[0])) {
      case (CLASS_GROUP):
        switch (DXGetGroupClass((Group)in[0])) {
          case (CLASS_GROUP):
             outobject = DXNewGroup();
             break;
          case (CLASS_SERIES):
             outobject = (Group)DXNewSeries();
             break;
          case (CLASS_COMPOSITEFIELD):
             outobject = (Group)DXNewCompositeField();
             break;
          case (CLASS_MULTIGRID):
             outobject = (Group)DXNewMultiGrid();
             break;
          default:
             DXSetError(ERROR_INVALID_DATA,"unknown group class");
             goto error;
        }
        if (!TextObject(in[0], outobject, h, f, x, y, direction_defaulted,
                        up_defaulted))
          goto error;
        break;
      case (CLASS_FIELD):
        outobject = DXNewGroup();
        textarg.fieldin = (Field)in[0];
        textarg.groupout = outobject;
        textarg.height = h;
        textarg.font = f;
        textarg.direction = x;
        textarg.up = y;
        textarg.direction_defaulted=direction_defaulted;
        textarg.up_defaulted=up_defaulted;
        if (!TextField((Pointer)&textarg))
           goto error;
        break;
     default:
        DXSetError(ERROR_BAD_PARAMETER,"#10112", "string");
        goto error; 
    }
    if (!DXExecuteTaskGroup())
      goto error;
    out[0] = (Object)outobject;
  }
  
  if (!out[0])
    return ERROR;
  
  return OK;
 error:
  out[0]=NULL;
  DXDelete((Object)outobject);
  return ERROR;
}
