/*
    This file is part of the FElt finite element analysis package.
    Copyright (C) 1993 Jason I. Gobat and Darren C. Atkinson

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/****************************************************************************
 * 
 * File:	stress.c
 *
 ***************************************************************************/

# include <stdio.h>
# include <string.h>
# include <ctype.h>
# include <X11/Xlib.h>
# include <X11/Xutil.h>
# include <X11/Intrinsic.h>
# include <X11/StringDefs.h>
# include <X11/Xaw/Form.h>
# include <X11/Xaw/Dialog.h>
# include <X11/Shell.h>
# include "Solution.h"
# include "fe.h"
# include "Drawing.h"
# include "allocate.h"

extern Widget toplevel;

# include "colormap.h"

# define NUMCELLS		128
# define MaxNodesPerElement 	24

static Widget		stressShell;
static unsigned long	pixel [NUMCELLS];   	
static unsigned		rev_pixel [256];
static Widget		stress_dw;
static Pixmap		pixmap;
static XImage		*ximage = NULL;

static unsigned	OkayButton = 1;
static unsigned SaveButton = 2;

static void	ButtonActions ();

static void	LoadImage ();
static void	HequalImage ();

void InitializeColormap ()
{
   Colormap		cmap;
   unsigned long	plane_masks [8];
   Status		result;
   XColor		colors [NUMCELLS];
   unsigned		cell;
   int			shift;
   unsigned		intensity;
   int			ncells;
   int			screen;
   Display		*display;

   display = XtDisplay (toplevel);
   screen = DefaultScreen (display);
   
   cmap = DefaultColormap (display, screen);

   for (ncells = NUMCELLS ; ncells > 1 ; ncells--) {

      result = XAllocColorCells (display, cmap, True, plane_masks, 0,
                                 pixel, ncells);
      if (result) 
         break;
   }

   for (cell = 0 ; cell < ncells ; cell++) {
      colors [cell].red = color_values [cell].r;
      colors [cell].green = color_values [cell].g;
      colors [cell].blue = color_values [cell].b;
      colors [cell].pixel = pixel [cell];
      colors [cell].flags = DoRed | DoGreen | DoBlue;
      
      rev_pixel [pixel [cell]] = cell;
   }

   XStoreColors (display, cmap, colors, ncells);
}

void CreateStressShell ( )
{
   Arg			arglist [10];
   Cardinal		count;
   Widget		formwidget,buttonwidget;

   stressShell = XtCreatePopupShell ("stressShell",transientShellWidgetClass,
                               toplevel,NULL, 0);

   count = 0;
   XtSetArg (arglist [count], XtNborderWidth, 0); count++;
   formwidget = XtCreateManagedWidget ("form", formWidgetClass, 
                                       stressShell, arglist, count);

   count = 0;
   XtSetArg (arglist [count], XtNgrid, False); count++;
   XtSetArg (arglist [count], XtNtop, XawChainTop); count++;
   XtSetArg (arglist [count], XtNbottom, XawChainBottom); count++;
   XtSetArg (arglist [count], XtNborderWidth, 3); count++;
   stress_dw = XtCreateManagedWidget ("stress_dw", drawingWidgetClass, 
                                      formwidget, arglist, count);

   count = 0;
   XtSetArg (arglist [count], XtNlabel, ""); count++;
   XtSetArg (arglist [count], XtNborderWidth, 0); count++;
   XtSetArg (arglist [count], XtNtop, XawChainBottom); count++;
   XtSetArg (arglist [count], XtNbottom, XawChainBottom); count++;
   XtSetArg (arglist [count], XtNleft, XawChainLeft); count++;
   XtSetArg (arglist [count], XtNright, XawChainLeft); count++;
   XtSetArg (arglist [count], XtNfromVert, stress_dw); count++;

   buttonwidget = XtCreateManagedWidget ("buttons", dialogWidgetClass,
                                         formwidget, arglist, count);

   XawDialogAddButton (buttonwidget, "dismiss", ButtonActions, &OkayButton);
   XawDialogAddButton (buttonwidget, "save", ButtonActions, &SaveButton);

   return; 
}

