/*
 * file:     plot.c
 * author:   Wes Barris
 * date:     5/27/92
 * purpose:  handles background functionality
 *
 * copyright info:
 *
 *                           @Copyright 1992
 *      Research Equipment Inc. dba Minnesota Supercomputer Center
 *
 * RESTRICTED RIGHTS LEGEND
 *
 * Use, duplication, or disclosure of this software and its documentation
 * by the Government is subject to restrictions as set forth in subdivision
 * { (b) (3) (ii) } of the Rights in Technical Data and Computer Software
 * clause at 52.227-7013.
 */

#include "icop.h"
#include "proto.h"
#include <math.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/GLwMDrawA.h>
#include <X11/StringDefs.h>
#include <Xm/Xm.h>
#include <Xm/ToggleB.h>
#include <Xm/Frame.h>

extern ICOP	icop;
extern Widget	plotType[5];
extern Widget	standard[2];
extern char	*tmpname;
extern int	havefile;
extern GLboolean doubleBuffer;

int		haveplot;
static long	zfar;
static double	xmin, xmid, xmax, ymin, ymid, ymax, zmin, zmid, zmax;
static int omx, mx, omy, my;	/* old and new mouse position*/
static GLclampf	bg[4];
static GLuint	base;

static char defaultTranslations[] =
    "<Btn1Down>:        arm() \n\
     <Btn1Up>:          disarm() \n\
     <Btn1Motion>:      move() \n ";

static XtActionsRec actionsTable[] = {
    {"arm",	MouseActionCB},
    {"disarm",	MouseActionCB},
    {"move",	MouseActionCB},
};

#define NOTHING 0
#define ORIENT 1

static int mode = 0;

void printString(char *s)
{
    glPushAttrib(GL_LIST_BIT);
    glListBase(base);
    glCallLists(strlen(s), GL_UNSIGNED_BYTE, (GLubyte *)s);
    glPopAttrib();
}

static void
PlotYSLimits(void)
{
   if (XmToggleButtonGetState(standard[0])) {	/* Limits for YIQ (NTSC) */
      glBegin(GL_POLYGON);
         glColor3ub(250, 250, 250);
         glVertex2f(0.0,   0.0);
         glVertex2f(0.792, 0.0);
         glColor3ub(0, 0, 0);
         glVertex2f(0.792, 1.0);
         glVertex2f(0.0,   1.0);
      glEnd();
      glColor3ub(30, 30, 30);
      glBegin(GL_POLYGON);
         glVertex2f(0.0,    0.0);
         glVertex2f(0.0,    1.0);
         glVertex2f(0.6255, 0.3745);
         glVertex2f(0.251,  0.0);
      glEnd();
      }
   else {					/* Limits for YUV (PAL) */
      glBegin(GL_POLYGON);
         glColor3ub(250, 250, 250);
         glVertex2f(0.0,   0.0);
         glVertex2f(0.792, 0.0);
         glColor3ub(0, 0, 0);
         glVertex2f(0.792, 1.0);
         glVertex2f(0.0,   1.0);
      glEnd();
      glColor3ub(30, 30, 30);
      glBegin(GL_POLYGON);
         glVertex2f(0.0,    0.0);
         glVertex2f(0.0,    1.334);
         glVertex2f(0.8365, 0.4975);
         glVertex2f(0.339,  0.0);
      glEnd();
      }
}


static void
PlotYSText(void)
{
   if (XmToggleButtonGetState(standard[0])) {	/* Limits for YIQ (NTSC) */
      glColor3ub(200, 200, 200);
      glRasterPos2f(0.1,  0.4);
      printString("Legal Colors");
      glColor3ub(20, 20, 20);
      glRasterPos3f(0.4,  0.1,  0.0);
      printString("Under Saturated");
      glColor3ub(200, 200, 200);
      glRasterPos3f(0.4,  0.8,  0.0);
      printString("Over Saturated");
      }
   else {					/* Limits for YUV (PAL) */
      glColor3ub(200, 200, 200);
      glRasterPos3f(0.1,  0.5,  0.0);
      printString("Legal Colors");
      glColor3ub(20, 20, 20);
      glRasterPos3f(0.45,  0.05,  0.0);
      printString("Under Saturated");
      glColor3ub(200, 200, 200);
      glRasterPos3f(0.45,  0.9,  0.0);
      printString("Over Saturated");
      }
}


