#include <X11/IntrinsicP.h> 
#include <X11/CoreP.h>
#include <X11/StringDefs.h> 
#include <Scale.h>
#include "ThreedeeP.h" 
#include "Plot3P.h"

#define offset(field) XtOffset(AtPlot3Widget, field)

static XtResource plot3_resources[] = {
  {XtNplotSurface, XtCPlotSurface, XtRSurface,
     sizeof(Surface), offset(plot3.plotSurface),
     XtRImmediate, NULL},
  {XtNedgeColor, XtCEdgeColor, XtRPixel,
     sizeof(Pixel), offset(plot3.edgeColor),
     XtRString, (caddr_t)XtDefaultForeground},
  {XtNfillColor, XtCFillColor, XtRPixel,
     sizeof(Pixel), offset(plot3.fillColor),
     XtRString, (caddr_t)XtDefaultForeground},
  {XtNlineWidth, XtCLineWidth, XtRInt, sizeof(int),
     offset(plot3.lineWidth), XtRImmediate,(caddr_t)0 },
  {XtNnoFill, XtCNoFill, XtRBoolean, sizeof(Boolean), 
     offset(plot3.noFill), XtRImmediate, (caddr_t)False }
};

#undef offset

/* Methods */
static void Initialize(AtPlot3Widget, AtPlot3Widget);
static void Destroy(AtPlot3Widget);
static Boolean SetValues(AtPlot3Widget, AtPlot3Widget, 
			 AtPlot3Widget);
static void Recompute(AtThreedeeWidget, AtPlot3Widget);

/* Projection Functions */
void ProjectPoint(AtPlot3Widget, XYZ, Pt);
Poly ProjectAsPolygon(AtPlot3Widget, XYZ *, int);
Projection ProjectSurface(AtPlot3Widget, Surface);
Projection ProjCreate();
void ProjDeleteSurface(Projection, AtPlot3Widget);
void ProjMerge(Projection, Projection);
Poly PolyCopy(Poly);
/* required to impl Merge so plotProjection
   isn't destroyedor modified when merged into cumProjection.
   Merge makes a copy and ruins that! */


/* Class Record */
AtPlot3ClassRec atPlot3ClassRec = {
  { /******* CoreClassPart *******/
    /* superclass           */  (WidgetClass) &widgetClassRec,
    /* class_name           */  "AtPlot3",
    /* widget_size          */  sizeof(AtPlot3Rec),
    /* class_initialize     */  NULL,
    /* class_part_initialize*/  NULL,
    /* class_inited         */  FALSE,
    /* initialize           */  (XtInitProc)Initialize,
    /* initialize_hook      */  NULL,
    /* realize              */  NULL,
    /* actions              */  NULL,
    /* num_actions          */  0,
    /* resources            */  plot3_resources,
    /* num_resources        */  XtNumber(plot3_resources),
    /* xrm_class            */  NULLQUARK,
    /* compress_motion      */  FALSE,
    /* compress_exposure    */  FALSE,
    /* compress_enterleave  */  FALSE,
    /* visible_interest     */  FALSE,
    /* destroy              */  (XtWidgetProc)Destroy,
    /* pad                  */  NULL,
    /* expose               */  NULL,
    /* set_values           */  (XtSetValuesFunc) SetValues,
    /* set_values_hook      */  NULL,
    /* set_values_almost    */  NULL,
    /* get_values_hook      */  NULL,
    /* accept_focus         */  NULL,
    /* version              */  XtVersion,
    /* callback_offsets     */  NULL,
    /* tm_table             */  NULL,
    /* query_geometry       */  NULL,
    /* display_accelerator  */  NULL,
    /* extension            */  NULL
  },
/* AtPlot3Part initialization */
  {
    /* resize()             */  Recompute
  }
};


WidgetClass atPlot3WidgetClass = (WidgetClass)&atPlot3ClassRec;

