/*----------------------------------------------------------------------*/
/* graphic.c --- xcircuit routines handling rendered graphic elements	*/
/* Copyright (c) 2005  Tim Edwards, MultiGiG, Inc.			*/
/*----------------------------------------------------------------------*/

/*----------------------------------------------------------------------*/
/*	written by Tim Edwards, 7/11/05					*/
/*----------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#ifndef _MSC_VER
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#endif

#ifdef OPENGL
#include <GL/gl.h>
#include <GL/glx.h>
#endif /* OPENGL */

#ifdef TCL_WRAPPER
#include <tk.h>
#endif

/*----------------------------------------------------------------------*/
/* Local includes							*/
/*----------------------------------------------------------------------*/

#include "xcircuit.h"
#include "colordefs.h"

/*----------------------------------------------------------------------*/
/* Function prototype declarations					*/
/*----------------------------------------------------------------------*/
#include "prototypes.h"

/*----------------------------------------------------------------------*/
/* Global Variable definitions						*/
/*----------------------------------------------------------------------*/

extern Display  *dpy;
extern Globaldata xobjs;
extern XCWindowData *areawin;
extern colorindex *colorlist;
extern int number_colors;

/*----------------------------------------------------------------------*/
/* Recursive search for graphic images in an object.			*/
/* Updates list "glist" when any image is found in an object or its	*/
/* descendents.								*/
/*----------------------------------------------------------------------*/

void count_graphics(objectptr thisobj, short *glist)
{
   genericptr *ge;
   graphicptr gp;
   Imagedata *iptr;
   int i;

   for (ge = thisobj->plist; ge < thisobj->plist + thisobj->parts; ge++) {
      if (IS_GRAPHIC(*ge)) {
	 gp = TOGRAPHIC(ge);
         for (i = 0; i < xobjs.images; i++) {
	    iptr = xobjs.imagelist + i;
            if (iptr->image == gp->source) {
	       glist[i]++;
	    }
	 }
      }
      else if (IS_OBJINST(*ge)) {
	 count_graphics(TOOBJINST(ge)->thisobject, glist);
      }
   }
}

/*----------------------------------------------------------------------*/
/* Given a list of pages, return a list of indices into the graphics	*/
/* buffer area of each graphic used on any of the indicated pages.	*/
/* The returned list is allocated and it is the responsibility of the	*/
/* calling routine to free it.						*/
/*----------------------------------------------------------------------*/

short *collect_graphics(short *pagelist)
{
   short *glist;
   int i;

   glist = (short *)malloc(xobjs.images * sizeof(short));

   for (i = 0; i < xobjs.images; i++) glist[i] = 0;

   for (i = 0; i < xobjs.pages; i++)
      if (pagelist[i] > 0)
	 count_graphics(xobjs.pagelist[i]->pageinst->thisobject, glist);
	
   return glist;
}

/*----------------------------------------------------------------------*/
/* Generate the target view of the indicated graphic image, combining	*/
/* the image's scale and rotation and the zoom factor of the current	*/
/* view.								*/
/*									*/
/* If the graphic is at the wrong scale or rotation but is not redrawn	*/
/* because it is outside the screen viewing area, return FALSE. 	*/
/* Otherwise, return TRUE.						*/
/*----------------------------------------------------------------------*/

