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

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>

#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 Clientdata areastruct;

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

/*----------------------------------------------------------------------*/
/* Declare all graphic image targets to be invalid.   This is required	*/
/* on any change in the zoom factor of the current object.		*/
/*----------------------------------------------------------------------*/

void invalidate_graphics(objectptr thisobj)
{
   genericptr *gp;

   for (gp = thisobj->plist; gp < thisobj->plist + thisobj->parts; gp++) {
      if (IS_GRAPHIC(*gp))
	 TOGRAPHIC(gp)->valid = FALSE;
      else if (IS_OBJINST(*gp))
	 invalidate_graphics((TOOBJINST(gp))->thisobject);
   }
}

/*----------------------------------------------------------------------*/
/* Generate the target view of the indicated graphic image, combining	*/
/* the image's scale and rotation and the zoom factor of the current	*/
/* view.								*/
/*----------------------------------------------------------------------*/

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

    scale = gp->scale * UTopTransScale(1.0);

    cosr = cos(RADFAC * gp->rotation);
    sinr = sin(RADFAC * gp->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 = gp->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;

    if (gp->target != NULL) XDestroyImage(gp->target);
    if (gp->clipmask != (Pixmap)NULL) XFreePixmap(dpy, gp->clipmask);

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

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

    if (gp->rotation != 0) {
       gp->clipmask = XCreatePixmap(dpy, areastruct.areawin, 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->valid = 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;
    int width, height;

    if (gp->valid == FALSE) transform_graphic(gp);

    user_to_window(gp->position, &ppt);

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

    if (gp->clipmask != (Pixmap)NULL) {
       XSetClipOrigin(dpy, areastruct.gc, ppt.x, ppt.y);
       XSetClipMask(dpy, areastruct.gc, gp->clipmask);
    }

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

    if (gp->clipmask != (Pixmap)NULL)
       XSetClipMask(dpy, areastruct.gc, None);
}

/*----------------------------------------------------------------------*/
/* 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 = 1;
   iptr->image = XCreateImage(dpy, DefaultVisual(dpy, screen),
		DefaultDepth(dpy, screen), ZPixmap, 0, NULL,
		width, height, 8, 0);
   iptr->image->data = (char *)calloc(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 nf, nr, width, height, imax, x, y, i;
    char id[5], c, buf[128];
    union {
       unsigned char b[4];
       unsigned long i;
    } pixel;

    locdestinst = (destinst == NULL) ? areastruct.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)) {
	  iptr->refcount++;
	  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);
          }
    }

    NEW_GRAPHIC(gp, destobject);
    destobject->parts++;

    (*gp)->valid = FALSE;
    (*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;

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

    register_for_undo(XCF_Graphic, UNDO_DONE, areastruct.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) {
	    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) XDestroyImage(gp->target);
   if (gp->clipmask != (Pixmap)NULL) XFreePixmap(dpy, gp->clipmask);
   freeimage(gp->source);
}

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