#define AtWarning(w, msg) XtWarning(msg)

     
static void Initialize(request, new)
     AtPlot3Widget request, new;
{
  XGCValues gcv;
  
  AtThreedeeWidget tw = (AtThreedeeWidget)XtParent(new);

#ifdef DEBUGPLOT3
  printf("Plot3Initialize begins\n");
#endif DEBUGPLOT3

  if(!(new->plot3.plotSurface))
    new->plot3.plotSurface = SurfaceCreate();
  
  new->plot3.plotProjection = ProjCreate();

  gcv.foreground = new->plot3.edgeColor;
  gcv.line_width = new->plot3.lineWidth;
  new->plot3.edgeGC = XtGetGC(new, GCForeground | GCLineWidth, &gcv);
  XSetRegion(XtDisplay((Widget)new), new->plot3.edgeGC, 
	     ((AtThreedeeWidget)XtParent(new))->threedee.plotRegion);

  gcv.foreground = new->plot3.fillColor;
  new->plot3.fillGC = XtGetGC(new, GCForeground, &gcv);
  XSetRegion(XtDisplay((Widget)new), new->plot3.fillGC, 
	     ((AtThreedeeWidget)XtParent(new))->threedee.plotRegion);

  new->plot3.xscale = tw->threedee.xscale;
  new->plot3.yscalex = tw->threedee.yscalex;
  new->plot3.yscaley = tw->threedee.yscaley;
  new->plot3.zscale = tw->threedee.zscale;

  new->plot3.minx = new->plot3.plotSurface->minx;
  new->plot3.maxx = new->plot3.plotSurface->maxx;
  new->plot3.miny = new->plot3.plotSurface->miny;
  new->plot3.maxy = new->plot3.plotSurface->maxy;
  new->plot3.minz = new->plot3.plotSurface->minz;
  new->plot3.maxz = new->plot3.plotSurface->maxz;
  ((AtThreedeeWidget)XtParent(new))->threedee.plotRegion;  
  new->plot3.xmin_pix = 
    ((AtThreedeeWidget)XtParent(new))->threedee.xscale->lowpix;
  new->plot3.yminx_pix = 
    ((AtThreedeeWidget)XtParent(new))->threedee.yscalex->lowpix;
  new->plot3.yminy_pix = 
    ((AtThreedeeWidget)XtParent(new))->threedee.yscaley->lowpix;
  new->plot3.zmin_pix = 
    ((AtThreedeeWidget)XtParent(new))->threedee.zscale->lowpix;

  new->plot3.valid = True;

#ifdef DEBUGPLOT3
  printf("Plot3Initialize ends\n");
#endif DEBUGPLOT3
}

static Boolean SetValues(current, request, new)
     AtPlot3Widget current, request, new;
{
  XGCValues gcv;
  Boolean redraw = False;
  
#define Changed(field) (new->plot3.field != current->plot3.field)

#ifdef DEBUGPLOT3
  printf("Plot3SetValues begins\n");
#endif DEBUGPLOT3

  if(Changed(edgeColor))
    {
      XSetForeground(XtDisplay((Widget)new), new->plot3.edgeGC, 
		     new->plot3.edgeColor);
      redraw = True;
    }

  if(Changed(fillColor))
    {
      XSetForeground(XtDisplay((Widget)new), new->plot3.fillGC, 
		     new->plot3.fillColor);
      redraw = True;
    }

  if(Changed(lineWidth))
    {
      XSetLineAttributes(XtDisplay((Widget)new), new->plot3.edgeGC, 
		     new->plot3.lineWidth, LineSolid, CapButt, JoinMiter);
      redraw = True;
    }

  if(Changed(noFill))
    redraw = True;

  if(Changed(plotSurface))
    {
      /*       AtWarning(new,
	       "Not equipped to change a surface. Restoring old surface.");
	       new->plotSurface = current->plotSurface;   */
      
      At3dReplaceSurface(((AtThreedeeWidget)XtParent(new))->
			 threedee.cumProjection, new);
      redraw = True;
    }

#ifdef DEBUGPLOT3
  printf("Plot3SetValues ends\n");
#endif DEBUGPLOT3

  return(redraw);
}

static void Recompute(AtThreedeeWidget tw, AtPlot3Widget pw)
{

#ifdef DEBUGPLOT3
  printf("Plot3Recompute begins \n");
#endif DEBUGPLOT3
  
  /* give edgeGC and fillGC new clipmasks */
  XSetRegion(XtDisplay((Widget)tw), pw->plot3.edgeGC, 
	     tw->threedee.plotRegion);
  XSetRegion(XtDisplay((Widget)tw), pw->plot3.fillGC, 
	     tw->threedee.plotRegion);
  
  /* set scales */
  pw->plot3.xscale = tw->threedee.xscale;
  pw->plot3.yscalex = tw->threedee.yscalex;
  pw->plot3.yscaley = tw->threedee.yscaley;
  pw->plot3.zscale = tw->threedee.zscale;
  
  /* recompute Projection */
  free(pw->plot3.plotProjection);
  pw->plot3.plotProjection = ProjectSurface(pw,
				      pw->plot3.plotSurface);
  
  /* reset valid to True */
  pw->plot3.valid = True;
  
#ifdef DEBUGPLOT3
  printf("Plot3Recompute ends \n");
#endif DEBUGPLOT3
  
}