Boolean transform_graphic(graphicptr gp)
{
    int width, height, twidth, theight, rotation;
    float scale, tscale;
    double cosr, sinr;
    int x, y, c, s, hw, hh, thw, thh, xorig, yorig, crot, xc, yc;
    int screen = DefaultScreen(dpy);
    static GC cmgc = (GC)NULL;

    tscale = UTopScale();
    scale = gp->scale * tscale;
    rotation = gp->rotation + UTopRotation();

    if (rotation >= 360) rotation -= 360;
    else if (rotation < 0) rotation += 360;

    /* Check if the top-level rotation and scale match the	*/
    /* saved target image.  If so, then we're done.		*/
    if ((rotation == gp->trot) && (scale == gp->tscale)) return TRUE;

    cosr = cos(RADFAC * rotation);
    sinr = sin(RADFAC * rotation);
    c = (int)(8192 * cosr / scale);
    s = (int)(8192 * sinr / scale);

    /* Determine the necessary width and height of the pixmap	*/
    /* that fits the rotated and scaled image.			*/

    crot = rotation;
    if (crot > 90 && crot < 180) crot = 180 - crot;
    if (crot > 270 && crot < 360) crot = 360 - crot;
    cosr = cos(RADFAC * crot);
    sinr = sin(RADFAC * crot);
    width = gp->source->width * scale;
    height = gp->source->height * scale;

    twidth = (int)(fabs(width * cosr + height * sinr));
    theight = (int)(fabs(width * sinr + height * cosr));
    if (twidth & 1) twidth++;
    if (theight & 1) theight++;

    /* Check whether this instance is going to be off-screen,	*/
    /* to avoid excessive computation.				*/

    UTopOffset(&xc, &yc);
    xc += (int)((float)gp->position.x * tscale);
    yc = areawin->height - yc;
    yc += (int)((float)gp->position.y * tscale);

    if (xc - (twidth >> 1) > areawin->width) return FALSE;
    if (xc + (twidth >> 1) < 0) return FALSE;
    if (yc - (theight >> 1) > areawin->height) return FALSE;
    if (yc + (theight >> 1) < 0) return FALSE;

    /* Generate the new target image */
    if (gp->target != NULL) {
	if (gp->target->data != NULL) {
	   /* Free target memory first, because we use our own malloc() */
	   free(gp->target->data);
	   gp->target->data = NULL;
	}
	XDestroyImage(gp->target);
    }
    if (gp->clipmask != (Pixmap)NULL) XFreePixmap(dpy, gp->clipmask);

    gp->target = XCreateImage(dpy, DefaultVisual(dpy, screen),
			DefaultDepth(dpy, screen), ZPixmap,
			0, 0, twidth, theight, 8, 0);
    gp->target->data = (char *)malloc(theight * gp->target->bytes_per_line);

    if (gp->target->data == (char *)NULL) {
       XDestroyImage(gp->target);
       gp->target = (XImage *)NULL;
       gp->clipmask = (Pixmap)NULL;
       return FALSE;
    }

    if (rotation != 0) {
       gp->clipmask = XCreatePixmap(dpy, areawin->window, twidth, theight, 1);
       if (cmgc == (GC)NULL) {
          XGCValues values;
          values.foreground = 0;
          values.background = 0;
          cmgc = XCreateGC(dpy, gp->clipmask, GCForeground | GCBackground, &values);
       }
       XSetForeground(dpy, cmgc, 1);
       XFillRectangle(dpy, gp->clipmask, cmgc, 0, 0, twidth, theight);
       XSetForeground(dpy, cmgc, 0);
    }
    else
       gp->clipmask = (Pixmap)NULL;

    hh = gp->source->height >> 1;
    hw = gp->source->width >> 1;
    thh = theight >> 1;
    thw = twidth >> 1;
    for (y = -thh; y < thh; y++) {
	for (x = -thw; x < thw; x++) {
	    xorig = ((x * c + y * s) >> 13) + hw;
	    yorig = ((-x * s + y * c) >> 13) + hh;

	    if ((xorig >= 0) && (yorig >= 0) &&
			(xorig < gp->source->width) && (yorig < gp->source->height))
	       XPutPixel(gp->target, x + thw, y + thh,
			XGetPixel(gp->source, xorig, yorig));
	    else if (gp->clipmask)
	       XDrawPoint(dpy, gp->clipmask, cmgc, x + thw, y + thh);
	}
    }
    gp->tscale = scale;
    gp->trot = rotation;
    return TRUE;
}