static void
PlotYS(void)
{
   int		ntsc;
   int		x, y;
   int		red, green, blue;
   double	sat, yy, i, q;
   double	i_new, q_new;
/*
 * Open the image file and plot Y versus S.
 */
   ntsc = XmToggleButtonGetState(standard[0]);
   glBegin(GL_POINTS);
   for(y=0; y<icop.image.height; y++) {
      for (x=0; x<(havefile == IMAGE_FILE?icop.image.width:1); x++) {
         red   = icop.image.origbuf[y*icop.image.width+x] >> 24 & 0xff;
         green = icop.image.origbuf[y*icop.image.width+x] >> 16 & 0xff;
         blue  = icop.image.origbuf[y*icop.image.width+x] >>  8 & 0xff;
         if (ntsc)
            rgbtoyiq(red, green, blue, &yy, &i, &q);
         else /* if standard == PAL */
            rgbtoyuv(red, green, blue, &yy, &i, &q);
         sat = sqrt(i*i + q*q);
         glColor3ub((GLubyte)red, (GLubyte)green, (GLubyte)blue);
         glVertex2d(sat, yy);
         }
      }
   glEnd();
}


static void
SetLimits(void)
{
   if (XmToggleButtonGetState(plotType[1])) {
      xmin = 0.0;
      xmax = 255.0;
      ymin = 0.0;
      ymax = 255.0;
      zmin = 0.0;
      zmax = 255.0;
      }
   else if (XmToggleButtonGetState(plotType[2])) {
      xmin = 0.0;
      xmax = 1.0;
      ymin = -0.596;
      ymax = 0.596;
      zmin = -0.523;
      zmax = 0.523;
      }
   else if (XmToggleButtonGetState(plotType[3])) {
      xmin = 0.0;
      xmax = 1.0;
      ymin = -0.436;
      ymax = 0.436;
      zmin = -0.615;
      zmax = 0.615;
      }
   xmid = 0.5*(xmin + xmax);
   ymid = 0.5*(ymin + ymax);
   zmid = 0.5*(zmin + zmax);
}