static void Destroy(AtPlot3Widget pw)
{
#ifdef DEBUGPLOT3
  printf("Plot3Destroy begins\n");
#endif DEBUGPLOT3
  
  XtReleaseGC(pw->plot3.edgeGC);
  XtReleaseGC(pw->plot3.fillGC);
  
  free(pw->plot3.plotSurface);
  free(pw->plot3.plotProjection);

#ifdef DEBUGPLOT3
  printf("Plot3Destroy ends\n");
#endif DEBUGPLOT3
}



/******************************************************/
/* FUNCTIONS FOR CALCULATING PROJECTION OF A  SURFACE */
/******************************************************/

void ProjectPoint(AtPlot3Widget pw, XYZ raw, Pt image)
{
  /*  Pt image; */
  /*  image = (Pt)malloc(sizeof(PtRec)); */

  image->depth = raw.y;
  image->loc.x = (AtScaleUserToPixel(pw->plot3.xscale, raw.x) +
              AtScaleUserToPixel(pw->plot3.yscalex, raw.y) -
              pw->plot3.yscalex->lowpix);
  image->loc.y = (AtScaleUserToPixel(pw->plot3.zscale, raw.z) -
              AtScaleUserToPixel(pw->plot3.yscaley, raw.y) +
              pw->plot3.yscaley->lowpix);

  /*  return(image); */
}


Poly ProjectAsPolygon(AtPlot3Widget pw, XYZ *vertices, int nverts)
{
  Pt temp;
  Poly image;
  int i;

  if(nverts < 0) {
    AtWarning(pw,"ProjectAsPolygon: nverts < 0");
    nverts = 0;
  }
  if((nverts == 0) && (vertices != NULL)){
    AtWarning(pw, "ProjectAsPolygon: nverts = 0 but vertices != NULL");
    vertices = NULL;
  }
  if((nverts != 0) && (vertices == NULL)){
    AtWarning(pw, "ProjectAsPolygon: nverts != 0 but vertices = NULL");
    nverts = 0;
  }
  temp = (Pt)malloc(sizeof(PtRec));
  image = (Poly)malloc(sizeof(PolyRec));
  image->num_verts = nverts;
  image->vertices = (XPoint *)XtMalloc(sizeof(XPoint) * nverts);

  ProjectPoint(pw, vertices[0], temp);
  image->vertices[0].x = temp->loc.x;
  image->vertices[0].y = temp->loc.y;
  image->depth = temp->depth;
  for (i = 1; i < nverts; i++) {
    ProjectPoint(pw, vertices[i], temp);
    image->vertices[i].x = temp->loc.x;
    image->vertices[i].y = temp->loc.y;
    if(temp->depth < image->depth)
      image->depth = temp->depth;
    /* a Poly's depth is the min of its vertex depths */
  }
  image->tag = (Widget)pw;
  return(image);
}