/*----------------------------------------------------------------------*/
/* Draw a graphic image by copying from the image to the window.	*/
/* Image is centered on the center point of the graphic image.		*/
/*----------------------------------------------------------------------*/

void UDrawGraphic(graphicptr gp)
{
    XPoint ppt;

    /* transform to current scale and rotation, if necessary */
    if (transform_graphic(gp) == FALSE) return;  /* Graphic off-screen */

    /* transform to current position */
    UTransformbyCTM(DCTM, &(gp->position), &ppt, 1);

    /* user_to_window(gp->position, &ppt); */

    ppt.x -= (gp->target->width >> 1);
    ppt.y -= (gp->target->height >> 1);

    if (gp->clipmask != (Pixmap)NULL) {
       if (areawin->clipped > 0) {
	  /* Clipmask areawin->clipmask already applied to window. */
	  /* Modify existing clipmask with the graphic's.	   */
	  XSetFunction(dpy, areawin->cmgc, GXand);
	  XCopyArea(dpy, gp->clipmask, areawin->clipmask, areawin->cmgc,
		0, 0, gp->target->width, gp->target->height, ppt.x, ppt.y);
          XSetClipMask(dpy, areawin->gc, areawin->clipmask);
	  XSetFunction(dpy, areawin->cmgc, GXcopy);
       }
       else {
          XSetClipOrigin(dpy, areawin->gc, ppt.x, ppt.y);
          XSetClipMask(dpy, areawin->gc, gp->clipmask);
       }
    }

    XPutImage(dpy, areawin->window, areawin->gc,
		gp->target, 0, 0, ppt.x, ppt.y, gp->target->width,
		gp->target->height);

    if (gp->clipmask != (Pixmap)NULL) {
       if (areawin->clipped <= 0) {
	  XSetClipMask(dpy, areawin->gc, None);
          XSetClipOrigin(dpy, areawin->gc, 0, 0);
       }
    }
}

/*----------------------------------------------------------------------*/
/* Allocate space for a new graphic source image of size width x height	*/
/*----------------------------------------------------------------------*/

Imagedata *addnewimage(char *name, int width, int height)
{
   Imagedata *iptr;
   int screen = DefaultScreen(dpy);

   /* Create the image and store in the global list of images */

   xobjs.images++;
   if (xobjs.imagelist)
      xobjs.imagelist = (Imagedata *)realloc(xobjs.imagelist,
		xobjs.images * sizeof(Imagedata));
   else
      xobjs.imagelist = (Imagedata *)malloc(sizeof(Imagedata));
    
   /* Save the image source in a file */
   iptr = xobjs.imagelist + xobjs.images - 1;
   if (name)
      iptr->filename = strdup(name);
   else
      iptr->filename = NULL;	/* must be filled in later! */
   iptr->refcount = 0;		/* no calls yet */
   iptr->image = XCreateImage(dpy, DefaultVisual(dpy, screen),
		DefaultDepth(dpy, screen), ZPixmap, 0, NULL,
		width, height, 8, 0);
   iptr->image->data = (char *)malloc(height * iptr->image->bytes_per_line);

   return iptr;
}

/*----------------------------------------------------------------------*/
/* Create a new graphic image from a PPM file, and position it at the	*/
/* indicated (px, py) coordinate in user space.				*/
/*									*/
/* This should be expanded to incorporate more PPM formats.  Also, it	*/
/* needs to be combined with the render.c routines to transform		*/
/* PostScript graphics into an internal XImage for faster rendering.	*/
/*----------------------------------------------------------------------*/