void InitializeDrawingShell (shell, dw, minX, maxX, minY, maxY,
                             x_scale, y_scale, wx, hy)
   Widget	shell, dw;
   float	maxX, minX,
		minY, maxY;	
   float	*x_scale, *y_scale;
   Dimension	*wx, *hy;
{
   Arg		arglist [12];
   Cardinal	count;
   Dimension	width, height;
   float	w,h;
   float	Xscale, Yscale;
   XSizeHints	hints;
   unsigned	i,j;

   XtRealizeWidget (shell);

   count = 0;
   XtSetArg (arglist [count], XtNwidth, &width); count++;
   XtSetArg (arglist [count], XtNheight, &height); count++;
   XtGetValues (dw, arglist, count);

   if (width < 250)
      width = 250;
   if (height < 250)
      height = 250;

   w = (float) width;
   h = (float) height;

   if ((maxX - minX)/w > (maxY - minY)/h) {
      Xscale = w / (maxX - minX);
      Yscale = Xscale;
      height = (Dimension) ((maxY - minY)*Yscale);
   }
   else {
      Yscale = h / (maxY - minY);
      Xscale = Yscale;
      width = (Dimension) ((maxX - minX)*Xscale);
   }

   count = 0;
   XtSetArg (arglist [count], XtNxMin, Float2Arg (minX)); count++;
   XtSetArg (arglist [count], XtNxMax, Float2Arg (maxX)); count++;
   XtSetArg (arglist [count], XtNyMin, Float2Arg (minY)); count++;
   XtSetArg (arglist [count], XtNyMax, Float2Arg (maxY)); count++;
   XtSetArg (arglist [count], XtNyScale, Float2Arg (Yscale)); count++;
   XtSetArg (arglist [count], XtNxScale, Float2Arg (Xscale)); count++;
   XtSetArg (arglist [count], XtNwidth, width); count++;
   XtSetArg (arglist [count], XtNheight, height); count++;
   XtSetValues (dw, arglist, count);

   hints.width = width + 10;
   hints.height = height + 80;
   hints.flags = PSize;
   
   count = 0;
   XtSetArg (arglist [count], XtNheight, (Dimension) hints.height); count++;
   XtSetArg (arglist [count], XtNwidth, (Dimension) hints.width); count++;
   XtSetValues (shell, arglist, count);

   XSetNormalHints (XtDisplay (shell), XtWindow (shell), &hints);

   XtPopup (shell, XtGrabNone);

   *x_scale = Xscale;
   *y_scale = Yscale;
   *wx = width;
   *hy = height;
}