Projection ProjectSurface(AtPlot3Widget pw, Surface sur)
{
  Projection new;
  int i, j, min, max, center, npolys;
  XYZ vertices[5];
  Poly poly, *temp;
  /* Project surface to plane 4 pts at a time */

#ifdef DEBUGPLOT3
  printf("ProjectSurface begins\n");
#endif DEBUGPLOT3


  
  
  new = (Projection)malloc(sizeof(ProjectionRec));
  new->num_polys = (sur->ncols - 1) * (sur->nrows - 1);
  new->polygons = (Poly *)malloc(sizeof(Poly) * new->num_polys);
  /* an array of polys */
  
  npolys = 0;
  temp = (Poly *)malloc(sizeof(Poly) * new->num_polys);

  /* row j */
  for(j = 0; j < sur->ncols - 1; j++) {
    vertices[4] = vertices[0] = sur->data[j][0];
    vertices[3] = sur->data[j+1][0];
    
    /* column i */
    for(i = 1; i < sur->nrows; i++) {
      vertices[1] = sur->data[j][i];
      vertices[2] = sur->data[j+1][i];

      poly = ProjectAsPolygon(pw, vertices, 5);
      
      if(npolys == 0){
        new->polygons[0] = poly;
        npolys = 1;
      }
      
      else {
        if(poly->depth < new->polygons[npolys - 1]->depth) {
          /* insert at high end of array */
          new->polygons[npolys] = poly;
          npolys++;
        }
        else if(poly->depth > new->polygons[0]->depth) {
          /* insert at low end of array */
          bcopy((char *)(new->polygons),
                (char *)temp,
                sizeof(Poly) * npolys);
          new->polygons[0] = poly;
          bcopy((char *)temp,
                (char *)(new->polygons + 1),
                sizeof(Poly) * npolys);
          npolys++;
        }
        else {
          /* binary search for insertion index */
          max = npolys - 1;
          min = 0;
          while(max - min > 1) {
            center = (int)((max + min)/2);
	    if(new->polygons[center]->depth < poly->depth) {
              /* center too shallow */
              max = center;
              continue;
            }
            if(new->polygons[center]->depth > poly->depth) {
              /* center too deep */
              min = center;
              continue;
            }
            /* else d(center) == d(poly) , so insert poly at
               new->polygons[center] and shift center...npolys - 1 to the
               right */
            bcopy((char *)(new->polygons + center),
                  (char *)temp,
                  sizeof(Poly) * (npolys - center));
            new->polygons[center] = poly;
            bcopy((char *)temp,
                  (char *)(new->polygons + center + 1),
                  sizeof(Poly) * (npolys - center));
            npolys++;
            break;
          }/* while */

          /* if max - min < 1 at this point, make the insertion at max */
          if(max <= min + 1) {
            bcopy((char *)(new->polygons + max),
                  (char *)temp,
                  sizeof(Poly) * (npolys - max));
            new->polygons[center] = poly;
            bcopy((char *)temp,
                  (char *)(new->polygons + max + 1),
                  sizeof(Poly) * (npolys - max));
            npolys++;

          } /* if(max<=min+1) */
        } /* else binary search */
      } /* else npolys > 0 */

      vertices[0] = vertices[1];
      vertices[4] = vertices[0];
      vertices[3] = vertices[2];
    } /* for i */
  } /* for j */

  if(npolys != new->num_polys)
    AtWarning(pw, "ProjectSurface: polygon counts differ");

#ifdef DEBUGPLOT3
  printf("ProjectSurface ends\n");
#endif DEBUGPLOT3

  return(new);
}


Projection ProjCreate()
{
  Projection new = (Projection)malloc(sizeof(ProjectionRec));
#ifdef DEBUGPLOT3
  printf("Call to ProjCreate\n");
#endif DEBUGPLOT3
  new->num_polys = 0;
  new->polygons = NULL;
  return(new);
}


void ProjDeleteSurface(Projection pro, AtPlot3Widget tag)
{
  Poly *new;
  int n, i, j;

#ifdef DEBUGPLOT3
  printf("Call to ProjDeleteSurface\n");
#endif DEBUGPLOT3

  n = 0;
  for(i = 0; i < pro->num_polys; i++) {
    if((AtPlot3Widget)pro->polygons[i]->tag != tag) {
      pro->polygons[n] = pro->polygons[i];
      n++;
    }
    pro->polygons = (Poly *)realloc(pro->polygons, n * sizeof(Poly));
  }
}


void ProjMerge(Projection expands, Projection unchanged)
{
  int new_num_polygons;
  Poly *new_polygons;
  int i, j, k;
  
#ifdef DEBUGPLOT3
  printf("ProjMerge begins\n");
#endif DEBUGPLOT3


  j = k = 0; /* i = current NEW poly,
                j = current UNCHANGED poly,
                k = current EXPANDS poly */

  new_num_polygons = expands->num_polys + unchanged->num_polys;
  new_polygons = (Poly *)malloc(sizeof(Poly) * new_num_polygons);
  for(i = 0; i < new_num_polygons; i++)
    {
      if(unchanged->polygons[j]->depth > expands->polygons[k]->depth) {
        new_polygons[i] = PolyCopy(unchanged->polygons[j]);
        j++;
      }

      else {
        new_polygons[i] = expands->polygons[k];
        k++;
      }

    }

  expands->num_polys = new_num_polygons;
  expands->polygons = new_polygons;

#ifdef DEBUGPLOT3
  printf("ProjMerge ends\n");
#endif DEBUGPLOT3

}


Poly PolyCopy(Poly orig)
{
  Poly new;
  new = (Poly)malloc(sizeof(PolyRec));
  new->depth = orig->depth;
  new->num_verts = orig->num_verts;
  new-> vertices = (XPoint *)XtMalloc(sizeof(XPoint) * orig->num_verts);
  bcopy((char *)orig->vertices,
        (char *)new->vertices,
        sizeof(XPoint) * orig->num_verts);
  new->tag = orig->tag;
  return(new);
}