static void
PlotAxes(int type)
{
   glColor3b((GLbyte)0, (GLbyte)0, (GLbyte)125);
   if (type == RGB_3D) {
      glRasterPos3f(0.0,  0.0,  0.0);
      printString(" Black");
      glRasterPos3f(xmax,  0.0,  0.0);
      printString(" Red");
      glRasterPos3f(0.0,  ymax,  0.0);
      printString(" Green");
      glRasterPos3f(0.0,  0.0,  zmax);
      printString(" Blue");
      glRasterPos3f(0.0,  ymax,  zmax);
      printString(" Cyan");
      glRasterPos3f(xmax,  0.0,  zmax);
      printString(" Magenta");
      glRasterPos3f(xmax,  ymax,  0.0);
      printString(" Yellow");
      glRasterPos3f(xmax,  ymax,  zmax);
      printString(" White");
      }
   else if (type == YIQ_3D) {
      glRasterPos3f(xmin,  ymid,  zmid);
      printString(" Ymin");
      glRasterPos3f(xmax,  ymid,  zmid);
      printString(" Ymax");
      glRasterPos3f(xmid,  ymin,  zmid);
      printString(" Imin");
      glRasterPos3f(xmid,  ymax,  zmid);
      printString(" Imax");
      glRasterPos3f(xmid,  ymid,  zmin);
      printString(" Qmin");
      glRasterPos3f(xmid,  ymid,  zmax);
      printString(" Qmax");
      }
   else if (type == YUV_3D) {
      glRasterPos3f(xmin,  ymid,  zmid);
      printString(" Ymin");
      glRasterPos3f(xmax,  ymid,  zmid);
      printString(" Ymax");
      glRasterPos3f(xmid,  ymin,  zmid);
      printString(" Umin");
      glRasterPos3f(xmid,  ymax,  zmid);
      printString(" Umax");
      glRasterPos3f(xmid,  ymid,  zmin);
      printString(" Vmin");
      glRasterPos3f(xmid,  ymid,  zmax);
      printString(" Vmax");
      }
	/* OGLXXX for multiple, independent line segments: use GL_LINES */
   glBegin(GL_LINE_STRIP);
      glVertex3d(xmin, ymin, zmin);
      glVertex3d(xmax, ymin, zmin);
      glVertex3d(xmax, ymax, zmin);
      glVertex3d(xmin, ymax, zmin);
      glVertex3d(xmin, ymin, zmin);
      glVertex3d(xmin, ymin, zmax);
      glVertex3d(xmax, ymin, zmax);
      glVertex3d(xmax, ymax, zmax);
      glVertex3d(xmin, ymax, zmax);
      glVertex3d(xmin, ymin, zmax);
   glEnd();
   glBegin(GL_LINE_STRIP);
      glVertex3d(xmax, ymin, zmin);
      glVertex3d(xmax, ymin, zmax);
   glEnd();
   glBegin(GL_LINE_STRIP);
      glVertex3d(xmax, ymax, zmin);
      glVertex3d(xmax, ymax, zmax);
   glEnd();
   glBegin(GL_LINE_STRIP);
      glVertex3d(xmin, ymax, zmin);
      glVertex3d(xmin, ymax, zmax);
   glEnd();
}


static void
PlotRGB(int stride)
{
   int		red, green, blue;
   int		x, y;
/*
 * Plot RGB from the image buffer.
 */
   glBegin(GL_POINTS);
   for(y=0; y<icop.image.height; y+=stride) {
      for (x=0; x<(havefile == IMAGE_FILE?icop.image.width:1); x+=stride) {
         red   = icop.image.origbuf[y*icop.image.width+x] >> 24 & 0xff;
         green = icop.image.origbuf[y*icop.image.width+x] >> 16 & 0xff;
         blue  = icop.image.origbuf[y*icop.image.width+x] >>  8 & 0xff;
         glColor3ub((GLubyte)red, (GLubyte)green, (GLubyte)blue);
         glVertex3i(red, green, blue);
         }
      }
   glEnd();
}


static void
PlotYIQ(int stride)
{
   int		red, green, blue;
   int		x, y;
   double	yy, i, q;
/*
 * Plot YIQ from the image buffer.
 */
   glBegin(GL_POINTS);
   for(y=0; y<icop.image.height; y+=stride) {
      for (x=0; x<(havefile == IMAGE_FILE?icop.image.width:1); x+=stride) {
         red   = icop.image.origbuf[y*icop.image.width+x] >> 24 & 0xff;
         green = icop.image.origbuf[y*icop.image.width+x] >> 16 & 0xff;
         blue  = icop.image.origbuf[y*icop.image.width+x] >>  8 & 0xff;
         rgbtoyiq(red, green, blue, &yy, &i, &q);
         glColor3ub((GLubyte)red, (GLubyte)green, (GLubyte)blue);
         glVertex3d(yy, i, q);
         }
      }
   glEnd();
}


static void
PlotYUV(int stride)
{
   int		red, green, blue;
   int		x, y;
   double	yy, u, v;
/*
 * Plot YUV from the image buffer.
 */
   glBegin(GL_POINTS);
   for(y=0; y<icop.image.height; y+=stride) {
      for (x=0; x<(havefile == IMAGE_FILE?icop.image.width:1); x+=stride) {
         red   = icop.image.origbuf[y*icop.image.width+x] >> 24 & 0xff;
         green = icop.image.origbuf[y*icop.image.width+x] >> 16 & 0xff;
         blue  = icop.image.origbuf[y*icop.image.width+x] >>  8 & 0xff;
         rgbtoyuv(red, green, blue, &yy, &u, &v);
         glColor3ub((GLubyte)red, (GLubyte)green, (GLubyte)blue);
         glVertex3d(yy, u, v);
         }
      }
   glEnd();
}