graphicptr new_graphic(objinstptr destinst, char *filename, int px, int py)
{
    graphicptr *gp;
    objectptr destobject;
    objinstptr locdestinst;
    Imagedata *iptr;
    FILE *fg;
    int nr, width, height, imax, x, y, i; /* nf, (jdk) */
    char id[5], c, buf[128];
    union {
       u_char b[4];
       u_long i;
    } pixel;

    locdestinst = (destinst == NULL) ? areawin->topinstance : destinst;
    destobject = locdestinst->thisobject;

    /* Check the existing list of images.  If there is a match,	*/
    /* re-use the source; don't load the file again.		*/

    for (i = 0; i < xobjs.images; i++) {
       iptr = xobjs.imagelist + i;
       if (!strcmp(iptr->filename, filename)) {
	  break;
       }
    }
    if (i == xobjs.images) {

       fg = fopen(filename, "r");
       if (fg == NULL) return NULL;

       /* This ONLY handles binary ppm files with max data = 255 */

       while (1) {
          nr = fscanf(fg, " %s", buf);
	  if (nr <= 0) return NULL;
	  if (buf[0] != '#') {
	     if (sscanf(buf, "%s", id) <= 0)
		return NULL;
	     break;
	  }
	  else fgets(buf, 127, fg);
       }
       if ((nr <= 0) || strncmp(id, "P6", 2)) return NULL;

       while (1) {
          nr = fscanf(fg, " %s", buf);
	  if (nr <= 0) return NULL;
	  if (buf[0] != '#') {
	     if (sscanf(buf, "%d", &width) <= 0)
		return NULL;
	     break;
	  }
	  else fgets(buf, 127, fg);
       }
       if (width <= 0) return NULL;

       while (1) {
          nr = fscanf(fg, " %s", buf);
	  if (nr <= 0) return NULL;
	  if (buf[0] != '#') {
	     if (sscanf(buf, "%d", &height) <= 0)
		return NULL;
	     break;
	  }
	  else fgets(buf, 127, fg);
       }
       if (height <= 0) return NULL;

       while (1) {
          nr = fscanf(fg, " %s", buf);
	  if (nr <= 0) return NULL;
	  if (buf[0] != '#') {
	     if (sscanf(buf, "%d", &imax) <= 0)
		return NULL;
	     break;
	  }
	  else fgets(buf, 127, fg);
       }
       if (imax != 255) return NULL;

       while (1) {
          fread(&c, 1, 1, fg);
          if (c == '\n') break;
          else if (c == '\0') return NULL;
       }

       iptr = addnewimage(filename, width, height);

       /* Read the image data from the PPM file */

       pixel.b[3] = 0;
       for (y = 0; y < height; y++)
          for (x = 0; x < width; x++) {
	     fread(&pixel.b[2], 1, 1, fg);
	     fread(&pixel.b[1], 1, 1, fg);
	     fread(&pixel.b[0], 1, 1, fg);
	     XPutPixel(iptr->image, x, y, pixel.i);
          }
    }

    iptr->refcount++;
    NEW_GRAPHIC(gp, destobject);

    (*gp)->scale = 1.0;
    (*gp)->position.x = px;
    (*gp)->position.y = py;
    (*gp)->rotation = 0;
    (*gp)->color = DEFAULTCOLOR;
    (*gp)->passed = NULL;
    (*gp)->clipmask = (Pixmap)NULL;
    (*gp)->source = iptr->image;
    (*gp)->target = NULL;
    (*gp)->trot = 0;
    (*gp)->tscale = 0;

    calcbboxvalues(locdestinst, (genericptr *)gp);
    updatepagebounds(destobject);
    incr_changes(destobject);

    register_for_undo(XCF_Graphic, UNDO_DONE, areawin->topinstance, *gp);

    return *gp;
}

/*----------------------------------------------------------------------*/
/* Create a gradient field graphic					*/
/* For now, gradient field is linear white-to-black size 100x100.	*/
/*----------------------------------------------------------------------*/