void PlotStressField (element, numelts, nd)
   Element	*element;
   unsigned	numelts,
		nd;
{
   static int	flag = 1;
   unsigned	i,j;
   unsigned	n;
   float	x_min, y_min,
		x_max, y_max;
   float	maxX,maxY,	
		minX,minY;
   float	Xscale, Yscale;
   float	*xd,*yd,*zd;
   float	*xi,*yi,**zi;
   Dimension	width, height;
   Point	points [MaxNodesPerElement];
   unsigned	result;
   Region	current, global;
   XPoint	rgpnt [MaxNodesPerElement];
   char		**mask;

   if (flag) {
      InitializeColormap ();
      CreateStressShell ();
      flag = 0;
   }
   
   if (ximage != NULL) {
      XDestroyImage (ximage);
      DW_RemoveAll (stress_dw);
      XFreePixmap (XtDisplay (toplevel), pixmap);
   }

	/*
	 * some preliminaries to set-up a properly 
 	 * proportioned window
	 */

   x_min = x_max = element[1] -> node[1] -> x;
   y_min = y_max = element[1] -> node[1] -> y;
   for (i = 1 ; i <= numelts ; i++) {
      for (j = 1 ; j <= element[i] -> definition -> numnodes ; j++) {
 
         if (element[i] -> node[j] -> x > x_max)
            x_max = element[i] -> node[j] -> x;
         else if (element[i] -> node[j] -> x < x_min)
            x_min = element[i] -> node[j] -> x;

         if (element[i] -> node[j] -> y > y_max)
            y_max = element[i] -> node[j] -> y;
         else if (element[i] -> node[j] -> y < y_min)
            y_min = element[i] -> node[j] -> y;
      }
   }

   minX = x_min - 0.05*(x_max - x_min);
   maxX = x_max + 0.05*(x_max - x_min);
   minY = y_min - 0.05*(y_max - y_min);
   maxY = y_max + 0.05*(y_max - y_min);

   if (x_max - x_min < y_max - y_min)
      maxX += 0.25*(x_max - x_min);
   else
      minY -= 0.25*(y_max - y_min); 

   InitializeDrawingShell (stressShell, stress_dw, minX, maxX, minY, maxY,
                           &Xscale, &Yscale, &width, &height);

   pixmap = DW_CreatePixmap (stress_dw, width, height);

	/*
	 * set-up to do the bivariate interpolation so
	 * we can fill in the entire stress field
	 */

   zi = Allocate (float *, height); 
   mask = Allocate (char *, height);
   for (i = 0 ; i < height ; i++) {
      zi [i] = Allocate (float, width); 
      mask [i] = Allocate (char, width);
   }

   xi = Allocate (float, width);
   UnitOffset (xi);
   yi = Allocate (float, height);
   UnitOffset (yi);

   xd = Allocate (float, nd);
   yd = Allocate (float, nd);
   zd = Allocate (float, nd);

   if (xd == NULL || yd == NULL || zd == NULL) 
      Fatal ("allocation error plotting stress field");   

   UnitOffset (xd);
   UnitOffset (yd);
   UnitOffset (zd);

   global = XCreateRegion ();

   n = 0;
   for (i = 1 ; i <= numelts ; i++) {
      for (j = 1; j <= element[i] -> definition -> numnodes ; j++) {
         rgpnt [j - 1].x = (int) ((element[i] -> node[j] -> x - minX)*Xscale);
         rgpnt [j - 1].y = (int) ((element[i] -> node[j] -> y - minY)*Yscale);
      }

      current=XPolygonRegion (rgpnt, element[i] -> definition -> numnodes, 
                              EvenOddRule);
      XUnionRegion (global, current, global);
      XDestroyRegion (current);

      for (j = 1; j <= element[i] -> ninteg ; j++) {

         xd[n+1] = (element[i] -> stress[j] -> x - minX)*Xscale;
         yd[n+1] = (element[i] -> stress[j] -> y - minY)*Yscale;
         zd[n+1] = element[i] -> stress[j] -> values[solution -> component];
         n++;
      }
   }

   for (i = 0 ; i < height ; i++) {
      for (j = 0 ; j < width ; j++) {
 
         if (XPointInRegion(global,j,i))
            mask[i][j] = 1;
         else
            mask[i][j] = 0; 
      }
   }

   XDestroyRegion (global);

   for (i = 1 ; i <= height; i++) 
      yi [i] = (float) i - 1.0;

   for (j = 1 ; j <= width ; j++) 
      xi [j] = (float) j - 1.0;

   result = BivariateInterp (nd, xd, yd, zd, width, height,xi,yi,zi,mask);
   if (result) {
      error ("could not interpolate to form stress field"); 
      return;
   }

   LoadImage (zi, width, height, mask, minX, minY, Xscale, Yscale);
   
   DW_SetAutoRedraw (stress_dw, False);

   DW_DrawPixmap (stress_dw, minX, minY, pixmap);

   DW_SetForeground (stress_dw, "black");
   for (i = 1 ; i <= numelts ; i++) {
      for (j = 0 ; j < element[i] -> definition -> numnodes ; j++) {
         points [j].x = element [i] -> node [j+1] -> x;
         points [j].y = element [i] -> node [j+1] -> y;
      }
      points [element[i] -> definition -> numnodes].x = 
        element[i] -> node[1] -> x;
      points [element[i] -> definition -> numnodes].y = 
        element[i] -> node[1] -> y;

      DW_DrawPolygon (stress_dw, True, points, 
                      element[i] -> definition -> numnodes + 1);
   }

   DW_SetAutoRedraw (stress_dw, True);

   ZeroOffset (xd); Deallocate (xd);
   ZeroOffset (yd); Deallocate (yd);
   ZeroOffset (zd); Deallocate (zd);

   ZeroOffset (xi); Deallocate (xi);
   ZeroOffset (yi); Deallocate (yi);

   for (i = 0 ; i < height ; i++) {
      Deallocate (zi [i]);
      Deallocate (mask [i]);
   }

   Deallocate (mask);
   Deallocate (zi);
}  