/*
 * This routine is used to make an X font available for GL drawing.
 */
void makeRasterFont(void)
{
   XFontStruct *fontInfo;
   Font id;
   unsigned int first, last;

   fontInfo = XLoadQueryFont(XtDisplay(icop.toplevel), 
       "-*-rock-*-*-*-*-*-*-*-*-*-*-*-*");
       /*"-adobe-helvetica-medium-r-normal--17-120-100-100-p-88-iso8859-1");*/
   if (fontInfo == NULL) {
      printf ("Font not found\n");
      exit (0);
      }

   id = fontInfo->fid;
   first = fontInfo->min_char_or_byte2;
   last = fontInfo->max_char_or_byte2;

   base = glGenLists((GLuint) last+1);
   if (base == 0) {
      printf ("out of display lists\n");
      exit (0);
      }
   glXUseXFont(id, first, last-first+1, base+first);
}


/*
 * This routine is used to initialize a glx widget.  It only has to set the
 * viewport so it can also be used for resizing.
 */
void
InitPlotCB(Widget w, XtPointer client_data, char *call_data)
{
   Arg args[1];
   GLwDrawingAreaCallbackStruct *cd;
   XVisualInfo *vi;

   cd = (GLwDrawingAreaCallbackStruct *)call_data;
   switch (cd->reason) {
      case GLwCR_GINIT:
         XtSetArg(args[0], GLwNvisualInfo, &vi);
         XtGetValues(w, args, 1);
         icop.plot.context = glXCreateContext(XtDisplay(icop.toplevel),
                                              vi, 0, GL_TRUE);
         GLwDrawingAreaMakeCurrent(w, (GLXContext)icop.plot.context);
         makeRasterFont();
         glClearColor(bg[0], bg[1], bg[2], bg[3]);
         glReadBuffer(GL_FRONT);
      /*case GLwCR_RESIZE:*/
         /*GLwDrawingAreaMakeCurrent(w, (GLXContext)icop.plot.context);*/
         glViewport(0,  0, cd->width, cd->height);
         break;
      default:
         fprintf(stderr, "WARNING -- Unknown reason in InitPlotCB.\n");
      }
}


static void
PlotRGBCube(int stride)
{
   glClear(GL_COLOR_BUFFER_BIT);		/* clear window */
   PlotRGB(stride);				/* plot RGB points */
   PlotAxes(RGB_3D);				/* plot RGB axes */
   if (doubleBuffer)
      GLwDrawingAreaSwapBuffers(icop.plot.w);	/* swap buffers */
   /*glFlush();*/					/* flush output */
}


static void
PlotYIQCube(int stride)
{
   glClear(GL_COLOR_BUFFER_BIT);
   PlotYIQ(stride);
   PlotAxes(YIQ_3D);
   if (doubleBuffer)
      GLwDrawingAreaSwapBuffers(icop.plot.w);
   /*glFlush();*/
}


static void
PlotYUVCube(int stride)
{
   glClear(GL_COLOR_BUFFER_BIT);
   PlotYUV(stride);
   PlotAxes(YUV_3D);
   if (doubleBuffer)
      GLwDrawingAreaSwapBuffers(icop.plot.w);
   /*glFlush();*/
}


static void
reset(void)
{
   GLdouble zNear, zFar, zTrans;

   zNear  = 0.5*xmax;
   zFar   = 3.0*xmax;
   zTrans = 1.8*xmax;

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(60.0,  1.0,  zNear,  zFar);
   glTranslatef(-xmid, -ymid, -(zmid+zTrans));
   glMatrixMode(GL_MODELVIEW);
}