graphicptr gradient_field(objinstptr destinst, int px, int py, int c1, int c2)
{
    graphicptr *gp;
    objectptr destobject;
    objinstptr locdestinst;
    Imagedata *iptr;
    int width, height, imax, x, y, i;
    int r, g, b, rd, gd, bd;
    char id[11];
    union {
       u_char b[4];
       u_long i;
    } pixel;

    locdestinst = (destinst == NULL) ? areawin->topinstance : destinst;
    destobject = locdestinst->thisobject;

    if (c1 < 0) c1 = 0;
    if (c1 >= number_colors) c1 = 1;
    if (c2 < 0) c2 = 0;
    if (c2 >= number_colors) c2 = 1;

    /* Create name "gradientXX"	*/

    y = 0;
    for (i = 0; i < xobjs.images; i++) {
       iptr = xobjs.imagelist + i;
       if (!strncmp(iptr->filename, "gradient", 8)) {
	  if (sscanf(iptr->filename + 8, "%2d", &x) == 1)
	     if (x >= y)
		y = x + 1;
       }
    }
    sprintf(id, "gradient%02d", y);

    width = height = 100;	/* Fixed size, at least for now */

    iptr = addnewimage(id, width, height);

    r = (colorlist[c1].color.red >> 8);
    g = (colorlist[c1].color.green >> 8);
    b = (colorlist[c1].color.blue >> 8);

    rd = (colorlist[c2].color.red >> 8) - r;
    gd = (colorlist[c2].color.green >> 8) - g;
    bd = (colorlist[c2].color.blue >> 8) - b;

    pixel.b[3] = 0;
    for (y = 0; y < height; y++) {
       pixel.b[2] = r + ((y * rd) / (height - 1));
       pixel.b[1] = g + ((y * gd) / (height - 1));
       pixel.b[0] = b + ((y * bd) / (height - 1));
       for (x = 0; x < width; x++) {
	  XPutPixel(iptr->image, x, y, pixel.i);
       }
    }

    iptr->refcount++;
    NEW_GRAPHIC(gp, destobject);

    (*gp)->scale = 1.0;
    (*gp)->position.x = px;
    (*gp)->position.y = py;
    (*gp)->rotation = 0;
    (*gp)->color = DEFAULTCOLOR;
    (*gp)->passed = NULL;
    (*gp)->clipmask = (Pixmap)NULL;
    (*gp)->source = iptr->image;
    (*gp)->target = NULL;
    (*gp)->trot = 0;
    (*gp)->tscale = 0;

    calcbboxvalues(locdestinst, (genericptr *)gp);
    updatepagebounds(destobject);
    incr_changes(destobject);

    register_for_undo(XCF_Graphic, UNDO_DONE, areawin->topinstance, *gp);

    return *gp;
}


/*----------------------------------------------------------------------*/
/* Free memory associated with the XImage structure for a graphic.	*/
/*----------------------------------------------------------------------*/

void freeimage(XImage *source)
{
   int i, j;
   Imagedata *iptr;

   for (i = 0; i < xobjs.images; i++) {
      iptr = xobjs.imagelist + i;
      if (iptr->image == source) {
	 iptr->refcount--;
	 if (iptr->refcount <= 0) {
	    /* Free data first, because we used our own malloc() */
	    if (iptr->image->data != NULL) {
	       free(iptr->image->data);
	       iptr->image->data = NULL;
	    }
	    XDestroyImage(iptr->image);
	    free(iptr->filename);

	    /* Remove this from the list of images */

	    for (j = i; j < xobjs.images - 1; j++)
	       *(xobjs.imagelist + j) = *(xobjs.imagelist + j + 1);
	    xobjs.images--;
	 }
	 break;
      }
   }
}

/*----------------------------------------------------------------------*/
/* Free memory allocated by a graphicptr structure.			*/
/*----------------------------------------------------------------------*/

void freegraphic(graphicptr gp)
{
   if (gp->target != NULL) {
      /* Free data first, because we used our own malloc() */
      if (gp->target->data != NULL) {
	 free(gp->target->data);
	 gp->target->data = NULL;
      }
      XDestroyImage(gp->target);
   }
   if (gp->clipmask != (Pixmap)NULL) XFreePixmap(dpy, gp->clipmask);
   freeimage(gp->source);
}

/*----------------------------------------------------------------------*/