static void LoadImage (z,width,height,mask,minX,minY,Xscale,Yscale)
   float	**z;
   Dimension	width,
		height;
   char		**mask;
   float	minX,minY;
   float	Xscale,Yscale;
{
   unsigned		x_max,y_max,x_min,y_min;
   float		max,min;
   Display		*display;
   Visual		*visual;
   GC			gc;
   unsigned		i,j;
   float		slope;
   int			point;
   unsigned 		flag;
   unsigned		count;
   unsigned long	value;
   char			*ptr;
   char			buffer1 [20];
   char			buffer2 [20];
   int			max_width;
   int			min_width;
   int			sx1,sy1,sx2,sy2;
   XFontStruct		*font;
   Font			fontid;
   
   display = XtDisplay (toplevel);
   visual = DefaultVisual (display, DefaultScreen (display));
   gc = DefaultGC (display, DefaultScreen (display)); 

   flag = 0;
   for (i = 0 ; i < height ; i++) {
      for (j = 0 ; j < width ; j++) {
 
         if (mask[i][j]) {
            if (!flag) {
               x_max = x_min = j;
               y_max = y_min = i;
               max = min = z[i][j];
               flag = 1;
            }
            else {
               if (z[i][j] > max)
                  max = z[i][j];
               else if (z[i][j] < min)
                  min = z[i][j];

               if (i > y_max) 
                  y_max = i;
               else if (i < y_min) 
                  y_min = i;
              
               if (j > x_max)
                  x_max = j;
               else if (j < x_min)
                  x_min = j;
            }
         }
      }
   }

   ximage = XCreateImage (display, visual, 8, ZPixmap, 0, 0, 
                          width, height, 8, 0);

   ximage -> data = Allocate (char, width * height);

   slope = (float) NUMCELLS / (max - min);

   count = 0;
   for (i = 0 ; i < height ; i++) {
      for (j = 0 ; j < width ; j++) {
     
         if (mask[i][j]) {
            if (z[i][j] != max)
               point = (int) (slope*(z[i][j] - min));
            else
               point = NUMCELLS - 1; 

            value = pixel [point];
            count++;
         }
         else
            value = WhitePixel (display, DefaultScreen (display));

         XPutPixel (ximage, j, height - i - 1, value);
      }
   }

   font = XLoadQueryFont (display, "6x9");
   if (font) {
      sprintf (buffer1,"%11.5g",min);
      ptr = buffer1;
      while (isspace (*ptr)) ptr++;
      min_width = XTextWidth (font, ptr, strlen (ptr));
      strcpy (buffer1, ptr);

      sprintf (buffer2,"%11.5g",max);
      ptr = buffer2;
      while (isspace (*ptr)) ptr++;
      max_width = XTextWidth (font, ptr, strlen (ptr));
      strcpy (buffer2, ptr);

      fontid = XLoadFont (display, "6x9");
      XSetFont (display, gc, fontid);
   }

   if (x_max - x_min >= y_max - y_min) {
      for (j = x_min ; j <= x_max ; j++) {
         for (i = (int) (0.88*height) ; i < height - 12 ; i++) {
            point = (int) ((float) (j-x_min)/(float) (x_max - x_min)*127.0); 
            XPutPixel (ximage, j, i, pixel [point]);
         }
      }
      sx1 = x_min;
      sx2 = x_max - max_width;
      sy1 = sy2 = height - 2;
   }
   else {
      for (i = y_min ; i <= y_max ; i++) {
         for (j = (int) (0.9*width) ; j < (int) (0.98*width) ; j++) {
            point = (int) ((float)(i-y_min)/((float)(y_max - y_min))*127.0); 
            XPutPixel (ximage, j, height - i - 1, pixel [point]);
         }
      }
      sy1 = y_max + 10;
      sy2 = y_min - 2;
      sx1 = width - min_width - 1;
      sx2 = width - max_width - 1; 
   }

   if (solution -> equalize)
      HequalImage (display, gc, WhitePixel (display, DefaultScreen (display)));

   XPutImage (display, pixmap, gc, ximage, 0, 0, 0, 0, width, height);

   if (font) {
      XDrawImageString (display, pixmap, gc, sx1,sy1,buffer1,strlen(buffer1));
      XDrawImageString (display, pixmap, gc, sx2,sy2,buffer2,strlen(buffer2));
   }
}

static void HequalImage (display, gc, white)
   Display		*display;
   GC			gc;
   long			white;
{
   unsigned		i,j;
   long			point;
   long			value;
   unsigned long	histogram [NUMCELLS];
   float		p [NUMCELLS],
			v [NUMCELLS],
			vstar [NUMCELLS],
			vmin, area, sum;
   
   for (i = 0 ; i < NUMCELLS ; i++)
      histogram [i] = 0;

   area = 0;
   for (i = 0 ; i < ximage -> height ; i++) {
      for (j = 0 ; j < ximage -> width ; j++) {

         point = XGetPixel (ximage, j, i);
         if (point != white) {
            area++;
            value = rev_pixel [point];         
            histogram [value]++;
         }
      }
   }

   for (i = 0 ; i < NUMCELLS ; i++)
      p [i] = histogram [i] / area;

   sum = 0.0;
   vmin = p[0];
   for (i = 0 ; i < NUMCELLS ; i++) {
      sum += p[i];
      v[i] = sum;
      if (v[i] < vmin)
         vmin = v[i];
   }

   for (i = 0 ; i < NUMCELLS ; i++)
      vstar[i] = ((((v[i] - vmin)/(1.0 - vmin))*127.0) + 0.5);

   for (i = 0 ; i < ximage -> height ; i++) {
      for (j = 0 ; j < ximage -> width ; j++) {

         point = XGetPixel (ximage, j, i);
         if (point != white) {
            value = rev_pixel [point];
            value = (char) vstar [value]; 
            point = pixel [value];
            XPutPixel (ximage, j, i, point);
         }
      }
   }
}

static void ButtonActions (w, client_data, call_data)
   Widget	w;
   XtPointer	client_data,
		call_data;
{
   unsigned	selected = *(unsigned *) client_data;

   if (selected == OkayButton) 
      XtPopdown (stressShell);
   else if (selected == SaveButton)
      SaveWidgetXWD (stress_dw);
}