static void
orient(int omx, int omy, int mx, int my)
{
   GLfloat current_matrix[16];

   glGetFloatv(GL_MODELVIEW_MATRIX, current_matrix);
   glLoadIdentity();
   glTranslatef(xmid,  ymid,  zmid);
   glRotatef((GLfloat)(.2*(mx-omx)), 0.0, 1.0, 0.0);
   glRotatef((GLfloat)(.2*(my-omy)), 1.0, 0.0, 0.0);
   glTranslatef(-xmid, -ymid, -zmid);
   glMultMatrixf(current_matrix);
}

static void
SaveWindow()
{
   GLfloat current_matrix[16];
/*
 * Save the current modelview matrix and then init.
 */
   glGetFloatv(GL_MODELVIEW_MATRIX, current_matrix);
   glLoadIdentity();
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluOrtho2D(0, icop.plot.width, 0, icop.plot.height);
   glMatrixMode(GL_MODELVIEW);
   glReadPixels(0, 0, icop.plot.width, icop.plot.height,
                   GL_RGBA, GL_UNSIGNED_BYTE, icop.plot.buf);
/*
 * Restore the modelview matrix.
 */
   glLoadMatrixf(current_matrix);
}

static void
RestoreWindow()
{
   GLfloat current_matrix[16];
/*
 * Save the current modelview matrix and then init.
 */
   glGetFloatv(GL_MODELVIEW_MATRIX, current_matrix);
   glLoadIdentity();
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluOrtho2D(0, icop.plot.width, 0, icop.plot.height);
   glMatrixMode(GL_MODELVIEW);
   glRasterPos2i(0, 0);
   glDrawPixels(icop.plot.width, icop.plot.height,
                  GL_RGBA, GL_UNSIGNED_BYTE,  icop.plot.buf);
   if (doubleBuffer)
      GLwDrawingAreaSwapBuffers(icop.plot.w);
/*
 * Restore the modelview matrix.
 */
   glLoadMatrixf(current_matrix);
}


/*
 * This routine will draw the icop background.
 */
void
DrawPlotCB(Widget w, XtPointer client_data, char *call_data)
{
   XEvent	event_return;

   while (XCheckWindowEvent(XtDisplay(w),
          XtWindow(w),
          ExposureMask, &event_return)) {
      /*printf("Discarded an expose event.\n");*/
      ;
      }

   GLwDrawingAreaMakeCurrent(w, (GLXContext)icop.plot.context);
/*
 * Do we already have an initialized data image?
 */
   if (haveplot)
      RestoreWindow();
   else {
      glClear(GL_COLOR_BUFFER_BIT);
      if (doubleBuffer) {
         GLwDrawingAreaSwapBuffers(icop.plot.w);
         glClear(GL_COLOR_BUFFER_BIT);
         }
      BusyCursorCB((Widget)NULL, (XtPointer)NULL, (char *)NULL);
      if (XmToggleButtonGetState(plotType[0])) {
         glMatrixMode(GL_PROJECTION);
         glLoadIdentity();
         gluOrtho2D(0.0, 1.0, 0.0, 1.0);
         glMatrixMode(GL_MODELVIEW);
         glLoadIdentity();
         PlotYSLimits();
         if (havefile)
            PlotYS();
         else
            PlotYSText();
         if (doubleBuffer)
            GLwDrawingAreaSwapBuffers(icop.plot.w);
         }
      else if (XmToggleButtonGetState(plotType[1])) {
         SetLimits();
         reset();
         glLoadIdentity();
         PlotRGBCube(1);
         }
      else if (XmToggleButtonGetState(plotType[2])) {
         SetLimits();
         reset();
         glLoadIdentity();
         PlotYIQCube(1);
         }
      else if (XmToggleButtonGetState(plotType[3])) {
         SetLimits();
         reset();
         glLoadIdentity();
         PlotYUVCube(1);
         }
      SaveWindow();
      haveplot = 1;
      UnBusyCursorCB((Widget)NULL, (XtPointer)NULL, (char *)NULL);
      }
}


static void
MouseActionCB(Widget w, XEvent *event, String *params, Cardinal *num_params)
{

   if (XmToggleButtonGetState(plotType[0]))
      return;
   switch(event->type) {
   case ButtonPress:
      switch(event->xbutton.button) {
      case Button1:
         GLwDrawingAreaMakeCurrent(w, (GLXContext)icop.plot.context);
         mode = ORIENT;
         omx = mx = event->xbutton.x;
         omy = my = event->xbutton.y;
         reset();
         break;
         }
      break;
   case ButtonRelease:
      switch(event->xbutton.button) {
      case Button1:
         BusyCursorCB((Widget)NULL, (XtPointer)NULL, (char *)NULL);
         if (XmToggleButtonGetState(plotType[1]))
            PlotRGBCube(1);
         else if (XmToggleButtonGetState(plotType[2]))
            PlotYIQCube(1);
         else if (XmToggleButtonGetState(plotType[3]))
            PlotYUVCube(1);
         UnBusyCursorCB((Widget)NULL, (XtPointer)NULL, (char *)NULL);
         /*glFinish();*/
         SaveWindow();
         mode = NOTHING;
         break;
         }
      break;
   case MotionNotify:
      if (mode == ORIENT && event->xmotion.state & Button1Mask) {
         omx = mx;
         omy = my;
         mx = event->xbutton.x;
         my = event->xbutton.y;
         orient(omx, omy, mx, my);
         if (XmToggleButtonGetState(plotType[1]))
            PlotRGBCube((int)(0.01*icop.image.width));
         else if (XmToggleButtonGetState(plotType[2]))
            PlotYIQCube((int)(0.02*icop.image.width));
         else if (XmToggleButtonGetState(plotType[3]))
            PlotYUVCube((int)(0.02*icop.image.width));
         }
      break;
    }
}


/*
 * Set the initial preferences.
 */
Widget
PlotInit(Widget parent, Widget neighbor)
{
   Arg		args[10];
   Widget	glPlot, f1;
   int		n;
   unsigned long background;

   XtAppAddActions(icop.appContext, actionsTable, XtNumber(actionsTable));
   n = 0;
   XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
   XtSetArg(args[n], XmNtopWidget, neighbor); n++;
   XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
   XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
   XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
   f1 = XtCreateManagedWidget("f1", xmFrameWidgetClass, parent, args, n);
/*
 * Create a glx widget for the background.
 */
   n = 0;
   XtSetArg(args[n], GLwNrgba, True); n++;
   if (doubleBuffer) {
      XtSetArg(args[n], GLwNdoublebuffer, True); n++;
      }
   XtSetArg(args[n], XmNtranslations, XtParseTranslationTable("")); n++;
   glPlot = GLwCreateMDrawingArea(f1, "plot", args, n);
/*
 * Get (and save) the size of the just created glx widget.
 * The X and Y size come from the app-defaults file.
 */
   n = 0;
   XtSetArg(args[n], XtNwidth, &icop.plot.width); n++;
   XtSetArg(args[n], XtNheight, &icop.plot.height); n++;
   XtGetValues(glPlot, args, n);
/*
 * Add some callbacks.
 */
   XtAddCallback(glPlot, GLwNginitCallback, (XtCallbackProc)InitPlotCB, 0);
   XtAddCallback(glPlot, GLwNexposeCallback, (XtCallbackProc)DrawPlotCB, 0);
   XtOverrideTranslations(glPlot, XtParseTranslationTable(defaultTranslations));
   background = WidgetBackgroundToGlRgb(glPlot);
   bg[0] = (background & 0xff)/255.0;
   bg[1] = (background >> 8 & 0xff)/255.0;
   bg[2] = (background >> 16 & 0xff)/255.0;
   bg[3] = 1.0;

   icop.plot.buf = (unsigned long *)malloc(icop.plot.width*icop.plot.height*sizeof(long));
   XtManageChild(glPlot);
   return glPlot;
}
